]> git.ipfire.org Git - thirdparty/rspamd.git/commitdiff
Speedup lua coverage collecting for functional test
authorAnton Yuzhaninov <citrin+git@citrin.ru>
Sat, 27 Oct 2018 17:18:04 +0000 (13:18 -0400)
committerAnton Yuzhaninov <citrin+git@citrin.ru>
Sat, 27 Oct 2018 17:36:52 +0000 (13:36 -0400)
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.

.drone.yml
test/functional/lib/rspamd.py

index ff1328300d0df0f32fe10bd38a18844989ef8dd2..834229210d68a8e2795fc148d944db6fec772861 100644 (file)
@@ -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
index 6fc1c0e67026756fbd06b393a2e2601727d6513d..bd3e0c382be0215948fcb3298b94cc6986a2abde 100644 (file)
@@ -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)