From 0f7a9ed9e8b39aa5848c1380bb547b896be8b78e Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Sun, 8 Aug 2021 11:50:30 -0600 Subject: [PATCH] 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. --- CHANGELOG.md | 9 ++-- src/spectator/dsl/builder.cr | 42 ------------------ src/spectator/dsl/hooks.cr | 47 -------------------- src/spectator/hooks.cr | 31 ------------- src/spectator/spec_builder.cr | 84 +++++++++++------------------------ 5 files changed, 29 insertions(+), 184 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7d70df..e7ebaa4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/src/spectator/dsl/builder.cr b/src/spectator/dsl/builder.cr index 9d60dba..4054906 100644 --- a/src/spectator/dsl/builder.cr +++ b/src/spectator/dsl/builder.cr @@ -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. # diff --git a/src/spectator/dsl/hooks.cr b/src/spectator/dsl/hooks.cr index 0cae6ee..d11ff08 100644 --- a/src/spectator/dsl/hooks.cr +++ b/src/spectator/dsl/hooks.cr @@ -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 diff --git a/src/spectator/hooks.cr b/src/spectator/hooks.cr index 1dd60db..f06b0e1 100644 --- a/src/spectator/hooks.cr +++ b/src/spectator/hooks.cr @@ -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}}) diff --git a/src/spectator/spec_builder.cr b/src/spectator/spec_builder.cr index cc274e5..c4d1485 100644 --- a/src/spectator/spec_builder.cr +++ b/src/spectator/spec_builder.cr @@ -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