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
4 changes: 4 additions & 0 deletions clients/deadline.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ def remaining(self) -> float:
left = self._timeout - elapsed
return left if left > 0 else 0.0

def remaining_ms(self) -> float:
"""Return milliseconds left before the deadline (never negative)."""
return self.remaining() * 1000

def expired(self) -> bool:
"""Return True when no time remains."""
return self.remaining() <= 0
Expand Down
19 changes: 19 additions & 0 deletions scenarios/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ def build_parser() -> argparse.ArgumentParser:
"location", help="Print catalog location for a scenario id"
)
location_parser.add_argument("scenario_id", help="Scenario id from the catalog")

find_parser = subparsers.add_parser(
"find", help="Find scenarios by id or description substring"
)
find_parser.add_argument("query", help="Case-insensitive match on id or description")
return parser


Expand Down Expand Up @@ -197,6 +202,20 @@ def main(argv: list[str] | None = None) -> int:
print(entry.location)
return 0

if args.command == "find":
query = args.query.casefold()
matches = [
entry.scenario_id
for entry in CATALOG
if query in entry.scenario_id.casefold()
or query in entry.description.casefold()
]
if not matches:
return 1
for scenario_id in matches:
print(scenario_id)
return 0

return 1


Expand Down
25 changes: 25 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,5 +251,30 @@ def test_location_unknown_scenario_returns_error(self):
self.assertIn("unknown scenario: missing", err.getvalue())



def test_find_matches_id_case_insensitive(self):
buffer = io.StringIO()
with redirect_stdout(buffer):
code = main(["find", "CIRCUIT-BREAKER"])
self.assertEqual(code, 0)
lines = [line for line in buffer.getvalue().splitlines() if line.strip()]
self.assertEqual(lines, ["circuit-breaker"])

def test_find_matches_description_case_insensitive(self):
buffer = io.StringIO()
with redirect_stdout(buffer):
code = main(["find", "rate-limit"])
self.assertEqual(code, 0)
lines = [line for line in buffer.getvalue().splitlines() if line.strip()]
self.assertEqual(lines, ["throttle"])

def test_find_no_matches_returns_error(self):
buffer = io.StringIO()
with redirect_stdout(buffer):
code = main(["find", "zzzz-no-such"])
self.assertEqual(code, 1)
self.assertEqual(buffer.getvalue().strip(), "")


if __name__ == "__main__":
unittest.main()
16 changes: 16 additions & 0 deletions tests/test_deadline.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,21 @@ def test_remaining_fraction_zero_timeout_is_zero(self):
self.assertEqual(deadline.remaining_fraction(), 0.0)



def test_remaining_ms_scales_remaining(self):
clock = FakeClock()
deadline = Deadline(clock, timeout=1.0)
self.assertAlmostEqual(deadline.remaining_ms(), 1000.0)
clock.sleep(0.4)
self.assertAlmostEqual(deadline.remaining_ms(), 600.0)
self.assertFalse(deadline.expired())

def test_remaining_ms_zero_when_expired(self):
clock = FakeClock()
deadline = Deadline(clock, timeout=0.5)
clock.sleep(0.5)
self.assertEqual(deadline.remaining_ms(), 0.0)


if __name__ == "__main__":
unittest.main()
Loading