mirror of
https://gitea.invidious.io/iv-org/shard-spectator.git
synced 2024-08-15 00:53:35 +00:00
Scratch work
Trying to implement hooks. Ran into a problem with contexts.
This commit is contained in:
parent
688c08b087
commit
f433405ece
7 changed files with 163 additions and 12 deletions
|
@ -35,6 +35,34 @@ module Spectator::DSL
|
||||||
@@builder.add_example(*args, &block)
|
@@builder.add_example(*args, &block)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Defines a block of code to execute before any and all examples in the current group.
|
||||||
|
#
|
||||||
|
# See `Spec::Builder#before_all` for usage details.
|
||||||
|
def before_all(&block)
|
||||||
|
@@builder.before_all(&block)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Defines a block of code to execute before every example in the current group
|
||||||
|
#
|
||||||
|
# See `Spec::Builder#before_each` for usage details.
|
||||||
|
def before_each(&block : Example, Context -> _)
|
||||||
|
@@builder.before_each(&block)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Defines a block of code to execute after any and all examples in the current group.
|
||||||
|
#
|
||||||
|
# See `Spec::Builder#after_all` for usage details.
|
||||||
|
def after_all(&block)
|
||||||
|
@@builder.after_all(&block)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Defines a block of code to execute after every example in the current group.
|
||||||
|
#
|
||||||
|
# See `Spec::Builder#after_each` for usage details.
|
||||||
|
def after_each(&block : Example, Context ->)
|
||||||
|
@@builder.after_each(&block)
|
||||||
|
end
|
||||||
|
|
||||||
# Sets the configuration of the spec.
|
# Sets the configuration of the spec.
|
||||||
#
|
#
|
||||||
# See `Spec::Builder#config=` for usage details.
|
# See `Spec::Builder#config=` for usage details.
|
||||||
|
|
|
@ -9,9 +9,7 @@ module Spectator::DSL
|
||||||
{{block.body}}
|
{{block.body}}
|
||||||
end
|
end
|
||||||
|
|
||||||
::Spectator::DSL::Builder.add_hook(
|
::Spectator::DSL::Builder.before_all { {{@type.name}.%hook }
|
||||||
:before
|
|
||||||
) { {{@type.name}.%hook }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
macro before_each(&block)
|
macro before_each(&block)
|
||||||
|
@ -21,10 +19,7 @@ module Spectator::DSL
|
||||||
{{block.body}}
|
{{block.body}}
|
||||||
end
|
end
|
||||||
|
|
||||||
::Spectator::DSL::Builder.add_context_hook(
|
::Spectator::DSL::Builder.before_each { |context| context.as({{@type.name}).%hook }
|
||||||
:before,
|
|
||||||
{{@type.name}}
|
|
||||||
) { |context| context.as({{@type.name}).%hook }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
macro after_all(&block)
|
macro after_all(&block)
|
||||||
|
|
62
src/spectator/events.cr
Normal file
62
src/spectator/events.cr
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
require "./example_context_delegate"
|
||||||
|
|
||||||
|
module Spectator
|
||||||
|
# Mix-in for managing events and hooks.
|
||||||
|
# This module is intended to be included by `ExampleGroup`.
|
||||||
|
module Events
|
||||||
|
# Defines an event for an example group.
|
||||||
|
# This event typically runs before or after an example group finishes.
|
||||||
|
# No contextual information (or example) is provided to the hooks.
|
||||||
|
# The *name* defines the name of the event.
|
||||||
|
# This must be unique across all events.
|
||||||
|
# Two methods are defined - one to add a hook and the other to trigger the event which calls every hook.
|
||||||
|
private macro group_event(name)
|
||||||
|
@{{name.id}}_hooks = Deque(->).new
|
||||||
|
|
||||||
|
# Defines a hook for the *{{name.id}}* event.
|
||||||
|
# The block of code given to this method is invoked when the event occurs.
|
||||||
|
def {{name.id}}(&block) : Nil
|
||||||
|
@{{name.id}}_hooks << block
|
||||||
|
end
|
||||||
|
|
||||||
|
# Signals that the *{{name.id}}* event has occurred.
|
||||||
|
# All hooks associated with the event will be called.
|
||||||
|
def call_{{name.id}} : Nil
|
||||||
|
@{{name.id}}_hooks.each(&.call)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Defines an event for an example.
|
||||||
|
# This event typically runs before or after an example finishes.
|
||||||
|
# The current example is provided to the hooks.
|
||||||
|
# The *name* defines the name of the event.
|
||||||
|
# This must be unique across all events.
|
||||||
|
# Three methods are defined - two to add a hook and the other to trigger the event which calls every hook.
|
||||||
|
# A hook can be added with an `ExampleContextDelegate` or a block that accepts an example (no context).
|
||||||
|
private macro example_event(name)
|
||||||
|
@{{name.id}}_hooks = Deque(ExampleContextMethod).new
|
||||||
|
|
||||||
|
# Defines a hook for the *{{name.id}}* event.
|
||||||
|
# The *delegate* will be called when the event occurs.
|
||||||
|
# The current example is provided to the delegate.
|
||||||
|
def {{name.id}}(delegate : ExampleContextDelegate) : Nil
|
||||||
|
@{{name.id}}_hooks << delegate
|
||||||
|
end
|
||||||
|
|
||||||
|
# Defines a hook for the *{{name.id}}* event.
|
||||||
|
# The block of code given to this method is invoked when the event occurs.
|
||||||
|
# The current example is provided as a block argument.
|
||||||
|
def {{name.id}}(&block : Example -> _) : Nil
|
||||||
|
delegate = ExampleContextDelegate.null(&block)
|
||||||
|
@{{name.id}}_hooks << delegate
|
||||||
|
end
|
||||||
|
|
||||||
|
# Signals that the *{{name.id}}* event has occurred.
|
||||||
|
# All hooks associated with the event will be called.
|
||||||
|
# The *example* should be the current example.
|
||||||
|
def call_{{name.id}}(example) : Nil
|
||||||
|
@{{name.id}}_hooks.each(&.call(example))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -19,12 +19,13 @@ module Spectator
|
||||||
getter result : Result = PendingResult.new
|
getter result : Result = PendingResult.new
|
||||||
|
|
||||||
# Creates the example.
|
# Creates the example.
|
||||||
# The *delegate* contains the test context and method that runs the test case.
|
# An instance to run the test code in is given by *context*.
|
||||||
|
# The *entrypoint* defines the test code (typically inside *context*).
|
||||||
# The *name* describes the purpose of the example.
|
# The *name* describes the purpose of the example.
|
||||||
# It can be a `Symbol` to describe a type.
|
# It can be a `Symbol` to describe a type.
|
||||||
# The *source* tracks where the example exists in source code.
|
# The *source* tracks where the example exists in source code.
|
||||||
# The example will be assigned to *group* if it is provided.
|
# The example will be assigned to *group* if it is provided.
|
||||||
def initialize(@delegate : ExampleContextDelegate,
|
def initialize(@context : Context, @entrypoint : ExampleContextMethod,
|
||||||
name : String? = nil, source : Source? = nil, group : ExampleGroup? = nil)
|
name : String? = nil, source : Source? = nil, group : ExampleGroup? = nil)
|
||||||
super(name, source, group)
|
super(name, source, group)
|
||||||
end
|
end
|
||||||
|
@ -37,7 +38,8 @@ module Spectator
|
||||||
# The *source* tracks where the example exists in source code.
|
# The *source* tracks where the example exists in source code.
|
||||||
# The example will be assigned to *group* if it is provided.
|
# The example will be assigned to *group* if it is provided.
|
||||||
def initialize(name : String? = nil, source : Source? = nil, group : ExampleGroup? = nil, &block : Example -> _)
|
def initialize(name : String? = nil, source : Source? = nil, group : ExampleGroup? = nil, &block : Example -> _)
|
||||||
@delegate = ExampleContextDelegate.null(&block)
|
@context = NullContext.new
|
||||||
|
@entrypoint = block
|
||||||
end
|
end
|
||||||
|
|
||||||
# Executes the test case.
|
# Executes the test case.
|
||||||
|
@ -46,13 +48,27 @@ module Spectator
|
||||||
def run : Result
|
def run : Result
|
||||||
@@current = self
|
@@current = self
|
||||||
Log.debug { "Running example #{self}" }
|
Log.debug { "Running example #{self}" }
|
||||||
Log.warn { "Example #{self} running more than once" } if @finished
|
Log.warn { "Example #{self} already ran" } if @finished
|
||||||
@result = Harness.run { @delegate.call(self) }
|
@result = Harness.run { @entrypoint.call(self, @context) }
|
||||||
ensure
|
ensure
|
||||||
@@current = nil
|
@@current = nil
|
||||||
@finished = true
|
@finished = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Executes code within the example's test context.
|
||||||
|
# This is an advanced method intended for internal usage only.
|
||||||
|
#
|
||||||
|
# The *klass* defines the type of the test context.
|
||||||
|
# This is typically only known by the code constructing the example.
|
||||||
|
# An error will be raised if *klass* doesn't match the test context's type.
|
||||||
|
# The block given to this method will be executed within the test context.
|
||||||
|
#
|
||||||
|
# TODO: Benchmark compiler performance using this method versus client-side casting in a proc.
|
||||||
|
def with_context(klass)
|
||||||
|
context = klass.cast(@delegate.context)
|
||||||
|
with context yield
|
||||||
|
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.
|
# Full example name.
|
||||||
|
|
|
@ -6,6 +6,9 @@ module Spectator
|
||||||
# Stores a test context and a method to call within it.
|
# Stores a test context and a method to call within it.
|
||||||
# This is a variant of `ContextDelegate` that accepts the current running example.
|
# This is a variant of `ContextDelegate` that accepts the current running example.
|
||||||
struct ExampleContextDelegate
|
struct ExampleContextDelegate
|
||||||
|
# Retrieves the underlying context.
|
||||||
|
protected getter context : Context
|
||||||
|
|
||||||
# Creates the delegate.
|
# Creates the delegate.
|
||||||
# The *context* is the instance of the test context.
|
# The *context* is the instance of the test context.
|
||||||
# The *method* is proc that downcasts *context* and calls a method on it.
|
# The *method* is proc that downcasts *context* and calls a method on it.
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
|
require "./events"
|
||||||
require "./example_node"
|
require "./example_node"
|
||||||
|
|
||||||
module Spectator
|
module Spectator
|
||||||
# Collection of examples and sub-groups.
|
# Collection of examples and sub-groups.
|
||||||
class ExampleGroup < ExampleNode
|
class ExampleGroup < ExampleNode
|
||||||
include Enumerable(ExampleNode)
|
include Enumerable(ExampleNode)
|
||||||
|
include Events
|
||||||
include Iterable(ExampleNode)
|
include Iterable(ExampleNode)
|
||||||
|
|
||||||
|
group_event before_all
|
||||||
|
group_event after_all
|
||||||
|
example_event before_each
|
||||||
|
example_event after_each
|
||||||
|
|
||||||
@nodes = [] of ExampleNode
|
@nodes = [] of ExampleNode
|
||||||
|
|
||||||
# Removes the specified *node* from the group.
|
# Removes the specified *node* from the group.
|
||||||
|
|
|
@ -98,6 +98,46 @@ module Spectator
|
||||||
# The example is added to the current group by `Example` initializer.
|
# The example is added to the current group by `Example` initializer.
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Defines a block of code to execute before any and all examples in the current group.
|
||||||
|
def before_all(&block)
|
||||||
|
Log.trace { "Add before_all hook" }
|
||||||
|
current_group.before_all(&block)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Defines a delegate to call before every example in the current group.
|
||||||
|
# The current example is provided to the delegate.
|
||||||
|
def before_each(delegate : ExampleContextDelegate)
|
||||||
|
Log.trace { "Add before_each hook delegate" }
|
||||||
|
current_group.before_each(delegate)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Defines a block of code to execute before every example in the current group.
|
||||||
|
# The current example is provided as a block argument.
|
||||||
|
def before_each(&block : Example -> _)
|
||||||
|
Log.trace { "Add before_each hook block" }
|
||||||
|
current_group.before_each(&block)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Defines a block of code to execute after any and all examples in the current group.
|
||||||
|
def after_all(&block)
|
||||||
|
Log.trace { "Add after_all hook" }
|
||||||
|
current_group.after_all(&block)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Defines a delegate to call after every example in the current group.
|
||||||
|
# The current example is provided to the delegate.
|
||||||
|
def after_each(delegate : ExampleContextDelegate)
|
||||||
|
Log.trace { "Add after_each hook delegate" }
|
||||||
|
current_group.after_each(delegate)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Defines a block of code to execute after every example in the current group.
|
||||||
|
# The current example is provided as a block argument.
|
||||||
|
def after_each(&block : Example -> _)
|
||||||
|
Log.trace { "Add after_each hook" }
|
||||||
|
current_group.after_each(&block)
|
||||||
|
end
|
||||||
|
|
||||||
# Builds the configuration to use for the spec.
|
# Builds the configuration to use for the spec.
|
||||||
# A `ConfigBuilder` is yielded to the block provided to this method.
|
# A `ConfigBuilder` is yielded to the block provided to this method.
|
||||||
# That builder will be used to create the configuration.
|
# That builder will be used to create the configuration.
|
||||||
|
|
Loading…
Reference in a new issue