From fbf574b0b94e27c52bc4a47dd76ff8a51436a1e8 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Sat, 5 Sep 2020 16:47:40 -0600 Subject: [PATCH] Create ExampleGroup and use shared ExampleNode type --- src/spectator/example_base.cr | 66 ++-------------------------------- src/spectator/example_group.cr | 38 ++++++++++++++++++++ src/spectator/example_node.cr | 64 +++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 64 deletions(-) create mode 100644 src/spectator/example_group.cr create mode 100644 src/spectator/example_node.cr diff --git a/src/spectator/example_base.cr b/src/spectator/example_base.cr index ef2a23b..0944cbb 100644 --- a/src/spectator/example_base.cr +++ b/src/spectator/example_base.cr @@ -1,52 +1,9 @@ -require "./example_group" +require "./example_node" require "./result" -require "./source" module Spectator # Common base type for all examples. - abstract class ExampleBase - # Location of the example in source code. - getter! source : Source - - # User-provided name or description of the test. - # This does not include the group name or descriptions. - # Use `#to_s` to get the full name. - # - # This value will be nil if no name was provided. - # In this case, the name should be set - # to the description of the first matcher that runs in the example. - # - # If this value is a `Symbol`, the user specified a type for the name. - getter! name : String | Symbol - - # Group the example belongs to. - # Hooks are used from this group. - getter! group : ExampleGroup - - # Assigns the group the example belongs to. - # If the example already belongs to a group, - # it will be removed from the previous group before adding it to the new group. - def group=(group : ExampleGroup?) - if (previous = @group) - previous.remove_example(self) - end - group.add_example(self) if group - @group = group - end - - # Creates the base of the example. - # The *name* describes the purpose of the example. - # It can be a `Symbol` to describe a type. - # The *source* tracks where the example exists in source code. - # The example will be assigned to *group* if it is provided. - def initialize(@name : String | Symbol? = nil, @source : Source? = nil, group : ExampleGroup? = nil) - # Ensure group is linked. - self.group = group - end - - # Indicates whether the example already ran. - abstract def finished? : Bool - + abstract class ExampleBase < ExampleNode # Retrieves the result of the last time the example ran. # This will be nil if the example hasn't run, # and should not be nil if it has. @@ -58,25 +15,6 @@ module Spectator result? || raise(NilAssertionError("Example has no result")) end - # Constructs the full name or description of the example. - # This prepends names of groups this example is part of. - def to_s(io) - name = @name - - # Prefix with group's full name if the example belongs to a group. - if (group = @group) - group.to_s(io) - - # Add padding between the group name and example name, - # only if the names appear to be symbolic. - if group.name.is_a?(Symbol) && name.is_a?(String) - io << ' ' unless name.starts_with?('#') || name.starts_with?('.') - end - end - - name.to_s(io) - end - # Exposes information about the example useful for debugging. def inspect(io) raise NotImplementedError.new("#inspect") diff --git a/src/spectator/example_group.cr b/src/spectator/example_group.cr new file mode 100644 index 0000000..01b64ce --- /dev/null +++ b/src/spectator/example_group.cr @@ -0,0 +1,38 @@ +module Spectator + # Collection of examples and sub-groups. + class ExampleGroup < ExampleNode + include Enumerable(ExampleNode) + + @nodes = [] of ExampleNode + + # Removes the specified *node* from the group. + # The node will be unassigned from this group. + def delete(node : ExampleNode) + # Only remove from the group if it is associated with this group. + return unless node.group == self + + node.group = nil + @nodes.delete(node) + end + + # Yields each node (example and sub-group). + def each + @nodes.each { |node| yield node } + end + + # Adds the specified *node* to the group. + # Assigns the node to this group. + # If the node already belongs to a group, + # it will be removed from the previous group before adding it to this group. + def <<(node : ExampleNode) + # Remove from existing group if the node is part of one. + if (previous = node.group?) + previous.delete(node) + end + + # Add the node to this group and associate with it. + @nodes << node + node.group = self + end + end +end diff --git a/src/spectator/example_node.cr b/src/spectator/example_node.cr new file mode 100644 index 0000000..5baaae1 --- /dev/null +++ b/src/spectator/example_node.cr @@ -0,0 +1,64 @@ +require "./source" + +module Spectator + # A single example or collection (group) of examples in an example tree. + abstract class ExampleNode + # Location of the node in source code. + getter! source : Source + + # User-provided name or description of the test. + # This does not include the group name or descriptions. + # Use `#to_s` to get the full name. + # + # This value will be nil if no name was provided. + # In this case, and the node is a runnable example, + # the name should be set to the description + # of the first matcher that runs in the test case. + # + # If this value is a `Symbol`, the user specified a type for the name. + getter! name : String | Symbol + + # Updates the name of the node. + protected def name=(@name : String) + end + + # Group the node belongs to. + getter! group : ExampleGroup + + # Assigns the node to the specified *group*. + # This is an internal method and should only be called from `ExampleGroup`. + # `ExampleGroup` manages the association of nodes to groups. + protected setter group : ExampleGroup? + + # Creates the node. + # The *name* describes the purpose of the node. + # It can be a `Symbol` to describe a type. + # The *source* tracks where the node exists in source code. + # The node will be assigned to *group* if it is provided. + def initialize(@name : String | Symbol? = nil, @source : Source? = nil, group : ExampleGroup? = nil) + # Ensure group is linked. + group << self if group + end + + # Indicates whether the node has completed. + abstract def finished? : Bool + + # Constructs the full name or description of the node. + # This prepends names of groups this node is part of. + def to_s(io) + name = @name + + # Prefix with group's full name if the node belongs to a group. + if (group = @group) + io << group + + # Add padding between the node names + # only if the names don't appear to be symbolic. + io << ' ' unless group.name.is_a?(Symbol) && name.is_a?(String) && + (name.starts_with?('#') || name.starts_with?('.')) + end + + io << name + end + end +end