diff --git a/ast_parser/general_unparser.py b/ast_parser/general_unparser.py index 64ea586..3a3967d 100644 --- a/ast_parser/general_unparser.py +++ b/ast_parser/general_unparser.py @@ -240,6 +240,7 @@ class GeneralUnparser: else: self.source += (self.conditional_delimiters[1] + " ") elif node.tag == GAZ_BLOCK_TAG: + self.source += self.indentation * self.indentation_character if node.get(GAZ_TY_KEY) == GAZ_TRUE_BLOCK_TAG: self.unparse_node(node) elif node.get(GAZ_TY_KEY) == GAZ_FALSE_BLOCK_TAG: @@ -267,7 +268,7 @@ class GeneralUnparser: i += 1 def unparse_unary(self, element_in: ET.Element): - self.source += " {}".format(self.translate_op(element_in.get("op"))) + self.source += "{}".format(self.translate_op(element_in.get("op"))) self.unparse_xhs(element_in.find(GAZ_RHS_TAG)) def unparse_single_arg(self, param): diff --git a/ast_parser/python_unparser.py b/ast_parser/python_unparser.py index c001796..d1ac86e 100644 --- a/ast_parser/python_unparser.py +++ b/ast_parser/python_unparser.py @@ -92,3 +92,16 @@ class PythonUnparser(GeneralUnparser): def format_single_arg(self, ty, name): return "{}: {}".format(name, ty) + + def unparse_block(self, node): + super().unparse_block(node) + self.source += f"{self.block_delimiters[0]}\n" + self.indentation += 4 + for child in node: + self.unparse_node(child) + self.source += self.indentation_character * self.indentation + "pass\n" + self.indentation -= 4 + if node.get(GAZ_TY_KEY) is None: + self.source += f"{self.block_delimiters[1]}\n\n" + elif node.get(GAZ_TY_KEY) in [GAZ_TRUE_BLOCK_TAG, GAZ_FALSE_BLOCK_TAG]: + self.source += f"{self.block_delimiters[1]}" diff --git a/fuzzer/ground_truth/ee b/fuzzer/ground_truth/ee new file mode 100644 index 0000000..e69de29 diff --git a/test/config.yaml b/test/config.yaml new file mode 100644 index 0000000..a769b9e --- /dev/null +++ b/test/config.yaml @@ -0,0 +1,94 @@ +# The default configuration for the Gazprea Fuzzer +--- +generation-options: + max-nesting-depth: 5 # maximum nesting depth for statements + max-conditionals-loops: 5 # maximum number of loops/conditionals per routine + max-number-of-routines: 5 # maximum number of routines (main will always be generated) + generate-dead-code: True # generate dead code +properties: + max-range-length: 5 # maximum length of ranges, vectors and tuples, (AxA matrices can exist) + use-english-words: True # use english words instead of random names (this may limit the maximum number of names) + id-length: # length of identifiers + min: 1 + max: 10 + function-name-length: # length of function names + min: 1 + max: 10 + number-of-arguments: # number of arguments to a routine + min: 1 + max: 10 + generate-max-int: True # if False, generate integers between [-1000, 1000] else +expression-weights: # weights for expressions + # the higher a weight, the more likely (0, 10000), 0 to exclude, 10000 for only that + brackets: 10 + + arithmetic: + addition: 80 + subtraction: 80 + multiplication: 30 + division: 10 + modulo: 10 + power: 5 + + comparison: + equality: 50 + inequality: 50 + less-than: 30 + greater-than: 30 + less-than-or-equal: 10 + greater-than-or-equal: 10 + + logical: + and: 50 + or: 50 + xor: 10 + + vector-or-string: + generator: 20 + range: 30 + filter: 10 + reverse: 10 + concatenation: 50 + + unary: + noop: 10 + negation: 20 + not: 10 + + +statement-weights: # set to 0 for any statements you wish to exclude + variable-declaration: 50 + routine-call: 20 + conditional: 30 + loop: 20 + assignment: 40 + out-stream: 20 + in-stream: 5 + +type-weights: + value-types: + integer: 50 + real: 50 + boolean: 50 + character: 50 + void: 10 + composite-types: + vector: 20 + tuple: 5 + matrix: 10 + string: 10 + composite: 0 #TODO add support for composite types + atomic: 40 + +routine-weights: + procedure: 20 + function: 50 + +misc-weights: + type-qualifier-weights: + const: 10 + var: 60 + +block-termination-probability: 0.2 # probability for a block to terminate + + diff --git a/test/test_correctness.py b/test/test_correctness.py new file mode 100644 index 0000000..a256fff --- /dev/null +++ b/test/test_correctness.py @@ -0,0 +1,134 @@ +import sys +import unittest +from contextlib import redirect_stdout +from io import StringIO + +import yaml + +import xml.etree.ElementTree as ET + +import signal + +from ast_generator.ast_generator import AstGenerator +from ast_parser.python_unparser import PythonUnparser + + +class MyTestCase(unittest.TestCase): + @classmethod + def setUpClass(cls): + with open("config.yaml", 'r') as stream: + cls.props = yaml.safe_load(stream) + + def setUp(cls): + cls.ast_gen = AstGenerator(cls.props) + cls.python_unparser = PythonUnparser(cls.ast_gen.ast, True) + def test_assignment(self): + self.ast_gen.ast = ET.Element("block") + self.ast_gen.current_ast_element = self.ast_gen.ast + self.ast_gen.generate_declaration() + self.ast_gen.generate_assignment() + + self.python_unparser.xml = self.ast_gen.ast + self.python_unparser.unparse() + + try: + exec(self.python_unparser.source) + except Exception as e: + self.fail(e) + + def test_binary_operator(self): + self.ast_gen.ast = ET.Element("block") + self.ast_gen.current_ast_element = self.ast_gen.ast + self.ast_gen.generate_declaration() + self.ast_gen.generate_binary('addition', 'int') + + self.python_unparser.xml = self.ast_gen.ast + self.python_unparser.unparse() + + try: + exec(self.python_unparser.source) + except Exception as e: + self.fail(e) + + def test_unary_operator(self): + self.ast_gen.ast = ET.Element("block") + self.ast_gen.current_ast_element = self.ast_gen.ast + self.ast_gen.generate_declaration() + self.ast_gen.generate_unary('negation', 'int') + + self.python_unparser.xml = self.ast_gen.ast + self.python_unparser.unparse() + + try: + exec(self.python_unparser.source) + except Exception as e: + print(self.python_unparser.source) + self.fail(e) + + def test_output(self): + self.ast_gen.ast = ET.Element("block") + self.ast_gen.current_ast_element = self.ast_gen.ast + self.ast_gen.generate_out_stream() + + output = StringIO() + + self.python_unparser.xml = self.ast_gen.ast + self.python_unparser.unparse() + + with redirect_stdout(output): + exec(self.python_unparser.source) + + try: + exec(output.getvalue()) + except Exception as e: + self.fail(e) + + outstring = output.getvalue() + self.assertNotEqual("", outstring, self.python_unparser.source) + + def test_input(self): #FIXME this doesn't actually accept user input, I think I need to create a global dict + # to store the input and then access it from the python in some way... + self.ast_gen.ast = ET.Element("block") + self.ast_gen.current_ast_element = self.ast_gen.ast + self.ast_gen.generate_in_stream() + + self.python_unparser.xml = self.ast_gen.ast + self.python_unparser.unparse() + + try: + exec(self.python_unparser.source) + except Exception as e: + self.fail(e) + + def test_conditional(self): + self.ast_gen.ast = ET.Element("block") + self.ast_gen.current_ast_element = self.ast_gen.ast + self.ast_gen.generate_conditional() + + self.python_unparser.xml = self.ast_gen.ast + self.python_unparser.unparse() + + try: + exec(self.python_unparser.source) + except Exception as e: + print(self.python_unparser.source) + self.fail(e) + + def test_loop(self): + self.ast_gen.ast = ET.Element("block") + self.ast_gen.current_ast_element = self.ast_gen.ast + self.ast_gen.generate_loop() + + self.python_unparser.xml = self.ast_gen.ast + self.python_unparser.unparse() + + try: + exec(self.python_unparser.source) + except Exception as e: + print(self.python_unparser.source) + self.fail(e) + + + +if __name__ == '__main__': + unittest.main()