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."