예제 #1
0
class FuzzyMinimiser(object):

    def __init__(self, lang, program, test):
        grm = lang_dict[language]
        parser, lexer = grm.load()
        parser.init_ast()
        self.parser = parser
        self.lexer = lexer
        self.grm = grm
        self.treemanager = TreeManager()
        self.treemanager.add_parser(parser, lexer, grm.name)
        self.treemanager.version = 1
        self.treemanager.last_saved_version = 1

        self.program = program
        self.test = test
        self.pos = 0
        self.lastexception = None

    def reset(self):
        self.parser.reset()
        self.treemanager = TreeManager()
        self.treemanager.add_parser(self.parser, self.lexer, self.grm)

    def auto(self):
        logging.info("Running auto-mode")
        # try to reduce snapshots first
        while True:
            self.original = self.test[:] # remember original tests
            self.reduce("snapshot")
            if self.test == self.original:
                logging.info("No more snapshot reductions possible.")
                break
        # try single reduce next
        while True:
            self.original = self.test[:] # remember original tests
            self.reduce("single")
            if self.test == self.original:
                logging.info("No more single reductions possible.")
                break

    def reduce(self, mode):
        self.pos = 0
        logging.info("Pos: {} Len: {} Start {}-reduce".format(self.pos, len(self.test), mode))
        try:
            # Run without changes to store exception
            self.run()
        except Exception as e:
            self.lastexception = e
        while self.pos < len(self.test):
            self.delete(mode)
            try:
                self.run() # check if first run causes error
                logging.info("Pos: {} Len: {} Fixed".format(self.pos, len(self.test)))
                self.revert()
            except Exception as e:
                if not self.same_exception(e):
                    logging.info("Pos: {} Len: {} Wrong exception: '{}'".format(self.pos, len(self.test), e))
                    self.revert()
                    continue
                else:
                    logging.info("Pos: {} Len: {} Still failing: '{}'".format(self.pos, len(self.test), e))
                    self.save() # save progress
        self.save()

    def same_exception(self, e):
        if not self.lastexception:
            self.lastexception = e
            return True
        if type(self.lastexception) is type(e) and \
                self.lastexception.args == e.args:
                    return True
        return False

    def delete(self, mode):
        self.old = self.test[:] # copy
        if mode == "single":
            del self.test[self.pos:self.pos+4] # remove one keystroke
            if self.pos < len(self.test) and \
                self.test[self.pos] == "self.treemanager.undo_snapshot()":
                    self.test.pop(self.pos)
        elif mode == "snapshot":
            # removes 1 snapshot consisting of 20 keystrokes (each 4 lines) and
            # a `self.treemanager.undo_snapshot()` line at the end
            del self.test[self.pos:self.pos+81]

    def revert(self):
        diff = len(self.old) - len(self.test)
        self.test = self.old[:] # revert changes
        self.pos += diff # and skip test lines that are needed

    def set_destination(self, dest):
        filename = dest + ".1"
        i = 1
        while os.path.isfile(filename):
            filename = dest + ".{}".format(i)
            i += 1
        self.dest = filename
        logging.info("Setting destination to '{}'".format(filename))

    def save(self):
        f = open(self.dest, "w")
        for l in self.test:
            f.write(l)
            f.write("\n")
        f.close()

    def run(self):
        self.reset()
        self.treemanager.import_file(self.program)
        for l in self.test:
            exec(l)

    def move(self, direction, times):
        for i in range(times): self.treemanager.cursor_movement(direction)
예제 #2
0
class Test_MultiTextNode:

    def setup_class(cls):
        grm = EcoFile("MultiTest", "test/calcmultistring.eco", "Multi")
        parser, lexer = grm.load()
        cls.lexer = lexer
        cls.parser = parser
        cls.parser.init_ast()
        cls.ast = cls.parser.previous_version
        cls.treemanager = TreeManager()
        cls.treemanager.add_parser(cls.parser, cls.lexer, calc.name)

        cls.treemanager.set_font_test(7, 17) # hard coded. PyQt segfaults in test suite

    def reset(self):
        self.parser.reset()
        self.treemanager = TreeManager()
        self.treemanager.add_parser(self.parser, self.lexer, calc.name)
        self.treemanager.set_font_test(7, 17)

    def test_simple(self):
        self.reset()
        self.treemanager.key_normal("1")
        self.treemanager.key_normal("+")
        self.treemanager.key_normal("\"")
        self.treemanager.key_normal("abc")
        assert self.parser.last_status == False

        self.treemanager.key_normal("\"")
        assert self.parser.last_status == True

    def test_newline(self):
        self.reset()
        self.treemanager.key_normal("1")
        self.treemanager.key_normal("+")
        self.treemanager.key_normal("\"")
        self.treemanager.key_normal("abc")
        self.treemanager.key_normal("\"")

        assert self.parser.last_status == True

        self.treemanager.cursor_movement(LEFT)
        self.treemanager.cursor_movement(LEFT)

        self.treemanager.key_normal("\r")

        assert self.parser.last_status == True

    def test_doublenewline(self):
        self.reset()
        self.treemanager.key_normal("1")
        self.treemanager.key_normal("+")
        self.treemanager.key_normal("\"")
        self.treemanager.key_normal("abcd")
        self.treemanager.key_normal("\"")

        assert self.parser.last_status == True

        self.treemanager.cursor_movement(LEFT)
        self.treemanager.cursor_movement(LEFT)

        self.treemanager.key_normal("\r")

        assert self.parser.last_status == True

        self.treemanager.cursor_movement(LEFT)
        self.treemanager.cursor_movement(LEFT)

        self.treemanager.key_normal("\r")

        assert self.parser.last_status == True

    def test_doublenewline_delete(self):
        self.reset()
        self.treemanager.key_normal("1")
        self.treemanager.key_normal("+")
        self.treemanager.key_normal("\"")
        self.treemanager.key_normal("abcd")
        self.treemanager.key_normal("\"")

        assert self.parser.last_status == True

        self.treemanager.cursor_movement(LEFT)
        self.treemanager.cursor_movement(LEFT)

        self.treemanager.key_normal("\r")

        assert self.parser.last_status == True

        self.treemanager.cursor_movement(LEFT)
        self.treemanager.cursor_movement(LEFT)

        self.treemanager.key_normal("\r")

        bos = self.parser.previous_version.parent.children[0]
        assert bos.next_term.next_term.next_term.children[0].symbol.name == "\"ab"
        assert bos.next_term.next_term.next_term.children[1].symbol.name == "\r"
        assert bos.next_term.next_term.next_term.children[2].symbol.name == "c"
        assert bos.next_term.next_term.next_term.children[3].symbol.name == "\r"
        assert bos.next_term.next_term.next_term.children[4].symbol.name == "d\""

        self.treemanager.cursor_movement(DOWN)
        self.treemanager.key_backspace()

        assert bos.next_term.symbol.name == "1"
        assert bos.next_term.next_term.symbol.name == "+"
        assert bos.next_term.next_term.next_term.children[0].symbol.name == "\"ab"
        assert bos.next_term.next_term.next_term.children[1].symbol.name == "\r"
        assert bos.next_term.next_term.next_term.children[2].symbol.name == "cd\""
        assert len(bos.next_term.next_term.next_term.children) == 3
        assert bos.next_term.next_term.next_term.children[2].next_term is None

        assert self.parser.last_status == True
예제 #3
0
class Test_MultiTextNodePython:

    def setup_class(cls):
        parser, lexer = python.load()
        cls.lexer = lexer
        cls.parser = parser
        cls.parser.init_ast()
        cls.ast = cls.parser.previous_version
        cls.treemanager = TreeManager()
        cls.treemanager.add_parser(cls.parser, cls.lexer, python.name)

        cls.treemanager.set_font_test(7, 17) # hard coded. PyQt segfaults in test suite

    def reset(self):
        self.parser.reset()
        self.treemanager = TreeManager()
        self.treemanager.add_parser(self.parser, self.lexer, calc.name)
        self.treemanager.set_font_test(7, 17)

    def test_simple(self):
        self.reset()
        inputstring = "x = \"\"\"abcdef\"\"\""
        for c in inputstring:
            self.treemanager.key_normal(c)

        self.treemanager.cursor_movement(LEFT)
        self.treemanager.cursor_movement(LEFT)
        self.treemanager.cursor_movement(LEFT)
        self.treemanager.cursor_movement(LEFT)
        self.treemanager.cursor_movement(LEFT)
        self.treemanager.cursor_movement(LEFT)

        self.treemanager.key_normal("\r")

    def test_relex_over_indentation(self):
        self.reset()
        inputstring = """class X:
    x = 1
    def x():
        pass
    y = 2"""

        self.treemanager.import_file(inputstring)

        assert self.parser.last_status == True

        self.treemanager.cursor_movement(DOWN)
        self.treemanager.cursor_movement(DOWN)
        self.treemanager.cursor_movement(DOWN)
        self.treemanager.key_end()

        assert self.treemanager.cursor.node.symbol.name == "pass"

        self.treemanager.key_normal("\"")
        self.treemanager.key_normal("\"")
        self.treemanager.key_normal("\"")

        self.treemanager.cursor_movement(UP)
        self.treemanager.cursor_movement(UP)
        self.treemanager.key_end()

        assert self.treemanager.cursor.node.symbol.name == "1"
        self.treemanager.cursor_movement(LEFT)

        self.treemanager.key_normal("\"")
        self.treemanager.key_normal("\"")
        self.treemanager.key_normal("\"")

        assert self.parser.last_status == True

    def test_indentation_to_string_and_back(self):
        self.reset()
        inputstring = """class X:
    a
    b"""

        self.treemanager.import_file(inputstring)

        assert self.parser.last_status == True

        self.treemanager.cursor_movement(DOWN)
        self.treemanager.cursor_movement(DOWN)
        self.treemanager.key_end()

        self.treemanager.key_normal("\"")
        self.treemanager.key_normal("\"")
        self.treemanager.key_normal("\"")

        self.treemanager.cursor_movement(UP)
        self.treemanager.cursor_movement(UP)
        self.treemanager.key_home()

        self.treemanager.key_normal("\"")
        self.treemanager.key_normal("\"")
        self.treemanager.key_normal("\"")

        assert self.parser.last_status == True

        self.treemanager.cursor_movement(DOWN)
        self.treemanager.cursor_movement(DOWN)
        self.treemanager.key_end()
        self.treemanager.key_backspace()

        assert self.parser.last_status == False

    def test_remember_open_lexing_states(self):

        self.reset()
        inputstring = """x = 1
y = 2"""

        self.treemanager.import_file(inputstring)

        assert self.parser.last_status == True

        self.treemanager.key_end()
        self.treemanager.cursor_movement(LEFT)
        self.treemanager.key_normal("\"")
        #assert self.parser.last_status == False # unfinished lexing jobs

        self.treemanager.key_end()
        self.treemanager.key_normal("\"")
        assert self.parser.last_status == True

    def test_triplequote_string(self):

        self.reset()
        inputstring = 'x="""abc"""'

        for i in inputstring:
            self.treemanager.key_normal(i)

        bos = self.parser.previous_version.parent.children[0]
        assert bos.next_term.symbol.name == "x"
        assert bos.next_term.next_term.symbol.name == "="
        assert bos.next_term.next_term.next_term.symbol.name == '"""abc"""'

        self.treemanager.cursor_movement(LEFT)
        self.treemanager.cursor_movement(LEFT)
        self.treemanager.cursor_movement(LEFT)
        self.treemanager.cursor_movement(LEFT)
        self.treemanager.key_normal("\"")

        assert bos.next_term.symbol.name == "x"
        assert bos.next_term.next_term.symbol.name == "="
        assert bos.next_term.next_term.next_term.symbol.name == '""'
        assert bos.next_term.next_term.next_term.next_term.symbol.name == '"ab"'
        assert bos.next_term.next_term.next_term.next_term.lookback == 1
        assert bos.next_term.next_term.next_term.next_term.next_term.symbol.name == 'c'
        assert bos.next_term.next_term.next_term.next_term.next_term.lookback == 2
        assert bos.next_term.next_term.next_term.next_term.next_term.next_term.symbol.name == '""'
        assert bos.next_term.next_term.next_term.next_term.next_term.next_term.next_term.symbol.name == '"'

        self.treemanager.key_normal("\"")

        assert bos.next_term.symbol.name == "x"
        assert bos.next_term.next_term.symbol.name == "="
        assert bos.next_term.next_term.next_term.symbol.name == '""'
        assert bos.next_term.next_term.next_term.next_term.symbol.name == '"ab"'
        assert bos.next_term.next_term.next_term.next_term.lookback == 1
        assert bos.next_term.next_term.next_term.next_term.next_term.symbol.name == '"c"'
        assert bos.next_term.next_term.next_term.next_term.next_term.lookback == 2
        assert bos.next_term.next_term.next_term.next_term.next_term.next_term.symbol.name == '""'


        self.treemanager.key_normal("\"")
        #assert self.parser.last_status == False


        assert bos.next_term.symbol.name == "x"
        assert bos.next_term.next_term.symbol.name == "="
        assert bos.next_term.next_term.next_term.symbol.name == '"""ab"""'
        assert bos.next_term.next_term.next_term.next_term.symbol.name == 'c'
        assert bos.next_term.next_term.next_term.next_term.next_term.symbol.name == '""'
        assert bos.next_term.next_term.next_term.next_term.next_term.next_term.symbol.name == '"'

        self.treemanager.key_end()
        self.treemanager.key_backspace()

        assert bos.next_term.symbol.name == "x"
        assert bos.next_term.next_term.symbol.name == "="
        assert bos.next_term.next_term.next_term.symbol.name == '"""ab"""'
        assert bos.next_term.next_term.next_term.next_term.symbol.name == 'c'
        assert bos.next_term.next_term.next_term.next_term.next_term.symbol.name == '""'

    def test_ignore_nonlbox_x80(self):

        self.reset()
        inputstring = 'x="""ab\x80c"""'

        for i in inputstring:
            self.treemanager.key_normal(i)

        bos = self.parser.previous_version.parent.children[0]
        assert bos.next_term.symbol.name == "x"
        assert bos.next_term.next_term.symbol.name == "="
        assert bos.next_term.next_term.next_term.symbol.name == '"""ab\x80c"""'

    def test_multinode_from_the_start(self):

        self.reset()
        inputstring = '''x="""a\rbc"""'''

        for i in inputstring:
            self.treemanager.key_normal(i)

        bos = self.parser.previous_version.parent.children[0]
        assert bos.next_term.symbol.name == "x"
        assert bos.next_term.next_term.symbol.name == "="
        assert bos.next_term.next_term.next_term.symbol.name == '<Multinode>'

    def test_multinode_and_nonlbox_x80(self):

        self.reset()
        inputstring = '''x="""a\x80bc"""'''

        for i in inputstring:
            self.treemanager.key_normal(i)

        bos = self.parser.previous_version.parent.children[0]
        assert bos.next_term.symbol.name == "x"
        assert bos.next_term.next_term.symbol.name == "="
        assert bos.next_term.next_term.next_term.symbol.name == '"""a\x80bc"""'

        self.treemanager.cursor_movement(LEFT)
        self.treemanager.cursor_movement(LEFT)
        self.treemanager.cursor_movement(LEFT)
        self.treemanager.cursor_movement(LEFT)

        self.treemanager.key_normal("\r")
        bos = self.parser.previous_version.parent.children[0]
        assert bos.next_term.symbol.name == "x"
        assert bos.next_term.next_term.symbol.name == "="
        assert bos.next_term.next_term.next_term.symbol.name == '<Multinode>'

    def test_multinode_nonlbox_and_lbox(self):
        self.reset()
        inputstring = '''x="""a\x80bc"""'''

        for i in inputstring:
            self.treemanager.key_normal(i)

        bos = self.parser.previous_version.parent.children[0]
        assert bos.next_term.symbol.name == "x"
        assert bos.next_term.next_term.symbol.name == "="
        assert bos.next_term.next_term.next_term.symbol.name == '"""a\x80bc"""'

        self.treemanager.cursor_movement(LEFT)
        self.treemanager.cursor_movement(LEFT)
        self.treemanager.cursor_movement(LEFT)
        self.treemanager.cursor_movement(LEFT)

        self.treemanager.add_languagebox(lang_dict["SQL"])
        self.treemanager.key_normal("S")
        bos = self.parser.previous_version.parent.children[0]
        assert bos.next_term.symbol.name == "x"
        assert bos.next_term.next_term.symbol.name == "="
        assert bos.next_term.next_term.next_term.symbol.name == '<Multinode>'
        multi = bos.next_term.next_term.next_term
        assert multi.children[0].symbol.name == "\"\"\"a\x80b"
        assert type(multi.children[1].symbol) is MagicTerminal
        assert multi.children[2].symbol.name == "c\"\"\""

    def test_multinode_merged_first(self):
        self.reset()
        inputstring = '''"""a\rbc"""'''

        for i in inputstring:
            self.treemanager.key_normal(i)

        for i in 'def"""':
            self.treemanager.key_normal(i)

        bos = self.parser.previous_version.parent.children[0]
        assert bos.next_term.symbol.name == "<Multinode>"
        assert bos.next_term.next_term.symbol.name == 'def'
        assert bos.next_term.next_term.next_term.symbol.name == '""'
        assert bos.next_term.next_term.next_term.next_term.symbol.name == '"'

        self.treemanager.cursor_movement(LEFT)
        self.treemanager.cursor_movement(LEFT)
        self.treemanager.cursor_movement(LEFT)
        self.treemanager.cursor_movement(LEFT)
        self.treemanager.cursor_movement(LEFT)
        self.treemanager.cursor_movement(LEFT)

        self.treemanager.key_backspace()
        self.treemanager.key_backspace()
        self.treemanager.key_backspace()

        assert bos.next_term.symbol.name == "<Multinode>"
        assert bos.next_term.next_term.symbol.name == "NEWLINE"
        assert bos.next_term.next_term.next_term.symbol.name == "eos"

    def test_multinode_string_bug(self):
        self.reset()
        inputstring = '''x="abc"'''
        for i in inputstring:
            self.treemanager.key_normal(i)

        self.treemanager.cursor_movement(LEFT)
        self.treemanager.add_languagebox(lang_dict["SQL"])
        self.treemanager.key_normal("x")

        bos = self.parser.previous_version.parent.children[0]
        x = bos.next_term
        assert x.symbol.name == "x"
        eq = x.next_term
        assert eq.symbol.name == "="
        multi = eq.next_term
        assert multi.lookup == "dstring"
        assert multi.symbol.name == "<Multinode>"

        self.treemanager.cursor_movement(RIGHT)
        self.treemanager.key_backspace()

        # removing the ending quote results in a lexingerror,
        # so the multinode remains
        assert eq.next_term.symbol.name == "<Multinode>"

        # now remove the first quote, which should lead to the destruction of
        # the multinode
        self.treemanager.cursor_movement(LEFT)
        self.treemanager.cursor_movement(LEFT)
        self.treemanager.cursor_movement(LEFT)
        self.treemanager.cursor_movement(LEFT)
        self.treemanager.key_backspace()
        assert eq.next_term.symbol.name == "abc"

    def test_multinode_string_bug2(self):
        self.reset()
        inputstring = '''x="abc"'''
        for i in inputstring:
            self.treemanager.key_normal(i)

        self.treemanager.cursor_movement(LEFT)
        self.treemanager.add_languagebox(lang_dict["SQL"])
        self.treemanager.key_normal("x")

        self.treemanager.leave_languagebox()
        self.treemanager.key_normal("z")
        bos = self.parser.previous_version.parent.children[0]
        x = bos.next_term
        assert x.symbol.name == "x"
        eq = x.next_term
        assert eq.symbol.name == "="
        multi = eq.next_term
        assert multi.children[0].symbol.name == "\"abc"
        assert multi.children[1].symbol.name == "<SQL>"
        assert multi.children[2].symbol.name == "z\""
예제 #4
0
class FuzzyTester():
    """Runs test that randomly modify a given program and tests for exceptions.
    If an exception occurs, or the modifications had unwanted side-effects, a
    log is saved which can be used to create a stand-alone test."""
    def __init__(self, filename):
        _, ext = os.path.splitext(filename)
        self.setlang(ext_to_lang[ext])
        with open(filename) as f:
            self.program = f.read()
        self.log = []
        self.filename = filename

    def reset(self):
        self.parser.reset()
        self.treemanager = TreeManager()
        self.treemanager.add_parser(self.parser, self.lexer, self.lang.name)

    def setlang(self, lang):
        self.lang = lang_dict[lang]
        self.parser, self.lexer = lang_dict[lang].load()
        self.treemanager = TreeManager()
        self.treemanager.add_parser(self.parser, self.lexer, self.lang.name)

    def move(self, direction, times):
        for i in range(times):
            self.treemanager.cursor_movement(direction)

    def text_compare(self, original):
        """Compare the textual representation of two programs. Faster than
        tree_compare, but less accurate."""
        original = original.replace("\r", "").split("\n")
        current = self.treemanager.export_as_text("/dev/null").replace(
            "\r", "").split("\n")
        for i in xrange(len(current)):
            assert original[i] == current[i]

    def next_node(self, node):
        if node.children:
            return node.children[0]
        while node.right_sibling() is None:
            node = node.parent
        return node.right_sibling()

    def tree_compare(self, node1, node2):
        """Given two root nodes, compares that both trees are equivalent"""
        while True:
            assert node1.symbol == node2.symbol
            if node1.right:
                assert node1.right.symbol == node2.right.symbol
            if node1.next_term:
                assert node1.next_term.symbol == node2.next_term.symbol
            if isinstance(node1.symbol, MagicTerminal):
                self.tree_compare(node1.symbol.ast, node2.symbol.ast)
            if isinstance(node1, EOS) and isinstance(node2, EOS):
                break
            node1 = self.next_node(node1)
            node2 = self.next_node(node2)

    def get_random_key(self):
        keys = list(
            "abcdefghijklmnopqrstuvwxyz0123456789 \r:,.[]{}()!$%^&*()_+=")
        return random.choice(keys)

    def random_deletion(self):
        """Delete random characters within a program."""
        print "Running random_deletion on {}".format(self.filename)
        program = self.program

        self.treemanager.import_file(program)
        assert self.parser.last_status == True

        self.text_compare(program)

        line_count = len(self.treemanager.lines)
        random_lines = range(line_count)
        random.shuffle(random_lines)
        random_lines = random_lines[:
                                    20]  # restrict to 20 lines to reduce runtime

        start_version = self.treemanager.version
        for linenr in random_lines:
            cols = range(20)
            random.shuffle(cols)
            for col in cols:
                self.treemanager.cursor_reset()
                self.log.append("self.treemanager.cursor_reset()")
                self.move(DOWN, linenr)
                self.log.append("self.move(DOWN, %s)" % linenr)
                self.move(RIGHT, col)
                self.log.append("self.move(RIGHT, %s)" % col)
                self.log.append("self.treemanager.key_delete()")
                x = self.treemanager.key_delete()
                if x == "eos":
                    continue
            self.treemanager.undo_snapshot()
            self.log.append("self.treemanager.undo_snapshot()")

        end_version = self.treemanager.version
        broken = self.treemanager.export_as_text()

        # undo all and compare with original
        while self.treemanager.version > start_version:
            self.treemanager.key_ctrl_z()
        self.text_compare(program)

        # redo all and compare with broken
        while self.treemanager.version < end_version:
            self.treemanager.key_shift_ctrl_z()
        self.text_compare(broken)

        # undo again and compare with original
        while self.treemanager.version > start_version:
            self.treemanager.key_ctrl_z()
        self.text_compare(program)

        t1 = TreeManager()
        parser, lexer = self.lang.load()
        parser.init_ast()
        t1.add_parser(parser, lexer, self.lang.name)
        t1.set_font_test(7, 17)
        t1.import_file(self.program)

        assert self.parser.last_status == True
        assert parser.last_status == True

        self.tree_compare(self.parser.previous_version.parent,
                          parser.previous_version.parent)

    def random_insertion(self):
        """Insert random characters at random locations within a program."""
        print "Running random_insert on {}".format(self.filename)
        self.reset()

        self.treemanager.import_file(self.program)
        assert self.parser.last_status == True

        self.text_compare(self.program)

        line_count = len(self.treemanager.lines)
        random_lines = range(line_count)
        random.shuffle(random_lines)

        start_version = self.treemanager.version
        for linenr in random_lines:
            cols = range(20)
            random.shuffle(cols)
            for col in cols:
                self.log.append("self.treemanager.cursor_reset()")
                self.log.append("self.move(DOWN, %s)" % linenr)
                self.log.append("self.move(RIGHT, %s)" % col)
                self.treemanager.cursor_reset()
                self.move(DOWN, linenr)
                self.move(RIGHT, col)
                k = self.get_random_key()
                self.log.append("self.treemanager.key_normal(%s)" % repr(k))
                x = self.treemanager.key_normal(k)
                if x == "eos":
                    continue
            self.log.append("self.treemanager.undo_snapshot()")
            self.treemanager.undo_snapshot()

        end_version = self.treemanager.version
        broken = self.treemanager.export_as_text()

        # undo all and compare with original
        while self.treemanager.version > start_version:
            self.treemanager.key_ctrl_z()
        self.text_compare(self.program)

        # redo all and compare with broken
        while self.treemanager.version < end_version:
            self.treemanager.key_shift_ctrl_z()
        self.text_compare(broken)

        # undo again and compare with original
        while self.treemanager.version > start_version:
            self.treemanager.key_ctrl_z()
        self.text_compare(self.program)

        t1 = TreeManager()
        parser, lexer = self.lang.load()
        t1.add_parser(parser, lexer, self.lang.name)
        t1.import_file(self.program)

        self.tree_compare(self.parser.previous_version.parent,
                          parser.previous_version.parent)

    def random_insertdelete(self):
        """Insert and delete random characters at random locations within a
        program."""
        print "Running random_insertdelete on {}".format(self.filename)
        self.reset()

        self.treemanager.import_file(self.program)
        assert self.parser.last_status == True

        self.text_compare(self.program)

        line_count = len(self.treemanager.lines)
        random_lines = range(line_count)
        random.shuffle(random_lines)
        random_lines = random_lines[:
                                    20]  # restrict to 20 lines to reduce runtime

        start_version = self.treemanager.version
        for linenr in random_lines:
            cols = range(20)
            random.shuffle(cols)
            for col in cols:
                self.log.append("self.treemanager.cursor_reset()")
                self.log.append("self.move(%s, %s)" % (DOWN, linenr))
                self.log.append("self.move(%s, %s)" % (RIGHT, col))
                self.treemanager.cursor_reset()
                self.move(DOWN, linenr)
                self.move(RIGHT, col)
                k = self.get_random_key()
                if k in [
                        "a", "c", "e", "g", "i", "k", "m", "1", "3", "5", "7"
                ]:
                    # for a few characters DELETE instead of INSERT
                    self.log.append("self.treemanager.key_delete()")
                    x = self.treemanager.key_delete()
                else:
                    rk = self.get_random_key()
                    self.log.append("self.treemanager.key_normal(%s)" % rk)
                    x = self.treemanager.key_normal(rk)
                if x == "eos":
                    continue
            self.log.append("self.treemanager.undo_snapshot()")
            self.treemanager.undo_snapshot()

        end_version = self.treemanager.version
        broken = self.treemanager.export_as_text()

        # undo all and compare with original
        while self.treemanager.version > start_version:
            self.treemanager.key_ctrl_z()
        self.text_compare(self.program)

        # redo all and compare with broken
        while self.treemanager.version < end_version:
            self.treemanager.key_shift_ctrl_z()
        self.text_compare(broken)

        # undo again and compare with original
        while self.treemanager.version > start_version:
            self.treemanager.key_ctrl_z()
        self.text_compare(self.program)

        t1 = TreeManager()
        parser, lexer = self.lang.load()
        t1.add_parser(parser, lexer, self.lang.name)
        t1.import_file(self.program)

        self.tree_compare(self.parser.previous_version.parent,
                          parser.previous_version.parent)

    def run(self):
        try:
            ft.random_deletion()
            self.reset()
            ft.random_insertion()
            self.reset()
            ft.random_insertdelete()
        except Exception as e:
            traceback.print_exc()
            print "Written log to 'fuzzy.log'."
            with open("fuzzy.log", "w") as f:
                for l in self.log:
                    f.write(l)
                    f.write("\n")
        else:
            print "Passed."