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 @@ -29,6 +29,10 @@ def elapsed(self) -> float:
"""Return seconds since start; uncapped, may exceed timeout."""
return self._clock.monotonic() - self._started_at

def elapsed_ms(self) -> float:
"""Return milliseconds since start; uncapped, may exceed timeout."""
return self.elapsed() * 1000

def fraction_used(self) -> float:
"""Return elapsed/timeout clamped to [0, 1]; timeout==0 yields 1.0."""
if self._timeout == 0:
Expand Down
17 changes: 12 additions & 5 deletions scenarios/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ def build_parser() -> argparse.ArgumentParser:
"find", help="Find scenarios by id or description substring"
)
find_parser.add_argument("query", help="Case-insensitive match on id or description")
find_parser.add_argument(
"--format",
choices=("text", "json"),
default="text",
help="Output format (default: text)",
)
return parser


Expand Down Expand Up @@ -210,11 +216,12 @@ def main(argv: list[str] | None = None) -> int:
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
if args.format == "json":
print(json.dumps(matches, indent=2))
else:
for scenario_id in matches:
print(scenario_id)
return 0 if matches else 1

return 1

Expand Down
24 changes: 24 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,30 @@ def test_find_no_matches_returns_error(self):
self.assertEqual(code, 1)
self.assertEqual(buffer.getvalue().strip(), "")

def test_find_json_format(self):
buffer = io.StringIO()
with redirect_stdout(buffer):
code = main(["find", "CIRCUIT-BREAKER", "--format", "json"])
self.assertEqual(code, 0)
parsed = json.loads(buffer.getvalue())
self.assertEqual(parsed, ["circuit-breaker"])

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

def test_find_json_no_matches_returns_empty_array(self):
buffer = io.StringIO()
with redirect_stdout(buffer):
code = main(["find", "zzzz-no-such", "--format", "json"])
self.assertEqual(code, 1)
parsed = json.loads(buffer.getvalue())
self.assertEqual(parsed, [])


if __name__ == "__main__":
unittest.main()
10 changes: 10 additions & 0 deletions tests/test_deadline.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@ def test_elapsed_tracks_fake_clock_uncapped(self):
self.assertAlmostEqual(deadline.elapsed(), 0.8)
self.assertGreater(deadline.elapsed(), 0.5)

def test_elapsed_ms_tracks_fake_clock_uncapped(self):
clock = FakeClock()
deadline = Deadline(clock, timeout=0.5)
self.assertAlmostEqual(deadline.elapsed_ms(), 0.0)
clock.sleep(0.4)
self.assertAlmostEqual(deadline.elapsed_ms(), 400.0)
clock.sleep(0.4)
self.assertAlmostEqual(deadline.elapsed_ms(), 800.0)
self.assertGreater(deadline.elapsed_ms(), 500.0)

def test_fraction_used_clamped_to_unit_interval(self):
clock = FakeClock()
deadline = Deadline(clock, timeout=1.0)
Expand Down
Loading