diff --git a/clients/deadline.py b/clients/deadline.py index dac5a08..ae11918 100644 --- a/clients/deadline.py +++ b/clients/deadline.py @@ -29,6 +29,17 @@ def elapsed(self) -> float: """Return seconds since start; uncapped, may exceed timeout.""" return self._clock.monotonic() - self._started_at + def fraction_used(self) -> float: + """Return elapsed/timeout clamped to [0, 1]; timeout==0 yields 1.0.""" + if self._timeout == 0: + return 1.0 + ratio = self.elapsed() / self._timeout + if ratio < 0: + return 0.0 + if ratio > 1: + return 1.0 + return ratio + def remaining(self) -> float: """Return seconds left before the deadline (never negative).""" elapsed = self._clock.monotonic() - self._started_at diff --git a/scenarios/cli.py b/scenarios/cli.py index 97b2093..de409fc 100644 --- a/scenarios/cli.py +++ b/scenarios/cli.py @@ -69,6 +69,11 @@ def build_parser() -> argparse.ArgumentParser: ) subparsers.add_parser("count", help="Print number of known scenarios") + + has_parser = subparsers.add_parser( + "has", help="Check whether a scenario id is known" + ) + has_parser.add_argument("scenario_id", help="Scenario id from the catalog") return parser @@ -173,6 +178,12 @@ def main(argv: list[str] | None = None) -> int: print(len(known_scenario_ids())) return 0 + if args.command == "has": + if args.scenario_id in known_scenario_ids(): + return 0 + print(f"unknown scenario: {args.scenario_id}", file=sys.stderr) + return 1 + return 1 diff --git a/tests/test_cli.py b/tests/test_cli.py index b16c784..03fb37c 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -2,7 +2,7 @@ import io import json import unittest -from contextlib import redirect_stdout +from contextlib import redirect_stderr, redirect_stdout from unittest.mock import patch from scenarios.cli import main @@ -223,5 +223,18 @@ def test_ids_text_format_default_unchanged(self): self.assertEqual(lines, list(known_scenario_ids())) + def test_has_known_scenario_returns_zero(self): + code = main(["has", "retry"]) + self.assertEqual(code, 0) + + def test_has_unknown_scenario_returns_error(self): + err = io.StringIO() + with redirect_stderr(err): + code = main(["has", "missing"]) + self.assertEqual(code, 1) + self.assertIn("unknown scenario: missing", err.getvalue()) + + + if __name__ == "__main__": unittest.main() diff --git a/tests/test_deadline.py b/tests/test_deadline.py index 572aebf..bd918ca 100644 --- a/tests/test_deadline.py +++ b/tests/test_deadline.py @@ -51,6 +51,22 @@ def test_elapsed_tracks_fake_clock_uncapped(self): self.assertAlmostEqual(deadline.elapsed(), 0.8) self.assertGreater(deadline.elapsed(), 0.5) + def test_fraction_used_clamped_to_unit_interval(self): + clock = FakeClock() + deadline = Deadline(clock, timeout=1.0) + self.assertAlmostEqual(deadline.fraction_used(), 0.0) + clock.sleep(0.25) + self.assertAlmostEqual(deadline.fraction_used(), 0.25) + clock.sleep(0.75) + self.assertAlmostEqual(deadline.fraction_used(), 1.0) + clock.sleep(0.5) + self.assertAlmostEqual(deadline.fraction_used(), 1.0) + + def test_fraction_used_zero_timeout_is_one(self): + clock = FakeClock() + deadline = Deadline(clock, timeout=0.0) + self.assertEqual(deadline.fraction_used(), 1.0) + if __name__ == "__main__": unittest.main()