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 920b4b9
Show file tree
Hide file tree
Showing 4 changed files with 112 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.

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

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

# Load and merge coverage data.
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)
tcovered = 0
total_executable = 0
for file_name, file_coverage in sorted(coverage.items()):

# Annotate lines with uncovered lines.
uncovered = [line for line, line_covered in file_coverage.items() if not line_covered]
i = 0
while i < len(uncovered):
j = i
while j + 1 < len(uncovered) and uncovered[j + 1] == uncovered[j] + 1:
j += 1
i = j + 1
print(f'::warning file={file_name},line={uncovered[i]},endLine={uncovered[j]},title=Uncovered lines,Lines {uncovered[i]} - {uncovered[j]} were not covered by tests')

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)
47 changes: 47 additions & 0 deletions devtools/gha/unittest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import importlib
import inspect
import json
import os
from pathlib import Path
import sys
import unittest

source = importlib.util.find_spec('nutils').origin.removesuffix('__init__.py')
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)
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()}
print(coverage)
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 920b4b9

Please sign in to comment.