From a08d5202fe50b9d7567a7bdfecee428728c0c49f Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Sat, 5 Jun 2021 12:51:46 -0600 Subject: [PATCH] Implement pending examples as lighweight examples Drop test code block if a pending, skip, or x-prefix macro is used. --- src/spectator/dsl/builder.cr | 8 +++++ src/spectator/dsl/examples.cr | 56 +++++++++++++++++++++++++++++++---- src/spectator/example.cr | 12 ++++++++ src/spectator/spec/builder.cr | 19 ++++++++++++ 4 files changed, 89 insertions(+), 6 deletions(-) diff --git a/src/spectator/dsl/builder.cr b/src/spectator/dsl/builder.cr index 3d9ec47..3054f18 100644 --- a/src/spectator/dsl/builder.cr +++ b/src/spectator/dsl/builder.cr @@ -37,6 +37,14 @@ module Spectator::DSL @@builder.add_example(*args, &block) 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. def before_all(location = nil, label = "before_all", &block) hook = ExampleGroupHook.new(location: location, label: label, &block) diff --git a/src/spectator/dsl/examples.cr b/src/spectator/dsl/examples.cr index 9cc9c85..53b01b5 100644 --- a/src/spectator/dsl/examples.cr +++ b/src/spectator/dsl/examples.cr @@ -11,6 +11,10 @@ module Spectator::DSL # Defines a macro to generate code for an example. # 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*. # The tags are merged with parent groups. # Any items with falsey values from *metadata* remove the corresponding tag. @@ -60,6 +64,50 @@ module Spectator::DSL 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 # Inserts the correct representation of a example's name. @@ -82,12 +130,8 @@ module Spectator::DSL define_example :specify - define_example :xexample, :pending + define_pending_example :pending - define_example :xspecify, :pending - - define_example :xit, :pending - - define_example :skip, :pending + define_pending_example :skip end end diff --git a/src/spectator/example.cr b/src/spectator/example.cr index bdef6a3..d3ac530 100644 --- a/src/spectator/example.cr +++ b/src/spectator/example.cr @@ -66,6 +66,18 @@ module Spectator group << self if group 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. # Returns the result of the execution. # The result will also be stored in `#result`. diff --git a/src/spectator/spec/builder.cr b/src/spectator/spec/builder.cr index 6aa6188..090de03 100644 --- a/src/spectator/spec/builder.cr +++ b/src/spectator/spec/builder.cr @@ -105,6 +105,25 @@ module Spectator # The example is added to the current group by `Example` initializer. 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. def before_all(hook) Log.trace { "Add before_all hook #{hook}" }