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.
- {shrinkray-25.12.27.3/src/shrinkray.egg-info → shrinkray-25.12.28.0}/PKG-INFO +1 -1
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/pyproject.toml +1 -1
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/passes/bytes.py +0 -125
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/passes/clangdelta.py +0 -24
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/passes/genericlanguages.py +0 -20
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/passes/json.py +0 -8
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/passes/patching.py +0 -63
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/passes/sequences.py +0 -25
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/reducer.py +0 -50
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/state.py +1 -4
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/subprocess/__init__.py +0 -4
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/subprocess/protocol.py +0 -11
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/subprocess/worker.py +0 -8
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0/src/shrinkray.egg-info}/PKG-INFO +1 -1
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray.egg-info/SOURCES.txt +0 -2
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_byte_reduction_passes.py +1 -97
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_clang_delta.py +2 -47
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_generic_language.py +0 -31
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_generic_shrinking_properties.py +0 -15
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_json_passes.py +0 -43
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_patching.py +0 -140
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_python_reducers.py +0 -11
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_reducer.py +1 -13
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_reduction_passes.py +8 -169
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_subprocess_protocol.py +0 -32
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_subprocess_worker.py +1 -13
- shrinkray-25.12.27.3/src/shrinkray/display.py +0 -75
- shrinkray-25.12.27.3/tests/test_display.py +0 -135
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/LICENSE +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/README.md +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/setup.cfg +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/__init__.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/__main__.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/cli.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/formatting.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/passes/__init__.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/passes/definitions.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/passes/python.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/passes/sat.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/problem.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/process.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/py.typed +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/subprocess/client.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/tui.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/ui.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/validation.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray/work.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray.egg-info/dependency_links.txt +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray.egg-info/entry_points.txt +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray.egg-info/requires.txt +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/src/shrinkray.egg-info/top_level.txt +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_cli.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_definitions.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_dimacs_cnf.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_formatting.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_main.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_misc_reduction_performance.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_natural_sort_orders.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_problem.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_process.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_sat.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_state.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_subprocess_client.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_subprocess_integration.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_tui.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_tui_snapshots.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_ui.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_validation.py +0 -0
- {shrinkray-25.12.27.3 → shrinkray-25.12.28.0}/tests/test_work.py +0 -0
|
@@ -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
|
|
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
|
|
|
@@ -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
|
|
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)
|