Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
ignore = E203,W503
max-line-length = 100
select = B,C,E,F,W,T4
extend-ignore = E501,B905
# when Python 3.10 is the minimum version, re-enable check B905 for zip + strict
extend-ignore = E501
extend-select = B9
per-file-ignores=
./tests/test_badgedir.py:B950
2 changes: 1 addition & 1 deletion .github/workflows/ci-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
matrix:
# The README.rst file mentions the versions tested, please update it as well
py-ver-major: [3]
py-ver-minor: [9, 10, 11, 12, 13, 14]
py-ver-minor: [10, 11, 12, 13, 14]
step: [lint, unit, mypy, bandit]

env:
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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 --py39-plus $^
pyupgrade --exit-zero-even-if-changed --py310-plus $^
auto-walrus $^

release-test: FORCE
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.9, 3.10, 3.11, 3.12, 3.13, and 3.14
This is written and tested for Python 3.10, 3.11, 3.12, 3.13, and 3.14

.. contents:: Table of Contents
:local:
Expand Down
61 changes: 30 additions & 31 deletions cwltest/compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

import hashlib
import json
from typing import Any, Callable, Optional
from collections.abc import Callable
from typing import Any

import cwltest.stdfsaccess

Expand All @@ -14,7 +15,7 @@ class CompareFail(Exception):

@classmethod
def format(
cls, expected: Any, actual: Any, cause: Optional[Any] = None
cls, expected: Any, actual: Any, cause: Any | None = None
) -> "CompareFail":
"""Load the difference details into the error message."""
message = "expected: {}\ngot: {}".format(
Expand Down Expand Up @@ -197,10 +198,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:
if "path" in actual:
path = actual["path"]
else:
path = actual["location"]
path = actual.get("path", actual["location"])

actual_size_on_disk = fs_access.size(path)

Expand Down Expand Up @@ -233,31 +231,32 @@ def compare(expected: Any, actual: Any, skip_details: bool = False) -> None:
raise CompareFail.format(expected, actual)

try:
if isinstance(expected, dict):
if not isinstance(actual, dict):
raise CompareFail.format(expected, actual)

if expected.get("class") == "File":
_compare_file(expected, actual, skip_details)
elif expected.get("class") == "Directory":
_compare_directory(expected, actual, skip_details)
else:
_compare_dict(expected, actual, skip_details)

elif isinstance(expected, list):
if not isinstance(actual, list):
raise CompareFail.format(expected, actual)

if len(expected) != len(actual):
raise CompareFail.format(expected, actual, "lengths don't match")
for c in range(0, len(expected)):
try:
compare(expected[c], actual[c], skip_details)
except CompareFail as e:
raise CompareFail.format(expected, actual, e) from e
else:
if expected != actual:
raise CompareFail.format(expected, actual)
match expected:
case dict():
if not isinstance(actual, dict):
raise CompareFail.format(expected, actual)

match expected.get("class"):
case "File":
_compare_file(expected, actual, skip_details)
case "Directory":
_compare_directory(expected, actual, skip_details)
case _:
_compare_dict(expected, actual, skip_details)
case list():
if not isinstance(actual, list):
raise CompareFail.format(expected, actual)

if len(expected) != len(actual):
raise CompareFail.format(expected, actual, "lengths don't match")
for c in range(0, len(expected)):
try:
compare(expected[c], actual[c], skip_details)
except CompareFail as e:
raise CompareFail.format(expected, actual, e) from e
case _:
if expected != actual:
raise CompareFail.format(expected, actual)

except Exception as e:
raise CompareFail(str(e)) from e
6 changes: 3 additions & 3 deletions cwltest/hooks.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"""Hooks for pytest-cwl users."""

from typing import Any, Optional
from typing import Any

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]]]:
config: utils.CWLTestConfig, processfile: str, jobfile: str | None
) -> tuple[int, dict[str, Any] | None]:
"""
Execute CWL test using a Python function instead of a command line runner.

Expand Down
4 changes: 2 additions & 2 deletions cwltest/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import sys
from collections import Counter, defaultdict
from concurrent.futures import ThreadPoolExecutor
from typing import Optional, cast
from typing import cast

import junit_xml
import schema_salad.avro
Expand Down Expand Up @@ -119,7 +119,7 @@ def main() -> int:
failures = 0
unsupported = 0
suite_name, _ = os.path.splitext(os.path.basename(args.test))
report: Optional[junit_xml.TestSuite] = junit_xml.TestSuite(suite_name, [])
report: junit_xml.TestSuite | None = junit_xml.TestSuite(suite_name, [])

load_optional_fsaccess_plugin()

Expand Down
10 changes: 5 additions & 5 deletions cwltest/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ class TestRunner(Protocol):
"""Protocol to type-check test runner functions via the pluggy hook."""

def __call__(
self, config: utils.CWLTestConfig, processfile: str, jobfile: Optional[str]
) -> list[Optional[dict[str, Any]]]:
self, config: utils.CWLTestConfig, processfile: str, jobfile: str | None
) -> list[dict[str, Any] | None]:
"""Type signature for pytest_cwl_execute_test hook results."""
...

Expand Down Expand Up @@ -224,7 +224,7 @@ def repr_failure(
)
)

def reportinfo(self) -> tuple[Union["os.PathLike[str]", str], Optional[int], str]:
def reportinfo(self) -> tuple[Union["os.PathLike[str]", str], int | None, str]:
"""Status report."""
return self.path, 0, "cwl test: %s" % self.name

Expand Down Expand Up @@ -372,7 +372,7 @@ def _doc_options() -> argparse.ArgumentParser:

def pytest_collect_file(
file_path: Path, parent: pytest.Collector
) -> Optional[pytest.Collector]:
) -> pytest.Collector | None:
"""Is this file for us."""
if (
file_path.suffix == ".yml" or file_path.suffix == ".yaml"
Expand All @@ -393,7 +393,7 @@ def pytest_configure(config: pytest.Config) -> 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))
tests, results = (list(item) for item in zip(*cwl_results, strict=True))
return tests, results


Expand Down
56 changes: 28 additions & 28 deletions cwltest/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
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 typing import Any, cast
from urllib.parse import urljoin

import junit_xml
Expand All @@ -36,31 +36,31 @@ def __init__(
self,
entry: str,
entry_line: str,
basedir: Optional[str] = None,
test_baseuri: Optional[str] = None,
test_basedir: Optional[str] = None,
outdir: Optional[str] = None,
classname: Optional[str] = None,
tool: Optional[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,
basedir: str | None = None,
test_baseuri: str | None = None,
test_basedir: str | None = None,
outdir: str | None = None,
classname: str | None = None,
tool: str | None = None,
args: list[str] | None = None,
testargs: list[str] | None = None,
timeout: int | None = None,
verbose: bool | None = None,
runner_quiet: bool | None = None,
) -> None:
"""Initialize test configuration."""
self.basedir: str = basedir or os.getcwd()
self.test_baseuri: str = test_baseuri or "file://" + self.basedir
self.test_basedir: str = test_basedir or self.basedir
self.outdir: Optional[str] = outdir
self.outdir: str | None = outdir
self.classname: str = classname or ""
self.entry = urljoin(
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.timeout: Optional[int] = timeout
self.timeout: int | None = timeout
self.verbose: bool = verbose or False
self.runner_quiet: bool = runner_quiet or True

Expand All @@ -70,11 +70,11 @@ class CWLTestReport:

def __init__(
self,
id: Union[int, str],
id: int | str,
category: list[str],
entry: str,
tool: str,
job: Optional[str],
job: str | None,
) -> None:
"""Initialize a CWLTestReport object."""
self.id = id
Expand All @@ -96,7 +96,7 @@ def __init__(
classname: str,
entry: str,
tool: str,
job: Optional[str],
job: str | None,
message: str = "",
) -> None:
"""Initialize a TestResult object."""
Expand Down Expand Up @@ -240,7 +240,7 @@ def generate_badges(

def get_test_number_by_key(
tests: list[dict[str, str]], key: str, value: str
) -> Optional[int]:
) -> int | None:
"""Retrieve the test index from its name."""
for i, test in enumerate(tests):
if key in test and test[key] == value:
Expand All @@ -256,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: dict[str, str | Graph | bool] | None = {
"https://w3id.org/cwl/cwltest/cwltest-schema.yml": fp.read()
}
(
Expand All @@ -283,8 +283,8 @@ def load_and_validate_tests(path: str) -> tuple[Any, dict[str, Any]]:
def parse_results(
results: Iterable[TestResult],
tests: list[dict[str, Any]],
suite_name: Optional[str] = None,
report: Optional[junit_xml.TestSuite] = None,
suite_name: str | None = None,
report: junit_xml.TestSuite | None = None,
) -> tuple[
int, # total
int, # passed
Expand All @@ -294,7 +294,7 @@ def parse_results(
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],
junit_xml.TestSuite | None,
]:
"""
Parse the results and return statistics and an optional report.
Expand Down Expand Up @@ -366,10 +366,10 @@ def parse_results(
def prepare_test_command(
tool: str,
args: list[str],
testargs: Optional[list[str]],
testargs: list[str] | None,
test: dict[str, Any],
cwd: str,
quiet: Optional[bool] = True,
quiet: bool | None = True,
) -> list[str]:
"""Turn the test into a command line."""
test_command = [tool]
Expand Down Expand Up @@ -407,7 +407,7 @@ def prepare_test_command(
def prepare_test_paths(
test: dict[str, str],
cwd: str,
) -> tuple[str, Optional[str]]:
) -> tuple[str, str | None]:
"""Determine the test path and the tool path."""
cwd = schema_salad.ref_resolver.file_uri(cwd)
processfile = test["tool"]
Expand All @@ -424,7 +424,7 @@ def prepare_test_paths(
def run_test_plain(
config: CWLTestConfig,
test: dict[str, str],
test_number: Optional[int] = None,
test_number: int | None = None,
) -> TestResult:
"""Plain test runner."""
out: dict[str, Any] = {}
Expand All @@ -443,7 +443,7 @@ def run_test_plain(

if test_number is not None:
number = str(test_number)
process: Optional[subprocess.Popen[str]] = None
process: subprocess.Popen[str] | None = None
try:
cwd = os.getcwd()
test_command = prepare_test_command(
Expand Down Expand Up @@ -668,7 +668,7 @@ def load_optional_fsaccess_plugin() -> None:

try:
# The interface to importlib.metadata.entry_points() changed
# several times between Python 3.9 and 3.13; the code below
# several times between Python 3.10 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.
Expand Down
3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,14 @@ classifiers = [
"Operating System :: POSIX",
"Operating System :: MacOS :: MacOS X",
"Development Status :: 5 - Production/Stable",
"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.9,<3.15"
requires-python = ">=3.10,<3.15"
dynamic = ["version", "dependencies"]

[project.readme]
Expand Down
3 changes: 1 addition & 2 deletions tests/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from contextlib import ExitStack
from importlib.resources import as_file, files
from pathlib import Path
from typing import Optional


def get_data(filename: str) -> str:
Expand All @@ -28,7 +27,7 @@ def get_data(filename: str) -> str:


def run_with_mock_cwl_runner(
args: list[str], cwl_runner: Optional[str] = None
args: list[str], cwl_runner: str | None = None
) -> tuple[int, str, str]:
"""Bind a mock cwlref-runner implementation to cwltest."""
if cwl_runner is None:
Expand Down
Loading
Loading