def test_prints_debug_information(capsys): reducer = Reducer(initial=bytes(10), predicate=lambda x: len(x) > 3, parallelism=1, debug=True) reducer.run() captured = capsys.readouterr() assert not captured.out assert len(captured.err.splitlines()) > 2
def test_removes_all_elements(parallelism): reducer = Reducer( initial=b"01" * 100, predicate=lambda t: t.count(b"0") == 100, parallelism=parallelism, ) reducer.run() assert reducer.target == b"0" * 100
def test_runs_to_fixation(ls): ordered = bytes(sorted(ls)) reducer = Reducer( initial=bytes(ls), predicate=lambda t: t and ordered.startswith(bytes(sorted(t))), parallelism=1, ) reducer.run() assert reducer.target == ordered[:1]
def test_reduces_to_min_size(initial, n, parallelism, method): assume(n <= len(initial)) reducer = Reducer( initial=initial, predicate=lambda t: len(t) >= n, parallelism=parallelism, ) getattr(reducer, method)() assert len(reducer.target) == n
def test_explore_arbitrary_predicate(b, data): results = {} best = b def test(t): nonlocal best if not t: return False if t == b: return True try: return results[t] except KeyError: pass result = data.draw(st.booleans()) results[t] = result if result: best = min(best, t, key=sort_key) return result class FakeRandom(object): def randrange(self, start, stop): if stop == start + 1: return start return data.draw(st.integers(start, stop - 1), label=f"randrange({start}, {stop})") reducer = Reducer( initial=b, predicate=test, parallelism=1, random=FakeRandom(), ) reducer.run() assert sort_key(reducer.target) <= sort_key(b) assert reducer.target == best
def test_runs_callbacks(): results = [] reducer = Reducer(initial=bytes(10), predicate=lambda x: len(x) > 3, parallelism=1, debug=True) reducer.on_improve(results.append) reducer.run() assert results[-1] == reducer.target
def test_argument_validation(initial, test): with pytest.raises(InvalidArguments): Reducer(initial=initial, predicate=test)
def reducer( debug, test, filename, timeout, working_dir, parallelism, seed, input_mode, lexical, also_interesting, slow_mode, replace, ): if not working_dir: file_basename = os.path.basename(filename) if file_basename == "-": file_basename = "stdin" base = os.path.join(".shrinkray", file_basename) try: os.makedirs(base) except FileExistsError: pass i = 1 while True: k = find_integer( lambda k: os.path.exists(os.path.join(base, str(i + k)))) i += k + 1 name = os.path.join(base, str(i)) try: os.mkdir(name) except FileExistsError: continue working_dir = name break print(f"Saving results in {working_dir}") else: try: os.makedirs(working_dir) except FileExistsError: print( "Working directory already exists and would be overwritten by reduction", file=sys.stderr, ) sys.exit(1) interesting_dir = os.path.join(working_dir, "interesting") reduction_dir = os.path.join(working_dir, "reductions") os.makedirs(reduction_dir) if input_mode == "file" and filename == "-": raise click.UsageError( "Cannot combine --input-mode=file with reading from stdin.") if filename == "-": initial = sys.stdin.buffer.read() else: with open(filename, "rb") as o: initial = o.read() if replace: if filename == "-": raise click.UsageError( "Cannot combine --replace with reading from stdin.") with open(filename + ".original", "wb") as o: o.write(initial) if debug: def dump_trace(signum, frame): traceback.print_stack() signal.signal(signal.SIGQUIT, dump_trace) basename = os.path.basename(filename) first_call = True def classify_data(string): nonlocal first_call should_print = debug and first_call and False first_call = False with tempfile.TemporaryDirectory() as d: sp = subprocess.Popen( test, stdin=subprocess.PIPE, stdout=sys.stdout if should_print else subprocess.DEVNULL, stderr=sys.stderr if should_print else subprocess.DEVNULL, universal_newlines=False, preexec_fn=os.setsid, cwd=d, ) original_string = string if input_mode == "file": with open(os.path.join(d, basename), "wb") as o: o.write(string) string = b"" try: sp.communicate(string, timeout=timeout) except subprocess.TimeoutExpired: return False finally: interrupt_wait_and_kill(sp) if 0 != sp.returncode == also_interesting: key = hashlib.sha1(string).hexdigest()[:10] if filename == "-": name = f"result-{key}" else: base, ext = os.path.splitext(os.path.basename(filename)) name = f"{base}-{key}{ext}" try: os.mkdir(interesting_dir) except FileExistsError: pass path = os.path.join(interesting_dir, name) try: with open(path, "xb") as o: o.write(original_string) if debug: print(f"Recording interesting variant in {path}", file=sys.stderr) except FileExistsError: pass return sp.returncode == 0 if replace: target = os.path.abspath(filename) elif filename == "-": target = os.path.join(working_dir, "result") else: target = os.path.join(working_dir, os.path.basename(filename)) timeout *= 10 if timeout <= 0: timeout = None if parallelism <= 0: parallelism = max(1, cpu_count() - 1) try: reducer = Reducer( initial, classify_data, debug=debug, parallelism=parallelism, random=Random(seed), lexical=lexical, slow=slow_mode, ) except InvalidArguments as e: raise click.UsageError(e.args[0]) pb = None prev = len(initial) reduction_count = 0 @reducer.on_improve def _(s): nonlocal reduction_count reduction_count += 1 with open( os.path.join(reduction_dir, f"{reduction_count}-" + file_basename), "bx") as o: o.write(s) if pb is not None: nonlocal prev pb.update(prev - len(s)) prev = len(s) with open(target, "wb") as o: o.write(s) try: if debug: reducer.run() else: with tqdm(total=len(initial)) as pb: reducer.run() finally: print(f"Reduced result left in {target}")