From 19913a28d1a7633522a7e0fb83f31b77c93df4d2 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Sat, 31 Aug 2019 13:12:40 -0600 Subject: [PATCH] Gut factories and example structure code --- src/spectator/dsl/example_factory.cr | 15 --- src/spectator/dsl/example_group_builder.cr | 87 ---------------- .../dsl/nested_example_group_builder.cr | 38 ------- .../dsl/root_example_group_builder.cr | 18 ---- .../dsl/sample_example_group_builder.cr | 73 -------------- src/spectator/dsl/structure_dsl.cr | 99 +------------------ 6 files changed, 2 insertions(+), 328 deletions(-) delete mode 100644 src/spectator/dsl/example_factory.cr delete mode 100644 src/spectator/dsl/example_group_builder.cr delete mode 100644 src/spectator/dsl/nested_example_group_builder.cr delete mode 100644 src/spectator/dsl/root_example_group_builder.cr delete mode 100644 src/spectator/dsl/sample_example_group_builder.cr diff --git a/src/spectator/dsl/example_factory.cr b/src/spectator/dsl/example_factory.cr deleted file mode 100644 index 6271683..0000000 --- a/src/spectator/dsl/example_factory.cr +++ /dev/null @@ -1,15 +0,0 @@ -module Spectator::DSL - # Creates instances of examples from a specified class. - class ExampleFactory - # Creates the factory. - # The type passed to this constructor must be a sub-type of `Example`. - def initialize(@example_type : Example.class) - end - - # Constructs a new example instance and returns it. - # The *group* is passed to `Example#initialize`. - def build(group : ExampleGroup, _sample_values : Internals::SampleValues) : Example - @example_type.new(group) - end - end -end diff --git a/src/spectator/dsl/example_group_builder.cr b/src/spectator/dsl/example_group_builder.cr deleted file mode 100644 index cc4e662..0000000 --- a/src/spectator/dsl/example_group_builder.cr +++ /dev/null @@ -1,87 +0,0 @@ -module Spectator::DSL - # Base class for building all example groups. - abstract class ExampleGroupBuilder - # Type alias for valid children of example groups. - # NOTE: `NestedExampleGroupBuilder` is used instead of `ExampleGroupBuilder`. - # That is because `RootExampleGroupBuilder` also inherits from this class, - # and the root example group can't be a child. - alias Child = ExampleFactory | NestedExampleGroupBuilder - - # Factories and builders for all examples and groups. - @children = [] of Child - - # Hooks added to the group so far. - @before_all_hooks = [] of -> - @before_each_hooks = [] of -> - @after_all_hooks = [] of -> - @after_each_hooks = [] of -> - @around_each_hooks = [] of Proc(Nil) -> - - # Pre and post conditions so far. - @pre_conditions = [] of -> - @post_conditions = [] of -> - - # Adds a new example factory or group builder to this group. - def add_child(child : Child) - @children << child - end - - # Adds a hook to run before all examples (and nested examples) in this group. - def add_before_all_hook(block : ->) : Nil - @before_all_hooks << block - end - - # Adds a hook to run before each example (and nested example) in this group. - def add_before_each_hook(block : ->) : Nil - @before_each_hooks << block - end - - # Adds a hook to run after all examples (and nested examples) in this group. - def add_after_all_hook(block : ->) : Nil - @after_all_hooks << block - end - - # Adds a hook to run after each example (and nested example) in this group. - def add_after_each_hook(block : ->) : Nil - @after_each_hooks << block - end - - # Adds a hook to run around each example (and nested example) in this group. - # The block of code will be given another proc as an argument. - # It is expected that the block will call the proc. - def add_around_each_hook(block : Proc(Nil) ->) : Nil - @around_each_hooks << block - end - - # Adds a pre-condition to run at the start of every example in this group. - def add_pre_condition(block : ->) : Nil - @pre_conditions << block - end - - # Adds a post-condition to run at the end of every example in this group. - def add_post_condition(block : ->) : Nil - @post_conditions << block - end - - # Constructs an `ExampleHooks` instance with all the hooks defined for this group. - # This method should be called only when the group is being built, - # otherwise some hooks may be missing. - private def hooks - ExampleHooks.new( - @before_all_hooks, - @before_each_hooks, - @after_all_hooks, - @after_each_hooks, - @around_each_hooks - ) - end - - # Constructs an `ExampleConditions` instance - # with all the pre- and post-conditions defined for this group. - # This method should be called only when the group is being built, - # otherwise some conditions may be missing. - private def conditions - ExampleConditions.new(@pre_conditions, @post_conditions) - end - end -end diff --git a/src/spectator/dsl/nested_example_group_builder.cr b/src/spectator/dsl/nested_example_group_builder.cr deleted file mode 100644 index efee6d1..0000000 --- a/src/spectator/dsl/nested_example_group_builder.cr +++ /dev/null @@ -1,38 +0,0 @@ -module Spectator::DSL - # Standard example group builder. - # Creates groups of examples and nested groups. - class NestedExampleGroupBuilder < ExampleGroupBuilder - # Creates a new group builder. - # The value for *what* should be the context for the group. - # - # For example, in these samples: - # ``` - # describe String do - # # ... - # context "with an empty string" do - # # ... - # end - # end - # ``` - # The value would be "String" for the describe block - # and "with an empty string" for the context block. - # Use a `Symbol` when referencing a type name. - def initialize(@what : Symbol | String) - end - - # Builds the example group. - # A new `NestedExampleGroup` will be returned - # which can have instances of `Example` and `ExampleGroup` nested in it. - # The *parent* should be the group that contains this group. - # The *sample_values* will be given to all of the examples (and groups) nested in this group. - def build(parent : ExampleGroup, sample_values : Internals::SampleValues) : NestedExampleGroup - NestedExampleGroup.new(@what, parent, hooks, conditions, sample_values).tap do |group| - # Set the group's children to built versions of the children from this instance. - group.children = @children.map do |child| - # Build the child and up-cast to prevent type errors. - child.build(group, sample_values).as(ExampleComponent) - end - end - end - end -end diff --git a/src/spectator/dsl/root_example_group_builder.cr b/src/spectator/dsl/root_example_group_builder.cr deleted file mode 100644 index 03b13fb..0000000 --- a/src/spectator/dsl/root_example_group_builder.cr +++ /dev/null @@ -1,18 +0,0 @@ -module Spectator::DSL - # Top-level example group builder. - # There should only be one instance of this class, - # and it should be at the top of the spec "tree". - class RootExampleGroupBuilder < ExampleGroupBuilder - # Creates a `RootExampleGroup` which can have instances of `Example` and `ExampleGroup` nested in it. - # The *sample_values* will be given to all of the examples (and groups) nested in this group. - def build(sample_values : Internals::SampleValues) : RootExampleGroup - RootExampleGroup.new(hooks, conditions, sample_values).tap do |group| - # Set the group's children to built versions of the children from this instance. - group.children = @children.map do |child| - # Build the child and up-cast to prevent type errors. - child.build(group, sample_values).as(ExampleComponent) - end - end - end - end -end diff --git a/src/spectator/dsl/sample_example_group_builder.cr b/src/spectator/dsl/sample_example_group_builder.cr deleted file mode 100644 index 9f1e144..0000000 --- a/src/spectator/dsl/sample_example_group_builder.cr +++ /dev/null @@ -1,73 +0,0 @@ -require "./nested_example_group_builder" - -module Spectator::DSL - # Specialized example group builder for "sample" groups. - # The type parameter `C` is the type to instantiate to create the collection. - # The type parameter `T` should be the type of each element in the sample collection. - # This builder creates a container group with groups inside for each item in the collection. - # The hooks are only defined for the container group. - # By doing so, the hooks are defined once, are inherited, and use less memory. - class SampleExampleGroupBuilder(C, T) < NestedExampleGroupBuilder - # Creates a new group builder. - # The value for *what* should be the text the user specified for the collection. - # The *collection_type* is the type to create that will produce the items. - # The *collection_builder* is a proc that takes an instance of *collection_type* - # and returns an actual array of items to create examples for. - # The *name* is the variable name that the user accesses the current collection item with. - # - # In this code: - # ``` - # sample random_integers do |integer| - # # ... - # end - # ``` - # The *what* would be "random_integers" - # and the collection would contain the items returned by calling *random_integers*. - # The *name* would be "integer". - # - # The *symbol* is passed along to the sample values - # so that the example code can retrieve the current item from the collection. - # The symbol should be unique. - def initialize(what : String, @collection_type : C.class, @collection_builder : C -> Array(T), - @name : String, @symbol : Symbol) - super(what) - end - - # Builds the example group. - # A new `NestedExampleGroup` will be returned - # which can have instances of `Example` and `ExampleGroup` nested in it. - # The *parent* should be the group that contains this group. - # The *sample_values* will be given to all of the examples (and groups) nested in this group. - def build(parent : ExampleGroup, sample_values : Internals::SampleValues) : NestedExampleGroup - collection = @collection_builder.call(@collection_type.new) - - # This creates the container for the sub-groups. - # The hooks are defined here, instead of repeating for each sub-group. - NestedExampleGroup.new(@what, parent, hooks, conditions, sample_values).tap do |group| - # Set the container group's children to be sub-groups for each item in the collection. - group.children = collection.map do |value| - # Create a sub-group for each item in the collection. - build_sub_group(group, sample_values, value).as(ExampleComponent) - end - end - end - - # Builds a sub-group for one item in the collection. - # The *parent* should be the container group currently being built by the `#build` call. - # The *sample_values* should be the same as what was passed to the `#build` call. - # The *value* is the current item in the collection. - # The value will be added to the sample values for the sub-group, - # so it shouldn't be added prior to calling this method. - private def build_sub_group(parent : ExampleGroup, sample_values : Internals::SampleValues, value : T) : NestedExampleGroup - # Add the value to sample values for this sub-group. - sub_values = sample_values.add(@symbol, @name, value) - NestedExampleGroup.new(value.to_s, parent, ExampleHooks.empty, ExampleConditions.empty, sub_values).tap do |group| - # Set the sub-group's children to built versions of the children from this instance. - group.children = @children.map do |child| - # Build the child and up-cast to prevent type errors. - child.build(group, sub_values).as(ExampleComponent) - end - end - end - end -end diff --git a/src/spectator/dsl/structure_dsl.cr b/src/spectator/dsl/structure_dsl.cr index a43a378..dce58d4 100644 --- a/src/spectator/dsl/structure_dsl.cr +++ b/src/spectator/dsl/structure_dsl.cr @@ -1426,21 +1426,7 @@ module Spectator::DSL _spectator_test(Test%example, %run) {{block}} {% end %} - # Create a class derived from `RunnableExample` to run the test code. - _spectator_example(Example%example, Test%example, ::Spectator::RunnableExample, {{what}}) do - # Source where the example originated from. - def source - ::Spectator::Source.new({{_source_file}}, {{_source_line}}) - end - - # Implement abstract method to run the wrapped example block. - protected def run_instance - @instance.%run - end - end - - # Add the example to the current group. - ::Spectator::DSL::Builder.add_example(Example%example) + # TODO end # Creates an example, or a test case. @@ -1500,19 +1486,7 @@ module Spectator::DSL # By creating a `#pending` test, the code will be referenced. # Thus, forcing the compiler to at least process the code, even if it isn't run. macro pending(what, _source_file = __FILE__, _source_line = __LINE__, &block) - # Create the wrapper class for the test code. - _spectator_test(Test%example, %run) {{block}} - - # Create a class derived from `PendingExample` to skip the test code. - _spectator_example(Example%example, Test%example, ::Spectator::PendingExample, {{what}}) do - # Source where the example originated from. - def source - ::Spectator::Source.new({{_source_file}}, {{_source_line}}) - end - end - - # Add the example to the current group. - ::Spectator::DSL::Builder.add_example(Example%example) + # TODO end # Creates an example, or a test case, that does not run. @@ -1553,74 +1527,5 @@ module Spectator::DSL macro xit(&block) pending({{block.body.stringify}}) {{block}} end - - # Creates a wrapper class for test code. - # The class serves multiple purposes, mostly dealing with scope. - # 1. Include the parent modules as mix-ins. - # 2. Enable DSL specific to examples. - # 3. Isolate methods in `Example` from the test code. - # - # Since the names are generated, and macros can't return values, - # the names for everything must be passed in as arguments. - # The *class_name* argument is the name of the class to define. - # The *run_method_name* argument is the name of the method in the wrapper class - # that will actually run the test code. - # The block passed to this macro is the actual test code. - private macro _spectator_test(class_name, run_method_name) - # Wrapper class for isolating the test code. - class {{class_name.id}} < {{@type.id}} - # Generated method for actually running the test code. - def {{run_method_name.id}} - {{yield}} - end - end - end - - # Creates an example class. - # Since the names are generated, and macros can't return values, - # the names for everything must be passed in as arguments. - # The *example_class_name* argument is the name of the class to define. - # The *test_class_name* argument is the name of the wrapper class to reference. - # This must be the same as `class_name` for `#_spectator_example_wrapper`. - # The *base_class* argument specifies which type of example class the new class should derive from. - # This should typically be `RunnableExample` or `PendingExample`. - # The *what* argument is the description passed to the `#it` or `#pending` block. - # And lastly, the block specified is additional content to put in the class. - # For instance, to define a method in the class, do it in the block. - # ``` - # _spectator_example(Example123, Test123, RunnableExample, "does something") do - # def something - # # This method is defined in the Example123 class. - # end - # end - # ``` - private macro _spectator_example(example_class_name, test_class_name, base_class, what, &block) - # Example class containing meta information and instructions for running the test. - class {{example_class_name.id}} < {{base_class.id}} - # Stores the group the example belongs to - # and sample values specific to this instance of the test. - # This method's signature must match the one used in `ExampleFactory#build`. - def initialize(group : ::Spectator::ExampleGroup) - super - @instance = {{test_class_name.id}}.new - end - - # Retrieves the underlying, wrapped test code. - getter instance - - # Indicates whether the example references a method. - def symbolic? - {{what.is_a?(StringLiteral) && what.starts_with?('#') ? true : false}} - end - - # Add the block's content. - {{block.body}} - - # Description for the test. - def what - {{what.is_a?(StringLiteral) ? what : what.stringify}} - end - end - end end end