shrinkray 25.12.27.3__py3-none-any.whl → 25.12.29.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -37,14 +37,6 @@ class OutputStream(Protocol):
37
37
  async def send(self, data: bytes) -> None: ...
38
38
 
39
39
 
40
- class StdoutStream:
41
- """Wrapper around sys.stdout for the OutputStream protocol."""
42
-
43
- async def send(self, data: bytes) -> None:
44
- sys.stdout.write(data.decode("utf-8"))
45
- sys.stdout.flush()
46
-
47
-
48
40
  class ReducerWorker:
49
41
  """Runs the reducer in a subprocess with JSON protocol communication."""
50
42
 
@@ -66,6 +58,11 @@ class ReducerWorker:
66
58
  self._output_stream = output_stream
67
59
  # Output directory for test output capture (cleaned up on shutdown)
68
60
  self._output_dir: str | None = None
61
+ # Size history for graphing: list of (runtime_seconds, size) tuples
62
+ self._size_history: list[tuple[float, int]] = []
63
+ self._last_sent_history_index: int = 0
64
+ self._last_recorded_size: int = 0
65
+ self._last_history_time: float = 0.0
69
66
 
70
67
  async def emit(self, msg: Response | ProgressUpdate) -> None:
71
68
  """Write a message to the output stream."""
@@ -165,9 +162,9 @@ class ReducerWorker:
165
162
  find_clang_delta,
166
163
  )
167
164
  from shrinkray.state import (
165
+ OutputCaptureManager,
168
166
  ShrinkRayDirectoryState,
169
167
  ShrinkRayStateSingleFile,
170
- TestOutputManager,
171
168
  )
172
169
  from shrinkray.work import Volume
173
170
 
@@ -221,7 +218,7 @@ class ReducerWorker:
221
218
 
222
219
  # Create output manager for test output capture (always enabled for TUI)
223
220
  self._output_dir = tempfile.mkdtemp(prefix="shrinkray-output-")
224
- self.state.output_manager = TestOutputManager(output_dir=self._output_dir)
221
+ self.state.output_manager = OutputCaptureManager(output_dir=self._output_dir)
225
222
 
226
223
  self.problem = self.state.problem
227
224
  self.reducer = self.state.reducer
@@ -312,17 +309,23 @@ class ReducerWorker:
312
309
  return Response(id=request_id, result={"status": "skipped"})
313
310
  return Response(id=request_id, error="Reducer does not support pass control")
314
311
 
315
- def _get_test_output_preview(self) -> tuple[str, int | None]:
316
- """Get preview of current test output and active test ID."""
312
+ def _get_test_output_preview(self) -> tuple[str, int | None, int | None]:
313
+ """Get preview of current test output, test ID, and return code.
314
+
315
+ Returns (content, test_id, return_code) where:
316
+ - content: the last 4KB of the output file
317
+ - test_id: the test ID being displayed
318
+ - return_code: None if test is still running, otherwise the exit code
319
+ """
317
320
  if self.state is None or self.state.output_manager is None:
318
- return "", None
321
+ return "", None, None
319
322
 
320
- manager = self.state.output_manager
321
- active_test_id = manager.get_active_test_id()
322
- output_path = manager.get_current_output_path()
323
+ output_path, test_id, return_code = (
324
+ self.state.output_manager.get_current_output()
325
+ )
323
326
 
324
327
  if output_path is None:
325
- return "", active_test_id
328
+ return "", None, None
326
329
 
327
330
  # Read last 4KB of file
328
331
  try:
@@ -334,9 +337,13 @@ class ReducerWorker:
334
337
  else:
335
338
  f.seek(0)
336
339
  data = f.read()
337
- return data.decode("utf-8", errors="replace"), active_test_id
340
+ return (
341
+ data.decode("utf-8", errors="replace"),
342
+ test_id,
343
+ return_code,
344
+ )
338
345
  except OSError:
339
- return "", active_test_id
346
+ return "", test_id, return_code
340
347
 
341
348
  def _get_content_preview(self) -> tuple[str, bool]:
342
349
  """Get a preview of the current test case content."""
@@ -387,6 +394,29 @@ class ReducerWorker:
387
394
  return None
388
395
 
389
396
  stats = self.problem.stats
397
+ runtime = time.time() - stats.start_time
398
+ current_size = stats.current_test_case_size
399
+
400
+ # Record size history when size changes or periodically
401
+ # Use 200ms interval for first 5 minutes, then 1s (ticks are at 1-minute intervals)
402
+ history_interval = 1.0 if runtime >= 300 else 0.2
403
+
404
+ if not self._size_history:
405
+ # First sample: record initial size at time 0
406
+ self._size_history.append((0.0, stats.initial_test_case_size))
407
+ self._last_recorded_size = stats.initial_test_case_size
408
+ self._last_history_time = 0.0
409
+
410
+ if current_size != self._last_recorded_size:
411
+ # Size changed - always record
412
+ self._size_history.append((runtime, current_size))
413
+ self._last_recorded_size = current_size
414
+ self._last_history_time = runtime
415
+ elif runtime - self._last_history_time >= history_interval:
416
+ # No size change but interval passed - record periodic update
417
+ self._size_history.append((runtime, current_size))
418
+ self._last_history_time = runtime
419
+
390
420
  content_preview, hex_mode = self._get_content_preview()
391
421
 
392
422
  # Get parallel workers count and track average
@@ -441,7 +471,13 @@ class ReducerWorker:
441
471
  disabled_passes = []
442
472
 
443
473
  # Get test output preview
444
- test_output_preview, active_test_id = self._get_test_output_preview()
474
+ test_output_preview, active_test_id, last_return_code = (
475
+ self._get_test_output_preview()
476
+ )
477
+
478
+ # Get new size history entries since last update
479
+ new_entries = self._size_history[self._last_sent_history_index :]
480
+ self._last_sent_history_index = len(self._size_history)
445
481
 
446
482
  return ProgressUpdate(
447
483
  status=self.reducer.status if self.reducer else "",
@@ -451,7 +487,7 @@ class ReducerWorker:
451
487
  reductions=stats.reductions,
452
488
  interesting_calls=stats.interesting_calls,
453
489
  wasted_calls=stats.wasted_interesting_calls,
454
- runtime=time.time() - stats.start_time,
490
+ runtime=runtime,
455
491
  parallel_workers=parallel_workers,
456
492
  average_parallelism=average_parallelism,
457
493
  effective_parallelism=effective_parallelism,
@@ -463,6 +499,8 @@ class ReducerWorker:
463
499
  disabled_passes=disabled_passes,
464
500
  test_output_preview=test_output_preview,
465
501
  active_test_id=active_test_id,
502
+ last_test_return_code=last_return_code,
503
+ new_size_history=new_entries,
466
504
  )
467
505
 
468
506
  async def emit_progress_updates(self) -> None: