mirror of
https://github.com/storytold/craft-common-actions.git
synced 2026-10-31 15:57:54 +00:00
* Provision coverage fonts for archive builds and test runners * Keep coverage fonts optional unless a pin is requested * Pin nested actions used by coverage jobs * Export the font path only after a requested checkout * Measure optional Python tooling coverage in Sonar * ci: restore craft-common-actions tracking main * ci: share Python coverage configuration with XML reporting
114 lines
5.5 KiB
Python
114 lines
5.5 KiB
Python
"""Exercise the optional coverage step's shell and failure propagation."""
|
|
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
|
|
import yaml
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
class PythonCoverageTests(unittest.TestCase):
|
|
def setUp(self):
|
|
workflow = yaml.load((ROOT / '.github/workflows/sonarcloud.yml').read_text(), Loader=yaml.BaseLoader)
|
|
self.inputs = workflow['on']['workflow_call']['inputs']
|
|
self.steps = workflow['jobs']['sonarcloud']['steps']
|
|
self.step = next(s for s in self.steps if s.get('name') == 'Generate Python coverage')
|
|
|
|
def run_coverage(self, *, test_status=0, report_status=0, missing=False, nonexistent=False, outside=False):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
source = root / 'source with spaces'
|
|
source.mkdir()
|
|
script = source / 'test tools.py'
|
|
script.write_text('')
|
|
binary = root / 'bin'
|
|
binary.mkdir()
|
|
stub = binary / 'python3'
|
|
stub.write_text('''#!/usr/bin/env python3
|
|
import json, os, pathlib, shutil, sys
|
|
args = sys.argv[1:]
|
|
with open(os.environ['CALL_LOG'], 'a') as handle:
|
|
handle.write(json.dumps(args) + '\\n')
|
|
if args[:2] == ['-m', 'venv']:
|
|
target = pathlib.Path(args[2]) / 'bin' / 'python'
|
|
target.parent.mkdir(parents=True)
|
|
shutil.copyfile(__file__, target)
|
|
target.chmod(0o700)
|
|
else:
|
|
if args[:3] == ['-m', 'coverage', 'run']:
|
|
sys.exit(int(os.environ['TEST_STATUS']))
|
|
if args[:3] == ['-m', 'coverage', 'xml']:
|
|
sys.exit(int(os.environ['REPORT_STATUS']))
|
|
'''.replace('#!/usr/bin/env python3', '#!' + os.sys.executable, 1))
|
|
stub.chmod(0o700)
|
|
log = root / 'calls.jsonl'
|
|
environment = dict(os.environ, PATH=str(binary) + os.pathsep + os.environ['PATH'],
|
|
RUNNER_TEMP=str(root), CALL_LOG=str(log),
|
|
TEST_STATUS=str(test_status), REPORT_STATUS=str(report_status),
|
|
PYTHON_COVERAGE_SOURCE=str(root.parent if outside else source),
|
|
PYTHON_COVERAGE_SCRIPT='' if missing else str(source / 'absent.py' if nonexistent else script))
|
|
result = subprocess.run(['bash', '-c', self.step['run']], cwd=root,
|
|
env=environment, text=True, capture_output=True)
|
|
calls = [json.loads(line) for line in log.read_text().splitlines()] if log.exists() else []
|
|
return result, calls, str(script)
|
|
|
|
def test_paths_with_spaces_remain_single_arguments(self):
|
|
result, calls, script = self.run_coverage()
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
run = next(args for args in calls if args[:3] == ['-m', 'coverage', 'run'])
|
|
self.assertEqual(run[-1], script)
|
|
self.assertEqual(len([arg for arg in run if arg.startswith('--rcfile=')]), 1)
|
|
|
|
def test_failed_tests_still_generate_report_but_fail_job(self):
|
|
result, calls, _ = self.run_coverage(test_status=7)
|
|
self.assertEqual(result.returncode, 7, result.stderr)
|
|
self.assertTrue(any(args[:3] == ['-m', 'coverage', 'xml'] for args in calls))
|
|
|
|
def test_failed_report_fails_job(self):
|
|
result, _, _ = self.run_coverage(report_status=9)
|
|
self.assertEqual(result.returncode, 9, result.stderr)
|
|
|
|
def test_partial_configuration_fails_before_installing(self):
|
|
result, calls, _ = self.run_coverage(missing=True)
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn('requires both', result.stdout)
|
|
self.assertEqual(calls, [])
|
|
|
|
def test_invalid_paths_fail_before_running_tools(self):
|
|
for options in ({'nonexistent': True}, {'outside': True}):
|
|
with self.subTest(options=options):
|
|
result, calls, _ = self.run_coverage(**options)
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn('::error::', result.stdout)
|
|
self.assertEqual(calls, [])
|
|
|
|
def test_opt_in_wiring_and_report_contract(self):
|
|
for suffix in ('source', 'script'):
|
|
self.assertEqual(self.inputs['python-coverage-' + suffix]['default'], '')
|
|
self.assertEqual(self.step['env']['PYTHON_COVERAGE_' + suffix.upper()], '${{ inputs.python-coverage-' + suffix + ' }}')
|
|
self.assertEqual(self.step['if'], "steps.token.outputs.available == 'true' && (inputs.python-coverage-source != '' || inputs.python-coverage-script != '')")
|
|
result, calls, script = self.run_coverage()
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
run = next(args for args in calls if args[:3] == ['-m', 'coverage', 'run'])
|
|
report = next(args for args in calls if args[:3] == ['-m', 'coverage', 'xml'])
|
|
config = next(arg for arg in run if arg.startswith('--rcfile='))
|
|
self.assertNotEqual(config, '--rcfile=/dev/null')
|
|
self.assertEqual(report, ['-m', 'coverage', 'xml', config, '-o', 'python-coverage.xml'])
|
|
self.assertIn('source = ${PYTHON_COVERAGE_SOURCE_REAL}', self.step['run'])
|
|
self.assertIn('omit = ${PYTHON_COVERAGE_SCRIPT_REAL}', self.step['run'])
|
|
|
|
def test_failure_is_not_suppressed_and_step_precedes_scan(self):
|
|
self.assertNotIn('continue-on-error', self.step)
|
|
scan = next(s for s in self.steps if s.get('name') == 'SonarCloud Scan')
|
|
self.assertLess(self.steps.index(self.step), self.steps.index(scan))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|