shrinkray 25.12.27.3__tar.gz → 25.12.28.0__tar.gz

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.
Files changed (69) hide show
  1. {shrinkray-25.12.27.3/src/shrinkray.egg-info → shrinkray-25.12.28.0}/PKG-INFO +1 -1
  2. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/pyproject.toml +1 -1
  3. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/passes/bytes.py +0 -125
  4. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/passes/clangdelta.py +0 -24
  5. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/passes/genericlanguages.py +0 -20
  6. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/passes/json.py +0 -8
  7. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/passes/patching.py +0 -63
  8. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/passes/sequences.py +0 -25
  9. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/reducer.py +0 -50
  10. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/state.py +1 -4
  11. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/subprocess/__init__.py +0 -4
  12. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/subprocess/protocol.py +0 -11
  13. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/subprocess/worker.py +0 -8
  14. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0/src/shrinkray.egg-info}/PKG-INFO +1 -1
  15. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray.egg-info/SOURCES.txt +0 -2
  16. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_byte_reduction_passes.py +1 -97
  17. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_clang_delta.py +2 -47
  18. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_generic_language.py +0 -31
  19. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_generic_shrinking_properties.py +0 -15
  20. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_json_passes.py +0 -43
  21. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_patching.py +0 -140
  22. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_python_reducers.py +0 -11
  23. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_reducer.py +1 -13
  24. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_reduction_passes.py +8 -169
  25. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_subprocess_protocol.py +0 -32
  26. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_subprocess_worker.py +1 -13
  27. shrinkray-25.12.27.3/src/shrinkray/display.py +0 -75
  28. shrinkray-25.12.27.3/tests/test_display.py +0 -135
  29. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/LICENSE +0 -0
  30. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/README.md +0 -0
  31. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/setup.cfg +0 -0
  32. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/__init__.py +0 -0
  33. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/__main__.py +0 -0
  34. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/cli.py +0 -0
  35. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/formatting.py +0 -0
  36. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/passes/__init__.py +0 -0
  37. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/passes/definitions.py +0 -0
  38. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/passes/python.py +0 -0
  39. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/passes/sat.py +0 -0
  40. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/problem.py +0 -0
  41. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/process.py +0 -0
  42. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/py.typed +0 -0
  43. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/subprocess/client.py +0 -0
  44. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/tui.py +0 -0
  45. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/ui.py +0 -0
  46. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/validation.py +0 -0
  47. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/work.py +0 -0
  48. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray.egg-info/dependency_links.txt +0 -0
  49. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray.egg-info/entry_points.txt +0 -0
  50. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray.egg-info/requires.txt +0 -0
  51. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray.egg-info/top_level.txt +0 -0
  52. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_cli.py +0 -0
  53. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_definitions.py +0 -0
  54. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_dimacs_cnf.py +0 -0
  55. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_formatting.py +0 -0
  56. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_main.py +0 -0
  57. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_misc_reduction_performance.py +0 -0
  58. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_natural_sort_orders.py +0 -0
  59. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_problem.py +0 -0
  60. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_process.py +0 -0
  61. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_sat.py +0 -0
  62. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_state.py +0 -0
  63. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_subprocess_client.py +0 -0
  64. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_subprocess_integration.py +0 -0
  65. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_tui.py +0 -0
  66. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_tui_snapshots.py +0 -0
  67. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_ui.py +0 -0
  68. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_validation.py +0 -0
  69. {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_work.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shrinkray
3
- Version: 25.12.27.3
3
+ Version: 25.12.28.0
4
4
  Summary: Shrink Ray
5
5
  Author-email: "David R. MacIver" <david@drmaciver.com>
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "shrinkray"
3
- version = "25.12.27.3"
3
+ version = "25.12.28.0"
4
4
  description = "Shrink Ray"
5
5
  authors = [
6
6
  {name = "David R. MacIver", email = "david@drmaciver.com"}
@@ -28,26 +28,6 @@ from shrinkray.passes.patching import Cuts, Patches, apply_patches
28
28
  from shrinkray.problem import Format, ReductionProblem
29
29
 
30
30
 
31
- @define(frozen=True)
32
- class Encoding(Format[bytes, str]):
33
- """Format that decodes/encodes bytes using a character encoding."""
34
-
35
- encoding: str
36
-
37
- def __repr__(self) -> str:
38
- return f"Encoding({repr(self.encoding)})"
39
-
40
- @property
41
- def name(self) -> str:
42
- return self.encoding
43
-
44
- def parse(self, input: bytes) -> str:
45
- return input.decode(self.encoding)
46
-
47
- def dumps(self, input: str) -> bytes:
48
- return input.encode(self.encoding)
49
-
50
-
51
31
  @define(frozen=True)
52
32
  class Split(Format[bytes, list[bytes]]):
53
33
  """Format that splits bytes by a delimiter.
@@ -165,9 +145,6 @@ def tokenize(text: bytes) -> list[bytes]:
165
145
  return result
166
146
 
167
147
 
168
- MAX_DELETE_INTERVAL = 8
169
-
170
-
171
148
  async def lexeme_based_deletions(
172
149
  problem: ReductionProblem[bytes], min_size: int = 8
173
150
  ) -> None:
@@ -601,108 +578,6 @@ async def lower_individual_bytes(problem: ReductionProblem[bytes]) -> None:
601
578
  await apply_patches(problem, IndividualByteReplacement(), patches)
602
579
 
603
580
 
604
- RegionReplacementPatch = list[tuple[int, int, int]]
605
-
606
-
607
- class RegionReplacement(Patches[RegionReplacementPatch, bytes]):
608
- @property
609
- def empty(self) -> RegionReplacementPatch:
610
- return []
611
-
612
- def combine(self, *patches: RegionReplacementPatch) -> RegionReplacementPatch:
613
- result: RegionReplacementPatch = []
614
- for p in patches:
615
- result.extend(p)
616
- return result
617
-
618
- def apply(self, patch: RegionReplacementPatch, target: bytes) -> bytes:
619
- result = bytearray(target)
620
- for i, j, d in patch:
621
- if d < result[i]:
622
- for k in range(i, j):
623
- result[k] = d
624
- return bytes(result)
625
-
626
- def size(self, patch: RegionReplacementPatch) -> int:
627
- return 0
628
-
629
-
630
- async def short_replacements(problem: ReductionProblem[bytes]) -> None:
631
- """Replace short regions with uniform byte values.
632
-
633
- Tries replacing 1-4 byte regions with uniform values like 0, 1,
634
- space, newline, or period. Useful for simplifying small sequences.
635
- """
636
- target = problem.current_test_case
637
- patches = [
638
- [(i, j, c)]
639
- for c in [0, 1] + list(b"01 \t\n\r.")
640
- for i in range(len(target))
641
- if target[i] > c
642
- for j in range(i + 1, min(i + 5, len(target) + 1))
643
- ]
644
-
645
- await apply_patches(problem, RegionReplacement(), patches)
646
-
647
-
648
- WHITESPACE = b" \t\r\n"
649
-
650
-
651
- async def sort_whitespace(problem: ReductionProblem[bytes]) -> None:
652
- """NB: This is a stupid pass that we only really need for artificial
653
- test cases, but it's helpful for allowing those artificial test cases
654
- to expose other issues."""
655
-
656
- whitespace_up_to = 0
657
- while (
658
- whitespace_up_to < len(problem.current_test_case)
659
- and problem.current_test_case[whitespace_up_to] not in WHITESPACE
660
- ):
661
- whitespace_up_to += 1
662
- while (
663
- whitespace_up_to < len(problem.current_test_case)
664
- and problem.current_test_case[whitespace_up_to] in WHITESPACE
665
- ):
666
- whitespace_up_to += 1
667
-
668
- # If the initial whitespace ends with a newline we want to keep it doing
669
- # that. This is mostly for Python purposes.
670
- if (
671
- whitespace_up_to > 0
672
- and problem.current_test_case[whitespace_up_to - 1] == b"\n"[0]
673
- ):
674
- whitespace_up_to -= 1
675
-
676
- i = whitespace_up_to + 1
677
-
678
- while i < len(problem.current_test_case):
679
- if problem.current_test_case[i] not in WHITESPACE:
680
- i += 1
681
- continue
682
-
683
- async def can_move_to_whitespace(k: int) -> bool:
684
- if i + k > len(problem.current_test_case):
685
- return False
686
-
687
- base = problem.current_test_case
688
- target = base[i : i + k]
689
-
690
- if any(c not in WHITESPACE for c in target):
691
- return False
692
-
693
- prefix = base[:whitespace_up_to]
694
- attempt = prefix + target + base[whitespace_up_to:i] + base[i + k :]
695
- return await problem.is_interesting(attempt)
696
-
697
- k = await problem.work.find_large_integer(can_move_to_whitespace)
698
- whitespace_up_to += k
699
- i += k + 1
700
- test_case = problem.current_test_case
701
- await problem.is_interesting(
702
- bytes(sorted(test_case[:whitespace_up_to])) + test_case[whitespace_up_to:]
703
- )
704
-
705
-
706
581
  # These are some cheat substitutions that are sometimes helpful, but mostly
707
582
  # for passing stupid tests.
708
583
  STANDARD_SUBSTITUTIONS = [(b"\0\0", b"\1"), (b"\0\0", b"\xff")]
@@ -1,6 +1,5 @@
1
1
  import os
2
2
  import subprocess
3
- from functools import lru_cache
4
3
  from glob import glob
5
4
  from shutil import which
6
5
  from tempfile import NamedTemporaryFile
@@ -26,29 +25,6 @@ def find_clang_delta():
26
25
  return clang_delta
27
26
 
28
27
 
29
- @lru_cache(maxsize=1)
30
- def clang_delta_works() -> bool:
31
- """Check if clang_delta can actually execute.
32
-
33
- This verifies not just that the binary exists, but that it can run.
34
- On some systems (e.g., Ubuntu 24.04), creduce is installed but
35
- clang_delta fails at runtime due to shared library issues.
36
- """
37
- clang_delta = find_clang_delta()
38
- if not clang_delta:
39
- return False
40
- try:
41
- # Run a simple test to verify clang_delta works
42
- result = subprocess.run(
43
- [clang_delta, "--help"],
44
- capture_output=True,
45
- timeout=5,
46
- )
47
- return result.returncode == 0
48
- except (OSError, subprocess.TimeoutExpired):
49
- return False
50
-
51
-
52
28
  TRANSFORMATIONS: list[str] = [
53
29
  "aggregate-to-scalar",
54
30
  "binop-simplification",
@@ -9,7 +9,6 @@ from string import ascii_lowercase, ascii_uppercase
9
9
  from typing import AnyStr
10
10
 
11
11
  import trio
12
- from attr import define
13
12
 
14
13
  from shrinkray.passes.bytes import ByteReplacement, delete_intervals
15
14
  from shrinkray.passes.definitions import ReductionPass
@@ -24,25 +23,6 @@ from shrinkray.problem import (
24
23
  from shrinkray.work import NotFound
25
24
 
26
25
 
27
- @define(frozen=True)
28
- class Substring(Format[AnyStr, AnyStr]):
29
- prefix: AnyStr
30
- suffix: AnyStr
31
-
32
- @property
33
- def name(self) -> str:
34
- return f"Substring({len(self.prefix)}, {len(self.suffix)})"
35
-
36
- def parse(self, input: AnyStr) -> AnyStr:
37
- if input.startswith(self.prefix) and input.endswith(self.suffix):
38
- return input[len(self.prefix) : len(input) - len(self.suffix)]
39
- else:
40
- raise ParseError()
41
-
42
- def dumps(self, input: AnyStr) -> AnyStr:
43
- return self.prefix + input + self.suffix
44
-
45
-
46
26
  class RegionReplacingPatches(Patches[dict[int, AnyStr], AnyStr]):
47
27
  def __init__(self, regions: list[tuple[int, int]]):
48
28
  assert regions
@@ -9,14 +9,6 @@ from shrinkray.passes.patching import Patches, apply_patches
9
9
  from shrinkray.problem import Format, ParseError, ReductionProblem
10
10
 
11
11
 
12
- def is_json(s: bytes) -> bool:
13
- try:
14
- json.loads(s)
15
- return True
16
- except ValueError:
17
- return False
18
-
19
-
20
12
  @define(frozen=True)
21
13
  class _JSON(Format[bytes, Any]):
22
14
  def __repr__(self) -> str:
@@ -1,7 +1,5 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from collections.abc import Callable, Iterable, Sequence
3
- from enum import Enum
4
- from random import Random
5
3
  from typing import Any, TypeVar, cast
6
4
 
7
5
  import trio
@@ -52,27 +50,6 @@ class SetPatches[T, TargetType](Patches[frozenset[T], TargetType]):
52
50
  return len(patch)
53
51
 
54
52
 
55
- class ListPatches[T, TargetType](Patches[list[T], TargetType]):
56
- def __init__(self, apply: Callable[[list[T], TargetType], TargetType]):
57
- self.__apply = apply
58
-
59
- @property
60
- def empty(self):
61
- return []
62
-
63
- def combine(self, *patches: list[T]) -> list[T]:
64
- result = []
65
- for p in patches:
66
- result.extend(p)
67
- return result
68
-
69
- def apply(self, patch: list[T], target: TargetType) -> TargetType:
70
- return self.__apply(patch, target)
71
-
72
- def size(self, patch: list[T]) -> int:
73
- return len(patch)
74
-
75
-
76
53
  class PatchApplier[PatchType, TargetType]:
77
54
  def __init__(
78
55
  self,
@@ -174,15 +151,6 @@ class PatchApplier[PatchType, TargetType]:
174
151
  return await receive_merge_result.receive()
175
152
 
176
153
 
177
- class Direction(Enum):
178
- LEFT = 0
179
- RIGHT = 1
180
-
181
-
182
- class Completed(Exception):
183
- pass
184
-
185
-
186
154
  async def apply_patches[PatchType, TargetType](
187
155
  problem: ReductionProblem[TargetType],
188
156
  patch_info: Patches[PatchType, TargetType],
@@ -220,37 +188,6 @@ async def apply_patches[PatchType, TargetType](
220
188
  await applier.try_apply_patch(patch)
221
189
 
222
190
 
223
- class LazyMutableRange:
224
- def __init__(self, n: int):
225
- self.__size = n
226
- self.__mask: dict[int, int] = {}
227
-
228
- def __getitem__(self, i: int) -> int:
229
- return self.__mask.get(i, i)
230
-
231
- def __setitem__(self, i: int, v: int) -> None:
232
- self.__mask[i] = v
233
-
234
- def __len__(self) -> int:
235
- return self.__size
236
-
237
- def pop(self) -> int:
238
- i = len(self) - 1
239
- result = self[i]
240
- self.__size = i
241
- self.__mask.pop(i, None)
242
- return result
243
-
244
-
245
- def lazy_shuffle[T](seq: Sequence[T], rnd: Random) -> Iterable[T]:
246
- indices = LazyMutableRange(len(seq))
247
- while indices:
248
- j = len(indices) - 1
249
- i = rnd.randrange(0, len(indices))
250
- indices[i], indices[j] = indices[j], indices[i]
251
- yield seq[indices.pop()]
252
-
253
-
254
191
  CutPatch = list[tuple[int, int]]
255
192
 
256
193
 
@@ -18,31 +18,6 @@ async def delete_elements[Seq: Sequence[Any]](problem: ReductionProblem[Seq]) ->
18
18
  )
19
19
 
20
20
 
21
- def merged_intervals(intervals: list[tuple[int, int]]) -> list[tuple[int, int]]:
22
- normalized: list[list[int]] = []
23
- for start, end in sorted(map(tuple, intervals)):
24
- if normalized and normalized[-1][-1] >= start:
25
- normalized[-1][-1] = max(normalized[-1][-1], end)
26
- else:
27
- normalized.append([start, end])
28
- return list(map(tuple, normalized)) # type: ignore
29
-
30
-
31
- def with_deletions[Seq: Sequence[Any]](
32
- target: Seq, deletions: list[tuple[int, int]]
33
- ) -> Seq:
34
- result: list[Any] = []
35
- prev = 0
36
- total_deleted = 0
37
- for start, end in deletions:
38
- total_deleted += end - start
39
- result.extend(target[prev:start])
40
- prev = end
41
- result.extend(target[prev:])
42
- assert len(result) + total_deleted == len(target)
43
- return type(target)(result) # type: ignore
44
-
45
-
46
21
  def block_deletion[Seq: Sequence[Any]](
47
22
  min_block: int, max_block: int
48
23
  ) -> ReductionPass[Seq]:
@@ -99,50 +99,6 @@ class Reducer[T](ABC):
99
99
  """Skip the currently running pass. Override in subclasses for pass control."""
100
100
 
101
101
 
102
- @define
103
- class BasicReducer[T](Reducer[T]):
104
- reduction_passes: Iterable[ReductionPass[T]]
105
- pumps: Iterable[ReductionPump[T]] = ()
106
- _status: str = "Starting up"
107
-
108
- def __attrs_post_init__(self) -> None:
109
- self.reduction_passes = list(self.reduction_passes)
110
-
111
- @property
112
- def status(self) -> str:
113
- return self._status
114
-
115
- @status.setter
116
- def status(self, value: str) -> None:
117
- self._status = value
118
-
119
- async def run_pass(self, rp: ReductionPass[T]) -> None:
120
- await rp(self.target)
121
-
122
- async def run(self) -> None:
123
- await self.target.setup()
124
-
125
- while True:
126
- prev = self.target.current_test_case
127
- for rp in self.reduction_passes:
128
- self.status = f"Running reduction pass {rp.__name__}"
129
- await self.run_pass(rp)
130
- for pump in self.pumps:
131
- self.status = f"Pumping with {pump.__name__}"
132
- pumped = await pump(self.target)
133
- if pumped != self.target.current_test_case:
134
- with self.backtrack(pumped):
135
- for rp in self.reduction_passes:
136
- self.status = f"Running reduction pass {rp.__name__} under pump {pump.__name__}"
137
- await self.run_pass(rp)
138
- if prev == self.target.current_test_case:
139
- return
140
-
141
-
142
- class RestartPass(Exception):
143
- pass
144
-
145
-
146
102
  @define
147
103
  class PassStats:
148
104
  """Statistics for a single reduction pass."""
@@ -181,12 +137,6 @@ class PassStatsTracker:
181
137
  return list(self._stats.values())
182
138
 
183
139
 
184
- class SkipPass(Exception):
185
- """Raised to skip the current pass."""
186
-
187
- pass
188
-
189
-
190
140
  @define
191
141
  class ShrinkRay(Reducer[bytes]):
192
142
  clang_delta: ClangDelta | None = None
@@ -11,7 +11,7 @@ from abc import ABC, abstractmethod
11
11
  from collections import deque
12
12
  from datetime import timedelta
13
13
  from tempfile import TemporaryDirectory
14
- from typing import Any, TypeVar
14
+ from typing import Any
15
15
 
16
16
  import humanize
17
17
  import trio
@@ -28,9 +28,6 @@ from shrinkray.reducer import DirectoryShrinkRay, Reducer, ShrinkRay
28
28
  from shrinkray.work import Volume, WorkContext
29
29
 
30
30
 
31
- T = TypeVar("T")
32
-
33
-
34
31
  class TimeoutExceededOnInitial(InvalidInitialExample):
35
32
  def __init__(self, runtime: float, timeout: float) -> None:
36
33
  self.runtime = runtime
@@ -5,9 +5,7 @@ from shrinkray.subprocess.protocol import (
5
5
  ProgressUpdate,
6
6
  Request,
7
7
  Response,
8
- decode_bytes,
9
8
  deserialize,
10
- encode_bytes,
11
9
  serialize,
12
10
  )
13
11
 
@@ -18,7 +16,5 @@ __all__ = [
18
16
  "ProgressUpdate",
19
17
  "serialize",
20
18
  "deserialize",
21
- "encode_bytes",
22
- "decode_bytes",
23
19
  "SubprocessClient",
24
20
  ]
@@ -1,6 +1,5 @@
1
1
  """Line-oriented JSON protocol for subprocess communication."""
2
2
 
3
- import base64
4
3
  import json
5
4
  from dataclasses import dataclass, field
6
5
  from typing import Any
@@ -186,13 +185,3 @@ def deserialize(line: str) -> Request | Response | ProgressUpdate:
186
185
  command=data["command"],
187
186
  params=data.get("params", {}),
188
187
  )
189
-
190
-
191
- def encode_bytes(data: bytes) -> str:
192
- """Encode bytes to base64 string for JSON transport."""
193
- return base64.b64encode(data).decode("ascii")
194
-
195
-
196
- def decode_bytes(data: str) -> bytes:
197
- """Decode base64 string back to bytes."""
198
- return base64.b64decode(data.encode("ascii"))
@@ -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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shrinkray
3
- Version: 25.12.27.3
3
+ Version: 25.12.28.0
4
4
  Summary: Shrink Ray
5
5
  Author-email: "David R. MacIver" <david@drmaciver.com>
6
6
  License: MIT
@@ -4,7 +4,6 @@ pyproject.toml
4
4
  src/shrinkray/__init__.py
5
5
  src/shrinkray/__main__.py
6
6
  src/shrinkray/cli.py
7
- src/shrinkray/display.py
8
7
  src/shrinkray/formatting.py
9
8
  src/shrinkray/problem.py
10
9
  src/shrinkray/process.py
@@ -40,7 +39,6 @@ tests/test_clang_delta.py
40
39
  tests/test_cli.py
41
40
  tests/test_definitions.py
42
41
  tests/test_dimacs_cnf.py
43
- tests/test_display.py
44
42
  tests/test_formatting.py
45
43
  tests/test_generic_language.py
46
44
  tests/test_generic_shrinking_properties.py
@@ -1,12 +1,10 @@
1
1
  import ast
2
- from collections import Counter
3
2
 
4
3
  import pytest
5
- from hypothesis import assume, example, given
4
+ from hypothesis import example, given
6
5
  from hypothesis import strategies as st
7
6
 
8
7
  from shrinkray.passes.bytes import (
9
- WHITESPACE,
10
8
  ByteReplacement,
11
9
  debracket,
12
10
  find_ngram_endpoints,
@@ -14,10 +12,8 @@ from shrinkray.passes.bytes import (
14
12
  lower_bytes,
15
13
  lower_individual_bytes,
16
14
  short_deletions,
17
- sort_whitespace,
18
15
  )
19
16
  from shrinkray.passes.patching import apply_patches
20
- from shrinkray.passes.python import is_python
21
17
  from shrinkray.problem import BasicReductionProblem, shortlex
22
18
  from shrinkray.work import WorkContext
23
19
  from tests.helpers import assert_reduces_to, direct_reductions, reduce_with
@@ -52,98 +48,6 @@ def test_ngram_endpoints(b):
52
48
  find_ngram_endpoints(b)
53
49
 
54
50
 
55
- def count_whitespace(b):
56
- return len([c for c in b if c in WHITESPACE])
57
-
58
-
59
- def count_regions(b):
60
- n = 0
61
- is_whitespace_last = True
62
- for c in b:
63
- typ = c in WHITESPACE
64
- if not typ and is_whitespace_last:
65
- n += 1
66
- is_whitespace_last = typ
67
- return n
68
-
69
-
70
- @pytest.mark.skip
71
- @example(initial=b"\n0\r")
72
- @given(st.builds(bytes, st.lists(st.sampled_from(b"\t\n\r 0123."))))
73
- def test_sorting_whitespace(initial):
74
- initial_count = count_whitespace(initial)
75
-
76
- assume(initial_count > 0)
77
-
78
- def is_interesting(tc):
79
- assert count_whitespace(tc) == initial_count
80
- assert Counter(tc) == Counter(initial)
81
- return True
82
-
83
- result = reduce_with([sort_whitespace], initial, is_interesting)
84
-
85
- for i, c in enumerate(result):
86
- assert (c in WHITESPACE) == (i < initial_count)
87
-
88
-
89
- @pytest.mark.skip
90
- @given(st.builds(bytes, st.lists(st.sampled_from(b"\t\n\r 0123."))))
91
- def test_sorting_whitespace_preserving_regions(initial):
92
- initial_count = count_whitespace(initial)
93
- initial_regions = count_regions(initial)
94
-
95
- assume(initial_count > 0)
96
- assume(initial_regions > 1)
97
-
98
- def is_interesting(tc):
99
- assert count_whitespace(tc) == initial_count
100
- assert Counter(tc) == Counter(initial)
101
- return count_regions(tc) == initial_regions
102
-
103
- result = reduce_with([sort_whitespace], initial, is_interesting)
104
-
105
- for run in runs_of_whitespace(result)[1:]:
106
- assert len(run) <= 1
107
-
108
-
109
- @pytest.mark.skip
110
- @pytest.mark.parametrize(
111
- "initial",
112
- [
113
- b"\t\t\t\t\nfrom\t\t\t\t\t\t\t\t\t\t.\timport\tA\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\nfrom\t\t\t.\t\t\t\t\t\t\t\t\t\timport\t\to\t\t\t\t\t\t\t\t\nfrom\t\t.\t\t\t\t\t\t\t\timport\t\tr\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
114
- b"from\t\t.\t\t\t\t\t\t\t\t\timport\ta\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\nclass\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ta\t\t\t\t\t\t\t\t\t\t\t(\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)\t\t\t\t\t\t\t\t:\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t()\ndef\tr\t\t\t\t\t\t\t\t\t\t\t\t\t\t(\t\t\t\t\t\t\t\t\t\t\t\t\t\t)\t\t\t\t\t\t\t\t:\t\t\t\t\t\t\t\t\t\t\t\t\t\t...\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
115
- ],
116
- )
117
- def test_sorting_whitespace_preserving_python(initial):
118
- initial_count = count_whitespace(initial)
119
-
120
- def is_interesting(tc):
121
- assert count_whitespace(tc) == initial_count
122
- assert Counter(tc) == Counter(initial)
123
- return is_python(tc)
124
-
125
- result = reduce_with([sort_whitespace], initial, is_interesting)
126
-
127
- for run in runs_of_whitespace(result)[1:]:
128
- assert len(run) <= 1
129
-
130
-
131
- def runs_of_whitespace(result):
132
- whitespace_runs = []
133
- i = 0
134
- while i < len(result):
135
- c = result[i]
136
- if c not in WHITESPACE:
137
- i += 1
138
- continue
139
- j = i + 1
140
- while j < len(result) and result[j] in WHITESPACE:
141
- j += 1
142
- whitespace_runs.append(result[i:j])
143
- i = j
144
- return whitespace_runs
145
-
146
-
147
51
  def test_debracket():
148
52
  assert (
149
53
  reduce_with([debracket], b"(1 + 2) + (3 + 4)", lambda x: b"(3 + 4)" in x)