Implement pending examples as lighweight examples

Drop test code block if a pending, skip, or x-prefix macro is used.
This commit is contained in:
Michael Miller 2021-06-05 12:51:46 -06:00
parent b738b6b3ff
commit a08d5202fe
No known key found for this signature in database
GPG key ID: FB9F12F7C646A4AD
4 changed files with 89 additions and 6 deletions

View file

@ -37,6 +37,14 @@ module Spectator::DSL
@@builder.add_example(*args, &block) @@builder.add_example(*args, &block)
end end
# Defines a new pending example.
# The example is added to the group currently on the top of the stack.
#
# See `Spec::Builder#add_pending_example` for usage details.
def add_pending_example(*args)
@@builder.add_pending_example(*args)
end
# Defines a block of code to execute before any and all examples in the current group. # 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) def before_all(location = nil, label = "before_all", &block)
hook = ExampleGroupHook.new(location: location, label: label, &block) hook = ExampleGroupHook.new(location: location, label: label, &block)

View file

@ -11,6 +11,10 @@ module Spectator::DSL
# Defines a macro to generate code for an example. # Defines a macro to generate code for an example.
# The *name* is the name given to the macro. # The *name* is the name given to the macro.
# #
# In addition, another macro is defined that marks the example as pending.
# The pending macro is prefixed with 'x'.
# For instance, `define_example :it` defines `it` and `xit`.
#
# Default tags can be provided with *tags* and *metadata*. # Default tags can be provided with *tags* and *metadata*.
# The tags are merged with parent groups. # The tags are merged with parent groups.
# Any items with falsey values from *metadata* remove the corresponding tag. # Any items with falsey values from *metadata* remove the corresponding tag.
@ -60,6 +64,50 @@ module Spectator::DSL
end end
end end
end end
define_pending_example :x{{name.id}}
end
# Defines a macro to generate code for a pending example.
# The *name* is the name given to the macro.
#
# The block for the example's content is discarded at compilation time.
# This prevents issues with undefined methods, signature differences, etc.
#
# Default tags can be provided with *tags* and *metadata*.
# The tags are merged with parent groups.
# Any items with falsey values from *metadata* remove the corresponding tag.
macro define_pending_example(name, *tags, **metadata)
# Defines a pending example.
#
# If a block is given, it is treated as the code to test.
# The block is provided the current example instance as an argument.
#
# The first argument names the example (test).
# Typically, this specifies what is being tested.
# It has no effect on the test and is purely used for output.
# If omitted, a name is generated from the first assertion in the test.
#
# The example will be marked as pending if the block is omitted.
# A block or name must be provided.
#
# Tags can be specified by adding symbols (keywords) after the first argument.
# Key-value pairs can also be specified.
# Any falsey items will remove a previously defined tag.
macro {{name.id}}(what = nil, *tags, **metadata, &block)
\{% raise "Cannot use '{{name.id}}' inside of a test block" if @def %}
\{% raise "A description or block must be provided. Cannot use '{{name.id}}' alone." unless what || block %}
\{% raise "Block argument count '{{name.id}}' hook must be 0..1" if block.args.size > 1 %}
_spectator_tags(%tags, :tags, {{tags.splat(",")}} {{metadata.double_splat}})
_spectator_tags(\%tags, %tags, \{{tags.splat(",")}} \{{metadata.double_splat}})
::Spectator::DSL::Builder.add_pending_example(
_spectator_example_name(\{{what}}),
::Spectator::Location.new(\{{block.filename}}, \{{block.line_number}}, \{{block.end_line_number}}),
\%tags
)
end
end end
# Inserts the correct representation of a example's name. # Inserts the correct representation of a example's name.
@ -82,12 +130,8 @@ module Spectator::DSL
define_example :specify define_example :specify
define_example :xexample, :pending define_pending_example :pending
define_example :xspecify, :pending define_pending_example :skip
define_example :xit, :pending
define_example :skip, :pending
end end
end end

View file

@ -66,6 +66,18 @@ module Spectator
group << self if group group << self if group
end end
# Creates a pending example.
# The *name* describes the purpose of the example.
# It can be a `Symbol` to describe a type.
# The *location* tracks where the example exists in source code.
# The example will be assigned to *group* if it is provided.
# A set of *tags* can be used for filtering and modifying example behavior.
# Note: The tags will not be merged with the parent tags.
def self.pending(name : String? = nil, location : Location? = nil,
group : ExampleGroup? = nil, tags = Tags.new)
new(name, location, group, tags.add(:pending)) { nil }
end
# Executes the test case. # Executes the test case.
# Returns the result of the execution. # Returns the result of the execution.
# The result will also be stored in `#result`. # The result will also be stored in `#result`.

View file

@ -105,6 +105,25 @@ 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 new pending example.
# The example is added to the group currently on the top of the stack.
#
# The *name* is the name or brief description of the example.
# This should be a string or nil.
# When nil, the example's name will be an anonymous example reference.
#
# The *location* optionally defined where the example originates in source code.
#
# A set of *tags* can be used for filtering and modifying example behavior.
# For instance, adding a "pending" tag will mark the test as pending and skip execution.
#
# The newly created example is returned.
def add_pending_example(name, location, tags = Tags.new) : Example
Log.trace { "Add pending example: #{name} @ #{location}; tags: #{tags}" }
Example.pending(name, location, current_group, tags)
# The example is added to the current group by `Example` initializer.
end
# Attaches a hook to be invoked before any and all examples in the current group. # Attaches a hook to be invoked before any and all examples in the current group.
def before_all(hook) def before_all(hook)
Log.trace { "Add before_all hook #{hook}" } Log.trace { "Add before_all hook #{hook}" }