Skip to content

Commit

Permalink
WIP: unittest
Browse files Browse the repository at this point in the history
  • Loading branch information
joostvanzwieten committed May 13, 2024
1 parent 256c05b commit c19654e
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 65 deletions.
34 changes: 24 additions & 10 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,6 @@ jobs:
MKL_NUM_THREADS: 1
PYTHONHASHSEED: 0
NUTILS_TENSORIAL: ${{ matrix.tensorial }}
# Use PEP669's sys.monitoring for faster coverage analysis, if available.
# This also fixes a significant regression in coverage analysis on Python
# 3.12. Related issue: https://github.com/python/cpython/issues/107674 .
COVERAGE_CORE: sysmon
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -96,7 +92,7 @@ jobs:
_numpy_version: ${{ matrix.numpy-version }}
run: |
python -um pip install --upgrade --upgrade-strategy eager wheel
python -um pip install --upgrade --upgrade-strategy eager coverage numpy$_numpy_version
python -um pip install --upgrade --upgrade-strategy eager numpy$_numpy_version
# Install Nutils from `dist` dir created in job `build-python-package`.
python -um pip install "$_wheel[import_gmsh,export_mpl]"
- name: Install Scipy
Expand All @@ -108,11 +104,29 @@ jobs:
python -um pip install --upgrade --upgrade-strategy eager mkl
python -um devtools.gha.configure_mkl
- name: Test
run: python -um coverage run -m unittest discover -b -q -t . -s tests
- name: Post-process coverage
run: python -um devtools.gha.coverage_report_xml
- name: Upload coverage
uses: codecov/codecov-action@v3
run: python -um devtools.gha.unittest
- name: Upload coverage artifact
uses: actions/upload-artifact@v4
with:
name: _coverage_${{ matrix.name }}
path: target/coverage/
if-no-files-found: error
process-coverage:
if: ${{ always() }}
needs: test
name: 'Process coverage'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download coverage artifacts
uses: actions/download-artifact@v4
with:
pattern: _coverage_*
path: target/coverage
merge-multiple: true
- name: Generete summary
run: python -um devtools.gha.report_coverage
test-examples:
needs: build-python-package
name: 'Test examples ${{ matrix.os }}'
Expand Down
55 changes: 0 additions & 55 deletions devtools/gha/coverage_report_xml.py

This file was deleted.

29 changes: 29 additions & 0 deletions devtools/gha/report_coverage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import json
import os
from pathlib import Path

coverage = {}
cov_dir = Path() / 'target' / 'coverage'

for part in cov_dir.glob('*.json'):
with part.open('r') as f:
part = json.load(f)
for file_name, part_file_coverage in part.items():
file_coverage = coverage.setdefault(file_name, {})
for line, covered in part_file_coverage.items():
file_coverage[line] = file_coverage.get(line, False) | covered

with open(os.environ.get('GITHUB_STEP_SUMMARY', None) or cov_dir / 'summary.md', 'w') as f:
print('| Name | Stmts | Miss | Cover |', file=f)
print('| ---- | ----: | ---: | ----: |', file=f)
total_covered = 0
total_executable = 0
for file_name, file_coverage in sorted(coverage.items()):
file_covered = sum(file_coverage.values())
file_executable = len(file_coverage)
file_percentage = 100 * file_covered / file_executable if file_executable else 0
print(f'| {file_name} | {file_executable} | {file_covered} | {file_percentage:.1f}% |', file=f)
total_covered += file_covered
total_executable += file_executable
total_percentage = 100 * total_covered / total_executable if total_executable else 0
print(f'| TOTAL | {total_executable} | {total_covered} | {total_percentage:.1f}% |', file=f)
49 changes: 49 additions & 0 deletions devtools/gha/unittest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import inspect
import json
import os
from pathlib import Path
import sys
import unittest

source = str(Path().resolve() / 'nutils') + os.sep
coverage = {}

if hasattr(sys, 'monitoring'):

def start(code, _):
if isinstance(code.co_filename, str) and code.co_filename.startswith(source) and not sys.monitoring.get_local_events(sys.monitoring.COVERAGE_ID, code):
file_coverage = coverage.setdefault(code.co_filename, {})
for _, _, l in code.co_lines():
if l:
file_coverage.setdefault(l, False)
sys.monitoring.set_local_events(sys.monitoring.COVERAGE_ID, code, sys.monitoring.events.LINE)
for obj in code.co_consts:
if inspect.iscode(obj):
start(obj, None)
elif isinstance(code.co_filename, str) and 'nutils' in code.co_filename and 'devtools' not in code.co_filename:
print(code.co_filename)
print(source)
raise ValueError
return sys.monitoring.DISABLE

def line(code, line_number):
coverage.get(code.co_filename)[line_number] = True
return sys.monitoring.DISABLE

sys.monitoring.register_callback(sys.monitoring.COVERAGE_ID, sys.monitoring.events.PY_START, start)
sys.monitoring.register_callback(sys.monitoring.COVERAGE_ID, sys.monitoring.events.LINE, line)
sys.monitoring.use_tool_id(sys.monitoring.COVERAGE_ID, 'test')
sys.monitoring.set_events(sys.monitoring.COVERAGE_ID, sys.monitoring.events.PY_START)

loader = unittest.TestLoader()
#suite = loader.discover('tests')
suite = loader.loadTestsFromName('tests.test_unit')
runner = unittest.TextTestRunner(buffer=True)
runner.run(suite)

coverage = {file_name[len(source) - 7:]: file_coverage for file_name, file_coverage in coverage.items()}
cov_dir = (Path() / 'target' / 'coverage')
cov_dir.mkdir(parents=True, exist_ok=True)
cov_file = cov_dir / ((os.environ.get('GITHUB_JOB', '') or 'coverage') + '.json')
with cov_file.open('w') as f:
json.dump(coverage, f)

0 comments on commit c19654e

Please sign in to comment.