summaryrefslogtreecommitdiffstats
path: root/test/functional/lib/rspamd.py
diff options
context:
space:
mode:
Diffstat (limited to 'test/functional/lib/rspamd.py')
-rw-r--r--test/functional/lib/rspamd.py357
1 files changed, 357 insertions, 0 deletions
diff --git a/test/functional/lib/rspamd.py b/test/functional/lib/rspamd.py
new file mode 100644
index 0000000..3976ca9
--- /dev/null
+++ b/test/functional/lib/rspamd.py
@@ -0,0 +1,357 @@
+from urllib.request import urlopen
+import glob
+import grp
+import http.client
+import os
+import os.path
+import psutil
+import pwd
+import shutil
+import signal
+import socket
+import stat
+import sys
+import tempfile
+
+from robot.api import logger
+from robot.libraries.BuiltIn import BuiltIn
+import demjson
+
+def Check_JSON(j):
+ d = demjson.decode(j, strict=True)
+ logger.debug('got json %s' % d)
+ assert len(d) > 0
+ assert 'error' not in d
+ return d
+
+def check_json_log(fn):
+ line_count = 0
+ f = open(fn, 'r')
+ for l in f.readlines():
+ d = demjson.decode(l, strict=True)
+ assert len(d) > 0
+ line_count = line_count + 1
+ assert line_count > 0
+
+def cleanup_temporary_directory(directory):
+ shutil.rmtree(directory)
+
+def save_run_results(directory, filenames):
+ current_directory = os.getcwd()
+ suite_name = BuiltIn().get_variable_value("${SUITE_NAME}")
+ test_name = BuiltIn().get_variable_value("${TEST NAME}")
+ if os.path.exists(directory):
+ onlyfiles = [f for f in os.listdir(directory) if os.path.isfile(os.path.join(directory, f))]
+ logger.debug('%s content before cleanup: %s' % (directory, onlyfiles))
+ if test_name is None:
+ # this is suite-level tear down
+ destination_directory = "%s/robot-save/%s" % (current_directory, suite_name)
+ else:
+ destination_directory = "%s/robot-save/%s/%s" % (current_directory, suite_name, test_name)
+ if not os.path.isdir(destination_directory):
+ os.makedirs(destination_directory)
+ for file in filenames.split(' '):
+ source_file = "%s/%s" % (directory, file)
+ logger.debug('check if we can save %s' % source_file)
+ if os.path.isfile(source_file):
+ logger.debug('found %s, save it' % file)
+ shutil.copy(source_file, "%s/%s" % (destination_directory, file))
+ shutil.copy(source_file, "%s/robot-save/%s.last" % (current_directory, file))
+
+def encode_filename(filename):
+ return "".join(['%%%0X' % ord(b) for b in filename])
+
+def get_test_directory():
+ return os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "../../")
+
+def get_top_dir():
+ if os.environ.get('RSPAMD_TOPDIR'):
+ return os.environ['RSPAMD_TOPDIR']
+
+ return get_test_directory() + "/../../"
+
+def get_install_root():
+ if os.environ.get('RSPAMD_INSTALLROOT'):
+ return os.path.abspath(os.environ['RSPAMD_INSTALLROOT'])
+
+ return os.path.abspath("../install/")
+
+def get_rspamd():
+ if os.environ.get('RSPAMD'):
+ return os.environ['RSPAMD']
+ if os.environ.get('RSPAMD_INSTALLROOT'):
+ return os.environ['RSPAMD_INSTALLROOT'] + "/bin/rspamd"
+ dname = get_top_dir()
+ return dname + "/src/rspamd"
+
+def get_rspamc():
+ if os.environ.get('RSPAMC'):
+ return os.environ['RSPAMC']
+ if os.environ.get('RSPAMD_INSTALLROOT'):
+ return os.environ['RSPAMD_INSTALLROOT'] + "/bin/rspamc"
+ dname = get_top_dir()
+ return dname + "/src/client/rspamc"
+
+def get_rspamadm():
+ if os.environ.get('RSPAMADM'):
+ return os.environ['RSPAMADM']
+ if os.environ.get('RSPAMD_INSTALLROOT'):
+ return os.environ['RSPAMD_INSTALLROOT'] + "/bin/rspamadm"
+ dname = get_top_dir()
+ return dname + "/src/rspamadm/rspamadm"
+
+def HTTP(method, host, port, path, data=None, headers={}):
+ c = http.client.HTTPConnection("%s:%s" % (host, port))
+ c.request(method, path, data, headers)
+ r = c.getresponse()
+ t = r.read()
+ s = r.status
+ c.close()
+ return [s, t]
+
+def hard_link(src, dst):
+ os.link(src, dst)
+
+def make_temporary_directory():
+ """Creates and returns a unique temporary directory
+
+ Example:
+ | ${RSPAMD_TMPDIR} = | Make Temporary Directory |
+ """
+ dirname = tempfile.mkdtemp()
+ os.chmod(dirname, stat.S_IRUSR |
+ stat.S_IXUSR |
+ stat.S_IWUSR |
+ stat.S_IRGRP |
+ stat.S_IXGRP |
+ stat.S_IROTH |
+ stat.S_IXOTH)
+ return dirname
+
+def make_temporary_file():
+ return tempfile.mktemp()
+
+def path_splitter(path):
+ dirname = os.path.dirname(path)
+ basename = os.path.basename(path)
+ return [dirname, basename]
+
+def rspamc(addr, port, filename):
+ mboxgoo = b"From MAILER-DAEMON Fri May 13 19:17:40 2016\r\n"
+ goo = open(filename, 'rb').read()
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.connect((addr, port))
+ s.send(b"CHECK RSPAMC/1.0\r\nContent-length: ")
+ s.send(str(len(goo+mboxgoo)).encode('utf-8'))
+ s.send(b"\r\n\r\n")
+ s.send(mboxgoo)
+ s.send(goo)
+ r = s.recv(2048)
+ return r.decode('utf-8')
+
+def Scan_File(filename, **headers):
+ addr = BuiltIn().get_variable_value("${RSPAMD_LOCAL_ADDR}")
+ port = BuiltIn().get_variable_value("${RSPAMD_PORT_NORMAL}")
+ headers["Queue-Id"] = BuiltIn().get_variable_value("${TEST_NAME}")
+ c = http.client.HTTPConnection("%s:%s" % (addr, port))
+ c.request("POST", "/checkv2", open(filename, "rb"), headers)
+ r = c.getresponse()
+ assert r.status == 200
+ d = demjson.decode(r.read())
+ c.close()
+ BuiltIn().set_test_variable("${SCAN_RESULT}", d)
+ return
+
+def Send_SIGUSR1(pid):
+ pid = int(pid)
+ os.kill(pid, signal.SIGUSR1)
+
+def set_directory_ownership(path, username, groupname):
+ if os.getuid() == 0:
+ uid=pwd.getpwnam(username).pw_uid
+ gid=grp.getgrnam(groupname).gr_gid
+ os.chown(path, uid, gid)
+
+def spamc(addr, port, filename):
+ goo = open(filename, 'rb').read()
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.connect((addr, port))
+ s.send(b"SYMBOLS SPAMC/1.0\r\nContent-length: ")
+ s.send(str(len(goo)).encode('utf-8'))
+ s.send(b"\r\n\r\n")
+ s.send(goo)
+ s.shutdown(socket.SHUT_WR)
+ r = s.recv(2048)
+ return r.decode('utf-8')
+
+def TCP_Connect(addr, port):
+ """Attempts to open a TCP connection to specified address:port
+
+ Example:
+ | Wait Until Keyword Succeeds | 5s | 10ms | TCP Connect | localhost | 8080 |
+ """
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.settimeout(5) # seconds
+ s.connect((addr, port))
+ s.close()
+
+def ping_rspamd(addr, port):
+ return str(urlopen("http://%s:%s/ping" % (addr, port)).read())
+
+def redis_check(addr, port):
+ """Attempts to open a TCP connection to specified address:port
+
+ Example:
+ | Wait Until Keyword Succeeds | 5s | 10ms | TCP Connect | localhost | 8080 |
+ """
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.settimeout(1.0) # seconds
+ s.connect((addr, port))
+ if s.sendall(b"ECHO TEST\n"):
+ result = s.recv(128)
+ return result == b'TEST\n'
+ else:
+ return False
+
+def update_dictionary(a, b):
+ a.update(b)
+ return a
+
+
+TERM_TIMEOUT = 10 # wait after sending a SIGTERM signal
+KILL_WAIT = 20 # additional wait after sending a SIGKILL signal
+
+def shutdown_process(process):
+ # send SIGTERM
+ process.terminate()
+
+ try:
+ process.wait(TERM_TIMEOUT)
+ return
+ except psutil.TimeoutExpired:
+ logger.info( "PID {} is not terminated in {} seconds, sending SIGKILL...".format(process.pid, TERM_TIMEOUT))
+ try:
+ # send SIGKILL
+ process.kill()
+ except psutil.NoSuchProcess:
+ # process exited just before we tried to kill
+ return
+
+ try:
+ process.wait(KILL_WAIT)
+ except psutil.TimeoutExpired:
+ raise RuntimeError("Failed to shutdown process {} ({})".format(process.pid, process.name()))
+
+
+def shutdown_process_with_children(pid):
+ pid = int(pid)
+ try:
+ process = psutil.Process(pid=pid)
+ except psutil.NoSuchProcess:
+ return
+ children = process.children(recursive=True)
+ shutdown_process(process)
+ for child in children:
+ try:
+ child.kill()
+ except psutil.NoSuchProcess:
+ pass
+ psutil.wait_procs(children, timeout=KILL_WAIT)
+
+def write_to_stdin(process_handle, text):
+ if not isinstance(text, bytes):
+ text = bytes(text, 'utf-8')
+ lib = BuiltIn().get_library_instance('Process')
+ obj = lib.get_process_object()
+ obj.stdin.write(text + b"\n")
+ obj.stdin.flush()
+ obj.stdin.close()
+ out = obj.stdout.read(4096)
+ return out.decode('utf-8')
+
+def get_file_if_exists(file_path):
+ if os.path.exists(file_path):
+ with open(file_path, 'r') as myfile:
+ return myfile.read()
+ return None
+
+def _merge_luacov_stats(statsfile, coverage):
+ """
+ Reads a coverage stats file written by luacov and merges coverage data to
+ 'coverage' dict: { src_file: hits_list }
+
+ Format of the file defined in:
+ https://github.com/keplerproject/luacov/blob/master/src/luacov/stats.lua
+ """
+ with open(statsfile, 'r') as fh:
+ while True:
+ # max_line:filename
+ line = fh.readline().rstrip()
+ if not line:
+ break
+
+ max_line, src_file = line.split(':')
+ counts = [int(x) for x in fh.readline().split()]
+ assert len(counts) == int(max_line)
+
+ if src_file in coverage:
+ # enlarge list if needed: lenght of list in different luacov.stats.out files may differ
+ old_len = len(coverage[src_file])
+ new_len = len(counts)
+ if new_len > old_len:
+ coverage[src_file].extend([0] * (new_len - old_len))
+ # sum execution counts for each line
+ for l, exe_cnt in enumerate(counts):
+ coverage[src_file][l] += exe_cnt
+ else:
+ coverage[src_file] = counts
+
+
+def _dump_luacov_stats(statsfile, coverage):
+ """
+ Saves data to the luacov stats file. Existing file is overwritted if exists.
+ """
+ src_files = sorted(coverage)
+
+ with open(statsfile, 'w') as fh:
+ for src in src_files:
+ stats = " ".join(str(n) for n in coverage[src])
+ fh.write("%s:%s\n%s\n" % (len(coverage[src]), src, stats))
+
+
+# File used by luacov to collect coverage stats
+LUA_STATSFILE = "luacov.stats.out"
+
+
+def collect_lua_coverage():
+ """
+ Merges ${RSPAMD_TMPDIR}/*.luacov.stats.out into luacov.stats.out
+
+ Example:
+ | Collect Lua Coverage |
+ """
+ # decided not to do optional coverage so far
+ #if not 'ENABLE_LUA_COVERAGE' in os.environ['HOME']:
+ # logger.info("ENABLE_LUA_COVERAGE is not present in env, will not collect Lua coverage")
+ # return
+
+ tmp_dir = BuiltIn().get_variable_value("${RSPAMD_TMPDIR}")
+
+ coverage = {}
+ input_files = []
+
+ for f in glob.iglob("%s/*.luacov.stats.out" % tmp_dir):
+ _merge_luacov_stats(f, coverage)
+ input_files.append(f)
+
+ if input_files:
+ if os.path.isfile(LUA_STATSFILE):
+ _merge_luacov_stats(LUA_STATSFILE, coverage)
+ _dump_luacov_stats(LUA_STATSFILE, coverage)
+ logger.info("%s merged into %s" % (", ".join(input_files), LUA_STATSFILE))
+ else:
+ logger.info("no *.luacov.stats.out files found in %s" % tmp_dir)
+
+
+def file_exists(file):
+ return os.path.isfile(file)