Page MenuHomePhorge

D239.1759772291.diff
No OneTemporary

Size
20 KB
Referenced Files
None
Subscribers
None

D239.1759772291.diff

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -24,7 +24,7 @@
script:
- apk add --no-cache python3 py3-virtualenv
- virtualenv --python=python3 /buildbot_venv
- - /buildbot_venv/bin/pip3 install 'twisted[tls]' jsonschema backports.tarfile
+ - /buildbot_venv/bin/pip3 install 'twisted[tls]' jsonschema backports.tarfile pycobertura
- . /buildbot_venv/bin/activate
- ./lilybuild/run-tests.sh worker
diff --git a/Containerfile.worker b/Containerfile.worker
--- a/Containerfile.worker
+++ b/Containerfile.worker
@@ -4,7 +4,7 @@
RUN apk add --no-cache podman podman-compose python3 \
py3-virtualenv dumb-init bash shadow git openssh rsync php php-curl \
&& virtualenv --python=python3 /buildbot_venv \
- && /buildbot_venv/bin/pip3 install 'twisted[tls]' \
+ && /buildbot_venv/bin/pip3 install 'twisted[tls]' pycobertura \
&& mkdir /buildbot \
&& useradd -ms /bin/bash buildbot \
&& mkdir -pv /tools \
diff --git a/lilybuild/lilybuild/coverage.py b/lilybuild/lilybuild/coverage.py
new file mode 100755
--- /dev/null
+++ b/lilybuild/lilybuild/coverage.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python3
+
+import json
+import sys
+import os
+import shutil
+from pycobertura import Cobertura
+from pycobertura.filesystem import DirectoryFileSystem
+
+def is_covered(covered):
+ # old version returns True for covered and False for uncovered
+ # new version is 'hit' 'partial' and 'miss'
+ # https://github.com/aconrad/pycobertura/commit/866da18254c3eaf645719b11158daccf73acfe18
+ return covered == 'hit' or covered == 'partial' or covered is True
+
+def convert_lines(cobertura, fs, filename):
+ statuses = cobertura.line_statuses(filename)
+ with fs.open(filename) as f:
+ lines = ['N' for _ in f]
+ for (line_num, covered) in statuses:
+ lines[line_num - 1] = 'C' if is_covered(covered) else 'U'
+ return ''.join(lines)
+
+def get_file_full_path(base_dir, file_name):
+ res_abs = os.path.realpath(base_dir, strict=True)
+ file_abs = os.path.realpath(os.path.join(base_dir, file_name), strict=True)
+ if not file_abs.startswith(res_abs + os.sep):
+ raise RuntimeError(f'File is not in the base dir: {file_name}')
+ return file_abs
+
+class SafeDirectoryFileSystem(DirectoryFileSystem):
+ def real_filename(self, filename):
+ return get_file_full_path(self.source_dir, filename)
+
+def main(kwargs):
+ source_dir = kwargs['source_dir']
+ result_dir = kwargs['result_dir']
+ untrusted_coverage_file = kwargs['untrusted_coverage_file']
+ output_dir = kwargs['output_dir']
+
+ coverage_file = get_file_full_path(result_dir, untrusted_coverage_file)
+
+ fs = SafeDirectoryFileSystem(source_dir)
+ cobertura = Cobertura(coverage_file, fs)
+ files = cobertura.files()
+
+ result = {}
+
+ for f in files:
+ try:
+ result[f] = convert_lines(cobertura, fs, f)
+ except Exception as e:
+ print(f'Error while processing coverage for file "{f}". The file does not exist in the source repository, or cannot be read.', file=sys.stderr)
+
+ shutil.copyfile(coverage_file, os.path.join(output_dir, 'coverage-cobertura.xml'))
+ with open(os.path.join(output_dir, 'coverage-phorge.json'), 'w') as f:
+ print(json.dumps(result), file=f)
+
+if __name__ == '__main__':
+ if len(sys.argv) > 1:
+ a = json.loads(sys.argv[1])
+ else:
+ a = json.loads(sys.stdin.read())
+ try:
+ main(a)
+ except:
+ print('Cannot read coverage file.')
diff --git a/lilybuild/lilybuild/tests/coverage_res/README b/lilybuild/lilybuild/tests/coverage_res/README
new file mode 100644
--- /dev/null
+++ b/lilybuild/lilybuild/tests/coverage_res/README
@@ -0,0 +1 @@
+The test resources under this directory is taken and/or adapted from the pycobertura repository: https://github.com/aconrad/pycobertura/tree/master/tests/dummy.source1 . The original license of that repository is MIT.
diff --git a/lilybuild/lilybuild/tests/coverage_res/dummy.result1/coverage-notfound.xml b/lilybuild/lilybuild/tests/coverage_res/dummy.result1/coverage-notfound.xml
new file mode 100644
--- /dev/null
+++ b/lilybuild/lilybuild/tests/coverage_res/dummy.result1/coverage-notfound.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" ?>
+<!DOCTYPE coverage
+ SYSTEM 'http://cobertura.sourceforge.net/xml/coverage-03.dtd'>
+<coverage branch-rate="0" line-rate="0.4167" timestamp="1420591320487" version="3.7.1">
+ <!-- Generated by coverage.py: http://nedbatchelder.com/code/coverage -->
+ <packages>
+ <package branch-rate="0" complexity="0" line-rate="0.4167" name="">
+ <classes>
+ <class branch-rate="0" complexity="0" filename="dummy/__init__.py" line-rate="0" name="dummy/__init__">
+ <methods/>
+ <lines/>
+ </class>
+ <class branch-rate="0" complexity="0" filename="dummy/dummy.py" line-rate="0.6" name="dummy/dummy">
+ <methods/>
+ <lines>
+ <line hits="1" number="1"/>
+ <line hits="1" number="2"/>
+ <line hits="1" number="4"/>
+ <line hits="0" number="5"/>
+ <line hits="0" number="6"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="dummy/dummy2.py" line-rate="1" name="dummy/dummy2">
+ <methods/>
+ <lines>
+ <line hits="1" number="1"/>
+ <line hits="1" number="2"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="dummy/dummy5.py" line-rate="0" name="dummy/dummy5">
+ <methods/>
+ <lines>
+ <line hits="0" number="1"/>
+ <line hits="0" number="2"/>
+ <line hits="0" number="4"/>
+ <line hits="0" number="5"/>
+ <line hits="0" number="6"/>
+ </lines>
+ </class>
+ </classes>
+ </package>
+ </packages>
+</coverage>
diff --git a/lilybuild/lilybuild/tests/coverage_res/dummy.result1/coverage-outofbounds.xml b/lilybuild/lilybuild/tests/coverage_res/dummy.result1/coverage-outofbounds.xml
new file mode 100644
--- /dev/null
+++ b/lilybuild/lilybuild/tests/coverage_res/dummy.result1/coverage-outofbounds.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" ?>
+<!DOCTYPE coverage
+ SYSTEM 'http://cobertura.sourceforge.net/xml/coverage-03.dtd'>
+<coverage branch-rate="0" line-rate="0.4167" timestamp="1420591320487" version="3.7.1">
+ <!-- Generated by coverage.py: http://nedbatchelder.com/code/coverage -->
+ <packages>
+ <package branch-rate="0" complexity="0" line-rate="0.4167" name="">
+ <classes>
+ <class branch-rate="0" complexity="0" filename="dummy/__init__.py" line-rate="0" name="dummy/__init__">
+ <methods/>
+ <lines/>
+ </class>
+ <class branch-rate="0" complexity="0" filename="dummy/dummy.py" line-rate="0.6" name="dummy/dummy">
+ <methods/>
+ <lines>
+ <line hits="1" number="1"/>
+ <line hits="1" number="2"/>
+ <line hits="1" number="4"/>
+ <line hits="0" number="5"/>
+ <line hits="0" number="6"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="../../pages_test.py" line-rate="1" name="pages_test">
+ <methods/>
+ <lines>
+ <line hits="1" number="1"/>
+ <line hits="1" number="2"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="/etc/passwd" line-rate="0" name="etc/passwd">
+ <methods/>
+ <lines>
+ <line hits="0" number="1"/>
+ <line hits="0" number="2"/>
+ <line hits="0" number="4"/>
+ <line hits="0" number="5"/>
+ <line hits="0" number="6"/>
+ </lines>
+ </class>
+ </classes>
+ </package>
+ </packages>
+</coverage>
diff --git a/lilybuild/lilybuild/tests/coverage_res/dummy.result1/coverage.xml b/lilybuild/lilybuild/tests/coverage_res/dummy.result1/coverage.xml
new file mode 100644
--- /dev/null
+++ b/lilybuild/lilybuild/tests/coverage_res/dummy.result1/coverage.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" ?>
+<!DOCTYPE coverage
+ SYSTEM 'http://cobertura.sourceforge.net/xml/coverage-03.dtd'>
+<coverage branch-rate="0" line-rate="0.4167" timestamp="1420591320487" version="3.7.1">
+ <!-- Generated by coverage.py: http://nedbatchelder.com/code/coverage -->
+ <packages>
+ <package branch-rate="0" complexity="0" line-rate="0.4167" name="">
+ <classes>
+ <class branch-rate="0" complexity="0" filename="dummy/__init__.py" line-rate="0" name="dummy/__init__">
+ <methods/>
+ <lines/>
+ </class>
+ <class branch-rate="0" complexity="0" filename="dummy/dummy.py" line-rate="0.6" name="dummy/dummy">
+ <methods/>
+ <lines>
+ <line hits="1" number="1"/>
+ <line hits="1" number="2"/>
+ <line hits="1" number="4"/>
+ <line hits="0" number="5"/>
+ <line hits="0" number="6"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="dummy/dummy2.py" line-rate="1" name="dummy/dummy2">
+ <methods/>
+ <lines>
+ <line hits="1" number="1"/>
+ <line hits="1" number="2"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="dummy/dummy4.py" line-rate="0" name="dummy/dummy4">
+ <methods/>
+ <lines>
+ <line hits="0" number="1"/>
+ <line hits="0" number="2"/>
+ <line hits="0" number="4"/>
+ <line hits="0" number="5"/>
+ <line hits="0" number="6"/>
+ </lines>
+ </class>
+ </classes>
+ </package>
+ </packages>
+</coverage>
diff --git a/lilybuild/lilybuild/tests/coverage_res/dummy.result2/coverage.xml b/lilybuild/lilybuild/tests/coverage_res/dummy.result2/coverage.xml
new file mode 120000
--- /dev/null
+++ b/lilybuild/lilybuild/tests/coverage_res/dummy.result2/coverage.xml
@@ -0,0 +1 @@
+../dummy.result1/coverage.xml
\ No newline at end of file
diff --git a/lilybuild/lilybuild/tests/coverage_res/dummy.source1/dummy/__init__.py b/lilybuild/lilybuild/tests/coverage_res/dummy.source1/dummy/__init__.py
new file mode 100644
diff --git a/lilybuild/lilybuild/tests/coverage_res/dummy.source1/dummy/dummy.py b/lilybuild/lilybuild/tests/coverage_res/dummy.source1/dummy/dummy.py
new file mode 100644
--- /dev/null
+++ b/lilybuild/lilybuild/tests/coverage_res/dummy.source1/dummy/dummy.py
@@ -0,0 +1,6 @@
+def foo():
+ pass
+
+def bar():
+ a = 'a'
+ b = 'b'
diff --git a/lilybuild/lilybuild/tests/coverage_res/dummy.source1/dummy/dummy2.py b/lilybuild/lilybuild/tests/coverage_res/dummy.source1/dummy/dummy2.py
new file mode 100644
--- /dev/null
+++ b/lilybuild/lilybuild/tests/coverage_res/dummy.source1/dummy/dummy2.py
@@ -0,0 +1,2 @@
+def baz():
+ pass
diff --git a/lilybuild/lilybuild/tests/coverage_res/dummy.source1/dummy/dummy4.py b/lilybuild/lilybuild/tests/coverage_res/dummy.source1/dummy/dummy4.py
new file mode 100644
--- /dev/null
+++ b/lilybuild/lilybuild/tests/coverage_res/dummy.source1/dummy/dummy4.py
@@ -0,0 +1,6 @@
+def barbaz():
+ pass
+
+def foobarbaz():
+ a = 1 + 3
+ pass
diff --git a/lilybuild/lilybuild/tests/coverage_res/dummy.source1/setup.cfg b/lilybuild/lilybuild/tests/coverage_res/dummy.source1/setup.cfg
new file mode 100644
--- /dev/null
+++ b/lilybuild/lilybuild/tests/coverage_res/dummy.source1/setup.cfg
@@ -0,0 +1,30 @@
+[pytest]
+norecursedirs = build docs/_build *.egg .tox *.venv tests/dummy
+addopts =
+ # Shows a line for every test
+ # You probably want to turn this off if you use pytest-sugar.
+ # Or you can keep it and run `py.test -q`.
+ --verbose
+
+ # Shorter tracebacks are sometimes easier to read
+ --tb=short
+
+ # Turn on --capture to have brief, less noisy output.
+ # You will only see output if the test fails.
+ # Use --capture no (same as -s) if you want to see it all or have problems
+ # debugging.
+ # --capture=fd
+ # --capture=no
+
+ # Show extra test summary info as specified by chars (f)ailed, (E)error, (s)skipped, (x)failed, (X)passed.
+ -rfEsxX
+
+ # Output test results to junit.xml for Jenkins to consume
+ --junitxml=junit.xml
+
+ # Measure code coverage
+ --cov=dummy --cov-report=xml --cov-report=term-missing
+
+ # Previous versions included the following, but it's a bad idea because it
+ # hard-codes the value and makes it hard to change from the command-line
+ test_dummy.py
diff --git a/lilybuild/lilybuild/tests/coverage_res/dummy.source1/test_dummy.py b/lilybuild/lilybuild/tests/coverage_res/dummy.source1/test_dummy.py
new file mode 100644
--- /dev/null
+++ b/lilybuild/lilybuild/tests/coverage_res/dummy.source1/test_dummy.py
@@ -0,0 +1,8 @@
+def test_foo():
+ from dummy.dummy import foo
+ foo()
+
+
+def test_baz():
+ from dummy.dummy2 import baz
+ baz()
diff --git a/lilybuild/lilybuild/tests/coverage_res/dummy.source2/dummy/__init__.py b/lilybuild/lilybuild/tests/coverage_res/dummy.source2/dummy/__init__.py
new file mode 100644
diff --git a/lilybuild/lilybuild/tests/coverage_res/dummy.source2/dummy/dummy.py b/lilybuild/lilybuild/tests/coverage_res/dummy.source2/dummy/dummy.py
new file mode 100644
--- /dev/null
+++ b/lilybuild/lilybuild/tests/coverage_res/dummy.source2/dummy/dummy.py
@@ -0,0 +1,6 @@
+def foo():
+ pass
+
+def bar():
+ a = 'a'
+ b = 'b'
diff --git a/lilybuild/lilybuild/tests/coverage_res/dummy.source2/dummy/dummy2.py b/lilybuild/lilybuild/tests/coverage_res/dummy.source2/dummy/dummy2.py
new file mode 100644
--- /dev/null
+++ b/lilybuild/lilybuild/tests/coverage_res/dummy.source2/dummy/dummy2.py
@@ -0,0 +1,2 @@
+def baz():
+ pass
diff --git a/lilybuild/lilybuild/tests/coverage_res/dummy.source2/dummy/dummy4.py b/lilybuild/lilybuild/tests/coverage_res/dummy.source2/dummy/dummy4.py
new file mode 120000
--- /dev/null
+++ b/lilybuild/lilybuild/tests/coverage_res/dummy.source2/dummy/dummy4.py
@@ -0,0 +1 @@
+../../dummy.source1/dummy/dummy4.py
\ No newline at end of file
diff --git a/lilybuild/lilybuild/tests/coverage_res/dummy.source2/setup.cfg b/lilybuild/lilybuild/tests/coverage_res/dummy.source2/setup.cfg
new file mode 100644
--- /dev/null
+++ b/lilybuild/lilybuild/tests/coverage_res/dummy.source2/setup.cfg
@@ -0,0 +1,30 @@
+[pytest]
+norecursedirs = build docs/_build *.egg .tox *.venv tests/dummy
+addopts =
+ # Shows a line for every test
+ # You probably want to turn this off if you use pytest-sugar.
+ # Or you can keep it and run `py.test -q`.
+ --verbose
+
+ # Shorter tracebacks are sometimes easier to read
+ --tb=short
+
+ # Turn on --capture to have brief, less noisy output.
+ # You will only see output if the test fails.
+ # Use --capture no (same as -s) if you want to see it all or have problems
+ # debugging.
+ # --capture=fd
+ # --capture=no
+
+ # Show extra test summary info as specified by chars (f)ailed, (E)error, (s)skipped, (x)failed, (X)passed.
+ -rfEsxX
+
+ # Output test results to junit.xml for Jenkins to consume
+ --junitxml=junit.xml
+
+ # Measure code coverage
+ --cov=dummy --cov-report=xml --cov-report=term-missing
+
+ # Previous versions included the following, but it's a bad idea because it
+ # hard-codes the value and makes it hard to change from the command-line
+ test_dummy.py
diff --git a/lilybuild/lilybuild/tests/coverage_res/dummy.source2/test_dummy.py b/lilybuild/lilybuild/tests/coverage_res/dummy.source2/test_dummy.py
new file mode 100644
--- /dev/null
+++ b/lilybuild/lilybuild/tests/coverage_res/dummy.source2/test_dummy.py
@@ -0,0 +1,8 @@
+def test_foo():
+ from dummy.dummy import foo
+ foo()
+
+
+def test_baz():
+ from dummy.dummy2 import baz
+ baz()
diff --git a/lilybuild/lilybuild/tests/coverage_test_worker.py b/lilybuild/lilybuild/tests/coverage_test_worker.py
new file mode 100644
--- /dev/null
+++ b/lilybuild/lilybuild/tests/coverage_test_worker.py
@@ -0,0 +1,116 @@
+
+import unittest
+import tempfile
+import os
+import json
+import lilybuild.coverage as lc
+
+self_dir = os.path.dirname(__file__)
+res_dir = os.path.join(self_dir, 'coverage_res')
+
+def r(name):
+ return os.path.join(res_dir, name)
+
+class CoverageTest(unittest.TestCase):
+ def test_simple(self):
+ with tempfile.TemporaryDirectory() as dir_name:
+ lc.main({
+ 'source_dir': r('dummy.source1'),
+ 'result_dir': r('dummy.result1'),
+ 'untrusted_coverage_file': 'coverage.xml',
+ 'output_dir': dir_name
+ })
+
+ self.assertTrue(os.path.exists(os.path.join(dir_name, 'coverage-cobertura.xml')))
+ self.assertTrue(os.path.exists(os.path.join(dir_name, 'coverage-phorge.json')))
+ with open(os.path.join(dir_name, 'coverage-phorge.json')) as f:
+ c = json.loads(f.read())
+ self.assertEqual(c, {
+ 'dummy/__init__.py': '',
+ 'dummy/dummy.py': 'CCNCUU',
+ 'dummy/dummy2.py': 'CC',
+ 'dummy/dummy4.py': 'UUNUUU',
+ })
+
+ def test_source_outofbounds(self):
+ with tempfile.TemporaryDirectory() as dir_name:
+ lc.main({
+ 'source_dir': r('dummy.source2'),
+ 'result_dir': r('dummy.result1'),
+ 'untrusted_coverage_file': 'coverage.xml',
+ 'output_dir': dir_name
+ })
+
+ self.assertTrue(os.path.exists(os.path.join(dir_name, 'coverage-cobertura.xml')))
+ self.assertTrue(os.path.exists(os.path.join(dir_name, 'coverage-phorge.json')))
+ with open(os.path.join(dir_name, 'coverage-phorge.json')) as f:
+ c = json.loads(f.read())
+ self.assertEqual(c, {
+ 'dummy/__init__.py': '',
+ 'dummy/dummy.py': 'CCNCUU',
+ 'dummy/dummy2.py': 'CC',
+ })
+
+ def test_notfound(self):
+ with tempfile.TemporaryDirectory() as dir_name:
+ lc.main({
+ 'source_dir': r('dummy.source1'),
+ 'result_dir': r('dummy.result1'),
+ 'untrusted_coverage_file': 'coverage-notfound.xml',
+ 'output_dir': dir_name
+ })
+
+ self.assertTrue(os.path.exists(os.path.join(dir_name, 'coverage-cobertura.xml')))
+ self.assertTrue(os.path.exists(os.path.join(dir_name, 'coverage-phorge.json')))
+ with open(os.path.join(dir_name, 'coverage-phorge.json')) as f:
+ c = json.loads(f.read())
+ self.assertEqual(c, {
+ 'dummy/__init__.py': '',
+ 'dummy/dummy.py': 'CCNCUU',
+ 'dummy/dummy2.py': 'CC',
+ })
+
+ def test_outofbounds(self):
+ with tempfile.TemporaryDirectory() as dir_name:
+ lc.main({
+ 'source_dir': r('dummy.source1'),
+ 'result_dir': r('dummy.result1'),
+ 'untrusted_coverage_file': 'coverage-outofbounds.xml',
+ 'output_dir': dir_name
+ })
+
+ self.assertTrue(os.path.exists(os.path.join(dir_name, 'coverage-cobertura.xml')))
+ self.assertTrue(os.path.exists(os.path.join(dir_name, 'coverage-phorge.json')))
+ with open(os.path.join(dir_name, 'coverage-phorge.json')) as f:
+ c = json.loads(f.read())
+ self.assertEqual(c, {
+ 'dummy/__init__.py': '',
+ 'dummy/dummy.py': 'CCNCUU',
+ })
+
+ def test_coverage_file_not_found(self):
+ with tempfile.TemporaryDirectory() as dir_name:
+ with self.assertRaises(RuntimeError):
+ lc.main({
+ 'source_dir': r('dummy.source1'),
+ 'result_dir': r('dummy.source1'),
+ 'untrusted_coverage_file': '../dummy.result1/coverage.xml',
+ 'output_dir': dir_name
+ })
+
+ with self.assertRaises(RuntimeError):
+ # A symlink to out-of-bounds file
+ lc.main({
+ 'source_dir': r('dummy.source1'),
+ 'result_dir': r('dummy.result2'),
+ 'untrusted_coverage_file': 'coverage.xml',
+ 'output_dir': dir_name
+ })
+
+ with self.assertRaises(FileNotFoundError):
+ lc.main({
+ 'source_dir': r('dummy.source1'),
+ 'result_dir': r('dummy.result1'),
+ 'untrusted_coverage_file': 'nosuchfile.xml',
+ 'output_dir': dir_name
+ })

File Metadata

Mime Type
text/plain
Expires
Mon, Oct 6, 10:38 AM (6 h, 11 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
499708
Default Alt Text
D239.1759772291.diff (20 KB)

Event Timeline