Skip to content
Open
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
1 change: 1 addition & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ jobs:
test_bench_stats.py test_bench_measure.py test_bench_runner.py
test_bench_arms.py test_sdk_commands.py test_tdfs_units.py
test_encryption_units.py test_sizes_units.py test_zip64_units.py
test_conftest_units.py test_registry_units.py
working-directory: xtest
- name: Lint and test otdf-local
run: |
Expand Down
509 changes: 509 additions & 0 deletions spec/DSPX-4794.md

Large diffs are not rendered by default.

88 changes: 64 additions & 24 deletions xtest/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import pytest

import registry
import sizes
import tdfs
from otdfctl import OpentdfCommandLineTool
Expand Down Expand Up @@ -316,6 +317,49 @@ def _add_benchmark_options(parser: pytest.Parser):
)


def resolve_sdks(
config: pytest.Config, option_names: list[str], role: str
) -> list[tdfs.SDK]:
"""SDK builds for one side of the matrix.

The first option in ``option_names`` that was given wins; otherwise the
default is every build actually installed under ``sdk/*/dist/``.

The empty case is an error rather than an empty parametrization, and only
on the default path. ``metafunc.parametrize`` over ``[]`` does not collect
zero items: pytest's ``empty_parameter_set_mark`` turns it into one *skip*
per test, so a checkout with nothing installed reports "20 skipped ... got
empty parameter set" and exits 0. A whole matrix disappears and the run
stays green -- the same failure mode :func:`sizes_opt_type` guards against
a few functions up.

An explicit ``--sdks`` that resolves to nothing is left alone: that is the
caller narrowing the run on purpose, possibly from a script, and is not
this function's to second-guess.
"""
for name in option_names:
v = config.getoption(name)
if v:
try:
return [
sdk for spec in str(v).split() for sdk in tdfs.parse_sdk_spec(spec)
]
except (FileNotFoundError, ValueError) as e:
raise pytest.UsageError(str(e)) from e
try:
installed = tdfs.installed_sdks()
except FileNotFoundError as e:
raise pytest.UsageError(str(e)) from e
if not installed:
raise pytest.UsageError(
f"no SDK builds are installed under sdk/*/dist/, so the {role} side "
"of the matrix is empty; every cell would report as a skip and the "
"run would exit 0. Install some (otdf-sdk-mgr install stable) or "
f"name them explicitly with {' / '.join(option_names)}."
)
return installed


def pytest_generate_tests(metafunc: pytest.Metafunc):
"""Dynamically parametrize test functions based on CLI options.

Expand All @@ -342,36 +386,14 @@ def list_opt(name: str, t: typing.Any) -> list[str]:
raise ValueError(f"Invalid value for {name}: {i}, must be one of {ttt}")
return a

def sdk_specs_opt(names: list[str]) -> list[str]:
"""Return SDK specifier tokens from the first matching option, or all sdk types."""
for name in names:
v = metafunc.config.getoption(name)
if v:
return v.split()
return list(typing.get_args(tdfs.sdk_type))

subject_sdks: set[tdfs.SDK] = set()

if "encrypt_sdk" in metafunc.fixturenames:
try:
e_sdks = [
sdk
for spec in sdk_specs_opt(["--sdks-encrypt", "--sdks"])
for sdk in tdfs.parse_sdk_spec(spec)
]
except (FileNotFoundError, ValueError) as e:
raise pytest.UsageError(str(e)) from e
e_sdks = resolve_sdks(metafunc.config, ["--sdks-encrypt", "--sdks"], "encrypt")
metafunc.parametrize("encrypt_sdk", e_sdks, ids=[str(x) for x in e_sdks])
subject_sdks |= set(e_sdks)
if "decrypt_sdk" in metafunc.fixturenames:
try:
d_sdks = [
sdk
for spec in sdk_specs_opt(["--sdks-decrypt", "--sdks"])
for sdk in tdfs.parse_sdk_spec(spec)
]
except (FileNotFoundError, ValueError) as e:
raise pytest.UsageError(str(e)) from e
d_sdks = resolve_sdks(metafunc.config, ["--sdks-decrypt", "--sdks"], "decrypt")
metafunc.parametrize("decrypt_sdk", d_sdks, ids=[str(x) for x in d_sdks])
subject_sdks |= set(d_sdks)

Expand Down Expand Up @@ -426,6 +448,24 @@ def _parametrize_bench_cells(metafunc: pytest.Metafunc):


def pytest_configure(config: pytest.Config):
# Entry-point discovery first: everything below validates names against
# the registries, and pytest_configure is the only hook late enough for
# plugins to be importable and early enough to precede
# pytest_generate_tests, where the names become parameters.
registry.load_all()

# Then XT_FORCE_SUPPORTS, whose parse validates names against the feature
# registry and so has to run after discovery. See
# tdfs.configure_forced_supports.
#
# UsageError, not the bare ValueError: a typo in XT_FORCE_SUPPORTS is a
# mistake in the invocation, and pytest reports a UsageError as such
# instead of as an INTERNALERROR traceback through the plugin manager.
try:
tdfs.configure_forced_supports()
except ValueError as e:
raise pytest.UsageError(str(e)) from e

if not config.getoption("--bench", default=False):
return
# Parallel workers contend for the CPU the benchmark is measuring, which
Expand Down
1 change: 1 addition & 0 deletions xtest/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ known-first-party = [
"assertions",
"fixtures",
"perf",
"registry",
"sizes",
"zipinspect",
]
Expand Down
Loading
Loading