diff --git a/.meta.toml b/.meta.toml index 8f865b8..412e846 100644 --- a/.meta.toml +++ b/.meta.toml @@ -17,7 +17,6 @@ with-free-threaded-python = false use-flake8 = true additional-envlist = [ "py311-datetime", - "combined-coverage", ] testenv-deps = [ "datetime: DateTime", @@ -29,34 +28,29 @@ testenv-setenv = [ ] testenv-commands = [ "python -V", - "pytest --cov=src --cov=tests --cov-report= tests {posargs}", + # A single Python version cannot reach the required coverage, only the + # combination of all of them can, thus the check is disabled here and + # done in the `coverage` environment. + "pytest --cov=src --cov=tests --cov-report= --cov-fail-under=0 tests {posargs}", ] -testenv-additional = [ - "", - "[testenv:combined-coverage]", - "basepython = python3", - "allowlist_externals =", - " mkdir", - "deps =", - " coverage", - " -cconstraints.txt", - "setenv =", - " COVERAGE_FILE=.coverage", - "commands =", - " mkdir -p {toxinidir}/parts/htmlcov", - " coverage erase", - " coverage combine", - " coverage html", - " coverage report -m --fail-under=100", - "depends = py310,py311,py311-datetime,py312,py313,py314,coverage", +coverage-command = [ + "coverage erase", + "coverage combine", ] -coverage-command = "pytest --cov=src --cov=tests --cov-report= tests {posargs}" coverage-setenv = [ "COVERAGE_FILE=.coverage", ] +coverage-additional = [ + "depends = py310,py311,py311-datetime,py312,py313,py314,py315", + ] [coverage] -fail-under = 97.1 +fail-under = 100 + +[coverage-run] +additional-config = [ + "relative_files = true", + ] [isort] additional-sources = "{toxinidir}/tests" diff --git a/CHANGES.rst b/CHANGES.rst index aa7865d..c05a3bd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,12 @@ Changes 8.5 (unreleased) ---------------- +- Fix the combined coverage report: the ``coverage`` tox environment now + combines the coverage data of all supported Python versions instead of + measuring a single one, and enforces 100 % coverage. The broken + ``combined-coverage`` environment has been removed, as it erased the data it + was supposed to combine. + 8.4 (2026-07-10) ---------------- diff --git a/pyproject.toml b/pyproject.toml index 5aa14cb..1725a19 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,9 +65,10 @@ Changelog = "https://github.com/zopefoundation/RestrictedPython/blob/master/CHAN [tool.coverage.run] branch = true source = ["RestrictedPython"] +relative_files = true [tool.coverage.report] -fail_under = 97.1 +fail_under = 100 precision = 2 ignore_errors = true show_missing = true diff --git a/src/RestrictedPython/Eval.py b/src/RestrictedPython/Eval.py index 3eda806..9bbab25 100644 --- a/src/RestrictedPython/Eval.py +++ b/src/RestrictedPython/Eval.py @@ -32,7 +32,7 @@ class _GetItem(typing.Protocol[_TK, _TV]): - def __getitem__(self, key: _TK) -> _TV: ... + def __getitem__(self, key: _TK) -> _TV: ... # pragma: no cover def default_guarded_getitem(ob: _GetItem[_TK, _TV], index: _TK) -> _TV: diff --git a/src/RestrictedPython/Limits.py b/src/RestrictedPython/Limits.py index 4c933ad..f91ff54 100644 --- a/src/RestrictedPython/Limits.py +++ b/src/RestrictedPython/Limits.py @@ -17,16 +17,16 @@ limited_builtins: dict[str, typing.Any] = {} -@typing.overload +@typing.overload # pragma: no cover def limited_range(iFirst: int) -> collections.abc.Sequence[int]: ... -@typing.overload +@typing.overload # pragma: no cover def limited_range(iStart: int, iEnd: int, / ) -> collections.abc.Sequence[int]: ... -@typing.overload +@typing.overload # pragma: no cover def limited_range(iStart: int, iEnd: int, iStep: int, / ) -> collections.abc.Sequence[int]: ... diff --git a/tests/test_compile_restricted_function.py b/tests/test_compile_restricted_function.py index b282ad4..6f0c0f0 100644 --- a/tests/test_compile_restricted_function.py +++ b/tests/test_compile_restricted_function.py @@ -269,6 +269,41 @@ def test_compile_restricted_function_pre_parse_exec(): assert hello_world() == 'Hello World!\n' +def test_compile_restricted_function_pre_parse_eval(): + p = '' + body = ast.parse('collected.append("Hello World!")', mode="eval") + name = "hello_world" + global_symbols = [] + + result = compile_restricted_function( + p, # parameters + body, + name, + filename='', + globalize=global_symbols + ) + + assert result.code is not None + assert result.errors == () + + collected = [] + safe_globals = { + '__name__': 'script', + '_getattr_': getattr, + '_print_': PrintCollector, + '__builtins__': safe_builtins, + 'collected': collected, + } + safe_locals = {} + exec(result.code, safe_globals, safe_locals) + hello_world = safe_locals['hello_world'] + assert type(hello_world) is FunctionType + # An `ast.Expression` body has no `return` statement, so the function + # itself returns `None`, but the expression is evaluated. + assert hello_world() is None + assert collected == ['Hello World!'] + + def test_compile_restricted_function_pre_parse_single(): p = '' body = ast.parse(""" diff --git a/tox.ini b/tox.ini index 2cd39fd..0e557aa 100644 --- a/tox.ini +++ b/tox.ini @@ -14,7 +14,6 @@ envlist = docs coverage py311-datetime - combined-coverage [testenv] usedevelop = true @@ -30,29 +29,12 @@ setenv = COVERAGE_FILE=.coverage.{envname} commands = python -V - pytest --cov=src --cov=tests --cov-report= tests {posargs} + pytest --cov=src --cov=tests --cov-report= --cov-fail-under=0 tests {posargs} sphinx-build -b doctest -d {envdir}/.cache/doctrees docs {envdir}/.cache/doctest extras = test docs -[testenv:combined-coverage] -basepython = python3 -allowlist_externals = - mkdir -deps = - coverage - -cconstraints.txt -setenv = - COVERAGE_FILE=.coverage -commands = - mkdir -p {toxinidir}/parts/htmlcov - coverage erase - coverage combine - coverage html - coverage report -m --fail-under=100 -depends = py310,py311,py311-datetime,py312,py313,py314,coverage - [testenv:setuptools-latest] basepython = python3 deps = @@ -111,7 +93,9 @@ setenv = COVERAGE_FILE=.coverage commands = mkdir -p {toxinidir}/parts/htmlcov - pytest --cov=src --cov=tests --cov-report= tests {posargs} + coverage erase + coverage combine coverage run -a -m sphinx -b doctest -d {envdir}/.cache/doctrees docs {envdir}/.cache/doctest coverage html coverage report +depends = py310,py311,py311-datetime,py312,py313,py314,py315