Merge branch 'example-restructure' of gitlab.com:arctic-fox/spectator into example-restructure

This commit is contained in:
Michael Miller 2019-09-08 11:02:02 -06:00
commit a100191875
9 changed files with 63 additions and 208 deletions

View file

@ -0,0 +1,22 @@
module Spectator::Builders
struct ExampleGroupStack
getter root = RootExampleGroupBuilder.new
@stack = Deque(ExampleGroupBuilder).new(1, @root)
def current
@stack.last
end
def push(group : NestedExampleGroupBuilder)
current.add_child(group)
@stack.push(group)
end
def pop
raise "Attempted to pop root example group from stack" if current == root
@stack.pop
end
end
end

View file

@ -6,26 +6,7 @@ module Spectator::DSL
module Builder
extend self
# Root group that contains all examples and groups in the spec.
private class_getter root_group = RootExampleGroupBuilder.new
# Stack for tracking the current group the spec is working in.
# The last item (top of the stack) is the current group.
# The first item (bottom of the stack) is the root group (`#root_group`).
# The root group should never be popped.
@@group_stack = Array(ExampleGroupBuilder).new(1, root_group)
# Retrieves the current group the spec is working in.
private def current_group
@@group_stack.last
end
# Adds a new group to the stack.
# Calling this method indicates the spec has entered a nested group.
private def push_group(group : NestedExampleGroupBuilder)
current_group.add_child(group)
@@group_stack.push(group)
end
@@stack = Builders::ExampleGroupStack.new
# Begins a new nested group in the spec.
# A corresponding `#end_group` call must be made
@ -34,7 +15,7 @@ module Spectator::DSL
# as arguments to this method are passed directly to it.
def start_group(*args) : Nil
group = NestedExampleGroupBuilder.new(*args)
push_group(group)
@@stack.push(group)
end
# Begins a new sample group in the spec -
@ -45,7 +26,7 @@ module Spectator::DSL
# as arguments to this method are passed directly to it.
def start_sample_group(*args) : Nil
group = SampleExampleGroupBuilder.new(*args)
push_group(group)
@@stack.push(group)
end
# Marks the end of a group in the spec.
@ -53,58 +34,61 @@ module Spectator::DSL
# It is also important to line up the start and end calls.
# Otherwise examples might get placed into wrong groups.
def end_group : Nil
@@group_stack.pop
@@stack.pop
end
# Adds an example type to the current group.
# The class name of the example should be passed as an argument.
# The example will be instantiated later.
def add_example(example_type : Example.class) : Nil
factory = ExampleFactory.new(example_type)
current_group.add_child(factory)
def add_example(description : String, source : Source,
example_type : ::SpectatorTest.class, &runner : ::SpectatorTest ->) : Nil
builder = ->{ example_type.new.as(::SpectatorTest) }
wrapper = TestWrapper.new(description, source, builder, runner)
example = Example.new(current_group, wrapper)
# TODO: Add to stack.
end
# Adds a block of code to run before all examples in the current group.
def add_before_all_hook(&block : ->) : Nil
current_group.add_before_all_hook(block)
@@stack.current.add_before_all_hook(block)
end
# Adds a block of code to run before each example in the current group.
def add_before_each_hook(&block : ->) : Nil
current_group.add_before_each_hook(block)
@@stack.current.add_before_each_hook(block)
end
# Adds a block of code to run after all examples in the current group.
def add_after_all_hook(&block : ->) : Nil
current_group.add_after_all_hook(block)
@@stack.current.add_after_all_hook(block)
end
# Adds a block of code to run after each example in the current group.
def add_after_each_hook(&block : ->) : Nil
current_group.add_after_each_hook(block)
@@stack.current.add_after_each_hook(block)
end
# Adds a block of code to run before and after each example in the current 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
current_group.add_around_each_hook(block)
@@stack.current.add_around_each_hook(block)
end
# Adds a pre-condition to run at the start of every example in the current group.
def add_pre_condition(&block : ->) : Nil
current_group.add_pre_condition(block)
@@stack.current.add_pre_condition(block)
end
# Adds a post-condition to run at the end of every example in the current group.
def add_post_condition(&block : ->) : Nil
current_group.add_post_condition(block)
@@stack.current.add_post_condition(block)
end
# Builds the entire spec and returns it as a test suite.
# This should be called only once after the entire spec has been defined.
protected def build(filter : ExampleFilter) : TestSuite
group = root_group.build(Internals::SampleValues.empty)
group = @@stack.root.build(Internals::SampleValues.empty)
TestSuite.new(group, filter)
end
end

View file

@ -1416,17 +1416,20 @@ module Spectator::DSL
# Create the wrapper class for the test code.
{% if block.is_a?(Nop) %}
{% if what.is_a?(Call) %}
_spectator_test(Test%example, %run) do
def %run
{{what}}
end
{% else %}
{% raise "Unrecognized syntax: `it #{what}`" %}
{% end %}
{% else %}
_spectator_test(Test%example, %run) {{block}}
def %run
{{block}}
end
{% end %}
# TODO
%source = ::Spectator::Source.new({{_source_file}}, {{_source_line}})
::Spectator::DSL::Builder.add_example({{what.stringify}}, %source, {{@type.name}}) { |test| test.as({{@type.name}}).%run }
end
# Creates an example, or a test case.

View file

@ -2,7 +2,7 @@ require "./example_component"
module Spectator
# Base class for all types of examples.
# Concrete types must implement the `#run_impl, `#what`, `#instance`, and `#source` methods.
# Concrete types must implement the `#run_impl` method.
abstract class Example < ExampleComponent
# Indicates whether the example has already been run.
getter? finished = false
@ -11,10 +11,16 @@ module Spectator
getter group : ExampleGroup
# Retrieves the internal wrapped instance.
abstract def instance
private getter @test_wrapper : TestWrapper
# Source where the example originated from.
abstract def source : Source
def source
@test_wrapper.source
end
def what
@test_wrapper.description
end
# Runs the example code.
# A result is returned, which represents the outcome of the test.
@ -32,7 +38,7 @@ module Spectator
# Creates the base of the example.
# The group should be the example group the example belongs to.
def initialize(@group)
def initialize(@group, @test_wrapper)
end
# Indicates there is only one example to run.

View file

@ -3,6 +3,7 @@ module Spectator
# This is used as the base node type for the composite design pattern.
abstract class ExampleComponent
# Text that describes the context or test.
# TODO: Rename to description.
abstract def what : String
# Indicates whether the example (or group) has been completely run.

View file

@ -15,13 +15,8 @@ module Spectator
include Enumerable(ExampleComponent)
include Iterable(ExampleComponent)
getter sample_values : Internals::SampleValues
# Creates the example group.
# The hooks are stored to be triggered later.
def initialize(@hooks : ExampleHooks, @conditions : ExampleConditions, @sample_values)
@before_all_hooks_run = false
@after_all_hooks_run = false
def initialize
end
# Retrieves the children in the group.
@ -123,72 +118,5 @@ module Spectator
def finished?
children.all?(&.finished?)
end
# Runs all of the "before-each" and "before-all" hooks.
# This should run prior to every example in the group.
def run_before_hooks
run_before_all_hooks
run_before_each_hooks
end
# Runs all of the "before-all" hooks.
# This should run prior to any examples in the group.
# The hooks will be run only once.
# Subsequent calls to this method will do nothing.
protected def run_before_all_hooks : Nil
return if @before_all_hooks_run
@hooks.run_before_all
@before_all_hooks_run = true
end
# Runs all of the "before-each" hooks.
# This method should run prior to every example in the group.
protected def run_before_each_hooks : Nil
@hooks.run_before_each
end
# Runs all of the "after-all" and "after-each" hooks.
# This should run following every example in the group.
def run_after_hooks
run_after_each_hooks
run_after_all_hooks
end
# Runs all of the "after-all" hooks.
# This should run following all examples in the group.
# The hooks will be run only once,
# and only after all examples in the group have finished.
# Subsequent calls after the hooks have been run will do nothing.
protected def run_after_all_hooks(ignore_unfinished = false) : Nil
return if @after_all_hooks_run
return unless ignore_unfinished || finished?
@hooks.run_after_all
@after_all_hooks_run = true
end
# Runs all of the "after-each" hooks.
# This method should run following every example in the group.
protected def run_after_each_hooks : Nil
@hooks.run_after_each
end
# Creates a proc that runs the "around-each" hooks
# in addition to a block passed to this method.
# To call the block and all "around-each" hooks,
# just invoke `Proc#call` on the returned proc.
def wrap_around_each_hooks(&block : ->) : ->
@hooks.wrap_around_each(&block)
end
# Runs all of the pre-conditions for an example.
def run_pre_conditions
@conditions.run_pre_conditions
end
# Runs all of the post-conditions for an example.
def run_post_conditions
@conditions.run_post_conditions
end
end
end

View file

@ -18,8 +18,7 @@ module Spectator
# The parent's children must contain this group,
# otherwise there may be unexpected behavior.
# The *hooks* are stored to be triggered later.
def initialize(@what, @parent, hooks : ExampleHooks, conditions : ExampleConditions, sample_values)
super(hooks, conditions, sample_values)
def initialize(@what, @parent)
end
# Indicates wheter the group references a type.
@ -27,69 +26,6 @@ module Spectator
@what.is_a?(Symbol)
end
# Runs all of the "before-all" hooks.
# This should run prior to any examples in the group.
# The hooks will be run only once.
# Subsequent calls to this method will do nothing.
# Parent "before-all" hooks will be run first.
protected def run_before_all_hooks : Nil
parent.run_before_all_hooks
super
end
# Runs all of the "before-each" hooks.
# This method should run prior to every example in the group.
# Parent "before-each" hooks will be run first.
protected def run_before_each_hooks : Nil
parent.run_before_each_hooks
super
end
# Runs all of the "after-all" hooks.
# This should run following all examples in the group.
# The hooks will be run only once,
# and only after all examples in the group have finished.
# Subsequent calls after the hooks have been run will do nothing.
# Parent "after-all" hooks will be run last.
protected def run_after_all_hooks(ignore_unfinished = false) : Nil
super
parent.run_after_all_hooks(ignore_unfinished)
end
# Runs all of the "after-each" hooks.
# This method should run following every example in the group.
# Parent "after-each" hooks will be run last.
protected def run_after_each_hooks : Nil
super
parent.run_after_each_hooks
end
# Creates a proc that runs the "around-each" hooks
# in addition to a block passed to this method.
# To call the block and all `around_each` hooks,
# just invoke `Proc#call` on the returned proc.
# Parent "around-each" hooks will be in the outermost wrappings.
def wrap_around_each_hooks(&block : ->) : ->
wrapper = super(&block)
parent.wrap_around_each_hooks(&wrapper)
end
# Runs all of the pre-condition checks.
# This method should run prior to every example in the group.
# Parent pre-conditions will be checked first.
def run_pre_conditions : Nil
parent.run_pre_conditions
super
end
# Runs all of the post-condition checks.
# This method should run following every example in the group.
# Parent post-conditions will be checked last.
def run_post_conditions : Nil
super
parent.run_post_conditions
end
# Creates a string representation of the group.
# The string consists of `#what` appended to the parent.
# This results in a string like:

View file

@ -3,7 +3,7 @@ require "./example"
module Spectator
# Common class for all examples marked as pending.
# This class will not run example code.
abstract class PendingExample < Example
class PendingExample < Example
# Returns a pending result.
private def run_impl
PendingResult.new(self)

View file

@ -1,11 +1,9 @@
require "./example"
module Spectator
# Common base for all examples that can be run.
# This class includes all the logic for running example hooks,
# Includes all the logic for running example hooks,
# the example code, and capturing a result.
# Sub-classes need to implement the `#what` and `#run_instance` methods.
abstract class RunnableExample < Example
class RunnableExample < Example
# Runs the example, hooks, and captures the result
# and translates to a usable result.
def run_impl
@ -14,25 +12,6 @@ module Spectator
translate_result(result, expectations)
end
# Runs the actual test code.
private abstract def run_instance
# Runs the hooks that should be performed before starting the test code.
private def run_before_hooks
group.run_before_hooks
rescue ex
# If an error occurs in the before hooks, skip running the example.
raise Exception.new("Error encountered while running before hooks", ex)
end
# Runs the hooks that should be performed after the test code finishes.
private def run_after_hooks
group.run_after_hooks
rescue ex
# If an error occurs in the after hooks, elevate it to abort testing.
raise Exception.new("Error encountered while running after hooks", ex)
end
# Runs all hooks and the example code.
# A captured result is returned.
private def capture_result
@ -40,9 +19,7 @@ module Spectator
# Get the proc that will call around-each hooks and the example.
wrapper = wrap_run_example(result)
run_before_hooks
run_wrapper(wrapper)
run_after_hooks
end
end
@ -70,9 +47,7 @@ module Spectator
# Capture how long it takes to run the test code.
result.elapsed = Time.measure do
begin
group.run_pre_conditions
run_instance # Actually run the example code.
group.run_post_conditions
test_wrapper.run {} # Actually run the example code.
rescue ex # Catch all errors and handle them later.
result.error = ex
end