From 9b50b00306e8a93949a43d839598e05d09552ea5 Mon Sep 17 00:00:00 2001 From: Thomas Sedlmayer Date: Wed, 15 Jul 2026 13:24:34 +0200 Subject: [PATCH 1/2] Align OSI compliance assertion naming Signed-off-by: Thomas Sedlmayer --- README.md | 18 +++--- osc_validation/assertions.py | 6 +- osc_validation/cli.py | 56 ++++++++++++------- osc_validation/pytest_plugin.py | 54 ++++++++++-------- .../trajectories/val_simple_trajectories.py | 4 +- .../val_simulation_time_start_trigger.py | 4 +- tests/test_assertions.py | 32 +++++------ tests/test_cli_entrypoints.py | 26 +++++---- 8 files changed, 112 insertions(+), 88 deletions(-) diff --git a/README.md b/README.md index 411ada7..7888659 100644 --- a/README.md +++ b/README.md @@ -94,13 +94,13 @@ osc-validate --tool --junitxml validation-results.xml Enable QC OSI trace checks at test case call sites with: ```bash -osc-validate --tool ESMini --toolpath C:/path/to/esmini/bin/esmini.exe --qc-osi-trace --qc-osi-version 3.7.0 +osc-validate --tool ESMini --toolpath C:/path/to/esmini/bin/esmini.exe --assert-osi-compliance --assert-osi-compliance-version 3.7.0 ``` Use the Omega Prime ruleset with: ```bash -osc-validate --tool ESMini --toolpath C:/path/to/esmini/bin/esmini.exe --qc-omega-prime +osc-validate --tool ESMini --toolpath C:/path/to/esmini/bin/esmini.exe --assert-osi-compliance-ruleset-preset omega-prime ``` For more information on available command-line options, run: @@ -292,21 +292,21 @@ Checker Bundles already integrated: - [qc-osi-trace - OSI Trace Checker](https://github.com/OpenSimulationInterface/qc-osi-trace) QC trace checks are opt-in for a validation run. -Test cases call the `assert_osi_trace_compliance` fixture where a generated trace should be checked. -The fixture is a no-op by default and runs qc-osi-trace only when `--qc-osi-trace` is passed. +Test cases call the `assert_osi_compliance` fixture where a generated trace should be checked. +The fixture is a no-op by default and runs qc-osi-trace only when `--assert-osi-compliance` or `--assert-osi-compliance-ruleset-preset omega-prime` is passed. ```python -def test_example(generate_tool_trace, assert_osi_trace_compliance, tmp_path): +def test_example(generate_tool_trace, assert_osi_compliance, tmp_path): tool_trace_channel_spec = generate_tool_trace(...) - assert_osi_trace_compliance( + assert_osi_compliance( tool_trace_channel_spec, result_file=tmp_path / "qc_result.xqar", output_config=tmp_path / "qc_config.xml", ) ``` -Use `--qc-osi-version` and `--qc-osi-ruleset` to provide default checker settings for the run. -Use `--qc-omega-prime` to use the Omega Prime ruleset as the default ruleset. +Use `--assert-osi-compliance-version` and `--assert-osi-compliance-ruleset` to provide default checker settings for the run. +Use `--assert-osi-compliance-ruleset-preset omega-prime` to use the Omega Prime ruleset as the default ruleset. A test can override those defaults per call with `osi_version=` and `ruleset=`. When a QC check fails, the fixture assertion message includes the trace path, checker statuses, issue counts, and the first reported issues. @@ -343,7 +343,7 @@ def example_test_case(osi_trace, odr_file, generate_tool_trace, tmp_path, moving - Integrate pytest fixtures' names in the test case function as function parameters - Resource fixtures (e.g., `osi_trace`, `odr_file`) - Tool execution fixture (`generate_tool_trace`) - - Optional QC fixture (`assert_osi_trace_compliance`) + - Optional QC fixture (`assert_osi_compliance`) - Pytest built-in fixtures (e.g., `tmp_path`, pytest markers such as `moving_object_id`) - Integrate a reference implementation (e.g., the OSI to OpenSCENARIO XML Converter osi2osc) to generate an OpenSCENARIO file from a baseline OSI reference trace - Use `generate_tool_trace` callable and the OpenSCENARIO file to generate the corresponding OSI tool trace diff --git a/osc_validation/assertions.py b/osc_validation/assertions.py index 8e5affd..ec3c5a8 100644 --- a/osc_validation/assertions.py +++ b/osc_validation/assertions.py @@ -16,7 +16,7 @@ def _optional_path(path): return pathlib.Path(path) -def make_assert_osi_trace_compliance( +def make_assert_osi_compliance( *, qc_enabled: bool, default_osi_version: str | None = None, @@ -25,7 +25,7 @@ def make_assert_osi_trace_compliance( ): default_ruleset = _optional_path(default_ruleset) - def assert_osi_trace_compliance( + def assert_osi_compliance( channel_spec, *, result_file=None, @@ -65,4 +65,4 @@ def assert_osi_trace_compliance( ) assert result, "QC OSI trace check failed." - return assert_osi_trace_compliance + return assert_osi_compliance diff --git a/osc_validation/cli.py b/osc_validation/cli.py index d0346be..5be8cce 100644 --- a/osc_validation/cli.py +++ b/osc_validation/cli.py @@ -67,29 +67,30 @@ def _parse_args(argv: list[str] | None) -> argparse.Namespace: help="Write a JUnit XML report to PATH.", ) parser.add_argument( - "--qc-osi-trace", + "--assert-osi-compliance", action="store_true", default=False, - help="Enable QC OSI trace checks at test case call sites.", + help="Enable OSI compliance assertions at test case call sites.", ) parser.add_argument( - "--qc-osi-version", + "--assert-osi-compliance-version", default=None, metavar="VERSION", - help="Default OSI version for QC OSI trace checks.", + help="Default OSI version for OSI compliance assertions.", ) ruleset_group = parser.add_mutually_exclusive_group() ruleset_group.add_argument( - "--qc-osi-ruleset", + "--assert-osi-compliance-ruleset", default=None, metavar="PATH", - help="Default OSI ruleset YAML file for QC OSI trace checks.", + help="Default OSI ruleset YAML file for OSI compliance assertions.", ) ruleset_group.add_argument( - "--qc-omega-prime", - action="store_true", - default=False, - help="Use the Omega Prime OSI 3.7.0 ruleset for QC OSI trace checks.", + "--assert-osi-compliance-ruleset-preset", + choices=["omega-prime"], + default=None, + metavar="PRESET", + help="Use a named OSI ruleset preset for OSI compliance assertions.", ) return parser.parse_args(argv) @@ -118,14 +119,26 @@ def _pytest_args(args: argparse.Namespace, validation_dir: Path) -> list[str]: ) if args.junitxml is not None: pytest_args.append(f"--junitxml={_resolve_from_cwd(args.junitxml)}") - if args.qc_osi_trace: - pytest_args.append("--qc-osi-trace") - if args.qc_osi_version is not None: - pytest_args.extend(["--qc-osi-version", args.qc_osi_version]) - if args.qc_osi_ruleset is not None: - pytest_args.extend(["--qc-osi-ruleset", _resolve_from_cwd(args.qc_osi_ruleset)]) - if args.qc_omega_prime: - pytest_args.append("--qc-omega-prime") + if args.assert_osi_compliance: + pytest_args.append("--assert-osi-compliance") + if args.assert_osi_compliance_version is not None: + pytest_args.extend( + ["--assert-osi-compliance-version", args.assert_osi_compliance_version] + ) + if args.assert_osi_compliance_ruleset is not None: + pytest_args.extend( + [ + "--assert-osi-compliance-ruleset", + _resolve_from_cwd(args.assert_osi_compliance_ruleset), + ] + ) + if args.assert_osi_compliance_ruleset_preset is not None: + pytest_args.extend( + [ + "--assert-osi-compliance-ruleset-preset", + args.assert_osi_compliance_ruleset_preset, + ] + ) return pytest_args @@ -133,9 +146,10 @@ def _pytest_args(args: argparse.Namespace, validation_dir: Path) -> list[str]: def main(argv: list[str] | None = None) -> int: args = _parse_args(argv) validation_dir = _validation_dir() - if not (validation_dir / "pytest.ini").exists() or not ( - validation_dir / "scenario" - ).exists(): + if ( + not (validation_dir / "pytest.ini").exists() + or not (validation_dir / "scenario").exists() + ): raise FileNotFoundError( "Installed validation suite not found at " f"'{validation_dir}'. " diff --git a/osc_validation/pytest_plugin.py b/osc_validation/pytest_plugin.py index 38f2181..7d08b04 100644 --- a/osc_validation/pytest_plugin.py +++ b/osc_validation/pytest_plugin.py @@ -11,8 +11,8 @@ ``--tool-wrapper-module``, ``--test-profile``). * Initialise the tool under test and expose it via the ``generate_tool_trace`` session fixture. -* Expose optional QC OSI trace checking via the - ``assert_osi_trace_compliance`` session fixture. +* Expose optional OSI compliance assertions via the + ``assert_osi_compliance`` session fixture. * Apply xfail markers from an optional test-profile file. * Add validation metadata to the pytest report header. @@ -33,16 +33,14 @@ import pytest from osc_validation import __version__ as osc_validation_version -from osc_validation.assertions import make_assert_osi_trace_compliance +from osc_validation.assertions import make_assert_osi_compliance from osc_validation.dataproviders import DownloadDataProvider from osc_validation.test_profile import load_test_profile from osc_validation.tools.esmini import ESMini from osc_validation.tools.gtgen_cli import GTGen_Simulator from osc_validation.tools.osc_simulator import OscSimulator -OMEGA_PRIME_OSI_370_RULESET_URL = ( - "https://raw.githubusercontent.com/thomassedlmayer/omega-prime/7ed09192ffeaa12bfab04281d17a5b5dc2702197/docs/osirules/omega-prime-osi_3-7-0.yml" -) +OMEGA_PRIME_OSI_370_RULESET_URL = "https://raw.githubusercontent.com/thomassedlmayer/omega-prime/7ed09192ffeaa12bfab04281d17a5b5dc2702197/docs/osirules/omega-prime-osi_3-7-0.yml" class UnknownToolError(ValueError): @@ -175,30 +173,31 @@ def pytest_addoption(parser): help="Path to a TOML test profile file declaring expected failures for this run", ) group.addoption( - "--qc-osi-trace", + "--assert-osi-compliance", action="store_true", default=False, - help="Enable QC OSI trace checks at assert_osi_trace_compliance fixture call sites", + help="Enable OSI compliance assertions at assert_osi_compliance fixture call sites", ) group.addoption( - "--qc-osi-version", + "--assert-osi-compliance-version", action="store", default=None, metavar="VERSION", - help="Default OSI version for QC OSI trace checks", + help="Default OSI version for OSI compliance assertions", ) group.addoption( - "--qc-osi-ruleset", + "--assert-osi-compliance-ruleset", action="store", default=None, metavar="PATH", - help="Default OSI ruleset YAML file for QC OSI trace checks", + help="Default OSI ruleset YAML file for OSI compliance assertions", ) group.addoption( - "--qc-omega-prime", - action="store_true", - default=False, - help="Use the Omega Prime OSI 3.7.0 ruleset for QC OSI trace checks", + "--assert-osi-compliance-ruleset-preset", + action="store", + default=None, + metavar="PRESET", + help="Use a named OSI ruleset preset for OSI compliance assertions", ) @@ -290,18 +289,25 @@ def pytest_collection_modifyitems(config, items): @pytest.fixture(scope="session") -def assert_osi_trace_compliance(request, tmp_path_factory): - """Assert OSI trace QC compliance when enabled by a QC option.""" - qc_enabled = request.config.getoption("--qc-osi-trace") or request.config.getoption( - "--qc-omega-prime" +def assert_osi_compliance(request, tmp_path_factory): + """Assert OSI trace compliance when enabled by an assertion option.""" + ruleset_preset = request.config.getoption("--assert-osi-compliance-ruleset-preset") + if ruleset_preset not in {None, "omega-prime"}: + raise pytest.UsageError( + "Unsupported OSI compliance ruleset preset: " + f"{ruleset_preset}. Supported presets: omega-prime." + ) + + qc_enabled = request.config.getoption("--assert-osi-compliance") or ( + ruleset_preset is not None ) - default_osi_version = request.config.getoption("--qc-osi-version") - default_ruleset = request.config.getoption("--qc-osi-ruleset") + default_osi_version = request.config.getoption("--assert-osi-compliance-version") + default_ruleset = request.config.getoption("--assert-osi-compliance-ruleset") - if request.config.getoption("--qc-omega-prime"): + if ruleset_preset == "omega-prime": default_ruleset = _download_omega_prime_ruleset(tmp_path_factory) - return make_assert_osi_trace_compliance( + return make_assert_osi_compliance( qc_enabled=qc_enabled, default_osi_version=default_osi_version, default_ruleset=default_ruleset, diff --git a/osc_validation/validation/scenario/trajectories/val_simple_trajectories.py b/osc_validation/validation/scenario/trajectories/val_simple_trajectories.py index 0d99953..9462b34 100644 --- a/osc_validation/validation/scenario/trajectories/val_simple_trajectories.py +++ b/osc_validation/validation/scenario/trajectories/val_simple_trajectories.py @@ -35,7 +35,7 @@ def test_trajectory_and_osi_compliance( osi_trace: Path, odr_file: Path, generate_tool_trace: Callable, - assert_osi_trace_compliance: Callable, + assert_osi_compliance: Callable, tmp_path: Path, moving_object_id: int, tolerance=1e-1, @@ -78,7 +78,7 @@ def test_trajectory_and_osi_compliance( rate=0.05, ) - assert_osi_trace_compliance( + assert_osi_compliance( channel_spec=tool_trace_channel_spec, result_file=tmp_path / "qc_result.xqar", output_config=tmp_path / "qc_config.xml", diff --git a/osc_validation/validation/scenario/triggers/val_simulation_time_start_trigger.py b/osc_validation/validation/scenario/triggers/val_simulation_time_start_trigger.py index 1a94520..d5ddc86 100644 --- a/osc_validation/validation/scenario/triggers/val_simulation_time_start_trigger.py +++ b/osc_validation/validation/scenario/triggers/val_simulation_time_start_trigger.py @@ -50,7 +50,7 @@ def test_simulation_time_start_trigger_delays_actor_trajectory( osi_trace: Path, odr_file: Path, generate_tool_trace: Callable, - assert_osi_trace_compliance: Callable, + assert_osi_compliance: Callable, tmp_path: Path, moving_object_id: int, ego_object_id: int, @@ -138,7 +138,7 @@ def test_simulation_time_start_trigger_delays_actor_trajectory( ) assert_no_osc_engine_errors(tool_trace_channel_spec) - assert_osi_trace_compliance( + assert_osi_compliance( channel_spec=tool_trace_channel_spec, result_file=tmp_path / "qc_result_trigger_delay.xqar", output_config=tmp_path / "qc_config_trigger_delay.xml", diff --git a/tests/test_assertions.py b/tests/test_assertions.py index 3b74d64..271f59b 100644 --- a/tests/test_assertions.py +++ b/tests/test_assertions.py @@ -2,7 +2,7 @@ import pytest -from osc_validation.assertions import make_assert_osi_trace_compliance +from osc_validation.assertions import make_assert_osi_compliance class _FakeCheckResult: @@ -37,19 +37,19 @@ def _reset_fake_checker(): _FakeChecker.instances = [] -def test_assert_osi_trace_compliance_noops_when_disabled(): - assert_osi_trace_compliance = make_assert_osi_trace_compliance( +def test_assert_osi_compliance_noops_when_disabled(): + assert_osi_compliance = make_assert_osi_compliance( qc_enabled=False, checker_cls=_FakeChecker, ) - assert_osi_trace_compliance(object()) + assert_osi_compliance(object()) assert _FakeChecker.instances == [] -def test_assert_osi_trace_compliance_uses_cli_defaults_when_enabled(): - assert_osi_trace_compliance = make_assert_osi_trace_compliance( +def test_assert_osi_compliance_uses_cli_defaults_when_enabled(): + assert_osi_compliance = make_assert_osi_compliance( qc_enabled=True, default_osi_version="3.7.0", default_ruleset="rules.yml", @@ -59,7 +59,7 @@ def test_assert_osi_trace_compliance_uses_cli_defaults_when_enabled(): channel_spec = object() result_file = Path("result.xqar") output_config = Path("config.xml") - assert_osi_trace_compliance( + assert_osi_compliance( channel_spec, result_file=result_file, output_config=output_config, @@ -77,15 +77,15 @@ def test_assert_osi_trace_compliance_uses_cli_defaults_when_enabled(): ] -def test_assert_osi_trace_compliance_call_options_override_cli_defaults(): - assert_osi_trace_compliance = make_assert_osi_trace_compliance( +def test_assert_osi_compliance_call_options_override_cli_defaults(): + assert_osi_compliance = make_assert_osi_compliance( qc_enabled=True, default_osi_version="3.6.0", default_ruleset="default.yml", checker_cls=_FakeChecker, ) - assert_osi_trace_compliance( + assert_osi_compliance( object(), osi_version="3.7.0", ruleset=Path("override.yml"), @@ -96,7 +96,7 @@ def test_assert_osi_trace_compliance_call_options_override_cli_defaults(): assert checker.ruleset == Path("override.yml") -def test_assert_osi_trace_compliance_fails_when_qc_fails(): +def test_assert_osi_compliance_fails_when_qc_fails(): class _FailingChecker(_FakeChecker): def __init__(self, osi_version=None, ruleset=None): super().__init__(osi_version=osi_version, ruleset=ruleset) @@ -105,16 +105,16 @@ def __init__(self, osi_version=None, ruleset=None): summary="QC OSI trace check failed.\n- deserialization: issue", ) - assert_osi_trace_compliance = make_assert_osi_trace_compliance( + assert_osi_compliance = make_assert_osi_compliance( qc_enabled=True, checker_cls=_FailingChecker, ) with pytest.raises(AssertionError, match="deserialization: issue"): - assert_osi_trace_compliance(object()) + assert_osi_compliance(object()) -def test_assert_osi_trace_compliance_supports_legacy_boolean_checker(): +def test_assert_osi_compliance_supports_legacy_boolean_checker(): class _FakeLegacyChecker: def __init__(self, osi_version=None, ruleset=None): self.osi_version = osi_version @@ -123,10 +123,10 @@ def __init__(self, osi_version=None, ruleset=None): def check(self, channel_spec, result_file=None, output_config=None): return False - assert_osi_trace_compliance = make_assert_osi_trace_compliance( + assert_osi_compliance = make_assert_osi_compliance( qc_enabled=True, checker_cls=_FakeLegacyChecker, ) with pytest.raises(AssertionError, match="QC OSI trace check failed"): - assert_osi_trace_compliance(object()) + assert_osi_compliance(object()) diff --git a/tests/test_cli_entrypoints.py b/tests/test_cli_entrypoints.py index af259f8..768b150 100644 --- a/tests/test_cli_entrypoints.py +++ b/tests/test_cli_entrypoints.py @@ -44,10 +44,10 @@ def fake_subprocess_run(command, check, cwd): "C:/tools/esmini.exe", "--test-profile", "profile.toml", - "--qc-osi-trace", - "--qc-osi-version", + "--assert-osi-compliance", + "--assert-osi-compliance-version", "3.7.0", - "--qc-osi-ruleset", + "--assert-osi-compliance-ruleset", "rules.yml", ] ) @@ -68,10 +68,10 @@ def fake_subprocess_run(command, check, cwd): str(Path("C:/tools/esmini.exe").resolve()), "--test-profile", str(Path("profile.toml").resolve()), - "--qc-osi-trace", - "--qc-osi-version", + "--assert-osi-compliance", + "--assert-osi-compliance-version", "3.7.0", - "--qc-osi-ruleset", + "--assert-osi-compliance-ruleset", str(Path("rules.yml").resolve()), ] @@ -111,7 +111,7 @@ def fake_subprocess_run(command, check, cwd): assert "--self-contained-html" in captured_command -def test_osc_validate_main_adds_omega_prime_qc_option(monkeypatch): +def test_osc_validate_main_adds_omega_prime_ruleset_preset(monkeypatch): captured_command = None validation_dir = Path("installed-validation") @@ -135,14 +135,16 @@ def fake_subprocess_run(command, check, cwd): [ "--tool", "GTGen", - "--qc-omega-prime", + "--assert-osi-compliance-ruleset-preset", + "omega-prime", ] ) == 0 ) - assert "--qc-osi-trace" not in captured_command - assert "--qc-omega-prime" in captured_command + assert "--assert-osi-compliance" not in captured_command + assert "--assert-osi-compliance-ruleset-preset" in captured_command + assert "omega-prime" in captured_command def test_osc_validate_main_adds_junitxml(monkeypatch): @@ -262,7 +264,9 @@ def test_osc_validate_main_rejects_positional_test_paths(monkeypatch): def test_osc_validate_main_requires_installed_validation_suite(monkeypatch): - monkeypatch.setattr(cli_module, "_validation_dir", lambda: Path("missing-validation")) + monkeypatch.setattr( + cli_module, "_validation_dir", lambda: Path("missing-validation") + ) monkeypatch.setattr(cli_module.Path, "exists", lambda self: False) monkeypatch.setattr( cli_module.subprocess, From eca2092e662f65301260800787feb1691a5e8b89 Mon Sep 17 00:00:00 2001 From: Thomas Sedlmayer Date: Wed, 15 Jul 2026 13:48:02 +0200 Subject: [PATCH 2/2] Add osi compliance assert for all tests Signed-off-by: Thomas Sedlmayer --- .../scenario/init_actions/val_init_actions.py | 15 +++++++++ .../object_state/val_vehicle_dimensions.py | 5 +++ .../sequencing/val_split_trajectory.py | 5 +++ ...follow_trajectory_future_time_reference.py | 5 +++ .../val_follow_trajectory_teleport.py | 5 +++ .../trajectories/val_interpolation.py | 5 +++ .../scenario/triggers/val_condition_delay.py | 4 +++ .../val_distance_euclidian_start_trigger.py | 24 ++++---------- ...val_distance_longitudinal_start_trigger.py | 26 +++++---------- .../val_simulation_time_start_trigger.py | 3 +- .../triggers/val_speed_start_trigger.py | 32 +++++++------------ .../val_time_to_collision_start_trigger.py | 24 ++++---------- .../val_traveled_distance_start_trigger.py | 24 ++++---------- 13 files changed, 83 insertions(+), 94 deletions(-) diff --git a/osc_validation/validation/scenario/init_actions/val_init_actions.py b/osc_validation/validation/scenario/init_actions/val_init_actions.py index d1fa0e7..233124d 100644 --- a/osc_validation/validation/scenario/init_actions/val_init_actions.py +++ b/osc_validation/validation/scenario/init_actions/val_init_actions.py @@ -64,6 +64,7 @@ def _compute_object_state_metric( def test_init_teleport_action_places_actor_at_expected_pose( odr_file: Path, generate_tool_trace: Callable, + assert_osi_compliance: Callable, tmp_path: Path, ): """ @@ -106,6 +107,10 @@ def test_init_teleport_action_places_actor_at_expected_pose( rate=rate, ) assert_no_osc_engine_errors(tool_trace_channel_spec) + assert_osi_compliance( + tool_trace_channel_spec, + result_file=tmp_path / "qc_result_init_teleport_action.xqar", + ) metric_result = _compute_object_state_metric( reference_channel_spec=case_result.reference_channel_spec, @@ -125,6 +130,7 @@ def test_init_teleport_action_places_actor_at_expected_pose( def test_init_add_entity_action_places_actor_at_expected_pose( odr_file: Path, generate_tool_trace: Callable, + assert_osi_compliance: Callable, tmp_path: Path, ): """ @@ -167,6 +173,10 @@ def test_init_add_entity_action_places_actor_at_expected_pose( rate=rate, ) assert_no_osc_engine_errors(tool_trace_channel_spec) + assert_osi_compliance( + tool_trace_channel_spec, + result_file=tmp_path / "qc_result_init_add_entity_action.xqar", + ) metric_result = _compute_object_state_metric( reference_channel_spec=case_result.reference_channel_spec, @@ -186,6 +196,7 @@ def test_init_add_entity_action_places_actor_at_expected_pose( def test_init_speed_action_sets_expected_motion( odr_file: Path, generate_tool_trace: Callable, + assert_osi_compliance: Callable, tmp_path: Path, ): """ @@ -229,6 +240,10 @@ def test_init_speed_action_sets_expected_motion( rate=rate, ) assert_no_osc_engine_errors(tool_trace_channel_spec) + assert_osi_compliance( + tool_trace_channel_spec, + result_file=tmp_path / "qc_result_init_speed_action.xqar", + ) metric_result = _compute_object_state_metric( reference_channel_spec=case_result.reference_channel_spec, diff --git a/osc_validation/validation/scenario/object_state/val_vehicle_dimensions.py b/osc_validation/validation/scenario/object_state/val_vehicle_dimensions.py index def47ec..e575ce7 100644 --- a/osc_validation/validation/scenario/object_state/val_vehicle_dimensions.py +++ b/osc_validation/validation/scenario/object_state/val_vehicle_dimensions.py @@ -33,6 +33,7 @@ def test_vehicle_dimensions_match_reference( osi_trace: Path, odr_file: Path, generate_tool_trace: Callable, + assert_osi_compliance: Callable, tmp_path: Path, moving_object_id: int, tolerance: float = 0.1, @@ -67,6 +68,10 @@ def test_vehicle_dimensions_match_reference( log_path=tmp_path, rate=0.05, ) + assert_osi_compliance( + tool_trace_channel_spec, + result_file=tmp_path / f"qc_result_vehicle_dimensions_{moving_object_id}.xqar", + ) result = ObjectStateMetric().compute( reference_channel_spec=reference_trace_channel_spec, diff --git a/osc_validation/validation/scenario/sequencing/val_split_trajectory.py b/osc_validation/validation/scenario/sequencing/val_split_trajectory.py index 883bb0d..ea1e998 100644 --- a/osc_validation/validation/scenario/sequencing/val_split_trajectory.py +++ b/osc_validation/validation/scenario/sequencing/val_split_trajectory.py @@ -44,6 +44,7 @@ def test_split_ego_trajectory_preserves_reference_motion( osi_trace: Path, odr_file: Path, generate_tool_trace: Callable, + assert_osi_compliance: Callable, tmp_path: Path, sequencing_level: str, entity_ref: str = "Ego", @@ -97,6 +98,10 @@ def test_split_ego_trajectory_preserves_reference_motion( rate=rate, ) assert_no_osc_engine_errors(tool_trace_channel_spec) + assert_osi_compliance( + tool_trace_channel_spec, + result_file=tmp_path / f"qc_result_split_{sequencing_level}.xqar", + ) metric = TrajectorySimilarityMetric( name="TrajectorySimilarityMetric", plot_path=tmp_path diff --git a/osc_validation/validation/scenario/trajectories/val_follow_trajectory_future_time_reference.py b/osc_validation/validation/scenario/trajectories/val_follow_trajectory_future_time_reference.py index 11ff663..12d0bed 100644 --- a/osc_validation/validation/scenario/trajectories/val_follow_trajectory_future_time_reference.py +++ b/osc_validation/validation/scenario/trajectories/val_follow_trajectory_future_time_reference.py @@ -60,6 +60,7 @@ def _off_trajectory_init_pose() -> TrajectoryInterpolationVertex: def test_follow_trajectory_future_time_reference_continues_init_speed_until_first_vertex( odr_file: Path, generate_tool_trace: Callable, + assert_osi_compliance: Callable, tmp_path: Path, ): """ @@ -114,6 +115,10 @@ def test_follow_trajectory_future_time_reference_continues_init_speed_until_firs rate=rate, ) assert_no_osc_engine_errors(tool_trace_channel_spec) + assert_osi_compliance( + tool_trace_channel_spec, + result_file=tmp_path / "qc_result_follow_trajectory_future_time_reference.xqar", + ) full_trace_result = ObjectStateMetric().compute( reference_channel_spec=case_result.reference_channel_spec, diff --git a/osc_validation/validation/scenario/trajectories/val_follow_trajectory_teleport.py b/osc_validation/validation/scenario/trajectories/val_follow_trajectory_teleport.py index 4b93951..f75ecb1 100644 --- a/osc_validation/validation/scenario/trajectories/val_follow_trajectory_teleport.py +++ b/osc_validation/validation/scenario/trajectories/val_follow_trajectory_teleport.py @@ -72,6 +72,7 @@ def _off_trajectory_init_pose() -> TrajectoryInterpolationVertex: def test_follow_trajectory_position_mode_teleports_to_current_trajectory_time( odr_file: Path, generate_tool_trace: Callable, + assert_osi_compliance: Callable, tmp_path: Path, case_name: str, action_start_time_s: float, @@ -125,6 +126,10 @@ def test_follow_trajectory_position_mode_teleports_to_current_trajectory_time( rate=rate, ) assert_no_osc_engine_errors(tool_trace_channel_spec) + assert_osi_compliance( + tool_trace_channel_spec, + result_file=tmp_path / f"qc_result_follow_trajectory_teleport_{case_name}.xqar", + ) metric_result = ObjectStateMetric().compute( reference_channel_spec=case_result.reference_channel_spec, diff --git a/osc_validation/validation/scenario/trajectories/val_interpolation.py b/osc_validation/validation/scenario/trajectories/val_interpolation.py index db2cced..c0d1c60 100644 --- a/osc_validation/validation/scenario/trajectories/val_interpolation.py +++ b/osc_validation/validation/scenario/trajectories/val_interpolation.py @@ -66,6 +66,7 @@ def _actor() -> TrajectoryInterpolationActor: def test_timed_polyline_trajectory_interpolation( odr_file: Path, generate_tool_trace: Callable, + assert_osi_compliance: Callable, tmp_path: Path, interpolation_mode: TrajectoryInterpolationReferenceMode, initial_speed_mps: float, @@ -120,6 +121,10 @@ def test_timed_polyline_trajectory_interpolation( rate=rate, ) assert_no_osc_engine_errors(tool_trace_channel_spec) + assert_osi_compliance( + tool_trace_channel_spec, + result_file=tmp_path / f"qc_result_trajectory_interpolation_{case_name}.xqar", + ) metric_result = ObjectStateMetric().compute( reference_channel_spec=case_result.reference_channel_spec, diff --git a/osc_validation/validation/scenario/triggers/val_condition_delay.py b/osc_validation/validation/scenario/triggers/val_condition_delay.py index 4976b55..dbf0d5f 100644 --- a/osc_validation/validation/scenario/triggers/val_condition_delay.py +++ b/osc_validation/validation/scenario/triggers/val_condition_delay.py @@ -31,6 +31,7 @@ def test_distance_longitudinal_start_trigger_respects_condition_delay( osi_trace: Path, odr_file: Path, generate_tool_trace: Callable, + assert_osi_compliance: Callable, tmp_path: Path, ): """ @@ -45,6 +46,7 @@ def test_distance_longitudinal_start_trigger_respects_condition_delay( osi_trace=osi_trace, odr_file=odr_file, generate_tool_trace=generate_tool_trace, + assert_osi_compliance=assert_osi_compliance, tmp_path=tmp_path, condition_delay_s=1.0, ) @@ -56,6 +58,7 @@ def test_speed_start_trigger_respects_condition_delay( osi_trace: Path, odr_file: Path, generate_tool_trace: Callable, + assert_osi_compliance: Callable, tmp_path: Path, ): """ @@ -70,6 +73,7 @@ def test_speed_start_trigger_respects_condition_delay( osi_trace=osi_trace, odr_file=odr_file, generate_tool_trace=generate_tool_trace, + assert_osi_compliance=assert_osi_compliance, tmp_path=tmp_path, condition_delay_s=1.0, ) diff --git a/osc_validation/validation/scenario/triggers/val_distance_euclidian_start_trigger.py b/osc_validation/validation/scenario/triggers/val_distance_euclidian_start_trigger.py index 25c8e59..347beb6 100644 --- a/osc_validation/validation/scenario/triggers/val_distance_euclidian_start_trigger.py +++ b/osc_validation/validation/scenario/triggers/val_distance_euclidian_start_trigger.py @@ -1,11 +1,10 @@ import logging from pathlib import Path from typing import Callable -from urllib.parse import urlparse import pytest -from osc_validation.dataproviders import BuiltinDataProvider, DownloadDataProvider +from osc_validation.dataproviders import BuiltinDataProvider from osc_validation.generation import ( DistancePositionTriggerSpec, TriggerTransformRequest, @@ -33,21 +32,6 @@ def osi_trace(request, builtin_data_path): provider.cleanup() -@pytest.fixture( - scope="module", - params=[ - "https://raw.githubusercontent.com/OpenSimulationInterface/qc-osi-trace/refs/heads/main/qc_ositrace/checks/osirules/rulesyml/osi_3_7_0.yml" - ], -) -def yaml_ruleset(request, tmp_path_factory): - uri = request.param - filename = Path(urlparse(uri).path).name - base_path = tmp_path_factory.mktemp("osirules") - provider = DownloadDataProvider(uri=uri, base_path=base_path) - yield provider.ensure_data_path(filename) - provider.cleanup() - - @pytest.fixture(scope="module") def odr_file(request): return request.getfixturevalue("osi_trace").with_suffix(".xodr") @@ -64,8 +48,8 @@ def odr_file(request): def test_distance_euclidian_start_trigger_activates_target_actor( osi_trace: Path, odr_file: Path, - yaml_ruleset: Path, generate_tool_trace: Callable, + assert_osi_compliance: Callable, tmp_path: Path, moving_object_id: int, trigger_object_id: int, @@ -138,6 +122,10 @@ def test_distance_euclidian_start_trigger_activates_target_actor( rate=rate, ) assert_no_osc_engine_errors(tool_trace_channel_spec) + assert_osi_compliance( + tool_trace_channel_spec, + result_file=tmp_path / "qc_result_distance_euclidian_start_trigger.xqar", + ) metric = TrajectoryAlignmentSimilarityMetric() reference_triggered_channel_spec = transform_result.reference_channel_spec diff --git a/osc_validation/validation/scenario/triggers/val_distance_longitudinal_start_trigger.py b/osc_validation/validation/scenario/triggers/val_distance_longitudinal_start_trigger.py index 809d168..83d7e84 100644 --- a/osc_validation/validation/scenario/triggers/val_distance_longitudinal_start_trigger.py +++ b/osc_validation/validation/scenario/triggers/val_distance_longitudinal_start_trigger.py @@ -1,11 +1,10 @@ import logging from pathlib import Path from typing import Callable -from urllib.parse import urlparse import pytest -from osc_validation.dataproviders import BuiltinDataProvider, DownloadDataProvider +from osc_validation.dataproviders import BuiltinDataProvider from osc_validation.generation import ( DistancePositionTriggerSpec, TriggerTransformRequest, @@ -66,21 +65,6 @@ def osi_trace(request, builtin_data_path): provider.cleanup() -@pytest.fixture( - scope="module", - params=[ - "https://raw.githubusercontent.com/OpenSimulationInterface/qc-osi-trace/refs/heads/main/qc_ositrace/checks/osirules/rulesyml/osi_3_7_0.yml" - ], -) -def yaml_ruleset(request, tmp_path_factory): - uri = request.param - filename = Path(urlparse(uri).path).name - base_path = tmp_path_factory.mktemp("osirules") - provider = DownloadDataProvider(uri=uri, base_path=base_path) - yield provider.ensure_data_path(filename) - provider.cleanup() - - @pytest.fixture(scope="module") def odr_file(request): return request.getfixturevalue("osi_trace").with_suffix(".xodr") @@ -90,6 +74,7 @@ def _run_distance_longitudinal_start_trigger_case( osi_trace: Path, odr_file: Path, generate_tool_trace: Callable, + assert_osi_compliance: Callable, tmp_path: Path, condition_delay_s: float = 0.0, moving_object_id: int = 2, @@ -190,6 +175,10 @@ def _run_distance_longitudinal_start_trigger_case( rate=rate, ) assert_no_osc_engine_errors(tool_trace_channel_spec) + assert_osi_compliance( + tool_trace_channel_spec, + result_file=tmp_path / f"qc_result_{case_name}.xqar", + ) metric = TrajectoryAlignmentSimilarityMetric() reference_triggered_channel_spec = transform_result.reference_channel_spec @@ -224,8 +213,8 @@ def _run_distance_longitudinal_start_trigger_case( def test_distance_longitudinal_start_trigger_activates_target_actor( osi_trace: Path, odr_file: Path, - yaml_ruleset: Path, generate_tool_trace: Callable, + assert_osi_compliance: Callable, tmp_path: Path, ): """ @@ -240,5 +229,6 @@ def test_distance_longitudinal_start_trigger_activates_target_actor( osi_trace=osi_trace, odr_file=odr_file, generate_tool_trace=generate_tool_trace, + assert_osi_compliance=assert_osi_compliance, tmp_path=tmp_path, ) diff --git a/osc_validation/validation/scenario/triggers/val_simulation_time_start_trigger.py b/osc_validation/validation/scenario/triggers/val_simulation_time_start_trigger.py index d5ddc86..1b7b1f7 100644 --- a/osc_validation/validation/scenario/triggers/val_simulation_time_start_trigger.py +++ b/osc_validation/validation/scenario/triggers/val_simulation_time_start_trigger.py @@ -1,11 +1,10 @@ import logging from pathlib import Path from typing import Callable -from urllib.parse import urlparse import pytest -from osc_validation.dataproviders import BuiltinDataProvider, DownloadDataProvider +from osc_validation.dataproviders import BuiltinDataProvider from osc_validation.generation import ( InitPoseOverride, SimulationTimeTriggerSpec, diff --git a/osc_validation/validation/scenario/triggers/val_speed_start_trigger.py b/osc_validation/validation/scenario/triggers/val_speed_start_trigger.py index 7152a5a..388ae47 100644 --- a/osc_validation/validation/scenario/triggers/val_speed_start_trigger.py +++ b/osc_validation/validation/scenario/triggers/val_speed_start_trigger.py @@ -2,11 +2,10 @@ import math from pathlib import Path from typing import Callable -from urllib.parse import urlparse import pytest -from osc_validation.dataproviders import BuiltinDataProvider, DownloadDataProvider +from osc_validation.dataproviders import BuiltinDataProvider from osc_validation.generation import ( SpeedTriggerSpec, TriggerTransformRequest, @@ -75,21 +74,6 @@ def osi_trace(request, builtin_data_path): provider.cleanup() -@pytest.fixture( - scope="module", - params=[ - "https://raw.githubusercontent.com/OpenSimulationInterface/qc-osi-trace/refs/heads/main/qc_ositrace/checks/osirules/rulesyml/osi_3_7_0.yml" - ], -) -def yaml_ruleset(request, tmp_path_factory): - uri = request.param - filename = Path(urlparse(uri).path).name - base_path = tmp_path_factory.mktemp("osirules") - provider = DownloadDataProvider(uri=uri, base_path=base_path) - yield provider.ensure_data_path(filename) - provider.cleanup() - - @pytest.fixture(scope="module") def odr_file(request): return request.getfixturevalue("osi_trace").with_suffix(".xodr") @@ -99,6 +83,7 @@ def _run_speed_start_trigger_case( osi_trace: Path, odr_file: Path, generate_tool_trace: Callable, + assert_osi_compliance: Callable, tmp_path: Path, condition_delay_s: float = 0.0, moving_object_id: int = 2, # Moving object to be evaluated for trigger activation and trajectory alignment by this test case @@ -180,6 +165,10 @@ def _run_speed_start_trigger_case( rate=rate, ) assert_no_osc_engine_errors(tool_trace_channel_spec) + assert_osi_compliance( + tool_trace_channel_spec, + result_file=tmp_path / f"qc_result_{case_name}.xqar", + ) metric = TrajectoryAlignmentSimilarityMetric() reference_triggered_channel_spec = transform_result.reference_channel_spec @@ -211,8 +200,8 @@ def _run_speed_start_trigger_case( def test_speed_start_trigger_activates_target_actor( osi_trace: Path, odr_file: Path, - yaml_ruleset: Path, generate_tool_trace: Callable, + assert_osi_compliance: Callable, tmp_path: Path, ): """ @@ -227,6 +216,7 @@ def test_speed_start_trigger_activates_target_actor( osi_trace=osi_trace, odr_file=odr_file, generate_tool_trace=generate_tool_trace, + assert_osi_compliance=assert_osi_compliance, tmp_path=tmp_path, trigger_speed_mps=50 / 3.6, condition_edge="rising", @@ -238,8 +228,8 @@ def test_speed_start_trigger_activates_target_actor( def test_speed_start_trigger_condition_edge_none_activates_when_initially_true( osi_trace: Path, odr_file: Path, - yaml_ruleset: Path, generate_tool_trace: Callable, + assert_osi_compliance: Callable, tmp_path: Path, ): """ @@ -254,6 +244,7 @@ def test_speed_start_trigger_condition_edge_none_activates_when_initially_true( osi_trace=osi_trace, odr_file=odr_file, generate_tool_trace=generate_tool_trace, + assert_osi_compliance=assert_osi_compliance, tmp_path=tmp_path, trigger_speed_mps=0.0, condition_edge="none", @@ -264,8 +255,8 @@ def test_speed_start_trigger_condition_edge_none_activates_when_initially_true( def test_speed_start_trigger_condition_edge_falling_activates( osi_trace: Path, odr_file: Path, - yaml_ruleset: Path, generate_tool_trace: Callable, + assert_osi_compliance: Callable, tmp_path: Path, ): """ @@ -280,6 +271,7 @@ def test_speed_start_trigger_condition_edge_falling_activates( osi_trace=osi_trace, odr_file=odr_file, generate_tool_trace=generate_tool_trace, + assert_osi_compliance=assert_osi_compliance, tmp_path=tmp_path, trigger_speed_mps=20 / 3.6, condition_edge="falling", diff --git a/osc_validation/validation/scenario/triggers/val_time_to_collision_start_trigger.py b/osc_validation/validation/scenario/triggers/val_time_to_collision_start_trigger.py index 1ff3328..2efb86d 100644 --- a/osc_validation/validation/scenario/triggers/val_time_to_collision_start_trigger.py +++ b/osc_validation/validation/scenario/triggers/val_time_to_collision_start_trigger.py @@ -1,11 +1,10 @@ import logging from pathlib import Path from typing import Callable -from urllib.parse import urlparse import pytest -from osc_validation.dataproviders import BuiltinDataProvider, DownloadDataProvider +from osc_validation.dataproviders import BuiltinDataProvider from osc_validation.generation import ( TimeToCollisionPositionTriggerSpec, TriggerTransformRequest, @@ -33,21 +32,6 @@ def osi_trace(request, builtin_data_path): provider.cleanup() -@pytest.fixture( - scope="module", - params=[ - "https://raw.githubusercontent.com/OpenSimulationInterface/qc-osi-trace/refs/heads/main/qc_ositrace/checks/osirules/rulesyml/osi_3_7_0.yml" - ], -) -def yaml_ruleset(request, tmp_path_factory): - uri = request.param - filename = Path(urlparse(uri).path).name - base_path = tmp_path_factory.mktemp("osirules") - provider = DownloadDataProvider(uri=uri, base_path=base_path) - yield provider.ensure_data_path(filename) - provider.cleanup() - - @pytest.fixture(scope="module") def odr_file(request): return request.getfixturevalue("osi_trace").with_suffix(".xodr") @@ -87,8 +71,8 @@ def _get_object_position_at_last_frame( def test_time_to_collision_start_trigger_activates_target_actor( osi_trace: Path, odr_file: Path, - yaml_ruleset: Path, generate_tool_trace: Callable, + assert_osi_compliance: Callable, tmp_path: Path, moving_object_id: int, trigger_object_id: int, @@ -196,6 +180,10 @@ def test_time_to_collision_start_trigger_activates_target_actor( rate=rate, ) assert_no_osc_engine_errors(tool_trace_channel_spec) + assert_osi_compliance( + tool_trace_channel_spec, + result_file=tmp_path / "qc_result_ttc_start_trigger.xqar", + ) metric = TrajectoryAlignmentSimilarityMetric() reference_triggered_channel_spec = transform_result.reference_channel_spec diff --git a/osc_validation/validation/scenario/triggers/val_traveled_distance_start_trigger.py b/osc_validation/validation/scenario/triggers/val_traveled_distance_start_trigger.py index 644b9ec..7b44664 100644 --- a/osc_validation/validation/scenario/triggers/val_traveled_distance_start_trigger.py +++ b/osc_validation/validation/scenario/triggers/val_traveled_distance_start_trigger.py @@ -1,11 +1,10 @@ import logging from pathlib import Path from typing import Callable -from urllib.parse import urlparse import pytest -from osc_validation.dataproviders import BuiltinDataProvider, DownloadDataProvider +from osc_validation.dataproviders import BuiltinDataProvider from osc_validation.generation import ( TraveledDistanceTriggerSpec, TriggerTransformRequest, @@ -31,21 +30,6 @@ def osi_trace(request, builtin_data_path): provider.cleanup() -@pytest.fixture( - scope="module", - params=[ - "https://raw.githubusercontent.com/OpenSimulationInterface/qc-osi-trace/refs/heads/main/qc_ositrace/checks/osirules/rulesyml/osi_3_7_0.yml" - ], -) -def yaml_ruleset(request, tmp_path_factory): - uri = request.param - filename = Path(urlparse(uri).path).name - base_path = tmp_path_factory.mktemp("osirules") - provider = DownloadDataProvider(uri=uri, base_path=base_path) - yield provider.ensure_data_path(filename) - provider.cleanup() - - @pytest.fixture(scope="module") def odr_file(request): return request.getfixturevalue("osi_trace").with_suffix(".xodr") @@ -62,8 +46,8 @@ def odr_file(request): def test_traveled_distance_start_trigger_activates_target_actor( osi_trace: Path, odr_file: Path, - yaml_ruleset: Path, generate_tool_trace: Callable, + assert_osi_compliance: Callable, tmp_path: Path, moving_object_id: int, trigger_object_id: int, @@ -132,6 +116,10 @@ def test_traveled_distance_start_trigger_activates_target_actor( rate=rate, ) assert_no_osc_engine_errors(tool_trace_channel_spec) + assert_osi_compliance( + tool_trace_channel_spec, + result_file=tmp_path / "qc_result_traveled_distance_start_trigger.xqar", + ) metric = TrajectoryAlignmentSimilarityMetric() delayed_reference_channel_spec = transform_result.reference_channel_spec