# Path: nimoy/ast_tools/specs.py
# class SpecTransformer(ast.NodeTransformer):
#     def __init__(self, runner_context: RunnerContext, spec_location, spec_metadata: List) -> None:
#         super().__init__()
#         self.runner_context = runner_context
#         self.spec_location = spec_location
#         self.spec_metadata = spec_metadata
# 
#     def visit_ClassDef(self, class_node):
# 
#         class_is_spec = class_node.name.endswith('Spec')
# 
#         if class_is_spec:
#             has_spec_name = hasattr(self.spec_location, 'spec_name')
#             if not has_spec_name or (has_spec_name and self.spec_location.spec_name == class_node.name):
# 
#                 metadata = SpecMetadata(class_node.name)
#                 self._register_spec(metadata)
#                 FeatureRegistrationTransformer(self.runner_context, self.spec_location, metadata).visit(class_node)
# 
#                 for feature_name in metadata.where_functions:
#                     feature = next(
#                         feature for feature in class_node.body if
#                         hasattr(feature, 'name') and feature_name == feature.name)
#                     index_of_feature = class_node.body.index(feature)
# 
#                     index_to_insert_where = index_of_feature + 1
#                     where_function_to_insert = metadata.where_functions[feature_name]
# 
#                     if (index_to_insert_where + 1) > len(class_node.body):
#                         class_node.body.append(where_function_to_insert)
#                     else:
#                         class_node.body.insert(index_to_insert_where, where_function_to_insert)
# 
#         return class_node
# 
#     def _register_spec(self, metadata):
#         self.spec_metadata.append(metadata)
# 
# Path: nimoy/runner/metadata.py
# class RunnerContext:
# 
#     def __init__(self, use_power_assertions: bool = False):
#         self.use_power_assertions = use_power_assertions
# 
# Path: nimoy/runner/spec_finder.py
# class Location:
#     def __init__(self, suggested_location):
# 
#         # Format may be:
#         # - some_spec.py
#         # - some_spec.py::SpecName
#         # - some_spec.py::feature_name
#         # - some_spec.py::SpecName::feature_name
#         split_suggested_location = suggested_location.split("::")
# 
#         # some_spec.py
#         if len(split_suggested_location) == 1:
#             self.spec_path = split_suggested_location[0]
# 
#         # some_spec.py::SpecName or some_spec.py::feature_name
#         if len(split_suggested_location) == 2:
#             self.spec_path = split_suggested_location[0]
# 
#             # some_spec.py::SpecName
#             if split_suggested_location[1][0].isupper():
#                 self.spec_name = split_suggested_location[1]
# 
#             # some_spec.py::feature_name
#             else:
#                 self.feature_name = split_suggested_location[1]
# 
#         # some_spec.py::SpecName::feature_name
#         if len(split_suggested_location) == 3:
#             self.spec_path = split_suggested_location[0]
#             self.spec_name = split_suggested_location[1]
#             self.feature_name = split_suggested_location[2]
# 
# Path: nimoy/specification.py
# class Specification(TestCase, metaclass=DataDrivenSpecification):
# 
#     def __init__(self, methodName='runTest'):
#         super().__init__(methodName)
#         self.thrown_exceptions = []
# 
#     def _feature_block_context(self, block_name):
#         return FeatureBlock(block_name, self.thrown_exceptions)
# 
#     def _compare(self, left, right, comparison_type_name):
#         Compare().compare(left, right, comparison_type_name)
# 
#     def _power_assert(self, expression: Dict):
#         PowerAssertions().assert_and_raise(expression)
# 
#     def _assert_mock(self, number_of_invocations, mock, method, *args):
#         MockAssertions().assert_mock(number_of_invocations, mock, method, *args)
# 
#     def _exception_thrown(self, expected_exception_type):
#         return ExceptionAssertions().assert_exception(self.thrown_exceptions, expected_exception_type)


import ast
from unittest import mock
from nimoy.ast_tools.specs import SpecTransformer
from nimoy.runner.metadata import RunnerContext
from nimoy.runner.spec_finder import Location
from nimoy.specification import Specification
        node = ast.parse(spec_definition, mode='exec')
        found_metadata = []

        with when:
            SpecTransformer(RunnerContext(), Location('some_spec.py'), found_metadata).visit(node)

        with then:
            len(found_metadata) == 2
            found_metadata[0].name == 'JimbobSpec'
            found_metadata[1].name == 'JonesSpec'

            feature_registration_transformer.expect_called_once()
            2 * feature_registration_transformer.return_value.visit()

    @mock.patch('nimoy.ast_tools.specs.FeatureRegistrationTransformer')
    def find_explicit_spec_in_module(self, feature_registration_transformer):
        with setup:
            spec_definition = """from nimoy.specification import Specification

class JimbobSpec(Specification):
    pass

class JonesSpec(Specification):
    pass
            """

        node = ast.parse(spec_definition, mode='exec')
        found_metadata = []

        with when:

# Path: nimoy/ast_tools/specs.py
# class SpecTransformer(ast.NodeTransformer):
#     def __init__(self, runner_context: RunnerContext, spec_location, spec_metadata: List) -> None:
#         super().__init__()
#         self.runner_context = runner_context
#         self.spec_location = spec_location
#         self.spec_metadata = spec_metadata
# 
#     def visit_ClassDef(self, class_node):
# 
#         class_is_spec = class_node.name.endswith('Spec')
# 
#         if class_is_spec:
#             has_spec_name = hasattr(self.spec_location, 'spec_name')
#             if not has_spec_name or (has_spec_name and self.spec_location.spec_name == class_node.name):
# 
#                 metadata = SpecMetadata(class_node.name)
#                 self._register_spec(metadata)
#                 FeatureRegistrationTransformer(self.runner_context, self.spec_location, metadata).visit(class_node)
# 
#                 for feature_name in metadata.where_functions:
#                     feature = next(
#                         feature for feature in class_node.body if
#                         hasattr(feature, 'name') and feature_name == feature.name)
#                     index_of_feature = class_node.body.index(feature)
# 
#                     index_to_insert_where = index_of_feature + 1
#                     where_function_to_insert = metadata.where_functions[feature_name]
# 
#                     if (index_to_insert_where + 1) > len(class_node.body):
#                         class_node.body.append(where_function_to_insert)
#                     else:
#                         class_node.body.insert(index_to_insert_where, where_function_to_insert)
# 
#         return class_node
# 
#     def _register_spec(self, metadata):
#         self.spec_metadata.append(metadata)
# 
# Path: nimoy/runner/metadata.py
# class RunnerContext:
# 
#     def __init__(self, use_power_assertions: bool = False):
#         self.use_power_assertions = use_power_assertions
# 
# Path: nimoy/runner/spec_finder.py
# class Location:
#     def __init__(self, suggested_location):
# 
#         # Format may be:
#         # - some_spec.py
#         # - some_spec.py::SpecName
#         # - some_spec.py::feature_name
#         # - some_spec.py::SpecName::feature_name
#         split_suggested_location = suggested_location.split("::")
# 
#         # some_spec.py
#         if len(split_suggested_location) == 1:
#             self.spec_path = split_suggested_location[0]
# 
#         # some_spec.py::SpecName or some_spec.py::feature_name
#         if len(split_suggested_location) == 2:
#             self.spec_path = split_suggested_location[0]
# 
#             # some_spec.py::SpecName
#             if split_suggested_location[1][0].isupper():
#                 self.spec_name = split_suggested_location[1]
# 
#             # some_spec.py::feature_name
#             else:
#                 self.feature_name = split_suggested_location[1]
# 
#         # some_spec.py::SpecName::feature_name
#         if len(split_suggested_location) == 3:
#             self.spec_path = split_suggested_location[0]
#             self.spec_name = split_suggested_location[1]
#             self.feature_name = split_suggested_location[2]
# 
# Path: nimoy/specification.py
# class Specification(TestCase, metaclass=DataDrivenSpecification):
# 
#     def __init__(self, methodName='runTest'):
#         super().__init__(methodName)
#         self.thrown_exceptions = []
# 
#     def _feature_block_context(self, block_name):
#         return FeatureBlock(block_name, self.thrown_exceptions)
# 
#     def _compare(self, left, right, comparison_type_name):
#         Compare().compare(left, right, comparison_type_name)
# 
#     def _power_assert(self, expression: Dict):
#         PowerAssertions().assert_and_raise(expression)
# 
#     def _assert_mock(self, number_of_invocations, mock, method, *args):
#         MockAssertions().assert_mock(number_of_invocations, mock, method, *args)
# 
#     def _exception_thrown(self, expected_exception_type):
#         return ExceptionAssertions().assert_exception(self.thrown_exceptions, expected_exception_type)


# Path: specs/nimoy/ast_tools/specs_spec.py
import ast
from unittest import mock
from nimoy.ast_tools.specs import SpecTransformer
from nimoy.runner.metadata import RunnerContext
from nimoy.runner.spec_finder import Location
from nimoy.specification import Specification

        node = ast.parse(spec_definition, mode='exec')
        found_metadata = []

        with when:
            SpecTransformer(RunnerContext(), Location('some_spec.py'), found_metadata).visit(node)

        with then:
            len(found_metadata) == 2
            found_metadata[0].name == 'JimbobSpec'
            found_metadata[1].name == 'JonesSpec'

            feature_registration_transformer.expect_called_once()
            2 * feature_registration_transformer.return_value.visit()

    @mock.patch('nimoy.ast_tools.specs.FeatureRegistrationTransformer')
    def find_explicit_spec_in_module(self, feature_registration_transformer):
        with setup:
            spec_definition = """from nimoy.specification import Specification

class JimbobSpec(Specification):
    pass

class JonesSpec(Specification):
    pass
            """

        node = ast.parse(spec_definition, mode='exec')
        found_metadata = []

        with when:

            SpecTransformer(RunnerContext(), Location('some_spec.py::JonesSpec'), found_metadata).visit(node)