mirror of
https://gitea.invidious.io/iv-org/shard-spectator.git
synced 2024-08-15 00:53:35 +00:00
Merge branch 'release/0.10' into reporting
This commit is contained in:
commit
e8848d6855
7 changed files with 211 additions and 154 deletions
|
@ -65,7 +65,7 @@ module Spectator::DSL
|
||||||
# Inserts the correct representation of a example's name.
|
# Inserts the correct representation of a example's name.
|
||||||
# If *what* is a string, then it is dropped in as-is.
|
# If *what* is a string, then it is dropped in as-is.
|
||||||
# For anything else, it is stringified.
|
# For anything else, it is stringified.
|
||||||
# This is intended to be used to convert a description from the spec DSL to `Spec::Node#name`.
|
# This is intended to be used to convert a description from the spec DSL to `Node#name`.
|
||||||
private macro _spectator_example_name(what)
|
private macro _spectator_example_name(what)
|
||||||
{% if what.is_a?(StringLiteral) ||
|
{% if what.is_a?(StringLiteral) ||
|
||||||
what.is_a?(StringInterpolation) ||
|
what.is_a?(StringInterpolation) ||
|
||||||
|
|
|
@ -56,7 +56,7 @@ module Spectator::DSL
|
||||||
# If *what* appears to be a type name, it will be symbolized.
|
# If *what* appears to be a type name, it will be symbolized.
|
||||||
# If it's a string, then it is dropped in as-is.
|
# If it's a string, then it is dropped in as-is.
|
||||||
# For anything else, it is stringified.
|
# For anything else, it is stringified.
|
||||||
# This is intended to be used to convert a description from the spec DSL to `Spec::Node#name`.
|
# This is intended to be used to convert a description from the spec DSL to `Node#name`.
|
||||||
private macro _spectator_group_name(what)
|
private macro _spectator_group_name(what)
|
||||||
{% if (what.is_a?(Generic) ||
|
{% if (what.is_a?(Generic) ||
|
||||||
what.is_a?(Path) ||
|
what.is_a?(Path) ||
|
||||||
|
|
|
@ -2,17 +2,25 @@ require "./example_context_delegate"
|
||||||
require "./example_group"
|
require "./example_group"
|
||||||
require "./harness"
|
require "./harness"
|
||||||
require "./location"
|
require "./location"
|
||||||
|
require "./node"
|
||||||
require "./pending_result"
|
require "./pending_result"
|
||||||
require "./result"
|
require "./result"
|
||||||
require "./spec/node"
|
|
||||||
require "./tags"
|
require "./tags"
|
||||||
|
|
||||||
module Spectator
|
module Spectator
|
||||||
# Standard example that runs a test case.
|
# Standard example that runs a test case.
|
||||||
class Example < Spec::Node
|
class Example < Node
|
||||||
# Currently running example.
|
# Currently running example.
|
||||||
class_getter! current : Example
|
class_getter! current : Example
|
||||||
|
|
||||||
|
# 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?
|
||||||
|
|
||||||
# Indicates whether the example already ran.
|
# Indicates whether the example already ran.
|
||||||
getter? finished : Bool = false
|
getter? finished : Bool = false
|
||||||
|
|
||||||
|
@ -31,8 +39,11 @@ module Spectator
|
||||||
# Note: The tags will not be merged with the parent tags.
|
# Note: The tags will not be merged with the parent tags.
|
||||||
def initialize(@context : Context, @entrypoint : self ->,
|
def initialize(@context : Context, @entrypoint : self ->,
|
||||||
name : String? = nil, location : Location? = nil,
|
name : String? = nil, location : Location? = nil,
|
||||||
group : ExampleGroup? = nil, tags = Tags.new)
|
@group : ExampleGroup? = nil, tags = Tags.new)
|
||||||
super(name, location, group, tags)
|
super(name, location, tags)
|
||||||
|
|
||||||
|
# Ensure group is linked.
|
||||||
|
group << self if group
|
||||||
end
|
end
|
||||||
|
|
||||||
# Creates a dynamic example.
|
# Creates a dynamic example.
|
||||||
|
@ -44,11 +55,15 @@ module Spectator
|
||||||
# The example will be assigned to *group* if it is provided.
|
# The example will be assigned to *group* if it is provided.
|
||||||
# A set of *tags* can be used for filtering and modifying example behavior.
|
# A set of *tags* can be used for filtering and modifying example behavior.
|
||||||
# Note: The tags will not be merged with the parent tags.
|
# Note: The tags will not be merged with the parent tags.
|
||||||
def initialize(name : String? = nil, location : Location? = nil, group : ExampleGroup? = nil,
|
def initialize(name : String? = nil, location : Location? = nil,
|
||||||
tags = Tags.new, &block : self ->)
|
@group : ExampleGroup? = nil, tags = Tags.new, &block : self ->)
|
||||||
super(name, location, group, tags)
|
super(name, location, tags)
|
||||||
|
|
||||||
@context = NullContext.new
|
@context = NullContext.new
|
||||||
@entrypoint = block
|
@entrypoint = block
|
||||||
|
|
||||||
|
# Ensure group is linked.
|
||||||
|
group << self if group
|
||||||
end
|
end
|
||||||
|
|
||||||
# Executes the test case.
|
# Executes the test case.
|
||||||
|
@ -68,13 +83,13 @@ module Spectator
|
||||||
|
|
||||||
begin
|
begin
|
||||||
@result = Harness.run do
|
@result = Harness.run do
|
||||||
group?.try(&.call_once_before_all)
|
@group.try(&.call_once_before_all)
|
||||||
if (parent = group?)
|
if (parent = @group)
|
||||||
parent.call_around_each(self) { run_internal }
|
parent.call_around_each(self) { run_internal }
|
||||||
else
|
else
|
||||||
run_internal
|
run_internal
|
||||||
end
|
end
|
||||||
if (parent = group?)
|
if (parent = @group)
|
||||||
parent.call_once_after_all if parent.finished?
|
parent.call_once_after_all if parent.finished?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -85,10 +100,10 @@ module Spectator
|
||||||
end
|
end
|
||||||
|
|
||||||
private def run_internal
|
private def run_internal
|
||||||
group?.try(&.call_before_each(self))
|
@group.try(&.call_before_each(self))
|
||||||
@entrypoint.call(self)
|
@entrypoint.call(self)
|
||||||
@finished = true
|
@finished = true
|
||||||
group?.try(&.call_after_each(self))
|
@group.try(&.call_after_each(self))
|
||||||
end
|
end
|
||||||
|
|
||||||
# Executes code within the example's test context.
|
# Executes code within the example's test context.
|
||||||
|
@ -124,26 +139,27 @@ module Spectator
|
||||||
# Constructs the full name or description of the example.
|
# Constructs the full name or description of the example.
|
||||||
# This prepends names of groups this example is part of.
|
# This prepends names of groups this example is part of.
|
||||||
def to_s(io)
|
def to_s(io)
|
||||||
if name?
|
name = @name
|
||||||
super
|
|
||||||
else
|
# Prefix with group's full name if the node belongs to a group.
|
||||||
io << "<anonymous>"
|
if (parent = @group)
|
||||||
|
parent.to_s(io)
|
||||||
|
|
||||||
|
# Add padding between the node names
|
||||||
|
# only if the names don't appear to be symbolic.
|
||||||
|
# Skip blank group names (like the root group).
|
||||||
|
io << ' ' unless !parent.name? || # ameba:disable Style/NegatedConditionsInUnless
|
||||||
|
(parent.name?.is_a?(Symbol) && name.is_a?(String) &&
|
||||||
|
(name.starts_with?('#') || name.starts_with?('.')))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
# Exposes information about the example useful for debugging.
|
# Exposes information about the example useful for debugging.
|
||||||
def inspect(io)
|
def inspect(io)
|
||||||
# Full example name.
|
super
|
||||||
io << '"'
|
io << ' '
|
||||||
to_s(io)
|
|
||||||
io << '"'
|
|
||||||
|
|
||||||
# Add location if it's available.
|
|
||||||
if (location = self.location)
|
|
||||||
io << " @ "
|
|
||||||
io << location
|
|
||||||
end
|
|
||||||
|
|
||||||
io << result
|
io << result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -153,6 +169,11 @@ module Spectator
|
||||||
json.string(to_s)
|
json.string(to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Creates a procsy from this example and the provided block.
|
||||||
|
def procsy(&block : ->)
|
||||||
|
Procsy.new(self, &block)
|
||||||
|
end
|
||||||
|
|
||||||
# Wraps an example to behave like a `Proc`.
|
# Wraps an example to behave like a `Proc`.
|
||||||
# This is typically used for an *around_each* hook.
|
# This is typically used for an *around_each* hook.
|
||||||
# Invoking `#call` or `#run` will run the example.
|
# Invoking `#call` or `#run` will run the example.
|
||||||
|
|
|
@ -1,71 +1,101 @@
|
||||||
require "./events"
|
require "./events"
|
||||||
require "./example_procsy_hook"
|
require "./example_procsy_hook"
|
||||||
require "./spec/node"
|
require "./node"
|
||||||
|
|
||||||
module Spectator
|
module Spectator
|
||||||
# Collection of examples and sub-groups.
|
# Collection of examples and sub-groups.
|
||||||
class ExampleGroup < Spec::Node
|
class ExampleGroup < Node
|
||||||
include Enumerable(Spec::Node)
|
include Enumerable(Node)
|
||||||
include Events
|
include Events
|
||||||
include Iterable(Spec::Node)
|
include Iterable(Node)
|
||||||
|
|
||||||
@nodes = [] of Spec::Node
|
@nodes = [] of Node
|
||||||
|
|
||||||
group_event before_all do |hooks|
|
# Parent group this group belongs to.
|
||||||
Log.trace { "Processing before_all hooks for #{self}" }
|
getter! group : ExampleGroup
|
||||||
|
|
||||||
if (parent = group?)
|
# Assigns this group to the specified *group*.
|
||||||
parent.call_once_before_all
|
# This is an internal method and should only be called from `ExampleGroup`.
|
||||||
|
# `ExampleGroup` manages the association of nodes to groups.
|
||||||
|
protected setter group : ExampleGroup?
|
||||||
|
|
||||||
|
# Calls all hooks from the parent group if there is a parent.
|
||||||
|
# The *hook* is the method name of the group hook to invoke.
|
||||||
|
private macro call_parent_hooks(hook)
|
||||||
|
if (parent = @group)
|
||||||
|
parent.{{hook.id}}
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Calls all hooks from the parent group if there is a parent.
|
||||||
|
# The *hook* is the method name of the example hook to invoke.
|
||||||
|
# The current *example* must be provided.
|
||||||
|
private macro call_parent_hooks(hook, example)
|
||||||
|
if (parent = @group)
|
||||||
|
parent.{{hook.id}}({{example}})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Calls group hooks of the current group.
|
||||||
|
private def call_hooks(hooks)
|
||||||
hooks.each do |hook|
|
hooks.each do |hook|
|
||||||
Log.trace { "Invoking hook #{hook}" }
|
Log.trace { "Invoking hook #{hook}" }
|
||||||
hook.call
|
hook.call
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Calls example hooks of the current group.
|
||||||
|
# Requires the current *example*.
|
||||||
|
private def call_hooks(hooks, example)
|
||||||
|
hooks.each do |hook|
|
||||||
|
Log.trace { "Invoking hook #{hook}" }
|
||||||
|
hook.call(example)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
group_event before_all do |hooks|
|
||||||
|
Log.trace { "Processing before_all hooks for #{self}" }
|
||||||
|
|
||||||
|
call_parent_hooks(:call_once_before_all)
|
||||||
|
call_hooks(hooks)
|
||||||
|
end
|
||||||
|
|
||||||
group_event after_all do |hooks|
|
group_event after_all do |hooks|
|
||||||
Log.trace { "Processing after_all hooks for #{self}" }
|
Log.trace { "Processing after_all hooks for #{self}" }
|
||||||
|
|
||||||
hooks.each do |hook|
|
call_hooks(hooks)
|
||||||
Log.trace { "Invoking hook #{hook}" }
|
call_parent_hooks(:call_once_after_all)
|
||||||
hook.call
|
|
||||||
end
|
|
||||||
|
|
||||||
if (parent = group?)
|
|
||||||
parent.call_once_after_all
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
example_event before_each do |hooks, example|
|
example_event before_each do |hooks, example|
|
||||||
Log.trace { "Processing before_each hooks for #{self}" }
|
Log.trace { "Processing before_each hooks for #{self}" }
|
||||||
|
|
||||||
if (parent = group?)
|
call_parent_hooks(:call_before_each, example)
|
||||||
parent.call_before_each(example)
|
call_hooks(hooks, example)
|
||||||
end
|
|
||||||
|
|
||||||
hooks.each do |hook|
|
|
||||||
Log.trace { "Invoking hook #{hook}" }
|
|
||||||
hook.call(example)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
example_event after_each do |hooks, example|
|
example_event after_each do |hooks, example|
|
||||||
Log.trace { "Processing after_each hooks for #{self}" }
|
Log.trace { "Processing after_each hooks for #{self}" }
|
||||||
|
|
||||||
hooks.each do |hook|
|
call_hooks(hooks, example)
|
||||||
Log.trace { "Invoking hook #{hook}" }
|
call_parent_hooks(:call_after_each, example)
|
||||||
hook.call(example)
|
end
|
||||||
end
|
|
||||||
|
|
||||||
if (parent = group?)
|
# Creates the example group.
|
||||||
parent.call_after_each(example)
|
# The *name* describes the purpose of the group.
|
||||||
end
|
# It can be a `Symbol` to describe a type.
|
||||||
|
# The *location* tracks where the group exists in source code.
|
||||||
|
# This group will be assigned to the parent *group* if it is provided.
|
||||||
|
# A set of *tags* can be used for filtering and modifying example behavior.
|
||||||
|
def initialize(@name : Label = nil, @location : Location? = nil,
|
||||||
|
@group : ExampleGroup? = nil, @tags : Tags = Tags.new)
|
||||||
|
# Ensure group is linked.
|
||||||
|
group << self if group
|
||||||
end
|
end
|
||||||
|
|
||||||
# Removes the specified *node* from the group.
|
# Removes the specified *node* from the group.
|
||||||
# The node will be unassigned from this group.
|
# The node will be unassigned from this group.
|
||||||
def delete(node : Spec::Node)
|
def delete(node : Node)
|
||||||
# Only remove from the group if it is associated with this group.
|
# Only remove from the group if it is associated with this group.
|
||||||
return unless node.group == self
|
return unless node.group == self
|
||||||
|
|
||||||
|
@ -88,11 +118,31 @@ module Spectator
|
||||||
@nodes.all?(&.finished?)
|
@nodes.all?(&.finished?)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Constructs the full name or description of the example group.
|
||||||
|
# This prepends names of groups this group is part of.
|
||||||
|
def to_s(io)
|
||||||
|
name = @name
|
||||||
|
|
||||||
|
# Prefix with group's full name if the node belongs to a group.
|
||||||
|
if (parent = @group)
|
||||||
|
parent.to_s(io)
|
||||||
|
|
||||||
|
# Add padding between the node names
|
||||||
|
# only if the names don't appear to be symbolic.
|
||||||
|
# Skip blank group names (like the root group).
|
||||||
|
io << ' ' unless !parent.name? || # ameba:disable Style/NegatedConditionsInUnless
|
||||||
|
(parent.name?.is_a?(Symbol) && name.is_a?(String) &&
|
||||||
|
(name.starts_with?('#') || name.starts_with?('.')))
|
||||||
|
end
|
||||||
|
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
# Adds the specified *node* to the group.
|
# Adds the specified *node* to the group.
|
||||||
# Assigns the node to this group.
|
# Assigns the node to this group.
|
||||||
# If the node already belongs to a group,
|
# If the node already belongs to a group,
|
||||||
# it will be removed from the previous group before adding it to this group.
|
# it will be removed from the previous group before adding it to this group.
|
||||||
def <<(node : Spec::Node)
|
def <<(node : Node)
|
||||||
# Remove from existing group if the node is part of one.
|
# Remove from existing group if the node is part of one.
|
||||||
if (previous = node.group?)
|
if (previous = node.group?)
|
||||||
previous.delete(node)
|
previous.delete(node)
|
||||||
|
@ -113,26 +163,26 @@ module Spectator
|
||||||
# Defines a hook for the *around_each* event.
|
# Defines a hook for the *around_each* event.
|
||||||
# The block of code given to this method is invoked when the event occurs.
|
# The block of code given to this method is invoked when the event occurs.
|
||||||
# The current example is provided as a block argument.
|
# The current example is provided as a block argument.
|
||||||
def around_each(&block : Example::Procsy ->) : Nil
|
def around_each(&block) : Nil
|
||||||
hook = ExampleProcsyHook.new(label: "around_each", &block)
|
hook = ExampleProcsyHook.new(label: "around_each", &block)
|
||||||
add_around_each_hook(hook)
|
add_around_each_hook(hook)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Signals that the *around_each* event has occurred.
|
# Signals that the *around_each* event has occurred.
|
||||||
# All hooks associated with the event will be called.
|
# All hooks associated with the event will be called.
|
||||||
def call_around_each(example : Example, &block : -> _) : Nil
|
def call_around_each(example, &block : -> _) : Nil
|
||||||
# Avoid overhead if there's no hooks.
|
# Avoid overhead if there's no hooks.
|
||||||
return yield if @around_hooks.empty?
|
return yield if @around_hooks.empty?
|
||||||
|
|
||||||
# Start with a procsy that wraps the original code.
|
# Start with a procsy that wraps the original code.
|
||||||
procsy = Example::Procsy.new(example, &block)
|
procsy = example.procsy(&block)
|
||||||
procsy = wrap_around_each(procsy)
|
procsy = wrap_around_each(procsy)
|
||||||
procsy.call
|
procsy.call
|
||||||
end
|
end
|
||||||
|
|
||||||
# Wraps a procsy with the *around_each* hooks from this example group.
|
# Wraps a procsy with the *around_each* hooks from this example group.
|
||||||
# The returned procsy will call each hook then *procsy*.
|
# The returned procsy will call each hook then *procsy*.
|
||||||
protected def wrap_around_each(procsy : Example::Procsy) : Example::Procsy
|
protected def wrap_around_each(procsy)
|
||||||
# Avoid overhead if there's no hooks.
|
# Avoid overhead if there's no hooks.
|
||||||
return procsy if @around_hooks.empty?
|
return procsy if @around_hooks.empty?
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
require "./example"
|
require "./example"
|
||||||
require "./example_group"
|
require "./example_group"
|
||||||
require "./spec/node"
|
require "./node"
|
||||||
|
|
||||||
module Spectator
|
module Spectator
|
||||||
# Iterates through all examples in a group and its nested groups.
|
# Iterates through all examples in a group and its nested groups.
|
||||||
|
@ -9,12 +9,12 @@ module Spectator
|
||||||
|
|
||||||
# Stack that contains the iterators for each group.
|
# Stack that contains the iterators for each group.
|
||||||
# A stack is used to track where in the tree this iterator is.
|
# A stack is used to track where in the tree this iterator is.
|
||||||
@stack : Array(Iterator(Spec::Node))
|
@stack : Array(Iterator(Node))
|
||||||
|
|
||||||
# Creates a new iterator.
|
# Creates a new iterator.
|
||||||
# The *group* is the example group to iterate through.
|
# The *group* is the example group to iterate through.
|
||||||
def initialize(@group : ExampleGroup)
|
def initialize(@group : ExampleGroup)
|
||||||
iter = @group.each.as(Iterator(Spec::Node))
|
iter = @group.each.as(Iterator(Node))
|
||||||
@stack = [iter]
|
@stack = [iter]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ module Spectator
|
||||||
# Restart the iterator at the beginning.
|
# Restart the iterator at the beginning.
|
||||||
def rewind
|
def rewind
|
||||||
# Same code as `#initialize`, but return self.
|
# Same code as `#initialize`, but return self.
|
||||||
iter = @group.each.as(Iterator(Spec::Node))
|
iter = @group.each.as(Iterator(Node))
|
||||||
@stack = [iter]
|
@stack = [iter]
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
69
src/spectator/node.cr
Normal file
69
src/spectator/node.cr
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
require "./label"
|
||||||
|
require "./location"
|
||||||
|
require "./tags"
|
||||||
|
|
||||||
|
module Spectator
|
||||||
|
# A single item in a test spec.
|
||||||
|
# This is commonly an `Example` or `ExampleGroup`,
|
||||||
|
# but can be anything that should be iterated over when running the spec.
|
||||||
|
abstract class Node
|
||||||
|
# Location of the node in source code.
|
||||||
|
getter! location : Location
|
||||||
|
|
||||||
|
# User-provided name or description of the node.
|
||||||
|
# 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 : Label
|
||||||
|
|
||||||
|
# Updates the name of the node.
|
||||||
|
protected def name=(@name : String)
|
||||||
|
end
|
||||||
|
|
||||||
|
# User-defined keywords used for filtering and behavior modification.
|
||||||
|
getter tags : Tags
|
||||||
|
|
||||||
|
# Creates the node.
|
||||||
|
# The *name* describes the purpose of the node.
|
||||||
|
# It can be a `Symbol` to describe a type.
|
||||||
|
# The *location* tracks where the node exists in source code.
|
||||||
|
# A set of *tags* can be used for filtering and modifying example behavior.
|
||||||
|
def initialize(@name : Label = nil, @location : Location? = nil, @tags : Tags = Tags.new)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Indicates whether the node has completed.
|
||||||
|
abstract def finished? : Bool
|
||||||
|
|
||||||
|
# Checks if the node has been marked as pending.
|
||||||
|
# Pending items should be skipped during execution.
|
||||||
|
def pending?
|
||||||
|
tags.includes?(:pending)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Constructs the full name or description of the node.
|
||||||
|
# This prepends names of groups this node is part of.
|
||||||
|
def to_s(io)
|
||||||
|
(@name || "<anonymous>").to_s(io)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Exposes information about the node useful for debugging.
|
||||||
|
def inspect(io)
|
||||||
|
# Full node name.
|
||||||
|
io << '"'
|
||||||
|
to_s(io)
|
||||||
|
io << '"'
|
||||||
|
|
||||||
|
# Add location if it's available.
|
||||||
|
if (location = self.location)
|
||||||
|
io << " @ "
|
||||||
|
io << location
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,83 +0,0 @@
|
||||||
require "../label"
|
|
||||||
require "../location"
|
|
||||||
require "../tags"
|
|
||||||
|
|
||||||
module Spectator
|
|
||||||
class Spec
|
|
||||||
# A single item in a test spec.
|
|
||||||
# This is commonly an `Example` or `ExampleGroup`,
|
|
||||||
# but can be anything that should be iterated over when running the spec.
|
|
||||||
abstract class Node
|
|
||||||
# Location of the node in source code.
|
|
||||||
getter! location : Location
|
|
||||||
|
|
||||||
# User-provided name or description of the node.
|
|
||||||
# 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 : Label
|
|
||||||
|
|
||||||
# Updates the name of the node.
|
|
||||||
protected def name=(@name : String)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Group the node belongs to.
|
|
||||||
getter! group : ExampleGroup
|
|
||||||
|
|
||||||
# User-defined keywords used for filtering and behavior modification.
|
|
||||||
getter tags : Tags
|
|
||||||
|
|
||||||
# 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 *location* tracks where the node exists in source code.
|
|
||||||
# The node will be assigned to *group* if it is provided.
|
|
||||||
# A set of *tags* can be used for filtering and modifying example behavior.
|
|
||||||
def initialize(@name : Label = nil, @location : Location? = nil,
|
|
||||||
group : ExampleGroup? = nil, @tags : Tags = Tags.new)
|
|
||||||
# Ensure group is linked.
|
|
||||||
group << self if group
|
|
||||||
end
|
|
||||||
|
|
||||||
# Indicates whether the node has completed.
|
|
||||||
abstract def finished? : Bool
|
|
||||||
|
|
||||||
# Checks if the node has been marked as pending.
|
|
||||||
# Pending items should be skipped during execution.
|
|
||||||
def pending?
|
|
||||||
tags.includes?(:pending)
|
|
||||||
end
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
group.to_s(io)
|
|
||||||
|
|
||||||
# Add padding between the node names
|
|
||||||
# only if the names don't appear to be symbolic.
|
|
||||||
# Skip blank group names (like the root group).
|
|
||||||
io << ' ' unless !group.name? || # ameba:disable Style/NegatedConditionsInUnless
|
|
||||||
(group.name?.is_a?(Symbol) && name.is_a?(String) &&
|
|
||||||
(name.starts_with?('#') || name.starts_with?('.')))
|
|
||||||
end
|
|
||||||
|
|
||||||
name.to_s(io)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
Loading…
Reference in a new issue