import argparse, contextlib, multiprocessing, os, tempfile, shutil, subprocess @contextlib.contextmanager def mkdtemp(): d = tempfile.mkdtemp() yield d shutil.rmtree(d, ignore_errors=True) def print_lines(lines): for line in lines: print(line) def write_to(file, lines): content = list((line + "\n" for line in lines)) if (len(content) > 0): with open(file, 'w') as f: f.writelines(content) def make_executable(p): os.chmod(p, 509) def quote(s): text = s.replace("'", "'\"'\"'") return "'{}'".format(text) class ScriptBuilder: def __init__(self): self.commands = ['#!/bin/bash', '', 'rm -rf .tmp.output', ''] def blank(self): self.commands.append('') def add_command(self, cmd, save=False): if save: self.commands.append(cmd + ' 2>&1 | tee .tmp.output') else: self.commands.append(cmd) def grep(self, text, file=None): self.add_command("grep -q -F {} {}".format(quote(text), file or '.tmp.output')) def check(self, equal_zero=False, result=1): op = 'eq' if equal_zero else 'ne' cmds = ['RES=$?', 'if [ $RES -{} "0" ]; then'.format(op), ' exit {}'.format(result), 'fi'] self.commands.extend(cmds) def write(self, p): write_to(p, self.commands) make_executable(p) def show(self): print_lines(self.commands) parser = argparse.ArgumentParser() parser.add_argument('--file', '-f', help='file to reduce', required=True) parser.add_argument('--text', '-t', action='append', help='expected output text') parser.add_argument('--keep', '-k', action='append', help='text to keep in source file') parser.add_argument('--check', '-c', action='append', help='command to check syntax') parser.add_argument('--dry-run', '-d', action='store_true', default=False, help='dry run') parser.add_argument('--timeout', action='store', default=300, help='Interestingness test timeout in seconds') parser.add_argument('--no-cache', action='store_true', default=False, help="Don't cache behavior of passes") parser.add_argument('--no-give-up', action='store_true', default=False, help="Don't give up on a pass that hasn't made progress for 50000 iterations") parser.add_argument('--sllooww', '--slow', action='store_true', default=False, help="Try harder to reduce, but perhaps take a long time to do so") parser.add_argument('command') parser.add_argument('args', nargs=argparse.REMAINDER) args = parser.parse_args() file = os.path.basename(args.file) sb = ScriptBuilder() for keep in args.keep or []: sb.grep(keep, file=file) sb.check() sb.blank() for check in args.check or []: sb.add_command(check+' '+file) sb.check() sb.blank() command = ' '.join([os.path.abspath(args.command)]+args.args+[file]) sb.add_command(command, save=args.text) if args.text: for t in args.text: sb.grep(t) sb.check() else: sb.check(equal_zero=True) sb.show() with mkdtemp() as d: script = os.path.join(d, 'reduce.sh') sb.write(script) if args.dry_run: shutil.copy(args.file, d) subprocess.check_call([script], cwd=d) else: creduce = ['creduce', '--n', str(multiprocessing.cpu_count())] creduce.append("--timeout") creduce.append(str(args.timeout)) if args.no_cache: creduce.append("--no-cache") if args.no_give_up: creduce.append("--no-give-up") if args.sllooww: creduce.append("--sllooww") subprocess.check_call(creduce + [script, args.file])