Cleaner distinction between metadata and tags

This commit is contained in:
Michael Miller 2021-06-12 16:45:45 -06:00
parent 704c28e822
commit 04d6c70f59
No known key found for this signature in database
GPG Key ID: FB9F12F7C646A4AD
10 changed files with 72 additions and 64 deletions

View File

@ -1,12 +1,12 @@
require "../context"
require "../location"
require "./builder"
require "./tags"
require "./metadata"
module Spectator::DSL
# DSL methods for defining examples and test code.
module Examples
include Tags
include Metadata
# Defines a macro to generate code for an example.
# The *name* is the name given to the macro.
@ -39,8 +39,8 @@ module Spectator::DSL
\{% 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 %}
_spectator_tags(%tags, :tags, {{tags.splat(",")}} {{metadata.double_splat}})
_spectator_tags(\%tags, %tags, \{{tags.splat(",")}} \{{metadata.double_splat}})
_spectator_metadata(%metadata, :metadata, {{tags.splat(",")}} {{metadata.double_splat}})
_spectator_metadata(\%metadata, %metadata, \{{tags.splat(",")}} \{{metadata.double_splat}})
\{% if block %}
\{% raise "Block argument count '{{name.id}}' hook must be 0..1" if block.args.size > 1 %}
@ -53,7 +53,7 @@ module Spectator::DSL
_spectator_example_name(\{{what}}),
::Spectator::Location.new(\{{block.filename}}, \{{block.line_number}}, \{{block.end_line_number}}),
new.as(::Spectator::Context),
\%tags
\%metadata
) do |example|
example.with_context(\{{@type.name}}) do
\{% if block.args.empty? %}
@ -68,7 +68,7 @@ module Spectator::DSL
::Spectator::DSL::Builder.add_pending_example(
_spectator_example_name(\{{what}}),
::Spectator::Location.new(\{{what.filename}}, \{{what.line_number}}),
\%tags,
\%metadata,
"Not yet implemented"
)
\{% end %}
@ -105,13 +105,13 @@ module Spectator::DSL
\{% 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 && block.args.size > 1 %}
_spectator_tags(%tags, :tags, {{tags.splat(",")}} {{metadata.double_splat}})
_spectator_tags(\%tags, %tags, \{{tags.splat(",")}} \{{metadata.double_splat}})
_spectator_metadata(%metadata, :metadata, {{tags.splat(",")}} {{metadata.double_splat}})
_spectator_metadata(\%metadata, %metadata, \{{tags.splat(",")}} \{{metadata.double_splat}})
::Spectator::DSL::Builder.add_pending_example(
_spectator_example_name(\{{what}}),
::Spectator::Location.new(\{{(what || block).filename}}, \{{(what || block).line_number}}, \{{(what || block).end_line_number}}),
\%tags,
\%metadata,
\{% if !block %}"Not yet implemented"\{% end %}
)
end

View File

@ -1,13 +1,13 @@
require "../location"
require "./builder"
require "./tags"
require "./memoize"
require "./metadata"
module Spectator::DSL
# DSL methods and macros for creating example groups.
# This module should be included as a mix-in.
module Groups
include Tags
include Metadata
# Defines a macro to generate code for an example group.
# The *name* is the name given to the macro.
@ -36,13 +36,13 @@ module Spectator::DSL
class Group\%group < \{{@type.id}}
_spectator_group_subject(\{{what}})
_spectator_tags(:tags, :super, {{tags.splat(", ")}} {{metadata.double_splat}})
_spectator_tags(:tags, :previous_def, \{{tags.splat(", ")}} \{{metadata.double_splat}})
_spectator_metadata(:metadata, :super, {{tags.splat(", ")}} {{metadata.double_splat}})
_spectator_metadata(:metadata, :previous_def, \{{tags.splat(", ")}} \{{metadata.double_splat}})
::Spectator::DSL::Builder.start_group(
_spectator_group_name(\{{what}}),
::Spectator::Location.new(\{{block.filename}}, \{{block.line_number}}),
tags
metadata
)
\{{block.body}}

View File

@ -1,25 +1,25 @@
module Spectator::DSL
module Tags
# Defines a class method named *name* that combines tags
module Metadata
# Defines a class method named *name* that combines metadata
# returned by *source* with *tags* and *metadata*.
# Any falsey items from *metadata* are removed.
private macro _spectator_tags(name, source, *tags, **metadata)
private macro _spectator_metadata(name, source, *tags, **metadata)
private def self.{{name.id}}
%tags = {{source.id}}.dup
%metadata = {{source.id}}.dup
{% for k in tags %}
%tags[{{k.id.symbolize}}] = nil
%metadata[{{k.id.symbolize}}] = nil
{% end %}
{% for k, v in metadata %}
%cond = begin
{{v}}
end
if %cond
%tags[{{k.id.symbolize}}] = %cond.to_s
%metadata[{{k.id.symbolize}}] = %cond.to_s
else
%tags.delete({{k.id.symbolize}})
%metadata.delete({{k.id.symbolize}})
end
{% end %}
%tags
%metadata
end
end
end

View File

@ -5,7 +5,7 @@ require "./location"
require "./node"
require "./pending_result"
require "./result"
require "./tags"
require "./metadata"
module Spectator
# Standard example that runs a test case.
@ -35,12 +35,12 @@ module Spectator
# 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.
# A set of *metadata* can be used for filtering and modifying example behavior.
# Note: The metadata will not be merged with the parent metadata.
def initialize(@context : Context, @entrypoint : self ->,
name : String? = nil, location : Location? = nil,
@group : ExampleGroup? = nil, tags = Tags.new)
super(name, location, tags)
@group : ExampleGroup? = nil, metadata = Metadata.new)
super(name, location, metadata)
# Ensure group is linked.
group << self if group
@ -53,11 +53,11 @@ module Spectator
# 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.
# A set of *metadata* can be used for filtering and modifying example behavior.
# Note: The metadata will not be merged with the parent metadata.
def initialize(name : String? = nil, location : Location? = nil,
@group : ExampleGroup? = nil, tags = Tags.new, &block : self ->)
super(name, location, tags)
@group : ExampleGroup? = nil, metadata = Metadata.new, &block : self ->)
super(name, location, metadata)
@context = NullContext.new
@entrypoint = block
@ -71,13 +71,13 @@ module Spectator
# 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.
# A set of *metadata* can be used for filtering and modifying example behavior.
# Note: The metadata will not be merged with the parent metadata.
def self.pending(name : String? = nil, location : Location? = nil,
group : ExampleGroup? = nil, tags = Tags.new, reason = nil)
group : ExampleGroup? = nil, metadata = Metadata.new, reason = nil)
# Add pending tag and reason if they don't exist.
tags = tags.merge({:pending => nil, :reason => reason}) { |_, v, _| v }
new(name, location, group, tags) { nil }
metadata = metadata.merge({:pending => nil, :reason => reason}) { |_, v, _| v }
new(name, location, group, metadata) { nil }
end
# Executes the test case.

View File

@ -86,9 +86,9 @@ module Spectator
# It can be a `Symbol` to describe a type.
# The *location* tracks where the group exists in source code.
# This group will be assigned to the parent *group* if it is provided.
# A set of *tags* can be used for filtering and modifying example behavior.
# A set of *metadata* can be used for filtering and modifying example behavior.
def initialize(@name : Label = nil, @location : Location? = nil,
@group : ExampleGroup? = nil, @tags : Tags = Tags.new)
@group : ExampleGroup? = nil, @metadata : Metadata = Metadata.new)
# Ensure group is linked.
group << self if group
end

View File

@ -37,6 +37,7 @@ require "./line_example_filter"
require "./location"
require "./location_example_filter"
require "./matchers"
require "./metadata"
require "./mocks"
require "./name_example_filter"
require "./null_context"
@ -47,7 +48,6 @@ require "./profile"
require "./report"
require "./result"
require "./spec"
require "./tags"
require "./test_context"
require "./value"
require "./wrapper"

View File

@ -1,8 +1,11 @@
module Spectator
# User-defined keywords used for filtering and behavior modification.
alias Tags = Set(Symbol)
# User-defined keywords used for filtering and behavior modification.
# The value of a tag is optional, but may contain useful information.
# If the value is nil, the tag exists, but has no data.
# However, when tags are given on examples and example groups,
# if the value is falsey (false or nil), then the tag should be removed from the overall collection.
alias Tags = Hash(Symbol, String?)
alias Metadata = Hash(Symbol, String?)
end

View File

@ -1,6 +1,6 @@
require "./label"
require "./location"
require "./tags"
require "./metadata"
module Spectator
# A single item in a test spec.
@ -29,15 +29,15 @@ module Spectator
protected def name=(@name : String)
end
# User-defined keywords used for filtering and behavior modification.
getter tags : Tags
# User-defined tags and values used for filtering and behavior modification.
getter metadata : Metadata
# Creates the node.
# The *name* describes the purpose of the node.
# It can be a `Symbol` to describe a type.
# The *location* tracks where the node exists in source code.
# A set of *tags* can be used for filtering and modifying example behavior.
def initialize(@name : Label = nil, @location : Location? = nil, @tags : Tags = Tags.new)
# A set of *metadata* can be used for filtering and modifying example behavior.
def initialize(@name : Label = nil, @location : Location? = nil, @metadata : Metadata = Metadata.new)
end
# Indicates whether the node has completed.
@ -46,12 +46,17 @@ module Spectator
# Checks if the node has been marked as pending.
# Pending items should be skipped during execution.
def pending?
tags.has_key?(:pending) || tags.has_key?(:skip)
metadata.has_key?(:pending) || metadata.has_key?(:skip)
end
# Gets the reason the node has been marked as pending.
def pending_reason
tags[:pending]? || tags[:skip]? || tags[:reason]? || DEFAULT_PENDING_REASON
metadata[:pending]? || metadata[:skip]? || metadata[:reason]? || DEFAULT_PENDING_REASON
end
# Retrieves just the tag names applied to the node.
def tags
Tags.new(metadata.keys)
end
# Constructs the full name or description of the node.

View File

@ -3,7 +3,7 @@ require "../example"
require "../example_context_method"
require "../example_group"
require "../spec"
require "../tags"
require "../metadata"
module Spectator
class Spec
@ -53,14 +53,14 @@ module Spectator
#
# The *location* optionally defined where the group originates in source code.
#
# A set of *tags* can be used for filtering and modifying example behavior.
# A set of *metadata* can be used for filtering and modifying example behavior.
# For instance, adding a "pending" tag will mark tests as pending and skip execution.
#
# The newly created group is returned.
# It shouldn't be used outside of this class until a matching `#end_group` is called.
def start_group(name, location = nil, tags = Tags.new) : ExampleGroup
Log.trace { "Start group: #{name.inspect} @ #{location}; tags: #{tags}" }
ExampleGroup.new(name, location, current_group, tags).tap do |group|
def start_group(name, location = nil, metadata = Metadata.new) : ExampleGroup
Log.trace { "Start group: #{name.inspect} @ #{location}; metadata: #{metadata}" }
ExampleGroup.new(name, location, current_group, metadata).tap do |group|
@group_stack << group
end
end
@ -90,7 +90,7 @@ module Spectator
# The *context* is an instance of the context the test code should run in.
# See `Context` for more information.
#
# A set of *tags* can be used for filtering and modifying example behavior.
# A set of *metadata* can be used for filtering and modifying example behavior.
# For instance, adding a "pending" tag will mark the test as pending and skip execution.
#
# A block must be provided.
@ -99,9 +99,9 @@ module Spectator
# It is expected that the test code runs when the block is called.
#
# The newly created example is returned.
def add_example(name, location, context, tags = Tags.new, &block : Example -> _) : Example
Log.trace { "Add example: #{name} @ #{location}; tags: #{tags}" }
Example.new(context, block, name, location, current_group, tags)
def add_example(name, location, context, metadata = Metadata.new, &block : Example -> _) : Example
Log.trace { "Add example: #{name} @ #{location}; metadata: #{metadata}" }
Example.new(context, block, name, location, current_group, metadata)
# The example is added to the current group by `Example` initializer.
end
@ -114,14 +114,14 @@ module Spectator
#
# The *location* optionally defined where the example originates in source code.
#
# A set of *tags* can be used for filtering and modifying example behavior.
# A set of *metadata* can be used for filtering and modifying example behavior.
# For instance, adding a "pending" tag will mark the test as pending and skip execution.
# A default *reason* can be given in case the user didn't provide one.
#
# The newly created example is returned.
def add_pending_example(name, location, tags = Tags.new, reason = nil) : Example
Log.trace { "Add pending example: #{name} @ #{location}; tags: #{tags}" }
Example.pending(name, location, current_group, tags, reason)
def add_pending_example(name, location, metadata = Metadata.new, reason = nil) : Example
Log.trace { "Add pending example: #{name} @ #{location}; metadata: #{metadata}" }
Example.pending(name, location, current_group, metadata, reason)
# The example is added to the current group by `Example` initializer.
end

View File

@ -1,7 +1,7 @@
require "./context"
require "./dsl"
require "./lazy_wrapper"
require "./tags"
require "./metadata"
# Class used as the base for all specs using the DSL.
# It adds methods and macros necessary to use the DSL from the spec.
@ -32,9 +32,9 @@ class SpectatorTestContext < SpectatorContext
@subject.get { _spectator_implicit_subject }
end
# Initial tags for tests.
# Initial metadata for tests.
# This method should be overridden by example groups and examples.
private def self.tags
::Spectator::Tags.new
private def self.metadata
::Spectator::Metadata.new
end
end