Remove append and prepend variants of hook definition methods

RSpec defines these as applying to a scope (example, context, suite) as 
opposed to example group.
Mimicing this is currently not possible in Spectator and would require a 
substantial restructure of how hooks are handled.
This may be implemented in the future.
This commit is contained in:
Michael Miller 2021-08-08 11:50:30 -06:00
parent 91d21b38e2
commit 0f7a9ed9e8
No known key found for this signature in database
GPG Key ID: FB9F12F7C646A4AD
5 changed files with 29 additions and 184 deletions

View File

@ -7,9 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Fixed
- Fix resolution of types with the same name in nested scopes. [#31](https://github.com/icy-arctic-fox/spectator/issues/31)
- `around_each` hooks wrap `before_all` and `after_all` hooks. [#12](https://github.com/icy-arctic-fox/spectator/issues/12)
- Hook execution order has been tweaked to match RSpec.
### Added
- Hooks are yielded the current example as a block argument.
- `before_each`, `after_each`, and `around_each` hooks are yielded the current example as a block argument.
- The `let` and `subject` blocks are yielded the current example as a block argument.
- Add internal logging that uses Crystal's `Log` utility. Provide the `LOG_LEVEL` environment variable to enable.
- Support dynamic creation of examples.
@ -26,11 +28,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Support custom messages for failed expectations. [#28](https://gitlab.com/arctic-fox/spectator/-/issues/28)
- Allow named arguments and assignments for `provided` (`given`) block.
- Add `aggregate_failures` to capture and report multiple failed expectations. [#24](https://gitlab.com/arctic-fox/spectator/-/issues/24)
- Add `append_` and `prepend_` variants of hook creation methods.
### Changed
- `around_each` hooks wrap `before_all` and `after_all` hooks. [#12](https://github.com/icy-arctic-fox/spectator/issues/12)
- Hook execution order has been tweaked to match RSpec.
- `given` (now `provided`) blocks changed to produce a single example. `it` can no longer be nested in a `provided` block.
- The "should" syntax no longer reports the source as inside Spectator.
- Short-hand "should" syntax must be included by using `require "spectator/should"` - `it { should eq("foo") }`
@ -38,7 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Prevent usage of reserved keywords in DSL (such as `initialize`).
- The count argument for `sample` and `random_sample` groups must be named (use `count: 5` instead of just `5`).
- Helper methods used as arguments for `sample` and `random_sample` must be class methods.
- Simplify and reduce defined types and generics. Should speed up compilation times.
- Simplify and reduce instanced types and generics. Should speed up compilation times.
- Overhaul example creation and handling.
- Overhaul storage of test values.
- Overhaul reporting and formatting. Cleaner output for failures and pending tests.

View File

@ -60,84 +60,42 @@ module Spectator::DSL
builder.before_suite(hook)
end
# Defines a block of code to execute before any and all examples in the test suite.
def prepend_before_suite(location = nil, label = "before_suite", &block)
hook = ExampleGroupHook.new(location: location, label: label, &block)
builder.prepend_before_suite(hook)
end
# Defines a block of code to execute before any and all examples in the current group.
def before_all(location = nil, label = "before_all", &block)
hook = ExampleGroupHook.new(location: location, label: label, &block)
builder.before_all(hook)
end
# Defines a block of code to execute before any and all examples in the current group.
def prepend_before_all(location = nil, label = "before_all", &block)
hook = ExampleGroupHook.new(location: location, label: label, &block)
builder.prepend_before_all(hook)
end
# Defines a block of code to execute before every example in the current group
def before_each(location = nil, label = "before_each", &block : Example -> _)
hook = ExampleHook.new(location: location, label: label, &block)
builder.before_each(hook)
end
# Defines a block of code to execute before every example in the current group
def prepend_before_each(location = nil, label = "before_each", &block : Example -> _)
hook = ExampleHook.new(location: location, label: label, &block)
builder.prepend_before_each(hook)
end
# Defines a block of code to execute after any and all examples in the test suite.
def after_suite(location = nil, label = "after_suite", &block)
hook = ExampleGroupHook.new(location: location, label: label, &block)
builder.after_suite(hook)
end
# Defines a block of code to execute after any and all examples in the test suite.
def append_after_suite(location = nil, label = "after_suite", &block)
hook = ExampleGroupHook.new(location: location, label: label, &block)
builder.append_after_suite(hook)
end
# Defines a block of code to execute after any and all examples in the current group.
def after_all(location = nil, label = "after_all", &block)
hook = ExampleGroupHook.new(location: location, label: label, &block)
builder.after_all(hook)
end
# Defines a block of code to execute after any and all examples in the current group.
def append_after_all(location = nil, label = "after_all", &block)
hook = ExampleGroupHook.new(location: location, label: label, &block)
builder.append_after_all(hook)
end
# Defines a block of code to execute after every example in the current group.
def after_each(location = nil, label = "after_each", &block : Example ->)
hook = ExampleHook.new(location: location, label: label, &block)
builder.after_each(hook)
end
# Defines a block of code to execute after every example in the current group.
def append_after_each(location = nil, label = "after_each", &block : Example ->)
hook = ExampleHook.new(location: location, label: label, &block)
builder.append_after_each(hook)
end
# Defines a block of code to execute around every example in the current group.
def around_each(location = nil, label = "around_each", &block : Example::Procsy ->)
hook = ExampleProcsyHook.new(location: location, label: label, &block)
builder.around_each(hook)
end
# Defines a block of code to execute around every example in the current group.
def prepend_around_each(location = nil, label = "around_each", &block : Example::Procsy ->)
hook = ExampleProcsyHook.new(location: location, label: label, &block)
builder.prepend_around_each(hook)
end
# Constructs the test spec.
# Returns the spec instance.
#

View File

@ -104,67 +104,31 @@ module Spectator::DSL
# This means that values defined by `let` and `subject` are not available.
define_example_group_hook :before_suite
# Defines a block of code that will be invoked once before any examples in the suite.
# The block will not run in the context of the current running example.
# This means that values defined by `let` and `subject` are not available.
# The hook is added before all others others of the same type in this context.
define_example_group_hook :prepend_before_suite
# Defines a block of code that will be invoked once after all examples in the suite.
# The block will not run in the context of the current running example.
# This means that values defined by `let` and `subject` are not available.
define_example_group_hook :after_suite
# Defines a block of code that will be invoked once after all examples in the suite.
# The block will not run in the context of the current running example.
# This means that values defined by `let` and `subject` are not available.
# The hook is added after all others others of the same type in this context.
define_example_group_hook :append_after_suite
# Defines a block of code that will be invoked once before any examples in the group.
# The block will not run in the context of the current running example.
# This means that values defined by `let` and `subject` are not available.
define_example_group_hook :before_all
# Defines a block of code that will be invoked once before any examples in the group.
# The block will not run in the context of the current running example.
# This means that values defined by `let` and `subject` are not available.
# The hook is added before all others others of the same type in this context.
define_example_group_hook :prepend_before_all
# Defines a block of code that will be invoked once after all examples in the group.
# The block will not run in the context of the current running example.
# This means that values defined by `let` and `subject` are not available.
define_example_group_hook :after_all
# Defines a block of code that will be invoked once after all examples in the group.
# The block will not run in the context of the current running example.
# This means that values defined by `let` and `subject` are not available.
# The hook is added after all others others of the same type in this context.
define_example_group_hook :append_after_all
# Defines a block of code that will be invoked before every example in the group.
# The block will be run in the context of the current running example.
# This means that values defined by `let` and `subject` are available.
define_example_hook :before_each
# Defines a block of code that will be invoked before every example in the group.
# The block will be run in the context of the current running example.
# This means that values defined by `let` and `subject` are available.
# The hook is added before all others others of the same type in this context.
define_example_hook :prepend_before_each
# Defines a block of code that will be invoked after every example in the group.
# The block will be run in the context of the current running example.
# This means that values defined by `let` and `subject` are available.
define_example_hook :after_each
# Defines a block of code that will be invoked after every example in the group.
# The block will be run in the context of the current running example.
# This means that values defined by `let` and `subject` are available.
# The hook is added after all others others of the same type in this context.
define_example_hook :append_after_each
# Defines a block of code that will be invoked around every example in the group.
# The block will be run in the context of the current running example.
# This means that values defined by `let` and `subject` are available.
@ -174,16 +138,5 @@ module Spectator::DSL
# The `Example::Procsy#run` method should be called to ensure the example runs.
# More code can run afterwards (in the block).
define_example_hook :around_each
# Defines a block of code that will be invoked around every example in the group.
# The block will be run in the context of the current running example.
# This means that values defined by `let` and `subject` are available.
# The hook is added before all others others of the same type in this context.
#
# The block will execute before the example.
# An `Example::Procsy` is passed to the block.
# The `Example::Procsy#run` method should be called to ensure the example runs.
# More code can run afterwards (in the block).
define_example_hook :prepend_around_each
end
end

View File

@ -16,11 +16,6 @@ module Spectator
# These take a pre-built hook instance, or arguments to pass to the hook type's initializer.
# The new hook is added a collection in the order specified by *order*.
#
# Alternate methods are also generated that add hooks in the opposite order of *order*.
# These are prefixed with the opposite order word.
# For instance, when *order* is "append", the prefix will be "prepend",
# resulting in a method named `prepend_some_hook`.
#
# A private getter method is created so that the hooks can be accessed if needed.
# The getter method has `_hooks` appended to the hook name.
# For instance, if the *declaration* contains `important_thing`, then the getter is `important_thing_hooks`.
@ -46,12 +41,8 @@ module Spectator
macro define_hook(declaration, order = :append, &block)
{% if order.id == :append.id
method = :push.id
alt_method = :unshift.id
alt_prefix = :prepend.id
elsif order.id == :prepend.id
method = :unshift.id
alt_method = :push.id
alt_prefix = :append.id
else
raise "Unknown hook order type - #{order}"
end %}
@ -81,28 +72,6 @@ module Spectator
{{declaration.var}}(hook)
end
# Registers a new "{{declaration.var}}" hook.
# The hook will be {{alt_prefix}}ed to the list.
def {{alt_prefix}}_{{declaration.var}}(hook : {{declaration.type}}) : Nil
@{{declaration.var}}_hooks.{{alt_method}}(hook)
end
# Registers a new "{{declaration.var}}" hook.
# The hook will be {{alt_prefix}}ed to the list.
# A new hook will be created by passing args to `{{declaration.type}}.new`.
def {{alt_prefix}}_{{declaration.var}}(*args, **kwargs) : Nil
hook = {{declaration.type}}.new(*args, **kwargs)
{{alt_prefix}}_{{declaration.var}}(hook)
end
# Registers a new "{{declaration.var}}" hook.
# The hook will be {{alt_prefix}}ed to the list.
# A new hook will be created by passing args to `{{declaration.type}}.new`.
def {{alt_prefix}}_{{declaration.var}}(*args, **kwargs, &block) : Nil
hook = {{declaration.type}}.new(*args, **kwargs, &block)
{{alt_prefix}}_{{declaration.var}}(hook)
end
{% if block %}
# Handles calling all "{{declaration.var}}" hooks.
protected def call_{{declaration.var}}({{block.args.splat}})

View File

@ -18,17 +18,7 @@ module Spectator
class SpecBuilder
Log = ::Spectator::Log.for(self)
delegate before_all,
prepend_before_all,
after_all,
append_after_all,
before_each,
prepend_before_each,
after_each,
append_after_each,
around_each,
prepend_around_each,
to: current
delegate before_all, after_all, before_each, after_each, around_each, to: current
# Stack tracking the current group.
# The bottom of the stack (first element) is the root group.
@ -73,6 +63,12 @@ module Spectator
def start_group(name, location = nil, metadata = Metadata.new) : Nil
Log.trace { "Start group: #{name.inspect} @ #{location}; metadata: #{metadata}" }
builder = ExampleGroupBuilder.new(name, location, metadata)
# `before_all` and `after_all` hooks from config are slightly different.
# They are applied to every top-level group (groups just under root).
apply_top_level_config_hooks(builder) if root?
# Add group to the stack.
current << builder
@stack.push(builder)
end
@ -93,6 +89,12 @@ module Spectator
def start_iterative_group(collection, name, iterator = nil, location = nil, metadata = Metadata.new) : Nil
Log.trace { "Start iterative group: #{name} (#{typeof(collection)}) @ #{location}; metadata: #{metadata}" }
builder = IterativeExampleGroupBuilder.new(collection, name, iterator, location, metadata)
# `before_all` and `after_all` hooks from config are slightly different.
# They are applied to every top-level group (groups just under root).
apply_top_level_config_hooks(builder) if root?
# Add group to the stack.
current << builder
@stack.push(builder)
end
@ -161,20 +163,6 @@ module Spectator
root.before_all(*args, **kwargs, &block)
end
# Registers a new "before_suite" hook.
# The hook will be prepended to the list.
# A new hook will be created by passing args to `ExampleGroupHook.new`.
def prepend_before_suite(*args, **kwargs) : Nil
root.prepend_before_all(*args, **kwargs)
end
# Registers a new "before_suite" hook.
# The hook will be prepended to the list.
# A new hook will be created by passing args to `ExampleGroupHook.new`.
def prepend_before_suite(*args, **kwargs, &block) : Nil
root.prepend_before_all(*args, **kwargs, &block)
end
# Registers a new "after_suite" hook.
# The hook will be prepended to the list.
# A new hook will be created by passing args to `ExampleGroupHook.new`.
@ -189,20 +177,6 @@ module Spectator
root.after_all(*args, **kwargs, &block)
end
# Registers a new "after_suite" hook.
# The hook will be appended to the list.
# A new hook will be created by passing args to `ExampleGroupHook.new`.
def append_after_suite(*args, **kwargs) : Nil
root.append_after_all(*args, **kwargs)
end
# Registers a new "after_suite" hook.
# The hook will be appended to the list.
# A new hook will be created by passing args to `ExampleGroupHook.new`.
def append_after_suite(*args, **kwargs, &block) : Nil
root.append_after_all(*args, **kwargs, &block)
end
# Checks if the current group is the root group.
private def root?
@stack.size == 1
@ -219,28 +193,20 @@ module Spectator
@stack.last
end
# Retrieves the configuration.
# If one wasn't previously set, a default configuration is used.
private def config : Config
@config || Config.default
# Copy all hooks from config to root group.
private def apply_config_hooks(group)
@config.before_suite_hooks.each { |hook| group.before_all(hook) }
@config.after_suite_hooks.reverse_each { |hook| group.after_all(hook) }
@config.before_each_hooks.each { |hook| group.before_each(hook) }
@config.after_each_hooks.reverse_each { |hook| group.after_each(hook) }
@config.around_each_hooks.each { |hook| group.around_each(hook) }
end
# Copy all hooks from config to top-level group.
private def apply_config_hooks(group)
config.before_suite_hooks.reverse_each { |hook| group.prepend_before_all(hook) }
config.after_suite_hooks.each { |hook| group.append_after_all(hook) }
config.before_each_hooks.reverse_each { |hook| group.prepend_before_each(hook) }
config.after_each_hooks.each { |hook| group.append_after_each(hook) }
config.around_each_hooks.reverse_each { |hook| group.prepend_around_each(hook) }
# `before_all` and `after_all` hooks from config are slightly different.
# They are applied to every top-level group (groups just under root).
group.each do |node|
next unless node.is_a?(Hooks)
config.before_all_hooks.reverse_each { |hook| node.prepend_before_all(hook.dup) }
config.after_all_hooks.each { |hook| node.append_after_all(hook.dup) }
end
# Copy `before_all` and `after_all` hooks to a group.
private def apply_top_level_config_hooks(group)
# Hooks are dupped so that they retain their original state (call once).
@config.before_all_hooks.each { |hook| group.before_all(hook.dup) }
@config.after_all_hooks.reverse_each { |hook| group.after_all(hook.dup) }
end
end
end