[med-svn] [Git][med-team/cwltest][upstream] New upstream version 2.6.20250818005349
Michael R. Crusoe (@crusoe)
gitlab at salsa.debian.org
Tue Sep 30 14:49:37 BST 2025
Michael R. Crusoe pushed to branch upstream at Debian Med / cwltest
Commits:
68eb88a7 by Michael R. Crusoe at 2025-09-30T14:56:01+02:00
New upstream version 2.6.20250818005349
- - - - -
29 changed files:
- .github/workflows/ci-tests.yml
- .github/workflows/codeql-analysis.yml
- Makefile
- PKG-INFO
- README.rst
- cwltest.egg-info/PKG-INFO
- cwltest.egg-info/SOURCES.txt
- cwltest.egg-info/requires.txt
- cwltest/_version.py
- cwltest/argparser.py
- cwltest/compare.py
- cwltest/hooks.py
- cwltest/main.py
- cwltest/plugin.py
- cwltest/utils.py
- dev-requirements.txt
- docs/requirements.txt
- mypy-requirements.txt
- pyproject.toml
- requirements.txt
- tests/test_badgedir.py
- tests/test_categories.py
- tests/test_compare.py
- + tests/test_numeric_id.py
- tests/test_plugin.py
- tests/test_short_names.py
- tests/test_timeout.py
- tests/util.py
- tox.ini
Changes:
=====================================
.github/workflows/ci-tests.yml
=====================================
@@ -15,14 +15,14 @@ jobs:
tox:
name: CI tests via Tox
- runs-on: ubuntu-20.04 # 22.04 doesn't support Python 3.6
+ runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# The README.rst file mentions the versions tested, please update it as well
py-ver-major: [3]
- py-ver-minor: [8, 9, 10, 11, 12, 13]
+ py-ver-minor: [9, 10, 11, 12, 13, 14]
step: [lint, unit, mypy, bandit]
env:
@@ -30,7 +30,7 @@ jobs:
TOXENV: ${{ format('py{0}{1}-{2}', matrix.py-ver-major, matrix.py-ver-minor, matrix.step) }}
steps:
- - uses: actions/checkout at v4
+ - uses: actions/checkout at v5
with:
fetch-depth: 0
@@ -50,6 +50,9 @@ jobs:
pip install -U pip setuptools wheel
pip install "tox>4,<5" "tox-gh-actions>3"
+ - name: install dev libraries
+ run: sudo apt-get install -y libxml2-dev libxslt-dev
+
- name: MyPy cache
if: ${{ matrix.step == 'mypy' }}
uses: actions/cache at v4
@@ -70,18 +73,18 @@ jobs:
tox-style:
name: CI linters via Tox
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
strategy:
matrix:
step: [lintreadme, pydocstyle]
env:
- py-semver: "3.12"
- TOXENV: ${{ format('py312-{0}', matrix.step) }}
+ py-semver: "3.13"
+ TOXENV: ${{ format('py313-{0}', matrix.step) }}
steps:
- - uses: actions/checkout at v4
+ - uses: actions/checkout at v5
with:
fetch-depth: 0
@@ -110,15 +113,15 @@ jobs:
release_test:
name: cwltest release test
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
steps:
- - uses: actions/checkout at v4
+ - uses: actions/checkout at v5
- name: Set up Python
uses: actions/setup-python at v5
with:
- python-version: "3.12"
+ python-version: "3.13"
cache: pip
cache-dependency-path: |
requirements.txt
=====================================
.github/workflows/codeql-analysis.yml
=====================================
@@ -19,7 +19,7 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout at v4
+ uses: actions/checkout at v5
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
=====================================
Makefile
=====================================
@@ -29,7 +29,7 @@ PYSOURCES=$(wildcard ${MODULE}/**.py tests/*.py)
DEVPKGS=-rdev-requirements.txt -rtest-requirements.txt -rmypy-requirements.txt
DEBDEVPKGS=pep8 python-autopep8 pylint python-coverage pydocstyle sloccount \
python-flake8 python-mock shellcheck
-VERSION=2.5.$(shell TZ=UTC git log --first-parent --max-count=1 \
+VERSION=2.6.$(shell TZ=UTC git log --first-parent --max-count=1 \
--format=format:%cd --date=format-local:%Y%m%d%H%M%S)
## all : default task (install in dev mode)
@@ -168,7 +168,7 @@ mypy: $(PYSOURCES)
MYPYPATH=$$MYPYPATH:mypy-stubs mypy $^
pyupgrade: $(filter-out schema_salad/metaschema.py,$(PYSOURCES))
- pyupgrade --exit-zero-even-if-changed --py38-plus $^
+ pyupgrade --exit-zero-even-if-changed --py39-plus $^
auto-walrus $^
release-test: FORCE
=====================================
PKG-INFO
=====================================
@@ -1,6 +1,6 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
Name: cwltest
-Version: 2.5.20241122133319
+Version: 2.6.20250818005349
Summary: Common Workflow Language testing framework
Author-email: Common workflow language working group <common-workflow-language at googlegroups.com>
License: Apache 2.0
@@ -12,23 +12,23 @@ Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: POSIX
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Development Status :: 5 - Production/Stable
-Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
Classifier: Typing :: Typed
-Requires-Python: <3.14,>=3.8
+Requires-Python: <3.15,>=3.9
Description-Content-Type: text/x-rst
License-File: LICENSE
Requires-Dist: schema-salad<9,>=5.0.20200220195218
Requires-Dist: junit-xml>=1.8
Requires-Dist: pytest<9,>=7
Requires-Dist: defusedxml
-Requires-Dist: importlib_resources>=1.4; python_version < "3.9"
Provides-Extra: pytest-plugin
Requires-Dist: pytest; extra == "pytest-plugin"
+Dynamic: license-file
##########################################
Common Workflow Language testing framework
@@ -64,7 +64,7 @@ This is a testing tool for checking the output of Tools and Workflows described
with the Common Workflow Language. Among other uses, it is used to run the CWL
conformance tests.
-This is written and tested for Python 3.8, 3.9, 3.10, 3.11, 3.12, and 3.13.
+This is written and tested for Python 3.9, 3.10, 3.11, 3.12, and 3.13.
.. contents:: Table of Contents
:local:
=====================================
README.rst
=====================================
@@ -32,7 +32,7 @@ This is a testing tool for checking the output of Tools and Workflows described
with the Common Workflow Language. Among other uses, it is used to run the CWL
conformance tests.
-This is written and tested for Python 3.8, 3.9, 3.10, 3.11, 3.12, and 3.13.
+This is written and tested for Python 3.9, 3.10, 3.11, 3.12, and 3.13.
.. contents:: Table of Contents
:local:
=====================================
cwltest.egg-info/PKG-INFO
=====================================
@@ -1,6 +1,6 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
Name: cwltest
-Version: 2.5.20241122133319
+Version: 2.6.20250818005349
Summary: Common Workflow Language testing framework
Author-email: Common workflow language working group <common-workflow-language at googlegroups.com>
License: Apache 2.0
@@ -12,23 +12,23 @@ Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: POSIX
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Development Status :: 5 - Production/Stable
-Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
Classifier: Typing :: Typed
-Requires-Python: <3.14,>=3.8
+Requires-Python: <3.15,>=3.9
Description-Content-Type: text/x-rst
License-File: LICENSE
Requires-Dist: schema-salad<9,>=5.0.20200220195218
Requires-Dist: junit-xml>=1.8
Requires-Dist: pytest<9,>=7
Requires-Dist: defusedxml
-Requires-Dist: importlib_resources>=1.4; python_version < "3.9"
Provides-Extra: pytest-plugin
Requires-Dist: pytest; extra == "pytest-plugin"
+Dynamic: license-file
##########################################
Common Workflow Language testing framework
@@ -64,7 +64,7 @@ This is a testing tool for checking the output of Tools and Workflows described
with the Common Workflow Language. Among other uses, it is used to run the CWL
conformance tests.
-This is written and tested for Python 3.8, 3.9, 3.10, 3.11, 3.12, and 3.13.
+This is written and tested for Python 3.9, 3.10, 3.11, 3.12, and 3.13.
.. contents:: Table of Contents
:local:
=====================================
cwltest.egg-info/SOURCES.txt
=====================================
@@ -99,6 +99,7 @@ tests/test_exclude_tags.py
tests/test_integer_id.py
tests/test_invalid_outputs.py
tests/test_multi_lined_doc.py
+tests/test_numeric_id.py
tests/test_plugin.py
tests/test_prepare.py
tests/test_short_names.py
=====================================
cwltest.egg-info/requires.txt
=====================================
@@ -3,8 +3,5 @@ junit-xml>=1.8
pytest<9,>=7
defusedxml
-[:python_version < "3.9"]
-importlib_resources>=1.4
-
[pytest-plugin]
pytest
=====================================
cwltest/_version.py
=====================================
@@ -1,16 +1,34 @@
-# file generated by setuptools_scm
+# file generated by setuptools-scm
# don't change, don't track in version control
+
+__all__ = [
+ "__version__",
+ "__version_tuple__",
+ "version",
+ "version_tuple",
+ "__commit_id__",
+ "commit_id",
+]
+
TYPE_CHECKING = False
if TYPE_CHECKING:
- from typing import Tuple, Union
+ from typing import Tuple
+ from typing import Union
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
+ COMMIT_ID = Union[str, None]
else:
VERSION_TUPLE = object
+ COMMIT_ID = object
version: str
__version__: str
__version_tuple__: VERSION_TUPLE
version_tuple: VERSION_TUPLE
+commit_id: COMMIT_ID
+__commit_id__: COMMIT_ID
+
+__version__ = version = '2.6.20250818005349'
+__version_tuple__ = version_tuple = (2, 6, 20250818005349)
-__version__ = version = '2.5.20241122133319'
-__version_tuple__ = version_tuple = (2, 5, 20241122133319)
+__commit_id__ = commit_id = None
=====================================
cwltest/argparser.py
=====================================
@@ -107,7 +107,8 @@ def arg_parser() -> argparse.ArgumentParser:
parser.add_argument(
"--badgedir",
type=str,
- help="Create JSON badges and store them in this directory.",
+ help="Create JSON badges, one for each tag (plus a computed 'all' tag) "
+ " and store them in this directory.",
)
try:
=====================================
cwltest/compare.py
=====================================
@@ -2,7 +2,8 @@
import hashlib
import json
-from typing import Any, Callable, Dict, Optional, Set
+from typing import Any, Callable, Optional
+
import cwltest.stdfsaccess
fs_access = cwltest.stdfsaccess.StdFsAccess("")
@@ -26,7 +27,7 @@ class CompareFail(Exception):
def _check_keys(
- keys: Set[str], expected: Dict[str, Any], actual: Dict[str, Any], skip_details: bool
+ keys: set[str], expected: dict[str, Any], actual: dict[str, Any], skip_details: bool
) -> None:
for k in keys:
try:
@@ -37,7 +38,7 @@ def _check_keys(
) from e
-def _compare_contents(expected: Dict[str, Any], actual: Dict[str, Any]) -> None:
+def _compare_contents(expected: dict[str, Any], actual: dict[str, Any]) -> None:
with open(actual["path"]) as f:
actual_contents = f.read()
if (expected_contents := expected["contents"]) != actual_contents:
@@ -52,7 +53,7 @@ def _compare_contents(expected: Dict[str, Any], actual: Dict[str, Any]) -> None:
def _compare_dict(
- expected: Dict[str, Any], actual: Dict[str, Any], skip_details: bool
+ expected: dict[str, Any], actual: dict[str, Any], skip_details: bool
) -> None:
for c in expected:
try:
@@ -68,7 +69,7 @@ def _compare_dict(
def _compare_directory(
- expected: Dict[str, Any], actual: Dict[str, Any], skip_details: bool
+ expected: dict[str, Any], actual: dict[str, Any], skip_details: bool
) -> None:
if actual.get("class") != "Directory":
raise CompareFail.format(
@@ -97,7 +98,7 @@ def _compare_directory(
def _compare_file(
- expected: Dict[str, Any], actual: Dict[str, Any], skip_details: bool
+ expected: dict[str, Any], actual: dict[str, Any], skip_details: bool
) -> None:
_compare_location(expected, actual, skip_details)
if "contents" in expected:
@@ -117,7 +118,7 @@ def _compare_file(
def _compare_location(
- expected: Dict[str, Any], actual: Dict[str, Any], skip_details: bool
+ expected: dict[str, Any], actual: dict[str, Any], skip_details: bool
) -> None:
if "path" in expected:
expected_comp = "path"
@@ -160,7 +161,7 @@ def _compare_location(
)
-def _compare_checksum(expected: Dict[str, Any], actual: Dict[str, Any]) -> None:
+def _compare_checksum(expected: dict[str, Any], actual: dict[str, Any]) -> None:
if "path" in actual:
path = actual["path"]
else:
@@ -195,7 +196,7 @@ def _compare_checksum(expected: Dict[str, Any], actual: Dict[str, Any]) -> None:
)
-def _compare_size(expected: Dict[str, Any], actual: Dict[str, Any]) -> None:
+def _compare_size(expected: dict[str, Any], actual: dict[str, Any]) -> None:
if "path" in actual:
path = actual["path"]
else:
=====================================
cwltest/hooks.py
=====================================
@@ -1,13 +1,13 @@
"""Hooks for pytest-cwl users."""
-from typing import Any, Dict, Optional, Tuple
+from typing import Any, Optional
from cwltest import utils
def pytest_cwl_execute_test( # type: ignore[empty-body]
config: utils.CWLTestConfig, processfile: str, jobfile: Optional[str]
-) -> Tuple[int, Optional[Dict[str, Any]]]:
+) -> tuple[int, Optional[dict[str, Any]]]:
"""
Execute CWL test using a Python function instead of a command line runner.
=====================================
cwltest/main.py
=====================================
@@ -6,12 +6,15 @@ import os
import sys
from collections import Counter, defaultdict
from concurrent.futures import ThreadPoolExecutor
-from typing import Dict, List, Optional, Set, cast
+from typing import Optional, cast
import junit_xml
import schema_salad.avro
import schema_salad.ref_resolver
import schema_salad.schema
+from schema_salad.exceptions import ValidationException
+
+from cwltest import logger, utils
from cwltest.argparser import arg_parser
from cwltest.utils import (
CWLTestConfig,
@@ -19,9 +22,6 @@ from cwltest.utils import (
TestResult,
load_optional_fsaccess_plugin,
)
-from schema_salad.exceptions import ValidationException
-
-from cwltest import logger, utils
if sys.stderr.isatty():
PREFIX = "\r"
@@ -33,7 +33,7 @@ else:
def _run_test(
args: argparse.Namespace,
- test: Dict[str, str],
+ test: dict[str, str],
test_number: int,
total_tests: int,
) -> TestResult:
@@ -78,8 +78,8 @@ def _run_test(
return utils.run_test_plain(config, test, test_number)
-def _expand_number_range(nr: str) -> List[int]:
- result: List[int] = []
+def _expand_number_range(nr: str) -> list[int]:
+ result: list[int] = []
for s in nr.split(","):
sp = s.split("-")
if len(sp) == 2:
@@ -123,8 +123,8 @@ def main() -> int:
load_optional_fsaccess_plugin()
- ntotal: Dict[str, int] = Counter()
- npassed: Dict[str, List[CWLTestReport]] = defaultdict(list)
+ ntotal: dict[str, int] = Counter()
+ npassed: dict[str, list[CWLTestReport]] = defaultdict(list)
if args.only_tools:
alltests = tests
@@ -161,11 +161,11 @@ def main() -> int:
logger.warning("The `id` field is missing.")
if args.show_tags:
- alltags: Set[str] = set()
+ alltags: set[str] = set()
for t in tests:
ts = t.get("tags", [])
alltags |= set(ts)
- for tag in alltags:
+ for tag in sorted(alltags):
print(tag)
return 0
=====================================
cwltest/plugin.py
=====================================
@@ -3,36 +3,22 @@
import argparse
import json
import os
+import pickle # nosec
import time
import traceback
+from collections.abc import Iterator
from io import StringIO
from pathlib import Path
-from typing import (
- TYPE_CHECKING,
- Any,
- Dict,
- Iterator,
- List,
- Optional,
- Protocol,
- Set,
- Tuple,
- Union,
- cast,
-)
+from typing import TYPE_CHECKING, Any, Optional, Protocol, Union, cast
from urllib.parse import urljoin
import pytest
-from cwltest.compare import CompareFail, compare
from cwltest import REQUIRED, UNSUPPORTED_FEATURE, logger, utils
+from cwltest.compare import CompareFail, compare
if TYPE_CHECKING:
- from _pytest._code.code import ExceptionInfo, TracebackStyle
- from _pytest.config import Config
- from _pytest.config import Config as PytestConfig
- from _pytest.config import PytestPluginManager
- from _pytest.config.argparsing import Parser as PytestParser
+ from _pytest._code.code import TracebackStyle
from _pytest.nodes import Node
from pluggy import HookCaller
@@ -42,12 +28,12 @@ class TestRunner(Protocol):
def __call__(
self, config: utils.CWLTestConfig, processfile: str, jobfile: Optional[str]
- ) -> List[Optional[Dict[str, Any]]]:
+ ) -> list[Optional[dict[str, Any]]]:
"""Type signature for pytest_cwl_execute_test hook results."""
...
-def _get_comma_separated_option(config: "Config", name: str) -> List[str]:
+def _get_comma_separated_option(config: pytest.Config, name: str) -> list[str]:
options = config.getoption(name)
if options is None:
return []
@@ -58,7 +44,7 @@ def _get_comma_separated_option(config: "Config", name: str) -> List[str]:
def _run_test_hook_or_plain(
- test: Dict[str, str],
+ test: dict[str, str],
config: utils.CWLTestConfig,
hook: "HookCaller",
) -> utils.TestResult:
@@ -76,7 +62,7 @@ def _run_test_hook_or_plain(
hook_out = hook(config=config, processfile=processfile, jobfile=jobfile)
if not hook_out:
return utils.run_test_plain(config, test)
- returncode, out = cast(Tuple[int, Optional[Dict[str, Any]]], hook_out[0])
+ returncode, out = cast(tuple[int, Optional[dict[str, Any]]], hook_out[0])
duration = time.time() - start_time
outstr = json.dumps(out) if out is not None else "{}"
if returncode == UNSUPPORTED_FEATURE:
@@ -164,7 +150,7 @@ class CWLItem(pytest.Item):
self,
name: str,
parent: Optional["Node"],
- spec: Dict[str, Any],
+ spec: dict[str, Any],
) -> None:
"""Initialize this CWLItem."""
super().__init__(name, parent)
@@ -198,7 +184,7 @@ class CWLItem(pytest.Item):
hook,
)
cwl_results = self.config.cwl_results # type: ignore[attr-defined]
- cast(List[Tuple[Dict[str, Any], utils.TestResult]], cwl_results).append(
+ cast(list[tuple[dict[str, Any], utils.TestResult]], cwl_results).append(
(self.spec, result)
)
if result.return_code != 0:
@@ -206,7 +192,7 @@ class CWLItem(pytest.Item):
def repr_failure(
self,
- excinfo: "ExceptionInfo[BaseException]",
+ excinfo: pytest.ExceptionInfo[BaseException],
style: Optional["TracebackStyle"] = None,
) -> str:
"""
@@ -238,7 +224,7 @@ class CWLItem(pytest.Item):
)
)
- def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]:
+ def reportinfo(self) -> tuple[Union["os.PathLike[str]", str], Optional[int], str]:
"""Status report."""
return self.path, 0, "cwl test: %s" % self.name
@@ -258,10 +244,10 @@ class CWLYamlFile(pytest.File):
def collect(self) -> Iterator[CWLItem]:
"""Load the cwltest file and yield parsed entries."""
- include: Set[str] = set(_get_comma_separated_option(self.config, "cwl_include"))
- exclude: Set[str] = set(_get_comma_separated_option(self.config, "cwl_exclude"))
- tags: Set[str] = set(_get_comma_separated_option(self.config, "cwl_tags"))
- exclude_tags: Set[str] = set(
+ include: set[str] = set(_get_comma_separated_option(self.config, "cwl_include"))
+ exclude: set[str] = set(_get_comma_separated_option(self.config, "cwl_exclude"))
+ tags: set[str] = set(_get_comma_separated_option(self.config, "cwl_tags"))
+ exclude_tags: set[str] = set(
_get_comma_separated_option(self.config, "cwl_exclude_tags")
)
tests, _ = utils.load_and_validate_tests(str(self.path))
@@ -302,7 +288,7 @@ class CWLYamlFile(pytest.File):
yield item
-__OPTIONS: List[Tuple[str, Dict[str, Any]]] = [
+__OPTIONS: list[tuple[str, dict[str, Any]]] = [
(
"--cwl-runner",
{
@@ -325,7 +311,8 @@ __OPTIONS: List[Tuple[str, Dict[str, Any]]] = [
"--cwl-badgedir",
{
"type": str,
- "help": "Create badge JSON files and store them in this directory.",
+ "help": "Create badge JSON files, one for each tag (plus a computed "
+ "'all' tag), and store them in this directory.",
},
),
(
@@ -369,7 +356,7 @@ __OPTIONS: List[Tuple[str, Dict[str, Any]]] = [
]
-def pytest_addoption(parser: "PytestParser") -> None:
+def pytest_addoption(parser: pytest.Parser) -> None:
"""Add our options to the pytest command line."""
for entry in __OPTIONS:
parser.addoption(entry[0], **entry[1])
@@ -397,21 +384,65 @@ def pytest_collect_file(
return None
-def pytest_configure(config: "PytestConfig") -> None:
+def pytest_configure(config: pytest.Config) -> None:
"""Store the raw tests and the test results."""
- cwl_results: List[Tuple[Dict[str, Any], utils.TestResult]] = []
+ cwl_results: list[tuple[dict[str, Any], utils.TestResult]] = []
config.cwl_results = cwl_results # type: ignore[attr-defined]
-def pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None:
+def _zip_results(
+ cwl_results: list[tuple[dict[str, Any], utils.TestResult]],
+) -> tuple[list[dict[str, Any]], list[utils.TestResult]]:
+ tests, results = (list(item) for item in zip(*cwl_results))
+ return tests, results
+
+
+def pytest_sessionfinish(session: pytest.Session) -> None:
"""Generate badges."""
+ cwl_badgedir = session.config.getoption("cwl_badgedir")
+ if not cwl_badgedir:
+ return
+
cwl_results = cast(
- List[Tuple[Dict[str, Any], utils.TestResult]],
- getattr(session.config, "cwl_results", None),
+ list[tuple[dict[str, Any], utils.TestResult]],
+ session.config.cwl_results, # type: ignore[attr-defined]
)
- if not cwl_results:
- return
- tests, results = (list(item) for item in zip(*cwl_results))
+
+ if session.config.pluginmanager.has_plugin("xdist"):
+ import xdist # type: ignore[import-untyped]
+
+ directory = cast(
+ pytest.TempPathFactory,
+ session.config._tmp_path_factory, # type: ignore[attr-defined]
+ ).getbasetemp()
+ if xdist.is_xdist_worker(session):
+ if not cwl_results:
+ return
+ pickle_filename = f"cwltest_{xdist.get_xdist_worker_id(session)}.pickle"
+ with (directory.parent / pickle_filename).open("wb") as handle:
+ pickle.dump(
+ _zip_results(cwl_results), handle, protocol=pickle.HIGHEST_PROTOCOL
+ )
+ return
+
+ if xdist.is_xdist_controller(session):
+ tests: list[dict[str, Any]] = []
+ results: list[utils.TestResult] = []
+ for pickle_filepath in directory.glob("cwltest_*"):
+ with pickle_filepath.open("rb") as handle:
+ new_tests, new_results = pickle.load(handle) # nosec
+ tests.extend(new_tests)
+ results.extend(new_results)
+ else:
+ if not cwl_results:
+ return
+ tests, results = _zip_results(cwl_results)
+
+ else:
+ if not cwl_results:
+ return
+ tests, results = _zip_results(cwl_results)
+
(
total,
passed,
@@ -423,11 +454,11 @@ def pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None:
nunsupported,
_,
) = utils.parse_results(results, tests)
- if cwl_badgedir := session.config.getoption("cwl_badgedir"):
- utils.generate_badges(cwl_badgedir, ntotal, npassed, nfailures, nunsupported)
+
+ utils.generate_badges(cwl_badgedir, ntotal, npassed, nfailures, nunsupported)
-def pytest_addhooks(pluginmanager: "PytestPluginManager") -> None:
+def pytest_addhooks(pluginmanager: pytest.PytestPluginManager) -> None:
"""Register our cwl hooks."""
from cwltest import hooks
=====================================
cwltest/utils.py
=====================================
@@ -8,18 +8,10 @@ import sys
import tempfile
import time
from collections import Counter, defaultdict
-from typing import (
- Any,
- Dict,
- Iterable,
- List,
- MutableMapping,
- MutableSequence,
- Optional,
- Tuple,
- Union,
- cast,
-)
+from collections.abc import Iterable, MutableMapping, MutableSequence
+from importlib.metadata import EntryPoint, entry_points
+from importlib.resources import files
+from typing import Any, Optional, Union, cast
from urllib.parse import urljoin
import junit_xml
@@ -27,25 +19,14 @@ import ruamel.yaml.scanner
import schema_salad.avro
import schema_salad.ref_resolver
import schema_salad.schema
-import cwltest.compare
-import cwltest.stdfsaccess
-from cwltest.compare import CompareFail, compare
from rdflib import Graph
from ruamel.yaml.scalarstring import ScalarString
from schema_salad.exceptions import ValidationException
-if sys.version_info >= (3, 9):
- from importlib.resources import as_file, files
-else:
- from importlib_resources import as_file, files
-
-# available since Python 3.8 (minimum version supports as of this
-# writing) so we don't need to fuss with backports
-from importlib.metadata import entry_points, EntryPoint
-
+import cwltest.compare
+import cwltest.stdfsaccess
from cwltest import REQUIRED, UNSUPPORTED_FEATURE, logger, templock
-
-__all__ = ["files", "as_file"]
+from cwltest.compare import CompareFail, compare
class CWLTestConfig:
@@ -61,8 +42,8 @@ class CWLTestConfig:
outdir: Optional[str] = None,
classname: Optional[str] = None,
tool: Optional[str] = None,
- args: Optional[List[str]] = None,
- testargs: Optional[List[str]] = None,
+ args: Optional[list[str]] = None,
+ testargs: Optional[list[str]] = None,
timeout: Optional[int] = None,
verbose: Optional[bool] = None,
runner_quiet: Optional[bool] = None,
@@ -77,8 +58,8 @@ class CWLTestConfig:
self.test_baseuri, os.path.basename(entry) + f"#L{entry_line}"
)
self.tool: str = tool or "cwl-runner"
- self.args: List[str] = args or []
- self.testargs: List[str] = testargs or []
+ self.args: list[str] = args or []
+ self.testargs: list[str] = testargs or []
self.timeout: Optional[int] = timeout
self.verbose: bool = verbose or False
self.runner_quiet: bool = runner_quiet or True
@@ -88,7 +69,12 @@ class CWLTestReport:
"""Encapsulate relevant test result data for a markdown report."""
def __init__(
- self, id: str, category: List[str], entry: str, tool: str, job: Optional[str]
+ self,
+ id: Union[int, str],
+ category: list[str],
+ entry: str,
+ tool: str,
+ job: Optional[str],
) -> None:
"""Initialize a CWLTestReport object."""
self.id = id
@@ -124,7 +110,7 @@ class TestResult:
self.tool = tool
self.job = job
- def create_test_case(self, test: Dict[str, Any]) -> junit_xml.TestCase:
+ def create_test_case(self, test: dict[str, Any]) -> junit_xml.TestCase:
"""Create a jUnit XML test case from this test result."""
doc = test.get("doc", "N/A").strip()
if test.get("tags"):
@@ -144,7 +130,8 @@ class TestResult:
case.failure_message = self.message
return case
- def create_report_entry(self, test: Dict[str, Any]) -> CWLTestReport:
+ def create_report_entry(self, test: dict[str, Any]) -> CWLTestReport:
+ """Package test result into a CWLTestReport."""
return CWLTestReport(
test.get("id", "no-id"),
test.get("tags", ["required"]),
@@ -154,7 +141,7 @@ class TestResult:
)
-def _clean_ruamel_list(obj: List[Any]) -> Any:
+def _clean_ruamel_list(obj: list[Any]) -> Any:
"""Entrypoint to transform roundtrip loaded ruamel.yaml to plain objects."""
new_list = []
for entry in obj:
@@ -188,10 +175,10 @@ def _clean_ruamel(obj: Any) -> Any:
def generate_badges(
badgedir: str,
- ntotal: Dict[str, int],
- npassed: Dict[str, List[CWLTestReport]],
- nfailures: Dict[str, List[CWLTestReport]],
- nunsupported: Dict[str, List[CWLTestReport]],
+ ntotal: dict[str, int],
+ npassed: dict[str, list[CWLTestReport]],
+ nfailures: dict[str, list[CWLTestReport]],
+ nunsupported: dict[str, list[CWLTestReport]],
) -> None:
"""Generate badges with conformance levels."""
os.mkdir(badgedir)
@@ -217,10 +204,9 @@ def generate_badges(
with open(f"{badgedir}/{t}.md", "w") as out:
print(f"# `{t}` tests", file=out)
-
print("## List of passed tests", file=out)
for e in npassed[t]:
- base = f"[{shortname(e.id)}]({e.entry})"
+ base = f"[{shortname(str(e.id))}]({e.entry})"
tool = f"[tool]({e.tool})"
if e.job:
arr = [tool, f"[job]({e.job})"]
@@ -231,7 +217,7 @@ def generate_badges(
print("## List of failed tests", file=out)
for e in nfailures[t]:
- base = f"[{shortname(e.id)}]({e.entry})"
+ base = f"[{shortname(str(e.id))}]({e.entry})"
tool = f"[tool]({e.tool})"
if e.job:
arr = [tool, f"[job]({e.job})"]
@@ -242,7 +228,7 @@ def generate_badges(
print("## List of unsupported tests", file=out)
for e in nunsupported[t]:
- base = f"[{shortname(e.id)}]({e.entry})"
+ base = f"[{shortname(str(e.id))}]({e.entry})"
tool = f"[tool]({e.tool})"
if e.job:
arr = [tool, f"[job]({e.job})"]
@@ -253,7 +239,7 @@ def generate_badges(
def get_test_number_by_key(
- tests: List[Dict[str, str]], key: str, value: str
+ tests: list[dict[str, str]], key: str, value: str
) -> Optional[int]:
"""Retrieve the test index from its name."""
for i, test in enumerate(tests):
@@ -262,7 +248,7 @@ def get_test_number_by_key(
return None
-def load_and_validate_tests(path: str) -> Tuple[Any, Dict[str, Any]]:
+def load_and_validate_tests(path: str) -> tuple[Any, dict[str, Any]]:
"""
Load and validate the given test file against the cwltest schema.
@@ -270,7 +256,7 @@ def load_and_validate_tests(path: str) -> Tuple[Any, Dict[str, Any]]:
"""
schema_resource = files("cwltest").joinpath("cwltest-schema.yml")
with schema_resource.open("r", encoding="utf-8") as fp:
- cache: Optional[Dict[str, Union[str, Graph, bool]]] = {
+ cache: Optional[dict[str, Union[str, Graph, bool]]] = {
"https://w3id.org/cwl/cwltest/cwltest-schema.yml": fp.read()
}
(
@@ -289,30 +275,33 @@ def load_and_validate_tests(path: str) -> Tuple[Any, Dict[str, Any]]:
tests, metadata = schema_salad.schema.load_and_validate(
document_loader, avsc_names, path, True
)
- tests = cast(List[Dict[str, Any]], _clean_ruamel_list(tests))
+ tests = cast(list[dict[str, Any]], _clean_ruamel_list(tests))
return tests, metadata
def parse_results(
results: Iterable[TestResult],
- tests: List[Dict[str, Any]],
+ tests: list[dict[str, Any]],
suite_name: Optional[str] = None,
report: Optional[junit_xml.TestSuite] = None,
-) -> Tuple[
+) -> tuple[
int, # total
int, # passed
int, # failures
int, # unsupported
- Dict[str, int], # total for each tag
- Dict[str, List[CWLTestReport]], # passed for each tag
- Dict[str, List[CWLTestReport]], # failures for each tag
- Dict[str, List[CWLTestReport]], # unsupported for each tag
+ dict[str, int], # total for each tag
+ dict[str, list[CWLTestReport]], # passed for each tag
+ dict[str, list[CWLTestReport]], # failures for each tag
+ dict[str, list[CWLTestReport]], # unsupported for each tag
Optional[junit_xml.TestSuite],
]:
"""
Parse the results and return statistics and an optional report.
+ An additional tag named "all" will be computed, containing all the test
+ results.
+
Returns the total number of tests, dictionary of test counts
(total, passed, failed, unsupported) by tag, and a jUnit XML report.
"""
@@ -320,10 +309,10 @@ def parse_results(
passed = 0
failures = 0
unsupported = 0
- ntotal: Dict[str, int] = Counter()
- nfailures: Dict[str, List[CWLTestReport]] = defaultdict(list)
- nunsupported: Dict[str, List[CWLTestReport]] = defaultdict(list)
- npassed: Dict[str, List[CWLTestReport]] = defaultdict(list)
+ ntotal: dict[str, int] = Counter()
+ nfailures: dict[str, list[CWLTestReport]] = defaultdict(list)
+ nunsupported: dict[str, list[CWLTestReport]] = defaultdict(list)
+ npassed: dict[str, list[CWLTestReport]] = defaultdict(list)
for i, test_result in enumerate(results):
test_case = test_result.create_test_case(tests[i])
@@ -334,7 +323,7 @@ def parse_results(
else "cwltest:#{i + 1}"
)
total += 1
- tags = tests[i].get("tags", [])
+ tags = tests[i].get("tags", []) + ["all"]
for t in tags:
ntotal[t] += 1
@@ -376,12 +365,12 @@ def parse_results(
def prepare_test_command(
tool: str,
- args: List[str],
- testargs: Optional[List[str]],
- test: Dict[str, Any],
+ args: list[str],
+ testargs: Optional[list[str]],
+ test: dict[str, Any],
cwd: str,
quiet: Optional[bool] = True,
-) -> List[str]:
+) -> list[str]:
"""Turn the test into a command line."""
test_command = [tool]
test_command.extend(args)
@@ -416,9 +405,9 @@ def prepare_test_command(
def prepare_test_paths(
- test: Dict[str, str],
+ test: dict[str, str],
cwd: str,
-) -> Tuple[str, Optional[str]]:
+) -> tuple[str, Optional[str]]:
"""Determine the test path and the tool path."""
cwd = schema_salad.ref_resolver.file_uri(cwd)
processfile = test["tool"]
@@ -434,13 +423,13 @@ def prepare_test_paths(
def run_test_plain(
config: CWLTestConfig,
- test: Dict[str, str],
+ test: dict[str, str],
test_number: Optional[int] = None,
) -> TestResult:
"""Plain test runner."""
- out: Dict[str, Any] = {}
+ out: dict[str, Any] = {}
outstr = outerr = ""
- test_command: List[str] = []
+ test_command: list[str] = []
duration = 0.0
number = "?"
@@ -509,12 +498,12 @@ def run_test_plain(
logger.error(
"""Test %i failed: %s""",
test_number,
- " ".join([shlex.quote(tc) for tc in test_command]),
+ shlex.join(test_command),
)
else:
logger.error(
"""Test failed: %s""",
- " ".join([shlex.quote(tc) for tc in test_command]),
+ shlex.join(test_command),
)
logger.error(test.get("doc", "").replace("\n", " ").strip())
if err.returncode == UNSUPPORTED_FEATURE:
@@ -536,7 +525,7 @@ def run_test_plain(
logger.error(
"""Test %s failed: %s""",
number,
- " ".join([shlex.quote(tc) for tc in test_command]),
+ shlex.join(test_command),
)
logger.error(outstr)
logger.error("Parse error %s", str(err))
@@ -545,14 +534,14 @@ def run_test_plain(
logger.error(
"""Test %s interrupted: %s""",
number,
- " ".join([shlex.quote(tc) for tc in test_command]),
+ shlex.join(test_command),
)
raise
except json.JSONDecodeError:
logger.error(
"""Test %s failed: %s""",
number,
- " ".join([shlex.quote(tc) for tc in test_command]),
+ shlex.join(test_command),
)
logger.error(test.get("doc", "").replace("\n", " ").strip())
invalid_json_msg = "Output is not a valid JSON document: '%s'" % outstr
@@ -572,7 +561,7 @@ def run_test_plain(
logger.error(
"""Test %s timed out: %s""",
number,
- " ".join([shlex.quote(tc) for tc in test_command]),
+ shlex.join(test_command),
)
logger.error(test.get("doc", "").replace("\n", " ").strip())
# Kill and re-communicate to get the logs and reap the child, as
@@ -608,7 +597,7 @@ def run_test_plain(
logger.warning(
"""Test %s failed: %s""",
number,
- " ".join([shlex.quote(tc) for tc in test_command]),
+ shlex.join(test_command),
)
logger.warning(test.get("doc", "").replace("\n", " ").strip())
logger.warning("Returned zero but it should be non-zero")
@@ -629,7 +618,7 @@ def run_test_plain(
logger.warning(
"""Test %s failed: %s""",
number,
- " ".join([shlex.quote(tc) for tc in test_command]),
+ shlex.join(test_command),
)
logger.warning(test.get("doc", "").replace("\n", " ").strip())
logger.warning("Compare failure %s", ex)
@@ -675,15 +664,15 @@ def load_optional_fsaccess_plugin() -> None:
use that to get a filesystem access object that will be used for
checking test output.
"""
- fsaccess_eps: List[EntryPoint]
+ fsaccess_eps: list[EntryPoint]
try:
# The interface to importlib.metadata.entry_points() changed
- # several times between Python 3.8 and 3.13; the code below
+ # several times between Python 3.9 and 3.13; the code below
# actually works fine on all of them but there's no single
# mypy annotation that works across of them. Explicitly cast
# it to a consistent type to make mypy shut up.
- fsaccess_eps = cast(List[EntryPoint], entry_points()["cwltest.fsaccess"]) # type: ignore [redundant-cast, unused-ignore]
+ fsaccess_eps = cast(list[EntryPoint], entry_points()["cwltest.fsaccess"]) # type: ignore [redundant-cast, unused-ignore]
except KeyError:
return
=====================================
dev-requirements.txt
=====================================
@@ -1,5 +1,5 @@
diff_cover
-black ~= 24.10
+black ~= 25.1
pylint
pep257
pydocstyle
=====================================
docs/requirements.txt
=====================================
@@ -2,5 +2,4 @@ sphinx >= 2.2
sphinx-rtd-theme==3.0.2
sphinx-autoapi
sphinx-autodoc-typehints
-typed_ast;python_version<'3.8'
sphinxcontrib-autoprogram
=====================================
mypy-requirements.txt
=====================================
@@ -1,4 +1,4 @@
-mypy==1.13.0
+mypy==1.17.1
pytest >= 8.3, < 9
types-setuptools
types-requests
=====================================
pyproject.toml
=====================================
@@ -1,7 +1,7 @@
[build-system]
requires = [
"setuptools>=61.2",
- "setuptools_scm>=8.0.4,<9",
+ "setuptools_scm>=8.0.4,<10",
]
build-backend = "setuptools.build_meta"
@@ -17,15 +17,15 @@ classifiers = [
"Operating System :: POSIX",
"Operating System :: MacOS :: MacOS X",
"Development Status :: 5 - Production/Stable",
- "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
+ "Programming Language :: Python :: 3.14",
"Typing :: Typed",
]
-requires-python = ">=3.8,<3.14"
+requires-python = ">=3.9,<3.15"
dynamic = ["version", "dependencies"]
[project.readme]
=====================================
requirements.txt
=====================================
@@ -2,4 +2,3 @@ schema-salad >= 5.0.20200220195218, < 9
junit-xml >= 1.8
pytest >= 7, < 9
defusedxml
-importlib_resources>=1.4;python_version<'3.9'
=====================================
tests/test_badgedir.py
=====================================
@@ -1,5 +1,5 @@
-import os
import json
+import os
from pathlib import Path
from textwrap import dedent
@@ -53,6 +53,15 @@ def test_badgedir(tmp_path: Path) -> None:
assert obj.get("color", "") == "yellow"
assert (badgedir / "command_line_tool.md").exists()
+ all_tests = badgedir / "all.json"
+ assert all_tests.exists()
+ with open(all_tests) as file:
+ obj = json.load(file)
+ assert obj.get("subject", "") == "all"
+ assert obj.get("status", "") == "0%"
+ assert obj.get("color", "") == "yellow"
+ assert (badgedir / "all.md").exists()
+
def test_badgedir_report_with_baseuri(tmp_path: Path) -> None:
badgedir = tmp_path / "badgedir"
=====================================
tests/test_categories.py
=====================================
@@ -3,8 +3,6 @@ import re
from os import linesep as n
from os import sep as p
from pathlib import Path
-from typing import cast
-from xml.etree.ElementTree import Element
import defusedxml.ElementTree as ET
import schema_salad.ref_resolver
@@ -88,8 +86,28 @@ def test_category_in_junit_xml(tmp_path: Path) -> None:
]
run_with_mock_cwl_runner(args)
tree = ET.parse(junit_xml_report)
- root = tree.getroot()
- category = cast(
- Element, cast(Element, root.find("testsuite")).find("testcase")
- ).attrib["class"]
+ assert (root := tree.getroot()) is not None
+ assert (testsuite_el := root.find("testsuite")) is not None
+ assert (testcase_el := testsuite_el.find("testcase")) is not None
+ category = testcase_el.attrib["class"]
assert category == "js, init_work_dir"
+
+
+def test_list_all_tags() -> None:
+ args = [
+ "--test",
+ schema_salad.ref_resolver.file_uri(
+ get_data("tests/test-data/conformance_test_v1.2.cwltest.yaml")
+ ),
+ "--show-tags",
+ ]
+ error_code, stdout, stderr = run_with_mock_cwl_runner(args)
+ assert error_code == 0, stderr
+ assert (
+ stdout
+ == """command_line_tool
+expression_tool
+inline_javascript
+required
+"""
+ )
=====================================
tests/test_compare.py
=====================================
@@ -1,8 +1,9 @@
import os
from pathlib import Path
-from typing import Any, Dict
+from typing import Any
import pytest
+
from cwltest.compare import CompareFail, _compare_directory, _compare_file, compare
from .util import get_data
@@ -10,7 +11,7 @@ from .util import get_data
def test_compare_any_success() -> None:
expected = "Any"
- actual: Dict[str, Any] = {}
+ actual: dict[str, Any] = {}
compare(expected, actual)
@@ -385,7 +386,7 @@ def test_compare_file_failure_none() -> None:
"class": "File",
"checksum": "sha1$7448d8798a4380162d4b56f9b452e2f6f9e24e7b",
}
- actual: Dict[str, Any] = {}
+ actual: dict[str, Any] = {}
with pytest.raises(CompareFail):
compare(expected, actual)
@@ -554,7 +555,7 @@ def test_compare_list_failure_type() -> None:
"10",
]
}
- actual: Dict[str, Any] = {"args": {}}
+ actual: dict[str, Any] = {"args": {}}
with pytest.raises(CompareFail):
compare(expected, actual)
=====================================
tests/test_numeric_id.py
=====================================
@@ -0,0 +1,15 @@
+from os import linesep as n
+
+from .util import get_data, run_with_mock_cwl_runner
+
+
+def test_include_by_number() -> None:
+ args = [
+ "--test",
+ get_data("tests/test-data/exclude-tags.yml"),
+ "-n1",
+ ]
+ error_code, stdout, stderr = run_with_mock_cwl_runner(args)
+ assert f"[1/3] opt-error1: Test with label{n}" in stderr
+ assert "opt-error2" not in stderr
+ assert "opt-error3" not in stderr
=====================================
tests/test_plugin.py
=====================================
@@ -1,12 +1,10 @@
import os
import shutil
from pathlib import Path
-from typing import TYPE_CHECKING
-from .util import get_data
+import pytest
-if TYPE_CHECKING:
- from _pytest.pytester import Pytester
+from .util import get_data
def _load_v1_0_dir(path: Path) -> None:
@@ -20,7 +18,7 @@ def _load_v1_0_dir(path: Path) -> None:
shutil.copy(get_data("tests/test-data/v1.0/args.py"), inner_dir)
-def test_include(pytester: "Pytester") -> None:
+def test_include(pytester: pytest.Pytester) -> None:
"""Test the pytest plugin using cwltool as cwl-runner."""
path = pytester.copy_example("conformance_test_v1.0.cwltest.yml")
shutil.copy(
@@ -36,7 +34,7 @@ def test_include(pytester: "Pytester") -> None:
result.assert_outcomes(passed=1, skipped=1)
-def test_exclude(pytester: "Pytester") -> None:
+def test_exclude(pytester: pytest.Pytester) -> None:
"""Test the pytest plugin using cwltool as cwl-runner."""
path = pytester.copy_example("conformance_test_v1.0.cwltest.yml")
shutil.copy(
@@ -52,7 +50,7 @@ def test_exclude(pytester: "Pytester") -> None:
result.assert_outcomes(passed=0, skipped=2)
-def test_tags(pytester: "Pytester") -> None:
+def test_tags(pytester: pytest.Pytester) -> None:
"""Test the pytest plugin using cwltool as cwl-runner."""
path = pytester.copy_example("conformance_test_v1.0.cwltest.yml")
shutil.copy(
@@ -65,7 +63,7 @@ def test_tags(pytester: "Pytester") -> None:
result.assert_outcomes(passed=1, skipped=1)
-def test_exclude_tags(pytester: "Pytester") -> None:
+def test_exclude_tags(pytester: pytest.Pytester) -> None:
"""Test the pytest plugin using cwltool as cwl-runner."""
path = pytester.copy_example("conformance_test_v1.0.cwltest.yml")
shutil.copy(
@@ -81,21 +79,52 @@ def test_exclude_tags(pytester: "Pytester") -> None:
result.assert_outcomes(skipped=2)
-def test_badgedir(pytester: "Pytester") -> None:
+def test_badgedir(pytester: pytest.Pytester) -> None:
"""Test the pytest plugin creates the badges directory."""
path = pytester.copy_example("conformance_test_v1.0.cwltest.yml")
shutil.copy(
get_data("tests/test-data/cwltool-conftest.py"), path.parent / "conftest.py"
)
_load_v1_0_dir(path)
- assert not os.path.exists("cwl-badges")
- pytester.runpytest(
- "-k", "conformance_test_v1.0.cwltest.yml", "--cwl-badgedir", "cwl-badges"
+ badge_path = path.parent / "cwl-badges"
+ assert not badge_path.exists()
+ result = pytester.runpytest_inprocess(
+ "-k", "conformance_test_v1.0.cwltest.yml", "--cwl-badgedir", str(badge_path)
+ )
+ result.assert_outcomes(passed=2)
+ assert badge_path.exists()
+ assert (badge_path / "command_line_tool.json").exists()
+ assert (badge_path / "command_line_tool.md").exists()
+ assert (badge_path / "required.json").exists()
+ assert (badge_path / "required.md").exists()
+
+
+def test_badgedir_xdist(pytester: pytest.Pytester) -> None:
+ """Test the pytest plugin creates the badges directory even with xdist."""
+ path = pytester.copy_example("conformance_test_v1.0.cwltest.yml")
+ shutil.copy(
+ get_data("tests/test-data/cwltool-conftest.py"), path.parent / "conftest.py"
+ )
+ _load_v1_0_dir(path)
+ badge_path = path.parent / "cwl-badges"
+ assert not badge_path.exists()
+ result = pytester.runpytest_inprocess(
+ "-n",
+ "2",
+ "-k",
+ "conformance_test_v1.0.cwltest.yml",
+ "--cwl-badgedir",
+ str(badge_path),
)
- assert os.path.exists("cwl-badges")
+ result.assert_outcomes(passed=2)
+ assert badge_path.exists()
+ assert (badge_path / "command_line_tool.json").exists()
+ assert (badge_path / "command_line_tool.md").exists()
+ assert (badge_path / "required.json").exists()
+ assert (badge_path / "required.md").exists()
-def test_no_label(pytester: "Pytester") -> None:
+def test_no_label(pytester: pytest.Pytester) -> None:
"""Test the pytest plugin correctly extracts test names from the id field when label is missing."""
path = pytester.copy_example("conformance_test_v1.2.cwltest.yaml")
shutil.copy(
@@ -108,7 +137,7 @@ def test_no_label(pytester: "Pytester") -> None:
result.assert_outcomes(passed=2, skipped=1)
-def test_cwltool_hook(pytester: "Pytester") -> None:
+def test_cwltool_hook(pytester: pytest.Pytester) -> None:
"""Test the pytest plugin using cwltool as cwl-runner."""
path = pytester.copy_example("conformance_test_v1.0.cwltest.yml")
shutil.copy(
@@ -119,7 +148,7 @@ def test_cwltool_hook(pytester: "Pytester") -> None:
result.assert_outcomes(passed=2)
-def test_no_hook(pytester: "Pytester") -> None:
+def test_no_hook(pytester: pytest.Pytester) -> None:
"""Test the pytest plugin using the default cwl-runner."""
path = pytester.copy_example("conformance_test_v1.0.cwltest.yml")
_load_v1_0_dir(path)
=====================================
tests/test_short_names.py
=====================================
@@ -1,7 +1,5 @@
from os import linesep as n
from pathlib import Path
-from typing import cast
-from xml.etree.ElementTree import Element
import defusedxml.ElementTree as ET
@@ -49,8 +47,8 @@ def test_short_name_in_junit_xml(tmp_path: Path) -> None:
]
run_with_mock_cwl_runner(args)
tree = ET.parse(junit_xml_report)
- root = tree.getroot()
- category = cast(
- Element, cast(Element, root.find("testsuite")).find("testcase")
- ).attrib["file"]
+ assert (root := tree.getroot()) is not None
+ assert (testsuite_el := root.find("testsuite")) is not None
+ assert (testcase_el := testsuite_el.find("testcase")) is not None
+ category = testcase_el.attrib["file"]
assert category == "opt-error"
=====================================
tests/test_timeout.py
=====================================
@@ -1,7 +1,5 @@
import os
from pathlib import Path
-from typing import cast
-from xml.etree.ElementTree import Element
import defusedxml.ElementTree as ET
import schema_salad.ref_resolver
@@ -31,20 +29,14 @@ def test_timeout_stderr_stdout(tmp_path: Path) -> None:
assert "Test 1 timed out" in stderr
tree = ET.parse(junit_xml_report)
try:
- root = tree.getroot()
- timeout_text = cast(
- Element,
- cast(Element, cast(Element, root.find("testsuite")).find("testcase")).find(
- "failure"
- ),
- ).text
- timeout_stderr = cast(
- Element,
- cast(Element, cast(Element, root.find("testsuite")).find("testcase")).find(
- "system-err"
- ),
- ).text
+ assert (root := tree.getroot()) is not None
+ assert (testsuite_el := root.find("testsuite")) is not None
+ assert (testcase_el := testsuite_el.find("testcase")) is not None
+ assert (failure_el := testcase_el.find("failure")) is not None
+ timeout_text = failure_el.text
assert timeout_text is not None and "Test timed out" in timeout_text
+ assert (system_err_el := testcase_el.find("system-err")) is not None
+ timeout_stderr = system_err_el.text
assert timeout_stderr is not None and "timeout stderr" in timeout_stderr
except AttributeError as e:
print(junit_xml_report.read_text())
=====================================
tests/util.py
=====================================
@@ -4,10 +4,9 @@ import atexit
import os
import subprocess # nosec
from contextlib import ExitStack
+from importlib.resources import as_file, files
from pathlib import Path
-from typing import List, Optional, Tuple
-
-from cwltest.utils import as_file, files
+from typing import Optional
def get_data(filename: str) -> str:
@@ -29,8 +28,8 @@ def get_data(filename: str) -> str:
def run_with_mock_cwl_runner(
- args: List[str], cwl_runner: Optional[str] = None
-) -> Tuple[int, str, str]:
+ args: list[str], cwl_runner: Optional[str] = None
+) -> tuple[int, str, str]:
"""Bind a mock cwlref-runner implementation to cwltest."""
if cwl_runner is None:
cwl_runner = get_data("tests/test-data/mock_cwl_runner.py")
=====================================
tox.ini
=====================================
@@ -1,9 +1,9 @@
[tox]
envlist =
- py3{8,9,10,11,12,13}-lint,
- py3{8,9,10,11,12,13}-unit,
- py3{8,9,10,11,12,13}-bandit,
- py3{8,9,10,11,12,13}-mypy,
+ py3{9,10,11,12,13,14}-lint,
+ py3{9,10,11,12,13,14}-unit,
+ py3{9,10,11,12,13,14}-bandit,
+ py3{9,10,11,12,13,14}-mypy,
py312-lintreadme,
py312-pydocstyle
@@ -17,63 +17,63 @@ testpaths = tests
[gh-actions]
python =
- 3.8: py38
3.9: py39
3.10: py310
3.11: py311
3.12: py312
3.13: py313
+ 3.14: py314
[testenv]
skipsdist =
- py3{8,9,10,11,12,13}-!{unit,mypy,lintreadme} = True
+ py3{9,10,11,12,13,14}-!{unit,mypy,lintreadme} = True
description =
- py3{8,9,10,11,12,13}-unit: Run the unit tests
- py3{8,9,10,11,12,13}-lint: Lint the Python code
- py3{8,9,10,11,12,13}-bandit: Search for common security issues
- py3{8,9,10,11,12,13}-mypy: Check for type safety
- py312-pydocstyle: docstring style checker
- py312-lintreadme: Lint the README.rst->.md conversion
+ py3{9,10,11,12,13,14}-unit: Run the unit tests
+ py3{9,10,11,12,13,14}-lint: Lint the Python code
+ py3{9,10,11,12,13,14}-bandit: Search for common security issues
+ py3{9,10,11,12,13,14}-mypy: Check for type safety
+ py313-pydocstyle: docstring style checker
+ py313-lintreadme: Lint the README.rst->.md conversion
passenv =
CI
GITHUB_*
deps =
- py3{8,9,10,11,12,13}-{unit,mypy}: -rrequirements.txt
- py3{8,9,10,11,12,13}-{unit,mypy}: -rtest-requirements.txt
- py3{8,9,10,11,12,13}-lint: flake8-bugbear
- py3{8,9,10,11,12,13}-lint: black~=23.1
- py3{8,9,10,11,12,13}-bandit: bandit
- py3{8,9,10,11,12,13}-mypy: -rmypy-requirements.txt
+ py3{9,10,11,12,13,14}-{unit,mypy}: -rrequirements.txt
+ py3{9,10,11,12,13,14}-{unit,mypy}: -rtest-requirements.txt
+ py3{9,10,11,12,13,14}-lint: flake8-bugbear
+ py3{9,10,11,12,13,14}-lint: black~=23.1
+ py3{9,10,11,12,13,14}-bandit: bandit
+ py3{9,10,11,12,13,14}-mypy: -rmypy-requirements.txt
set_env =
- py3{8,3,10,11,12,13}-unit: LC_ALL = C.UTF-8
+ py3{9,10,11,12,13,14}-unit: LC_ALL = C.UTF-8
COV_CORE_SOURCE=cwltest
COV_CORE_CONFIG={toxinidir}/.coveragerc
COV_CORE_DATAFILE={toxinidir}/.coverage.eager
commands =
- py3{8,9,10,11,12,13}-unit: python -m pip install -U pip setuptools wheel
- py3{8,9,10,11,12,13}-unit: python -m pytest --cov --cov-config={toxinidir}/.coveragerc --cov-append {posargs}
- py3{8,9,10,11,12,13}-unit: coverage xml
- py3{8,9,10,11,12,13}-bandit: bandit --recursive cwltest
- py3{8,9,10,11,12,13}-lint: make flake8
- py3{8,9,10,11,12,13}-lint: make format-check
- py3{8,9,10,11,12,13}-mypy: make mypy
+ py3{9,10,11,12,13,14}-unit: python -m pip install -U pip setuptools wheel
+ py3{9,10,11,12,13,14}-unit: python -m pytest --cov --cov-config={toxinidir}/.coveragerc --cov-append {posargs}
+ py3{9,10,11,12,13,14}-unit: coverage xml
+ py3{9,10,11,12,13,14}-bandit: bandit --recursive cwltest
+ py3{9,10,11,12,13,14}-lint: make flake8
+ py3{9,10,11,12,13,14}-lint: make format-check
+ py3{9,10,11,12,13,14}-mypy: make mypy
allowlist_externals =
- py3{8,9,10,11,12,13}-lint: flake8
- py3{8,9,10,11,12,13}-lint: black
- py3{8,9,10,11,12,13}-{mypy,shellcheck,lint,unit}: make
+ py3{9,10,11,12,13,14}-lint: flake8
+ py3{9,10,11,12,13,14}-lint: black
+ py3{9,10,11,12,13,14}-{mypy,shellcheck,lint,unit}: make
skip_install =
- py3{8,9,10,11,12,13}-lint: true
- py3{8,9,10,11,12,13}-bandit: true
+ py3{9,10,11,12,13,14}-lint: true
+ py3{9,10,11,12,13,14}-bandit: true
-[testenv:py312-pydocstyle]
+[testenv:py313-pydocstyle]
allowlist_externals = make
commands = make diff_pydocstyle_report
deps =
@@ -81,7 +81,7 @@ deps =
diff-cover
skip_install = true
-[testenv:py312-lintreadme]
+[testenv:py313-lintreadme]
description = Lint the README.rst->.md conversion
commands =
python -m build --outdir dist
View it on GitLab: https://salsa.debian.org/med-team/cwltest/-/commit/68eb88a7ac008577769f8ea18f2fc092fe334e85
--
View it on GitLab: https://salsa.debian.org/med-team/cwltest/-/commit/68eb88a7ac008577769f8ea18f2fc092fe334e85
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/debian-med-commit/attachments/20250930/be38e9e8/attachment-0001.htm>
More information about the debian-med-commit
mailing list