Create ExampleGroup and use shared ExampleNode type

This commit is contained in:
Michael Miller 2020-09-05 16:47:40 -06:00
parent 0f9c1ad09c
commit fbf574b0b9
No known key found for this signature in database
GPG key ID: FB9F12F7C646A4AD
3 changed files with 104 additions and 64 deletions

View file

@ -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")

View file

@ -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

View file

@ -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