Skip to content

Commit

Permalink
replace coverage with custom framework (#874)
Browse files Browse the repository at this point in the history
Python 3.12 has a significant regression in coverage analysis. Related issue:
python/cpython#107674 . This PR replaces coverage
with a custom framework built using `sys.monitoring`, which is only available
as of Python 3.12.
  • Loading branch information
joostvanzwieten committed May 24, 2024
2 parents 7b34870 + 5a34e22 commit f640e1b
Show file tree
Hide file tree
Showing 11 changed files with 346 additions and 92 deletions.
42 changes: 35 additions & 7 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ jobs:
- {name: "mkl linux", os: ubuntu-latest, python-version: "3.12", matrix-backend: mkl, nprocs: 1}
- {name: "mkl linux parallel", os: ubuntu-latest, python-version: "3.12", matrix-backend: mkl, nprocs: 2}
- {name: "mkl windows", os: windows-latest, python-version: "3.12", matrix-backend: mkl, nprocs: 1}
- {name: "mkl macos", os: macos-latest, python-version: "3.12", matrix-backend: mkl, nprocs: 1}
- {name: "parallel", os: ubuntu-latest, python-version: "3.12", matrix-backend: numpy, nprocs: 2}
- {name: "numpy 1.17", os: ubuntu-latest, python-version: "3.8", matrix-backend: numpy, nprocs: 1, numpy-version: ==1.17.3}
- {name: "tensorial", os: ubuntu-latest, python-version: "3.12", matrix-backend: numpy, nprocs: 1, tensorial: test}
Expand Down Expand Up @@ -92,7 +91,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 @@ -104,11 +103,40 @@ 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
env:
COVERAGE_ID: ${{ matrix.name }}
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: 'Test 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: Generate summary
run: python -um devtools.gha.report_coverage
- name: Upload lcov artifact
uses: actions/upload-artifact@v4
with:
name: coverage
path: target/coverage/coverage.info
- name: Delete temporary coverage artifacts
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: python -um devtools.gha.delete_coverage_artifacts
test-examples:
needs: build-python-package
name: 'Test examples ${{ matrix.os }}'
Expand Down
56 changes: 41 additions & 15 deletions devtools/_log_default.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,52 @@
from typing import Any
import functools
from typing import Any, Optional, Union

debug = print

def _log_msg(*msg, color: Optional[str] = None, title: Optional[str] = None, file: Optional[str] = None, line: Union[int, range, None] = None, column: Union[int, range, None] = None):
params = []

def info(*args: Any) -> None:
print('\033[1;37m', end='')
for line in ' '.join(map(str, args)).split('\n'):
print(line)
print('\033[0m', end='', flush=True)
if file:
params.append(f'file={file}')

if isinstance(line, range) and line.start == line.stop + 1 and line.step == 1:
line = line.stop
if isinstance(line, int):
params.append(f'line={line}')
elif isinstance(line, range) and line.start < line.stop and line.step == 1:
params.append(f'lines={line.start}-{line.stop-1}')

def warning(*args: Any) -> None:
print('\033[1;33m', end='')
for line in ' '.join(map(str, args)).split('\n'):
print('WARNING: {}'.format(line))
print('\033[0m', end='', flush=True)
if isinstance(column, range) and column.start == column.stop + 1 and column.step == 1:
column = column.stop
if isinstance(column, int):
params.append(f'column={column}')
elif isinstance(column, range) and column.start < column.stop and column.step == 1:
params.append(f'columns={column.start}-{column.stop-1}')

if title:
params.append(f'title={title}')

if color:
print(f'\033[{color}m', end='')

if params:
print('--', ','.join(params))

def error(*args: Any) -> None:
print('\033[1;31m', end='')
print(*msg)

if color:
print(f'\033[0m', end='')


debug = functools.partial(_log_msg)
notice = functools.partial(_log_msg, type='1;36')
warning = functools.partial(_log_msg, color='1;33')
error = functools.partial(_log_msg, type='1;31')


def info(*args: Any) -> None:
print('\033[1;37m', end='')
for line in ' '.join(map(str, args)).split('\n'):
print('ERROR: {}'.format(line))
print(line)
print('\033[0m', end='', flush=True)


Expand Down
50 changes: 38 additions & 12 deletions devtools/_log_gha.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,51 @@
import functools
import os
from pathlib import Path
from typing import Any
from typing import Any, Optional, Union

debug = print

def _log_msg(*msg, type: str, title: Optional[str] = None, file: Optional[str] = None, line: Union[int, range, None] = None, column: Union[int, range, None] = None):
params = []

def info(*args: Any) -> None:
print('\033[1;37m', end='')
for line in ' '.join(map(str, args)).split('\n'):
print(line)
print('\033[0m', end='', flush=True)
if file:
params.append(f'file={file}')

if isinstance(line, range) and line.start == line.stop + 1 and line.step == 1:
line = line.stop
if isinstance(line, int):
params.append(f'line={line}')
elif isinstance(line, range) and line.start < line.stop and line.step == 1:
params.append(f'line={line.start}')
params.append(f'endLine={line.stop-1}')

def warning(*args: Any) -> None:
for line in ' '.join(map(str, args)).split('\n'):
print('::warning ::{}'.format(line))
if isinstance(column, range) and column.start == column.stop + 1 and column.step == 1:
column = column.stop
if isinstance(column, int):
params.append(f'col={column}')
elif isinstance(column, range) and column.start < column.stop and column.step == 1:
params.append(f'col={column.start}')
params.append(f'endColumn={column.stop-1}')

if title:
params.append(f'title={title}')

prefix = f'::{type} {",".join(params)}::'

for line in ' '.join(map(str, msg)).split('\n'):
print(prefix + line)

def error(*args: Any) -> None:

debug = functools.partial(_log_msg, type='debug')
notice = functools.partial(_log_msg, type='notice')
warning = functools.partial(_log_msg, type='warning')
error = functools.partial(_log_msg, type='error')


def info(*args: Any) -> None:
print('\033[1;37m', end='')
for line in ' '.join(map(str, args)).split('\n'):
print('::error ::{}'.format(line))
print(line)
print('\033[0m', end='', flush=True)


def set_output(key: str, value: str) -> None:
Expand Down
38 changes: 38 additions & 0 deletions devtools/gha/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from http.client import HTTPSConnection
import json
import os
import sys

_token = os.environ.get('GITHUB_TOKEN')
if not _token:
import getpass
_token = getpass.getpass('GitHub token: ')

repo = os.environ.get('GITHUB_REPOSITORY', 'evalf/nutils')

host = 'api.github.com'
_conn = HTTPSConnection(host)

def _request(method, url, *, desired_status=200):
_conn.request(
method,
url,
headers={
'Host': host,
'User-Agent': 'Nutils devtools',
'Accept': 'application/vnd.github+json',
'Authorization': f'Bearer {_token}',
'X-GitHub-Api-Version': '2022-11-28',
},
)
response = _conn.getresponse()
if response.status != desired_status:
raise RuntimeError(f'ERROR: {method} https://{host}{url} failed: {response.status} {response.reason}')
return response.read()

def list_workflow_run_artifacts(run_id: str):
# TODO: implement pagination: https://docs.github.com/en/rest/using-the-rest-api/using-pagination-in-the-rest-api?apiVersion=2022-11-28
return json.loads(_request('GET', f'/repos/{repo}/actions/runs/{run_id}/artifacts'))['artifacts']

def delete_artifact(artifact_id: str):
_request('DELETE', f'/repos/{repo}/actions/artifacts/{artifact_id}', desired_status=204)
55 changes: 0 additions & 55 deletions devtools/gha/coverage_report_xml.py

This file was deleted.

12 changes: 12 additions & 0 deletions devtools/gha/delete_coverage_artifacts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from . import api
from .. import log
import os

run_id = os.environ.get('GITHUB_RUN_ID')
if not run_id:
raise RuntimeError('ERROR: environment variable GITHUB_RUN_ID not set')

for artifact in api.list_workflow_run_artifacts(run_id):
if artifact['name'].startswith('_coverage_'):
log.debug(f'deleting {artifact["name"]}')
api.delete_artifact(artifact['id'])
Loading

0 comments on commit f640e1b

Please sign in to comment.