diff options
author | Anton Yuzhaninov <citrin+git@citrin.ru> | 2018-10-27 13:18:04 -0400 |
---|---|---|
committer | Anton Yuzhaninov <citrin+git@citrin.ru> | 2018-10-27 13:36:52 -0400 |
commit | 3ebf458996b18c42f78ff35ed7fbd66179dc2eea (patch) | |
tree | 4323289a561a2ae30fd6b4dd88d598a5418e05f5 | |
parent | c974d1eaaaee82906415b3608089f24461daf86c (diff) | |
download | rspamd-3ebf458996b18c42f78ff35ed7fbd66179dc2eea.tar.gz rspamd-3ebf458996b18c42f78ff35ed7fbd66179dc2eea.zip |
Speedup lua coverage collecting for functional test
luacov-coveralls merge mode (-j flag) was created to join reports
containing coverage for different source files (e.g. C and Lua code).
Coverage for the same file in two report is not merged, instead one
source file is added several times to source_files array in JSON. As
a result if we use luacov-coveralls -j on report for same source files
it ends up spending a lot of time on parsing and dumping big JSON files.
This change reduces functional test time from 7+ minutes to 4+ minutes.
-rw-r--r-- | .drone.yml | 6 | ||||
-rw-r--r-- | test/functional/lib/rspamd.py | 93 |
2 files changed, 70 insertions, 29 deletions
diff --git a/.drone.yml b/.drone.yml index ff1328300..834229210 100644 --- a/.drone.yml +++ b/.drone.yml @@ -121,10 +121,14 @@ pipeline: - cd /rspamd/build # extract coverage data for C code from .gcda files and save it in a format suitable for coveralls.io - $CI_WORKSPACE/test/tools/gcov_coveralls.py --exclude test --prefix /rspamd/build --prefix $CI_WORKSPACE --out coverage.c.json + # luacov-coveralls reads luacov.stats.out generated by functional tests + # (see collect_lua_coverage() in test/functional/lib/rspamd.py) + # and writes json report for coveralls.io + - luacov-coveralls -o coverage.functional.lua.json --dryrun # * merge coverage for C and Lua code # * remove prefixes from absolute paths (in luacov-coveralls files), filter test, contrib, e. t.c # * upload report to coveralls.io - - $CI_WORKSPACE/test/tools/merge_coveralls.py --root $CI_WORKSPACE --input coverage.c.json unit_test_lua.json lua_coverage_report.json --token=$COVERALLS_REPO_TOKEN + - $CI_WORKSPACE/test/tools/merge_coveralls.py --root $CI_WORKSPACE --input coverage.c.json unit_test_lua.json coverage.functional.lua.json --token=$COVERALLS_REPO_TOKEN when: branch: master # don't send coverage report for pull request diff --git a/test/functional/lib/rspamd.py b/test/functional/lib/rspamd.py index 6fc1c0e67..bd3e0c382 100644 --- a/test/functional/lib/rspamd.py +++ b/test/functional/lib/rspamd.py @@ -10,7 +10,6 @@ import signal import socket import sys import tempfile -import subprocess from robot.libraries.BuiltIn import BuiltIn from robot.api import logger @@ -296,41 +295,79 @@ def python3_which(cmd, mode=os.F_OK | os.X_OK, path=None): 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, 'rb') 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, 'wb') 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(): - if python3_which("luacov-coveralls") is None: - logger.info("luacov-coveralls not found, will not collect Lua coverage") - return + """ + Merges ${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 - current_directory = os.getcwd() - report_file = current_directory + "/lua_coverage_report.json" - old_report = current_directory + "/lua_coverage_report.json.old" - tmp_dir = BuiltIn().get_variable_value("${TMPDIR}") - coverage_files = glob.glob('%s/*.luacov.stats.out' % (tmp_dir)) - - for stat_file in coverage_files: - shutil.move(stat_file, "luacov.stats.out") - # logger.console("statfile: " + stat_file) - if (os.path.isfile(report_file)): - shutil.move(report_file, old_report) - p = subprocess.Popen(["luacov-coveralls", "-o", report_file, "-j", old_report, "--merge", "--dryrun"], - stdout = subprocess.PIPE, stderr= subprocess.PIPE) - output,error = p.communicate() + coverage = {} + input_files = [] - logger.info("luacov-coveralls stdout: " + output) - logger.info("luacov-coveralls stderr: " + error) - os.remove(old_report) - else: - p = subprocess.Popen(["luacov-coveralls", "-o", report_file, "--dryrun"], stdout = subprocess.PIPE, stderr= subprocess.PIPE) - output,error = p.communicate() - - logger.info("luacov-coveralls stdout: " + output) - logger.info("luacov-coveralls stderr: " + error) - os.remove("luacov.stats.out") + 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) |