diff --git a/py2c/abc/tests/test_manager.py b/py2c/abc/tests/test_manager.py index f7c8711..ec95527 100644 --- a/py2c/abc/tests/test_manager.py +++ b/py2c/abc/tests/test_manager.py @@ -1,7 +1,7 @@ """Unit-tests for `py2c.abc.manager.Manager` """ -from py2c.tests import Test +from py2c.tests import Test, data_driven_test from nose.tools import assert_raises from py2c.abc.manager import Manager @@ -43,6 +43,30 @@ class SuperCallingManager(Manager): def run(self, node): super().run(node) +initialization_invalid_cases = [ + ( + "without options attribute or run method", + EmptyManager, TypeError, ["EmptyManager"] + ), + ( + "without run method", + NoRunManager, TypeError, ["NoRunManager", "run"] + ), + ( + "without options attribute", + NoOptionsManager, AttributeError, + ["NoOptionsManager", "options", "attribute"] + ), + ( + "with a non-dictionary options attribute", + OptionsNotADictManager, TypeError, + [ + "OptionsNotADictManager", "options", "should be", "instance", + "dict" + ] + ), +] + # ----------------------------------------------------------------------------- # Tests @@ -54,37 +78,13 @@ class TestBaseManager(Test): def test_initializes_a_subclass_with_all_required_methods(self): GoodManager() - def check_bad_initialization(self, manager_class, err, required_phrases): + @data_driven_test(initialization_invalid_cases, True, "raises error initializing: ") # noqa + def test_initialization_invalid_cases(self, manager_class, err, required_phrases): # noqa with assert_raises(err) as context: manager_class() self.assert_error_message_contains(context.exception, required_phrases) - def test_does_not_do_bad_initialization(self): - yield from self.yield_tests(self.check_bad_initialization, [ - ( - "without options attribute or run method", - EmptyManager, TypeError, ["EmptyManager"] - ), - ( - "without run method", - NoRunManager, TypeError, ["NoRunManager", "run"] - ), - ( - "without options attribute", - NoOptionsManager, AttributeError, - ["NoOptionsManager", "options", "attribute"] - ), - ( - "with a non-dictionary options attribute", - OptionsNotADictManager, TypeError, - [ - "OptionsNotADictManager", "options", "should be", - "instance", dict.__qualname__ - ] - ), - ], described=True, prefix="does not initialize subclass ") - def test_blocks_subclass_with_calling_super_run_method(self): manager = SuperCallingManager() diff --git a/py2c/abc/tests/test_source_handler.py b/py2c/abc/tests/test_source_handler.py index 4151d68..330b1ce 100644 --- a/py2c/abc/tests/test_source_handler.py +++ b/py2c/abc/tests/test_source_handler.py @@ -1,7 +1,7 @@ """Unit-tests for `py2c.abc.source_handler.SourceHandler` """ -from py2c.tests import Test +from py2c.tests import Test, data_driven_test from nose.tools import assert_raises from py2c.abc.source_handler import SourceHandler @@ -64,6 +64,29 @@ def get_source(self, file_name): def write_source(self, file_name, source): super().write_source(file_name, source) +initialization_invalid_cases = [ + ( + "without any method", + EmptySourceHandler, TypeError, ["EmptySourceHandler"] + ), + ( + "without get_files method", + NoGetFilesSourceHandler, TypeError, + ["NoGetFilesSourceHandler", "get_files"] + ), + ( + "without get_source method", + NoGetSourceSourceHandler, TypeError, + ["NoGetSourceSourceHandler", "get_source"] + ), + ( + "without write_source method", + NoWriteSourceSourceHandler, TypeError, + ["NoWriteSourceSourceHandler", "write_source"] + ), + # MARK:: Should I add the only-one method cases as well? +] + # ----------------------------------------------------------------------------- # Tests @@ -75,36 +98,13 @@ class TestBaseSourceHandler(Test): def test_initializes_a_subclass_with_all_required_methods(self): GoodSourceHandler() - def check_bad_initialization(self, source_handler_class, err, required_phrases): + @data_driven_test(initialization_invalid_cases, True, "raises error initializing subclass: ") # noqa + def test_initialization_invalid_cases(self, source_handler_class, err, required_phrases): # noqa with assert_raises(err) as context: source_handler_class() self.assert_error_message_contains(context.exception, required_phrases) - def test_does_not_do_bad_initialization(self): - yield from self.yield_tests(self.check_bad_initialization, [ - ( - "without any method", - EmptySourceHandler, TypeError, ["EmptySourceHandler"] - ), - ( - "without get_files method", - NoGetFilesSourceHandler, TypeError, - ["NoGetFilesSourceHandler", "get_files"] - ), - ( - "without get_source method", - NoGetSourceSourceHandler, TypeError, - ["NoGetSourceSourceHandler", "get_source"] - ), - ( - "without write_source method", - NoWriteSourceSourceHandler, TypeError, - ["NoWriteSourceSourceHandler", "write_source"] - ), - # MARK:: Should I add the only-one method cases as well? - ], described=True, prefix="does not initialize subclass ") - def test_raises_error_when_subclass_calls_an_abstract_method(self): source_handler = SuperCallingSourceHandler() diff --git a/py2c/abc/tests/test_worker.py b/py2c/abc/tests/test_worker.py index 739306f..3f72c64 100644 --- a/py2c/abc/tests/test_worker.py +++ b/py2c/abc/tests/test_worker.py @@ -5,7 +5,7 @@ from py2c.abc.worker import Worker -from py2c.tests import Test, mock +from py2c.tests import Test, mock, data_driven_test from nose.tools import assert_raises, assert_true, assert_is_instance @@ -29,6 +29,13 @@ class SuperCallingWorker(Worker): def work(self): super().work() +initialization_invalid_cases = [ + ( + "without work method", + BadWorker, TypeError, ["BadWorker", "work"] + ), +] + # ----------------------------------------------------------------------------- # Tests @@ -40,20 +47,13 @@ class TestBaseWorker(Test): def test_initializes_a_subclass_with_all_required_methods(self): GoodWorker() - def check_bad_initialization(self, manager_class, err, required_phrases): + @data_driven_test(initialization_invalid_cases, True, "raises error initializing subclass: ") # noqa + def test_initialization_invalid_cases(self, worker_class, err, required_phrases): # noqa with assert_raises(err) as context: - manager_class() + worker_class() self.assert_error_message_contains(context.exception, required_phrases) - def test_does_not_do_bad_initialization(self): - yield from self.yield_tests(self.check_bad_initialization, [ - ( - "without work method", - BadWorker, TypeError, ["BadWorker", "work"] - ), - ], described=True, prefix="does not initialize subclass ") - def test_blocks_subclass_with_calling_super_work_method(self): worker = SuperCallingWorker() diff --git a/py2c/common/tests/test_configuration.py b/py2c/common/tests/test_configuration.py index 1ca8031..16d7591 100644 --- a/py2c/common/tests/test_configuration.py +++ b/py2c/common/tests/test_configuration.py @@ -7,7 +7,7 @@ ) from nose.tools import assert_is, assert_raises, assert_equal -from py2c.tests import Test, runmodule +from py2c.tests import Test, data_driven_test class TestConfiguration(Test): @@ -40,27 +40,29 @@ def test_does_set_and_get_a_registered_option(self): val = self.config.get_option("registered_set_option") assert_is(val, obj) - def check_option_registeration(self, option_name, should_work=True): + @data_driven_test(described=True, prefix="registers valid option: ", data=[ + ("a simple name", "a_simple_name"), + ("a dotted name", "a.dotted.name"), + ]) + def test_registers_valid_option(self, name): try: - Configuration().register_option(option_name) - except InvalidOptionError: - assert not should_work, "Should have registered..." + Configuration().register_option(name) + except Exception: + self.fail("Should have registered name: {}".format(name)) + + @data_driven_test(described=True, prefix="raises error when registering invalid option: ", data=[ # noqa + ("a name starting with dot", ".invalid"), + ("a dotted name starting with dot", ".invalid.name"), + ("a name with spaces", "invalid name"), + ("a non-string name", 1200), + ]) + def test_raises_error_registering_invalid_option(self, name): + try: + Configuration().register_option(name) + except Exception: + pass else: - assert should_work, "Should not have registered..." - - def test_z_does_valid_option_registeration(self): - yield from self.yield_tests(self.check_option_registeration, [ - ("a simple name", "a_simple_name"), - ("a dotted name", "a.dotted.name"), - ], described=True, prefix="does register option with ") - - def test_z_does_not_do_invalid_option_registeration(self): - yield from self.yield_tests(self.check_option_registeration, [ - ("a name starting with dot", ".invalid", False), - ("a dotted name starting with dot", ".invalid.name", False), - ("a name with spaces", "invalid name", False), - ("a non-string name", 1200, False), - ], described=True, prefix="does not register option with ") + self.fail("Should not have registered name: {}".format(name)) def test_does_reset_options_to_default_value_correctly(self): self.config.register_option("option_name", "Yo!") @@ -76,4 +78,6 @@ def test_does_reset_options_to_default_value_correctly(self): assert_equal(self.config.get_option("option_name"), "Yo!") if __name__ == '__main__': + from py2c.tests import runmodule + runmodule(capture=False) diff --git a/py2c/processing/tests/test_to_ast.py b/py2c/processing/tests/test_to_ast.py index 5ff7119..5339439 100644 --- a/py2c/processing/tests/test_to_ast.py +++ b/py2c/processing/tests/test_to_ast.py @@ -6,7 +6,7 @@ from py2c.processing.to_ast import SourceToAST, SourceToASTTranslationError -from py2c.tests import Test +from py2c.tests import Test, data_driven_test from nose.tools import assert_equal, assert_raises @@ -14,40 +14,22 @@ class TestPythonSourceToPythonAST(Test): """py2c.processing.to_ast.SourceToAST """ - def check_conversion(self, code, node, error=None): - """Test the conversion of Python source to Python's AST - """ - code = textwrap.dedent(code) - - convertor = SourceToAST() - - if node is None: - if error is None: - self.fail("Only one of node and error should be non-zero.") - with assert_raises(error): - convertor.work(code) - else: - if error is not None: - self.fail("Only one of node and error should be non-zero.") - assert_equal(ast.dump(convertor.work(code)), ast.dump(node)) - - # Could make more extenstive, I guess don't need to - def test_does_source_to_AST_conversion_correctly(self): - yield from self.yield_tests(self.check_conversion, [ - [ - "a simple statement", - "pass", ast.Module(body=[ast.Pass()]) - ] - ], described=True, prefix="does convert correctly ") - - def test_does_not_convert_invalid_code(self): - yield from self.yield_tests(self.check_conversion, [ - [ - "invalid code", - "$", None, SourceToASTTranslationError - ] - ], described=True, prefix="does not convert ") - + # Could make more extensive, I guess don't need to + @data_driven_test(described=True, prefix="converts valid input correctly: ", data=[ # noqa + ["a pass statement", "pass", ast.Module(body=[ast.Pass()])] + ]) + def test_valid_input_cases(self, code, node): + assert_equal( + ast.dump(SourceToAST().work(textwrap.dedent(code))), + ast.dump(node) + ) + + @data_driven_test(described=True, prefix="raises error when given: ", data=[ # noqa + ["invalid code", "$", SourceToASTTranslationError] + ]) + def test_invalid_input_cases(self, code, error): + with assert_raises(error): + SourceToAST().work(textwrap.dedent(code)) if __name__ == '__main__': from py2c.tests import runmodule diff --git a/py2c/source_handlers/tests/test_file_source_handler.py b/py2c/source_handlers/tests/test_file_source_handler.py index be4d7c7..5cbe562 100644 --- a/py2c/source_handlers/tests/test_file_source_handler.py +++ b/py2c/source_handlers/tests/test_file_source_handler.py @@ -11,7 +11,7 @@ from py2c.source_handlers import FileSourceHandler from nose.tools import assert_raises, assert_equal -from py2c.tests import Test +from py2c.tests import Test, data_driven_test class TestFileSourceHandler(Test): @@ -53,7 +53,11 @@ def test_needs_an_argument_to_initialize(self): FileSourceHandler() self.assert_error_message_contains(context.exception, ["require", "1"]) - def check_file_name_matches(self, error, method, required_phrases, *args): + @data_driven_test(described=True, prefix="checks file name before ", data=[ # noqa + ("getting sources", CouldNotGetSourceError, "get_source"), + ("writing sources", CouldNotWriteSourceError, "write_source", ""), + ]) + def check_file_name_matches(self, error, method, *args): file_name = self.get_temporary_file_name() fsh = FileSourceHandler(file_name) @@ -64,20 +68,6 @@ def check_file_name_matches(self, error, method, required_phrases, *args): context.exception, ["unexpected", "file name"] ) - def test_does_check_file_name_before_operation(self): - yield from self.yield_tests(self.check_file_name_matches, [ - ( - "getting sources", - CouldNotGetSourceError, "get_source", - ["unexpected", "file name"] - ), - ( - "writing sources", - CouldNotWriteSourceError, "write_source", - ["unexpected", "file name"], "" - ), - ], described=True, prefix="does check file name before ") - def test_lists_only_passed_file_name(self): fsh = FileSourceHandler("magic.py") diff --git a/py2c/tests/test_utils.py b/py2c/tests/test_utils.py index 6a6fa39..a6c82a9 100644 --- a/py2c/tests/test_utils.py +++ b/py2c/tests/test_utils.py @@ -58,6 +58,7 @@ def test_does_not_allow_attribute_to_be_of_wrong_type(self): class TestIsValidDottedIdentifier(Test): """py2c.utils.is_valid_dotted_identifier """ + # MARK:: Should this be into a data_driven_test? def test_verifies_valid_case(self): # If we see an error, it's a failed test. diff --git a/py2c/tree/node_gen.py b/py2c/tree/node_gen.py index 9883b1b..044f456 100644 --- a/py2c/tree/node_gen.py +++ b/py2c/tree/node_gen.py @@ -92,7 +92,7 @@ def t_newline(self, t): t.lexer.lineno += 1 def t_error(self, t): - raise ParserError("Unable to generate tokens from: " + repr(t.value)) + raise ParserError("Cannot generate tokens from: " + repr(t.value)) def _reset(self): self.seen_node_names = set() @@ -111,7 +111,7 @@ def parse(self, text): # Parsing # ------------------------------------------------------------------------- def p_error(self, t): - raise ParserError("Unexpected token: " + str(t)) + raise ParserError("Got unexpected token: " + str(t)) def p_empty(self, p): "empty : " @@ -134,7 +134,7 @@ def p_declaration(self, p): name, parent, fields = (p[1], p[2], p[3]) if name in self.seen_node_names: raise ParserError( - "Multiple declarations of name {!r}".format(name) + "{!r} has multiple declarations".format(name) ) self.seen_node_names.add(name) @@ -149,7 +149,7 @@ def p_declaration(self, p): seen_fields.append(field_name) if duplicated_fields: - msg = "Multiple declarations in {!r} of attribute{} {!r}" + msg = "{!r} has multiple attribute{} named {!r}" raise ParserError(msg.format( name, "s" if len(duplicated_fields) > 1 else "", diff --git a/py2c/tree/tests/data_init.py b/py2c/tree/tests/data_init.py new file mode 100644 index 0000000..118ae93 --- /dev/null +++ b/py2c/tree/tests/data_init.py @@ -0,0 +1,409 @@ +"""Data for unit-tests for `py2c.tree` +""" + +from py2c import tree + + +# ============================================================================= +# Helper classes +# ============================================================================= +class NodeWithoutFieldsAttribute(tree.Node): + """A base node that doesn't define the fields attribute + """ + + +class EmptyNode(tree.Node): + """An empty node with no fields + """ + _fields = [] + + +class BasicNode(tree.Node): + """Basic node + """ + _fields = [ + ('f1', int, "NEEDED"), + ] + + +class InheritingNodeWithoutFieldsAttributeNode(BasicNode): + """A node that inherits from BasicNode but doesn't declare fields. + + (It should inherit the fields from parent) + """ + + +class BasicNodeCopy(tree.Node): + """Equivalent but not equal to BasicNode + """ + _fields = [ + ('f1', int, "NEEDED"), + ] + + +class AllIntModifiersNode(tree.Node): + """Node with all modifiers + """ + _fields = [ + ('f1', int, "NEEDED"), + ('f2', int, "OPTIONAL"), + ('f3', int, "ZERO_OR_MORE"), + ('f4', int, "ONE_OR_MORE"), + ] + + +class NodeWithANodeField(tree.Node): + """Node with another node as child + """ + _fields = [ + ('child', BasicNode, "NEEDED"), + ] + + +class InvalidModifierNode(tree.Node): + """Node with invalid modifier + """ + _fields = [ + ('f1', int, None), + ] + + +# ----------------------------------------------------------------------------- + + +class SubClass(tree.identifier): + """A subclass of identifier. + + Used for checking identifier's behaviour with subclasses. + """ + + +# ============================================================================= +# Test Cases +# ============================================================================= +Node_initialization_valid_cases = [ + ( + "zero fields; without any arguments", + EmptyNode, [], {}, {} + ), + ( + "one NEEDED field; without any arguments", + BasicNode, [], {}, {} + ), + ( + "one NEEDED field; with 1 valid positional argument", + BasicNode, [1], {}, {"f1": 1} + ), + ( + "one NEEDED field; with 1 valid keyword argument", + BasicNode, [], {"f1": 1}, {"f1": 1} + ), + ( + "all types of fields; with minimal valid positional arguments", + AllIntModifiersNode, [1, None, (), (2,)], {}, { + "f1": 1, "f2": None, "f3": (), "f4": (2,) + } + ), + ( + "all types of fields; with valid positional arguments", + AllIntModifiersNode, [1, 2, (3, 4, 5), (6, 7, 8)], {}, { + "f1": 1, "f2": 2, "f3": (3, 4, 5), "f4": (6, 7, 8), + } + ), + ( + "one inherited NEEDED field; without any arguments", + InheritingNodeWithoutFieldsAttributeNode, [], {}, {} + ), + ( + "one inherited NEEDED field; with 1 valid positional argument", + InheritingNodeWithoutFieldsAttributeNode, [1], {}, {"f1": 1} + ), + ( + "one inherited NEEDED field; with 1 valid keyword argument", + InheritingNodeWithoutFieldsAttributeNode, [], {"f1": 1}, {"f1": 1} + ), +] + +Node_initialization_invalid_cases = [ + ( + "no fields attribute defined", + NodeWithoutFieldsAttribute, [], {}, + tree.InvalidInitializationError, + ["iterable", "_fields"] + ), + ( + "zero fields with some argument", + EmptyNode, [1], {}, + tree.InvalidInitializationError, + ["no", "arguments"] + ), + ( + "one field with extra arguments", + BasicNode, [1, 2], {}, + tree.InvalidInitializationError, + ["0 or 1", "argument", "!arguments"] + ), + ( + "modifiers with incorrect number of arguments", + AllIntModifiersNode, [1], {}, + tree.InvalidInitializationError, + ["0 or 4", "arguments"] + ), + ( + "missing arguments", + AllIntModifiersNode, [1], {}, + tree.InvalidInitializationError, + ["AllIntModifiersNode", "0 or 4", "arguments"] + ), + ( + "a child with missing arguments", + lambda: NodeWithANodeField(AllIntModifiersNode(1)), [], {}, + tree.InvalidInitializationError, + ["AllIntModifiersNode", "0 or 4", "arguments"] + ), + ( + "an invalid/unknown modifier", + InvalidModifierNode, [], {}, + tree.InvalidInitializationError, + ["InvalidModifierNode", "f1", "invalid modifier"] + ), +] + +Node_assignment_valid_cases = [ + ( + "NEEDED with False-ish value", + AllIntModifiersNode, "f1", 0 + ), + ( + "NEEDED with True-ish value", + AllIntModifiersNode, "f1", 1 + ), + ( + "OPTIONAL with False-ish value", + AllIntModifiersNode, "f2", 0 + ), + ( + "OPTIONAL with True-ish value", + AllIntModifiersNode, "f2", 1 + ), + ( + "OPTIONAL with None", + AllIntModifiersNode, "f2", None + ), + ( + "ZERO_OR_MORE with an empty tuple", + AllIntModifiersNode, "f3", () + ), + ( + "ZERO_OR_MORE with a tuple with one element", + AllIntModifiersNode, "f3", (1,) + ), + ( + "ZERO_OR_MORE with a tuple with four element", + AllIntModifiersNode, "f3", (1, 2, 3, 4) + ), + ( + "ZERO_OR_MORE with an empty list", + AllIntModifiersNode, "f3", [] + ), + ( + "ZERO_OR_MORE with a list with one element", + AllIntModifiersNode, "f3", [1] + ), + ( + "ZERO_OR_MORE with a list with four element", + AllIntModifiersNode, "f3", [1, 2, 3, 4] + ), + ( + "ONE_OR_MORE with a tuple with one element", + AllIntModifiersNode, "f4", (1,) + ), + ( + "ONE_OR_MORE with a tuple with four element", + AllIntModifiersNode, "f4", (1, 2, 3, 4) + ), + ( + "ONE_OR_MORE with a list with one element", + AllIntModifiersNode, "f4", [1] + ), + ( + "ONE_OR_MORE with a list with four element", + AllIntModifiersNode, "f4", [1, 2, 3, 4] + ), +] + +Node_assignment_invalid_cases = [ + ( + "non existent field", + BasicNode, "bar", 1, + tree.FieldError, ["bar", "no field"] + ), + ( + "NEEDED with incorrect type", + AllIntModifiersNode, "f1", "", + tree.WrongTypeError + ), + ( + "OPTIONAL with incorrect type", + AllIntModifiersNode, "f2", "", + tree.WrongTypeError + ), + ( + "ZERO_OR_MORE with incorrect type", + AllIntModifiersNode, "f3", "", + tree.WrongTypeError + ), + ( + "ZERO_OR_MORE with tuple containing incorrect type", + AllIntModifiersNode, "f3", ("",), + tree.WrongTypeError + ), + ( + "ZERO_OR_MORE with list containing incorrect type", + AllIntModifiersNode, "f3", [""], + tree.WrongTypeError + ), + ( + "ONE_OR_MORE with incorrect type", + AllIntModifiersNode, "f4", "", + tree.WrongTypeError + ), + ( + "ONE_OR_MORE with empty tuple", + AllIntModifiersNode, "f4", (), + tree.WrongTypeError + ), + ( + "ONE_OR_MORE with empty list", + AllIntModifiersNode, "f4", [], + tree.WrongTypeError + ), + ( + "ONE_OR_MORE with tuple containing incorrect type", + AllIntModifiersNode, "f4", ("",), + tree.WrongTypeError + ), + ( + "ONE_OR_MORE with list containing incorrect type", + AllIntModifiersNode, "f4", [""], + tree.WrongTypeError + ), +] + +Node_finalization_valid_cases = [ + ( + "valid attributes", + AllIntModifiersNode(1, 2, [], [3]), + {"f1": 1, "f2": 2, "f3": (), "f4": (3,)} + ), + ( + "valid attributes (optionals not given)", + AllIntModifiersNode(f1=1, f4=[2]), + {"f1": 1, "f2": None, "f3": (), "f4": (2,)} + ), +] + +Node_finalization_invalid_cases = [ + ( + "no parameters", + AllIntModifiersNode(), + ["missing", "f1", "f4", "!f2", "!f3"] + ), + ( + "a child without parameters", + NodeWithANodeField(BasicNode()), + ["missing", "f1"] + ) +] + +Node_equality_equal_cases = [ + ( + "same class nodes with equal attributes", + BasicNode(1), BasicNode(1) + ), + ( + "same class nodes with same class children " + "with equal attributes", + NodeWithANodeField(BasicNode(1)), + NodeWithANodeField(BasicNode(1)) + ), +] + +Node_equality_not_equal_cases = [ + ( + "same class nodes with non-equal attributes", + BasicNode(0), BasicNode(1) + ), + ( + "same class nodes with same class children with non-equal " + "attributes", + NodeWithANodeField(BasicNode(0)), + NodeWithANodeField(BasicNode(1)) + ), + ( + "different class nodes with same attributes", + BasicNode(1), BasicNodeCopy(1) + ), +] + +Node_repr_cases = [ + ( + "no-field node", + BasicNode(), "BasicNode()" + ), + ( + "no-field node with a different name", + BasicNodeCopy(), "BasicNodeCopy()" + ), + ( + "multi-field node with no arguments", + AllIntModifiersNode(), "AllIntModifiersNode()" + ), + ( + "node with Node fields but no arguments", + NodeWithANodeField(), "NodeWithANodeField()" + ), + ( + "multi-field node with minimal number of arguments", + AllIntModifiersNode(f1=1, f2=None), + "AllIntModifiersNode(f1=1, f2=None)" + ), + ( + "multi-field node with optional arguments provided", + AllIntModifiersNode(f1=1, f2=None, f3=[3], f4=(4, 5, 6)), + "AllIntModifiersNode(f1=1, f2=None, f3=[3], f4=(4, 5, 6))" + ) +] + +# ----------------------------------------------------------------------------- + +identifier_valid_cases = [ + ("a valid string", "valid_name"), + ("a really long string", "really" * 100 + "_long_name"), + ("a single character string", "c"), + ("a snake_case string", "valid_name"), + ("a CamelCase string", "ValidName"), + ("a LOUD_CASE string", "VALID_NAME"), + ("a valid string with numbers and underscore", "Valid_1_name"), + ("a string with leading and trailing underscores", "_valid_name_"), + # Might never be used but support is **very** important! :P + ("a string containing a unicode character", "虎"), +] + +identifier_invalid_cases = [ + ("an empty string", ""), + ("a string containing spaces", "invalid name"), + ("a string containing hyphen", "invalid-name"), + ("a string containing full-stop/dot", "invalid.name"), +] + +identifier_is_subclass_cases = [ + ("str", str), + ("an inheriting sub-class", SubClass), +] + +identifier_not_is_subclass_cases = [ + ("object", object), + ("float", float), + ("int", int), +] diff --git a/py2c/tree/tests/data_node_gen.py b/py2c/tree/tests/data_node_gen.py new file mode 100644 index 0000000..efa2d88 --- /dev/null +++ b/py2c/tree/tests/data_node_gen.py @@ -0,0 +1,240 @@ +"""Data for unit-tests for `py2c.tree.node_gen` +""" + +from py2c.tree import node_gen + +# ============================================================================= +# Test cases +# ============================================================================= + +remove_comments_cases = [ + ( + "an empty string", + "", + "" + ), + ( + "a string with full line comment without trailing newline", + "#test: [a,string]", + "" + ), + ( + "a string with full line comment with a trailing newline", + "# test: [a,string]\n", + "\n" + ), + ( + "a string with full line comment with a trailing newline and text on next line", # noqa + "# test: [a,string]\nbar: []", + "\nbar: []" + ), + ( + "a string with inline comment with a trailing newline", + "foo: [] # test: [a,string]", + "foo: [] " + ), + ( + "a string with inline comment with a trailing newline and text on next line", # noqa + "foo: [] # test: [a,string]\nbar: []", + "foo: [] \nbar: []" + ), +] + +# ----------------------------------------------------------------------------- + +Parser_valid_cases = [ + ( + "single node with no parent and zero fields", + "FooBar", + [node_gen.Definition('FooBar', None, [])], + """ + class FooBar(object): + @fields_decorator + def _fields(cls): + return [] + """ + ), + ( + "single node with parent and zero fields", + "FooBar(Base)", + [node_gen.Definition('FooBar', 'Base', [])], + """ + class FooBar(Base): + @fields_decorator + def _fields(cls): + return [] + """ + ), + ( + "single node with no parent and zero fields", + "FooBar: []", + [node_gen.Definition('FooBar', None, [])], + """ + class FooBar(object): + @fields_decorator + def _fields(cls): + return [] + """ + ), + ( + "single node with parent and inherited fields", + "FooBar(AST): inherit", + [node_gen.Definition('FooBar', 'AST', 'inherit')], + """ + class FooBar(AST): + pass + """ + ), + ( + "single node with no parent and one field", + "FooBar: [int bar]", + [node_gen.Definition('FooBar', None, [('bar', 'int', 'NEEDED')])], + """ + class FooBar(object): + @fields_decorator + def _fields(cls): + return [ + ('bar', int, 'NEEDED'), + ] + """ + ), + ( + "single node with parent and one field", + "FooBar(AST): [int bar]", + [node_gen.Definition('FooBar', 'AST', [('bar', 'int', 'NEEDED')])], + """ + class FooBar(AST): + @fields_decorator + def _fields(cls): + return [ + ('bar', int, 'NEEDED'), + ] + """ + ), + ( + "single node with no parent and 4 fields of all types", + "FooBar: [int f1, int+ f2, int* f3, int? f4]", + [ + node_gen.Definition( + "FooBar", None, + [ + ('f1', 'int', 'NEEDED'), + ('f2', 'int', 'ONE_OR_MORE'), + ('f3', 'int', 'ZERO_OR_MORE'), + ('f4', 'int', 'OPTIONAL'), + ] + ) + ], + """ + class FooBar(object): + @fields_decorator + def _fields(cls): + return [ + ('f1', int, 'NEEDED'), + ('f2', int, 'ONE_OR_MORE'), + ('f3', int, 'ZERO_OR_MORE'), + ('f4', int, 'OPTIONAL'), + ] + """ + ), + ( + "multiple nodes with inheritance", + """ + base1: [int field1] + base2(base1): [int field2] + obj(base2): [] + """, + [ + node_gen.Definition("base1", None, [("field1", "int", "NEEDED")]), + node_gen.Definition("base2", "base1", [("field2", "int", "NEEDED")]), # noqa + node_gen.Definition("obj", "base2", []), + ], + """ + class base1(object): + @fields_decorator + def _fields(cls): + return [ + ('field1', int, 'NEEDED'), + ] + + + class base2(base1): + @fields_decorator + def _fields(cls): + return [ + ('field2', int, 'NEEDED'), + ] + + + class obj(base2): + @fields_decorator + def _fields(cls): + return [] + """ + ), + ( + "multiple nodes without bothering about the indentation", + """ + base1: [int field1] + base2(base1): [int field2] + obj: [] + """, + [ + node_gen.Definition("base1", None, [("field1", "int", "NEEDED")]), + node_gen.Definition("base2", "base1", [("field2", "int", "NEEDED")]), # noqa + node_gen.Definition("obj", None, []), + ], + """ + class base1(object): + @fields_decorator + def _fields(cls): + return [ + ('field1', int, 'NEEDED'), + ] + + + class base2(base1): + @fields_decorator + def _fields(cls): + return [ + ('field2', int, 'NEEDED'), + ] + + + class obj(object): + @fields_decorator + def _fields(cls): + return [] + """ + ) +] + +Parser_invalid_cases = [ + ( + "multiple attributes with same name", + "foo: [int bar, str bar]", # type should not matter + ["multiple", "attribute", "foo", "bar"] + ), + ( + "multiple declarations of node", + "foo: []\n" * 2, + ["multiple", "declaration", "foo"] + ), + ( + "invalid token", + "$foo: []", + ["not", "generate", "token", "$foo"] + ), + ( + "no data-type", + "foo: [bar, baz]", + ["unexpected", "','"] + ), + ( + "a node that inherits, without parent", + "foo: inherit", + ['inherit', 'need', 'parent', 'foo'] + ), +] + +SourceGenerator_valid_cases = Parser_valid_cases diff --git a/py2c/tree/tests/data_visitors.py b/py2c/tree/tests/data_visitors.py new file mode 100644 index 0000000..350e4a7 --- /dev/null +++ b/py2c/tree/tests/data_visitors.py @@ -0,0 +1,91 @@ +"""Data for unit-tests for `py2c.tree.visitors` +""" + +from py2c import tree +from py2c.tree import visitors + + +# ============================================================================= +# Helper classes +# ============================================================================= +class BasicNode(tree.Node): + """Basic node + """ + _fields = [] + + +class BasicNodeCopy(tree.Node): + """Equivalent but not equal to BasicNode + """ + _fields = [] + + +class ParentNode(tree.Node): + """Node with another node as child + """ + _fields = [ + ('child', tree.Node, 'NEEDED'), + ] + + +class ParentNodeWithChildrenList(tree.Node): + """Node with another node as child + """ + _fields = [ + ('child', tree.Node, 'ONE_OR_MORE'), + ] + + +class SimpleVisitor(visitors.RecursiveNodeVisitor): + + def __init__(self): + super().__init__() + self.visited = [] + + def generic_visit(self, node): + self.visited.append(node.__class__.__name__) + super().generic_visit(node) + + def visit_BasicNodeCopy(self, node): + self.visited.append("") + + +# ============================================================================= +# Tests +# ============================================================================= +RecursiveNodeVisitor_visit_order_cases = [ + ( + "node without children", + BasicNode(), ["BasicNode"] + ), + ( + "node without children (calls overidden method)", + BasicNodeCopy(), [""] + ), + ( + "node with children", + ParentNode(BasicNode()), ["ParentNode", "BasicNode"] + ), + ( + "node with children (calls overidden method)", + ParentNode(BasicNodeCopy()), ["ParentNode", ""] + ), + ( + "node with grand children", + ParentNode(ParentNode(BasicNode())), + ["ParentNode", "ParentNode", "BasicNode"] + ), + ( + "node with grand children (calls overidden method)", + ParentNode(ParentNode(BasicNodeCopy())), + ["ParentNode", "ParentNode", ""] + ), + ( + "node with list of children with grand children " + "(calls overidden method)", + ParentNodeWithChildrenList( + [ParentNode(BasicNode()), BasicNodeCopy()] + ), + ["ParentNodeWithChildrenList", "ParentNode", "BasicNode", ""] + ), +] diff --git a/py2c/tree/tests/test_init.py b/py2c/tree/tests/test_init.py new file mode 100644 index 0000000..eebd9c9 --- /dev/null +++ b/py2c/tree/tests/test_init.py @@ -0,0 +1,159 @@ +"""Unit-tests for `py2c.tree` +""" + +from py2c import tree + +from nose.tools import assert_raises, assert_equal, assert_not_equal +from py2c.tests import Test, data_driven_test # noqa + +import py2c.tree.tests.data_init as data + + +# ----------------------------------------------------------------------------- +# Tests +# ----------------------------------------------------------------------------- +class TestNode(Test): + """py2c.tree.Node + """ + + @data_driven_test(data.Node_initialization_valid_cases, True, "initializes successfully: subclass with ") # noqa + def test_initialization_valid_cases(self, cls, args, kwargs, expected_dict): # noqa + try: + node = cls(*args, **kwargs) + except tree.WrongTypeError: + self.fail("Unexpectedly raised exception") + else: + for name, value in expected_dict.items(): + assert_equal(getattr(node, name), value) + + @data_driven_test(data.Node_initialization_invalid_cases, True, "raises error initializing: subclass with ") # noqa + def test_initialization_invalid_cases(self, cls, args, kwargs, error, required_phrases): # noqa + with assert_raises(error) as context: + cls(*args, **kwargs) + + self.assert_error_message_contains(context.exception, required_phrases) + + @data_driven_test(data.Node_assignment_valid_cases, True, "assigns to: ") # noqa + def test_assignment_valid_cases(self, cls, attr, value): # noqa + node = cls() + + try: + setattr(node, attr, value) + except Exception as e: + self.fail("Raised error for valid assignment", e) + else: + assert_equal( + getattr(node, attr), value, + "Expected value to be set after assignment" + ) + + @data_driven_test(data.Node_assignment_invalid_cases, True, "raises error assigning to: ") # noqa + def test_assignment_invalid_cases(self, cls, attr, value, error_cls, required_phrases=None): # noqa + node = cls() + + try: + setattr(node, attr, value) + except error_cls as err: + self.assert_error_message_contains(err, required_phrases or []) + else: + self.fail("Did not raise {} for invalid assignment".format( + error_cls.__name__ + )) + + @data_driven_test(data.Node_finalization_valid_cases, True, "finalizes: subclass with ") # noqa + def test_finalization_valid_cases(self, node, final_attrs): # noqa + node.finalize() + for attr, val in final_attrs.items(): + assert_equal(getattr(node, attr), val) + + @data_driven_test(data.Node_finalization_invalid_cases, True, "raises error while finalizing: subclass with ") # noqa + def test_finalization_invalid_cases(self, node, required_phrases): # noqa + try: + node.finalize() + except Exception as err: + self.assert_error_message_contains(err, required_phrases) + else: + self.fail("Did not raise an exception for invalid finalize") + + @data_driven_test(data.Node_equality_equal_cases, True, "reports equality correctly: ") # noqa + def test_equality_equal_cases(self, node1, node2): # noqa + node1.finalize() + node2.finalize() + assert_equal(node1, node2) + + @data_driven_test(data.Node_equality_not_equal_cases, True, "reports in-equality correctly: ") # noqa + def test_equality_not_equal_cases(self, node1, node2): # noqa + node1.finalize() + node2.finalize() + assert_not_equal(node1, node2) + + @data_driven_test(data.Node_repr_cases, True, "gives correct representation: ") # noqa + def test_repr_cases(self, node, expected): # noqa + assert_equal(repr(node), expected) + + +# ----------------------------------------------------------------------------- +# identifier tests +# ----------------------------------------------------------------------------- +class TestIdentifier(Test): + """py2c.tree.identifier + """ + + @data_driven_test(data.identifier_valid_cases, True, "initializes successfully: ") # noqa + def test_initialization_valid_cases(self, arg): + obj = tree.identifier(arg) + assert_equal(obj, arg) + + @data_driven_test(data.identifier_invalid_cases, True, "raises error when initialized: ") # noqa + def test_initialization_invalid_cases(self, arg): + with assert_raises(tree.WrongAttributeValueError): + tree.identifier(arg) + + @data_driven_test(data.identifier_valid_cases, True, "should be an instance: ") # noqa + def test_is_instance_cases(self, value): + assert isinstance(value, tree.identifier) + + @data_driven_test(data.identifier_invalid_cases, True, "should not be an instance: ") # noqa + def test_is_instance_not_cases(self, value): + assert not isinstance(value, tree.identifier) + + @data_driven_test(data.identifier_is_subclass_cases, True, "should be a subclass: ") # noqa + def test_is_subclass_cases(self, value): + assert issubclass(value, tree.identifier) + + @data_driven_test(data.identifier_not_is_subclass_cases, True, "should not be a subclass: ") # noqa + def test_is_subclass_not_cases(self, value): + assert not issubclass(value, tree.identifier) + + @data_driven_test(data.identifier_valid_cases, True, "returns argument passed as is: ") # noqa + def test_returns_the_value_passed(self, name): + assert_equal(tree.identifier(name), name) + assert type(tree.identifier(name)) == str, "Should give a str" + + +# ----------------------------------------------------------------------------- +# fields_decorator +# ----------------------------------------------------------------------------- +class TestFieldsDecorator(Test): + """py2c.tree.fields_decorator + """ + + def test_does_behave_correctly(self): + # TODO:: Break into multiple tests if possible. + + class Caller(object): + called = False + + @tree.fields_decorator + def func(cls): + assert cls == Caller + cls.called = True + return 1 + + assert Caller().func == 1 + assert Caller.called + +if __name__ == '__main__': + from py2c.tests import runmodule + + runmodule() diff --git a/py2c/tree/tests/test_init_Node.py b/py2c/tree/tests/test_init_Node.py deleted file mode 100644 index b9f95f5..0000000 --- a/py2c/tree/tests/test_init_Node.py +++ /dev/null @@ -1,458 +0,0 @@ -"""Unit-tests for `py2c.tree.Node` -""" - -from nose.tools import assert_raises, assert_equal, assert_not_equal - -from py2c import tree -from py2c.tests import Test - - -# ----------------------------------------------------------------------------- -# A bunch of nodes used during testing -# ----------------------------------------------------------------------------- -class NodeWithoutFieldsAttribute(tree.Node): - """A base node that doesn't define the fields attribute - """ - - -class EmptyNode(tree.Node): - """An empty node with no fields - """ - _fields = [] - - -class BasicNode(tree.Node): - """Basic node - """ - _fields = [ - ('f1', int, "NEEDED"), - ] - - -class InheritingNodeWithoutFieldsAttribute(BasicNode): - """A node that inherits from BasicNode but doesn't declare fields. - - (It should inherit the fields from parent) - """ - - -class BasicNodeCopy(tree.Node): - """Equivalent but not equal to BasicNode - """ - _fields = [ - ('f1', int, "NEEDED"), - ] - - -class AllIntModifiersNode(tree.Node): - """Node with all modifiers - """ - _fields = [ - ('f1', int, "NEEDED"), - ('f2', int, "OPTIONAL"), - ('f3', int, "ZERO_OR_MORE"), - ('f4', int, "ONE_OR_MORE"), - ] - - -class NodeWithANodeField(tree.Node): - """Node with another node as child - """ - _fields = [ - ('child', BasicNode, "NEEDED"), - ] - - -class InvalidModifierNode(tree.Node): - """Node with invalid modifier - """ - _fields = [ - ('f1', int, None), - ] - - -# ----------------------------------------------------------------------------- -# Tests -# ----------------------------------------------------------------------------- -class TestNode(Test): - """py2c.tree.Node - """ - - def check_valid_initialization(self, cls, args, kwargs, expected_dict): - try: - node = cls(*args, **kwargs) - except tree.WrongTypeError: - self.fail("Unexpectedly raised exception") - else: - for name, value in expected_dict.items(): - assert_equal(getattr(node, name), value) - - def test_does_initialize_valid_node(self): - yield from self.yield_tests(self.check_valid_initialization, [ - ( - "zero fields without any arguments", - EmptyNode, [], {}, {} - ), - ( - "one NEEDED field without any arguments", - BasicNode, [], {}, {} - ), - ( - "one NEEDED field with 1 valid positional argument", - BasicNode, [1], {}, {"f1": 1} - ), - ( - "one NEEDED field with 1 valid keyword argument", - BasicNode, [], {"f1": 1}, {"f1": 1} - ), - ( - "all types of fields with minimal valid positional arguments", - AllIntModifiersNode, [1, None, (), (2,)], {}, { - "f1": 1, "f2": None, "f3": (), "f4": (2,) - } - ), - ( - "all types of fields with valid positional arguments", - AllIntModifiersNode, [1, 2, (3, 4, 5), (6, 7, 8)], {}, { - "f1": 1, "f2": 2, "f3": (3, 4, 5), "f4": (6, 7, 8), - } - ), - ( - "one inherited NEEDED field without any arguments", - InheritingNodeWithoutFieldsAttribute, [], {}, {} - ), - ( - "one inherited NEEDED field with 1 valid positional argument", - InheritingNodeWithoutFieldsAttribute, [1], {}, {"f1": 1} - ), - ( - "one inherited NEEDED field with 1 valid keyword argument", - InheritingNodeWithoutFieldsAttribute, [], {"f1": 1}, {"f1": 1} - ), - - ], described=True, prefix="does initialize a node with ") - - def check_invalid_initialization(self, cls, args, kwargs, error, required_phrases): # noqa - with assert_raises(error) as context: - cls(*args, **kwargs) - - self.assert_error_message_contains(context.exception, required_phrases) - - def test_does_not_initialize_invalid_node(self): - yield from self.yield_tests(self.check_invalid_initialization, [ - ( - "no fields attribute defined", - NodeWithoutFieldsAttribute, [], {}, - tree.InvalidInitializationError, - ["iterable", "_fields"] - ), - ( - "zero fields with some argument", - EmptyNode, [1], {}, - tree.InvalidInitializationError, - ["no", "arguments"] - ), - ( - "one field with extra arguments", - BasicNode, [1, 2], {}, - tree.InvalidInitializationError, - ["0 or 1", "argument", "!arguments"] - ), - ( - "modifiers with incorrect number of arguments", - AllIntModifiersNode, [1], {}, - tree.InvalidInitializationError, - ["0 or 4", "arguments"] - ), - ( - "missing arguments", - AllIntModifiersNode, [1], {}, - tree.InvalidInitializationError, - ["AllIntModifiersNode", "0 or 4", "arguments"] - ), - ( - "a child with missing arguments", - lambda: NodeWithANodeField(AllIntModifiersNode(1)), [], {}, - tree.InvalidInitializationError, - ["AllIntModifiersNode", "0 or 4", "arguments"] - ), - ( - "an invalid/unknown modifier", - InvalidModifierNode, [], {}, - tree.InvalidInitializationError, - ["InvalidModifierNode", "f1", "invalid modifier"] - ), - ], described=True, prefix="does not initialize a node with ") - - def check_assignment(self, cls, attr, value, error_cls=None, required_phrases=None): # noqa - node = cls() - - try: - setattr(node, attr, value) - except Exception as err: - if error_cls is None or not isinstance(err, error_cls): - self.fail("Raised Exception for valid assignment") - self.assert_error_message_contains(err, required_phrases or []) - else: - if error_cls is not None: - self.fail("Did not raise {} for invalid assignment".format( - error_cls.__name__ - )) - else: - assert_equal( - getattr(node, attr), value, - "Expected value to be set after assignment" - ) - - def test_does_assign_with_valid_values(self): - yield from self.yield_tests(self.check_assignment, [ - ( - "NEEDED with False-ish value", - AllIntModifiersNode, "f1", 0 - ), - ( - "NEEDED with True-ish value", - AllIntModifiersNode, "f1", 1 - ), - ( - "OPTIONAL with False-ish value", - AllIntModifiersNode, "f2", 0 - ), - ( - "OPTIONAL with True-ish value", - AllIntModifiersNode, "f2", 1 - ), - ( - "OPTIONAL with None", - AllIntModifiersNode, "f2", None - ), - ( - "ZERO_OR_MORE with an empty tuple", - AllIntModifiersNode, "f3", () - ), - ( - "ZERO_OR_MORE with a tuple with one element", - AllIntModifiersNode, "f3", (1,) - ), - ( - "ZERO_OR_MORE with a tuple with four element", - AllIntModifiersNode, "f3", (1, 2, 3, 4) - ), - ( - "ZERO_OR_MORE with an empty list", - AllIntModifiersNode, "f3", [] - ), - ( - "ZERO_OR_MORE with a list with one element", - AllIntModifiersNode, "f3", [1] - ), - ( - "ZERO_OR_MORE with a list with four element", - AllIntModifiersNode, "f3", [1, 2, 3, 4] - ), - ( - "ONE_OR_MORE with a tuple with one element", - AllIntModifiersNode, "f4", (1,) - ), - ( - "ONE_OR_MORE with a tuple with four element", - AllIntModifiersNode, "f4", (1, 2, 3, 4) - ), - ( - "ONE_OR_MORE with a list with one element", - AllIntModifiersNode, "f4", [1] - ), - ( - "ONE_OR_MORE with a list with four element", - AllIntModifiersNode, "f4", [1, 2, 3, 4] - ), - ], described=True, prefix="does assign ") - - def test_does_not_assign_with_invalid_values(self): - yield from self.yield_tests(self.check_assignment, [ - ( - "non existent field", - BasicNode, "bar", 1, - tree.FieldError, ["bar", "no field"] - ), - ( - "NEEDED with incorrect type", - AllIntModifiersNode, "f1", "", - tree.WrongTypeError - ), - ( - "OPTIONAL with incorrect type", - AllIntModifiersNode, "f2", "", - tree.WrongTypeError - ), - ( - "ZERO_OR_MORE with incorrect type", - AllIntModifiersNode, "f3", "", - tree.WrongTypeError - ), - ( - "ZERO_OR_MORE with tuple containing incorrect type", - AllIntModifiersNode, "f3", ("",), - tree.WrongTypeError - ), - ( - "ZERO_OR_MORE with list containing incorrect type", - AllIntModifiersNode, "f3", [""], - tree.WrongTypeError - ), - ( - "ONE_OR_MORE with incorrect type", - AllIntModifiersNode, "f4", "", - tree.WrongTypeError - ), - ( - "ONE_OR_MORE with empty tuple", - AllIntModifiersNode, "f4", (), - tree.WrongTypeError - ), - ( - "ONE_OR_MORE with empty list", - AllIntModifiersNode, "f4", [], - tree.WrongTypeError - ), - ( - "ONE_OR_MORE with tuple containing incorrect type", - AllIntModifiersNode, "f4", ("",), - tree.WrongTypeError - ), - ( - "ONE_OR_MORE with list containing incorrect type", - AllIntModifiersNode, "f4", [""], - tree.WrongTypeError - ), - ], described=True, prefix="does not assign ") - - def check_finalize(self, node, final_attrs=None, required_phrases=None): # noqa - try: - node.finalize() - except Exception as err: - if required_phrases is None: - self.fail( - "Raised Exception on finalize when values were OK" - ) - self.assert_error_message_contains(err, required_phrases) - else: - if required_phrases is not None: - self.fail("Did not raise error for invalid finalize") - elif final_attrs is not None: - for attr, val in final_attrs.items(): - assert_equal(getattr(node, attr), val) - - def test_does_finalize_node(self): - yield from self.yield_tests(self.check_finalize, [ - ( - "valid attributes", - AllIntModifiersNode(1, 2, [], [3]), - {"f1": 1, "f2": 2, "f3": (), "f4": (3,)} - ), - ( - "valid attributes (optionals not given)", - AllIntModifiersNode(f1=1, f4=[2]), - {"f1": 1, "f2": None, "f3": (), "f4": (2,)} - ), - ], described=True, prefix="does finalize a node with ") - - def test_does_not_finalize_node(self): - yield from self.yield_tests(self.check_finalize, [ - ( - "no parameters", - AllIntModifiersNode(), - None, ["missing", "f1", "f4", "!f2", "!f3"] - ), - ( - "a child without parameters", - NodeWithANodeField(BasicNode()), - None, ["missing", "f1"] - ) - ], described=True, prefix="does not finalize a node with ") - - def check_equality(self, node1, node2, is_equal): - node1.finalize() - node2.finalize() - - if is_equal: - assert_equal(node1, node2) - else: - assert_not_equal(node1, node2) - - def test_does_report_node_equality_correctly(self): - yield from self.yield_tests(self.check_equality, [ - ( - "same class nodes with equal attributes", - BasicNode(1), BasicNode(1), True - ), - ( - "same class nodes with same class children " - "with equal attributes", - NodeWithANodeField(BasicNode(1)), - NodeWithANodeField(BasicNode(1)), - True - ), - ], described=True, prefix="does report equality correctly for ") - - def test_does_report_node_inequality_correctly(self): - yield from self.yield_tests(self.check_equality, [ - ( - "same class nodes with non-equal attributes", - BasicNode(0), BasicNode(1), False - ), - ( - "same class nodes with same class children with non-equal " - "attributes", - NodeWithANodeField(BasicNode(0)), - NodeWithANodeField(BasicNode(1)), - False - ), - ( - "different class nodes with same attributes", - BasicNode(1), BasicNodeCopy(1), False - ), - ], described=True, prefix="does report in-equality correctly for ") - - def check_node_repr(self, node, expected): - assert_equal(repr(node), expected) - - def test_does_represent_node_correctly(self): - yield from self.yield_tests(self.check_node_repr, [ - ( - "no-field node", - BasicNode(), - "BasicNode()" - ), - ( - "no-field node with a different name", - BasicNodeCopy(), - "BasicNodeCopy()" - ), - ( - "multi-field node with no arguments", - AllIntModifiersNode(), - "AllIntModifiersNode()" - ), - ( - "multi-field node with Node fields but no arguments", - NodeWithANodeField(), - "NodeWithANodeField()" - ), - ( - "multi-field node with minimal number of arguments", - AllIntModifiersNode(f1=1, f2=None), - "AllIntModifiersNode(f1=1, f2=None)" - ), - ( - "multi-field node with optional arguments provided", - AllIntModifiersNode(f1=1, f2=None, f3=[3], f4=(4, 5, 6)), - "AllIntModifiersNode(f1=1, f2=None, f3=[3], f4=(4, 5, 6))" - ) - ], described=True, prefix="does give correct representation of a ") - - -if __name__ == '__main__': - from py2c.tests import runmodule - - runmodule() diff --git a/py2c/tree/tests/test_init_data_types.py b/py2c/tree/tests/test_init_data_types.py deleted file mode 100644 index 6e92748..0000000 --- a/py2c/tree/tests/test_init_data_types.py +++ /dev/null @@ -1,132 +0,0 @@ -"""Unit-tests for supporting data-types in `py2c.tree` -""" - -from py2c.tree import ( - identifier, WrongAttributeValueError, Node, fields_decorator -) - -from py2c.tests import Test -from nose.tools import ( - assert_raises, assert_equal, assert_is_instance, assert_not_is_instance -) - - -# ----------------------------------------------------------------------------- -# A bunch of nodes used during testing -# ----------------------------------------------------------------------------- -class AllIdentifierModifersNode(Node): - """Node with all modifiers of identifier type - """ - _fields = [ - ('f1', identifier, 'NEEDED'), - ('f2', identifier, 'OPTIONAL'), - ('f3', identifier, 'ZERO_OR_MORE'), - ('f4', identifier, 'ONE_OR_MORE'), - ] - - -# ----------------------------------------------------------------------------- -# Data-Type specific tests -# ----------------------------------------------------------------------------- -class TestIdentifier(Test): - """py2c.tree.identifier - """ - - class_ = identifier - - def check_valid_initialization(self, arg): - obj = self.class_(arg) - assert_equal(obj, arg) - - def test_does_initialize_with_valid_value(self): - yield from self.yield_tests(self.check_valid_initialization, [ - ["valid name", "valid_name"], - ["really long name", "really" * 100 + "_long_name"], - ["short name", "a"], - ], described=True, prefix="does initialize a ") - - def check_invalid_initialization(self, arg): - with assert_raises(WrongAttributeValueError): - self.class_(arg) - - def test_does_not_initialize_with_invalid_value(self): - yield from self.yield_tests(self.check_invalid_initialization, [ - ["with spaces", "invalid name"], - ["with hyphen", "invalid-name"], - ], described=True, prefix="does not initialize an invalid name ") - - def check_instance(self, value, is_): - if is_: - assert_is_instance(value, self.class_) - else: - assert_not_is_instance(value, self.class_) - - def test_is_an_instance(self): - yield from self.yield_tests(self.check_instance, [ - ("a snake_case name", "valid_name", True), - ("a CamelCase name", "ValidName", True), - ("a LOUD_CASE name", "VALID_NAME", True), - ("a valid name with numbers and underscore", "Valid_1_name", True), - ("a valid identifier", identifier("valid_name"), True), - ("an unicode character", "虎", True), # won't ever be used :P - ( - "a name with leading and trailing underscores", - "_valid_name_", True - ), - ("an invalid name with spaces", "invalid name", False), - ("an invalid name with dash", "invalid-name", False), - ("an invalid name with dots", "invalid.attr", False), - ], described=True, prefix="is an identifier instance ") - - def check_subclass(self, value, is_): - if is_: - assert issubclass(value, self.class_) - else: - assert not issubclass(value, self.class_) - - def test_is_a_subclass(self): - class SubClass(identifier): - pass - - yield from self.yield_tests(self.check_subclass, [ - ("str", str, True), - ("an inheriting sub-class", SubClass, True), - ("int (not)", int, False), - ], described=True, prefix="is an identifier subclass ") - - def test_is_equal_to_a_string_with_value_passed(self): - assert_equal(identifier("name"), "name") - - def test_has_representation_that_is_consistent_with_str(self): - assert_equal(repr(identifier("a_name")), "'a_name'") - assert_equal(repr(identifier("some_name")), "'some_name'") - assert_equal(repr(identifier("camelCase")), "'camelCase'") - - -# ----------------------------------------------------------------------------- -# fields_decorator -# ----------------------------------------------------------------------------- -class TestFieldDecorator(Test): - """py2c.tree.fields_decorator - """ - - def test_does_behave_correctly(self): - # MARK:: Should this be broken down into multiple functions? - - class Caller(object): - called = False - - @fields_decorator - def func(cls): - assert cls == Caller - cls.called = True - return 1 - - assert Caller().func == 1 - assert Caller.called - - -if __name__ == '__main__': - from py2c.tests import runmodule - - runmodule() diff --git a/py2c/tree/tests/test_node_gen.py b/py2c/tree/tests/test_node_gen.py index a4e5b03..b3cb55d 100644 --- a/py2c/tree/tests/test_node_gen.py +++ b/py2c/tree/tests/test_node_gen.py @@ -3,350 +3,54 @@ from textwrap import dedent -from py2c.tree.node_gen import ( - Parser, Definition, SourceGenerator, remove_comments, ParserError -) +from py2c.tree import node_gen -from py2c.tests import Test -from nose.tools import assert_equal, assert_in, assert_raises +from py2c.tests import Test, data_driven_test # noqa +from nose.tools import assert_equal, assert_raises + +import py2c.tree.tests.data_node_gen as data # ----------------------------------------------------------------------------- # Tests # ----------------------------------------------------------------------------- -def check_remove_comments(test_string, expected): - assert_equal(remove_comments(test_string), expected) - - -def test_remove_comments(): - "py2c.tree.node_gen.remove_comments" # Don't want a trailing newline - - yield from Test().yield_tests(check_remove_comments, [ - ( - "empty input", - "", - "" - ), - ( - "full line comment without trailing newline", - "#test: [a,string]", - "" - ), - ( - "full line comment with text on next line", - "# test: [a,string]\nsome text!", - "\nsome text!" - ), - ( - "an inline comment", - "foo: [] # test: [a,string]", - "foo: [] " - ), - ( - "an inline comment with a text on next line", - "foo:[] # test\nsome text!", - "foo:[] \nsome text!" - ) - ], described=True, prefix="does remove correct sub-string given ") +@data_driven_test(data.remove_comments_cases, True, "removes comments correctly: ") # noqa +def test_remove_comments(test_string, expected): + "py2c.tree.node_gen.remove_comments" + assert_equal(node_gen.remove_comments(test_string), expected) class TestParser(Test): """py2c.tree.node_gen.Parser """ - parser = Parser() - - def check_property_parsing(self, test_string, expected): - self.parser._reset() - assert_equal(self.parser.parse(dedent(test_string)), tuple(expected)) - def test_does_parse_correctly(self): - yield from self.yield_tests(self.check_property_parsing, [ - ( - "an node without parent and no fields", - "foo", - [Definition('foo', None, [])] - ), - ( - "an node with parent and no fields", - "foo(Base)", - [Definition('foo', 'Base', [])] - ), - ( - "an node without parent with zero fields", - "foo: []", - [Definition('foo', None, [])] - ), - ( - "an empty node with parent", - "foo(AST): []", - [Definition('foo', 'AST', [])] - ), - ( - "a node that inherits, with parent", - "foo(AST): inherit", - [Definition('foo', 'AST', 'inherit')] - ), - ( - "a simple node, without parent", - "foo: [int bar]", - [Definition( - 'foo', - None, - [('bar', 'int', 'NEEDED')], - )] - ), - ( - "a simple node, with parent", - "foo(AST): [int bar]", - [Definition( - 'foo', - 'AST', - [('bar', 'int', 'NEEDED')], - )] - ), - ( - "a node with all modifiers", - "FooBar: [int foo, int+ bar, int* baz, int? spam]", - [ - Definition( - "FooBar", None, - [ - ('foo', 'int', 'NEEDED'), - ('bar', 'int', 'ONE_OR_MORE'), - ('baz', 'int', 'ZERO_OR_MORE'), - ('spam', 'int', 'OPTIONAL'), - ] - ) - ] - ), - ( - "multiple nodes with inheritance", - """ - base1: [int field1] - base2(base1): [int field2] - obj(base2): [] - """, - [ - Definition( - "base1", None, - [("field1", "int", "NEEDED")] - ), - Definition( - "base2", "base1", - [("field2", "int", "NEEDED")] - ), - Definition( - "obj", "base2", - [] - ), - ] - ), - ( - "a node, without bothering about the indentation", - """ - base1: [int field1] - base2(base1): [int field2] - obj(Definition): [] - """, - [ - Definition( - "base1", None, - [("field1", "int", "NEEDED")] - ), - Definition( - "base2", "base1", - [("field2", "int", "NEEDED")] - ), - Definition( - "obj", "Definition", - [] - ), - ] - ) - ], described=True, prefix="does parse correctly ") + @data_driven_test(data.Parser_valid_cases, True, "parses valid input correctly: ") # noqa + def test_valid_cases(self, test_string, expected, _): + parser = node_gen.Parser() - def check_error_reporting(self, test_string, required_words): - with assert_raises(ParserError) as context: - Parser().parse(test_string) - msg = context.exception.args[0].lower() + assert_equal( + parser.parse(dedent(test_string)), + tuple(expected) + ) - for word in required_words: - assert_in(word, msg) + @data_driven_test(data.Parser_invalid_cases, True, "raises error parsing: ") # noqa + def test_invalid_cases(self, test_string, required_phrases): + with assert_raises(node_gen.ParserError) as context: + node_gen.Parser().parse(test_string) - def test_does_report_errors(self): - yield from self.yield_tests(self.check_error_reporting, [ - ( - "multiple attributes with same name", - "foo: [int bar, int bar]", - ["multiple", "attribute", "foo", "bar"] - ), - ( - "multiple declarations of node", - "foo: []\n" * 2, - ["multiple", "declaration", "foo"] - ), - ( - "invalid token", - "$foo: []", - ["token", "unable", "$foo"] - ), - ( - "no data-type", - "foo: [bar, baz]", - ["unexpected", "','"] - ), - ( - "a node that inherits, without parent", - "foo: inherit", - ['inherit', 'need', 'parent', 'foo'] - ), - ], described=True, prefix="does report error given ") + self.assert_error_message_contains(context.exception, required_phrases) class TestSourceGenerator(Test): """py2c.tree.node_gen.SourceGenerator """ - def check_generated_source(self, data, expected_output): - src_gen = SourceGenerator() - generated = src_gen.generate_sources(data) - - assert_equal( - dedent(expected_output).strip(), generated.strip() - ) - - def test_code_generation(self): - """Tests SourceGenerator's code-generation - """ - yield from self.yield_tests(self.check_generated_source, [ - ( - "without fields, without parent", - [Definition('FooBar', None, [])], - """ - class FooBar(object): - @fields_decorator - def _fields(cls): - return [] - """ - ), - ( - "without fields, with parent", - [Definition('FooBar', 'AST', [])], - """ - class FooBar(AST): - @fields_decorator - def _fields(cls): - return [] - """ - ), - ( - "inheriting fields from parent", - [Definition('FooBar', 'AST', 'inherit')], - """ - class FooBar(AST): - pass - """ - ), - ( - "with fields, without parent", - [Definition( - 'FooBar', - None, - [('bar', 'int', 'NEEDED')], - )], - """ - class FooBar(object): - @fields_decorator - def _fields(cls): - return [ - ('bar', int, 'NEEDED'), - ] - """ - ), - ( - "with fields, with parent", - [Definition( - 'FooBar', - 'AST', - [('bar', 'int', 'NEEDED')], - )], - """ - class FooBar(AST): - @fields_decorator - def _fields(cls): - return [ - ('bar', int, 'NEEDED'), - ] - """ - ), - ( - "with fields, with parent, with all modifiers", - [Definition( - 'FooBar', - 'AST', - [ - ('foo', 'int', 'NEEDED'), - ('bar', 'int', 'ONE_OR_MORE'), - ('baz', 'int', 'ZERO_OR_MORE'), - ('spam', 'int', 'OPTIONAL'), - ] - )], - """ - class FooBar(AST): - @fields_decorator - def _fields(cls): - return [ - ('foo', int, 'NEEDED'), - ('bar', int, 'ONE_OR_MORE'), - ('baz', int, 'ZERO_OR_MORE'), - ('spam', int, 'OPTIONAL'), - ] - """ - ), - ( - "with multiple fields", - [ - Definition( - 'base1', - None, - [('field1', 'int', 'NEEDED')], - ), - Definition( - 'base2', - 'base1', - [('field2', 'int', 'NEEDED')], - ), - Definition( - 'obj', - 'base2', - [], - ), - ], - """ - class base1(object): - @fields_decorator - def _fields(cls): - return [ - ('field1', int, 'NEEDED'), - ] - - - class base2(base1): - @fields_decorator - def _fields(cls): - return [ - ('field2', int, 'NEEDED'), - ] - - - class obj(base2): - @fields_decorator - def _fields(cls): - return [] - """ - ) - ], described=True, prefix="does generate correct code for a node ") + @data_driven_test(data.SourceGenerator_valid_cases, True, "generates code correctly: ") # noqa + def test_valid_cases(self, _, definitions, expected): + src_gen = node_gen.SourceGenerator() + generated = src_gen.generate_sources(definitions) + assert_equal(dedent(expected).strip(), generated.strip()) if __name__ == '__main__': from py2c.tests import runmodule diff --git a/py2c/tree/tests/test_visitors.py b/py2c/tree/tests/test_visitors.py index f01c31b..4b0526b 100644 --- a/py2c/tree/tests/test_visitors.py +++ b/py2c/tree/tests/test_visitors.py @@ -1,57 +1,10 @@ """Unit-tests for `tree.visitors` """ -from py2c.tree import Node -from py2c.tree.visitors import RecursiveNodeVisitor, RecursiveNodeTransformer - - -from py2c.tests import Test +from py2c.tests import Test, data_driven_test from nose.tools import assert_equal - -# ----------------------------------------------------------------------------- -# Test helpers -# ----------------------------------------------------------------------------- -class BasicNode(Node): - """Basic node - """ - _fields = [] - - -class BasicNodeCopy(Node): - """Equivalent but not equal to BasicNode - """ - _fields = [] - - -class ParentNode(Node): - """Node with another node as child - """ - _fields = [ - ('child', Node, 'NEEDED'), - ] - - -class ParentNodeWithChildrenList(Node): - """Node with another node as child - """ - _fields = [ - ('child', Node, 'ONE_OR_MORE'), - ] - - -class SimpleVisitor(RecursiveNodeVisitor): - - def __init__(self): - super().__init__() - self.visited = [] - - def generic_visit(self, node): - self.visited.append(node.__class__.__name__) - super().generic_visit(node) - - def visit_BasicNodeCopy(self, node): - self.visited.append("") +import py2c.tree.tests.data_visitors as data # ----------------------------------------------------------------------------- @@ -61,50 +14,13 @@ class TestRecursiveASTVisitor(Test): """py2c.tree.visitors.RecursiveNodeVisitor """ - def check_visit(self, node, expected_visited): - visitor = SimpleVisitor() + @data_driven_test(data.RecursiveNodeVisitor_visit_order_cases, True, "visits in correct order: ") # noqa + def test_visit_order(self, node, expected_visited): + visitor = data.SimpleVisitor() retval = visitor.visit(node) assert_equal(retval, None) assert_equal(visitor.visited, expected_visited) - def test_visit_and_generic_visit(self): - yield from self.yield_tests(self.check_visit, [ - ( - "a node without children", - BasicNode(), ["BasicNode"] - ), - ( - "a node without children (calls overidden method)", - BasicNodeCopy(), [""] - ), - ( - "a node with children", - ParentNode(BasicNode()), ["ParentNode", "BasicNode"] - ), - ( - "a node with children (calls overidden method)", - ParentNode(BasicNodeCopy()), ["ParentNode", ""] - ), - ( - "a node with grand children", - ParentNode(ParentNode(BasicNode())), - ["ParentNode", "ParentNode", "BasicNode"] - ), - ( - "a node with grand children (calls overidden method)", - ParentNode(ParentNode(BasicNodeCopy())), - ["ParentNode", "ParentNode", ""] - ), - ( - "a node with list of children with grand children " - "(calls overidden method)", - ParentNodeWithChildrenList( - [ParentNode(BasicNode()), BasicNodeCopy()] - ), - ["ParentNodeWithChildrenList", "ParentNode", "BasicNode", ""] - ), - ], described=True, prefix="does visit in correct order ") - class TestRecursiveASTTransformer(Test): """py2c.tree.visitors.RecursiveNodeTransformer