diff --git a/CHANGELOG.md b/CHANGELOG.md index c13bd55..704bc9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Simplify string representation of mock-related types. - Remove unnecessary redefinitions of methods when adding stub functionality to a type. +- Allow metadata to be stored as nil to reduce overhead when tracking nodes without tags. ## [0.11.4] ### Added diff --git a/src/spectator/dsl/metadata.cr b/src/spectator/dsl/metadata.cr index 308bcbd..04092b9 100644 --- a/src/spectator/dsl/metadata.cr +++ b/src/spectator/dsl/metadata.cr @@ -6,6 +6,9 @@ module Spectator::DSL private macro _spectator_metadata(name, source, *tags, **metadata) private def self.{{name.id}} %metadata = {{source.id}}.dup + {% unless tags.empty? && metadata.empty? %} + %metadata ||= ::Spectator::Metadata.new + {% end %} {% for k in tags %} %metadata[{{k.id.symbolize}}] = nil {% end %} diff --git a/src/spectator/example.cr b/src/spectator/example.cr index 3625ded..67483e0 100644 --- a/src/spectator/example.cr +++ b/src/spectator/example.cr @@ -40,7 +40,7 @@ module Spectator # 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, metadata = Metadata.new) + @group : ExampleGroup? = nil, metadata = nil) super(name, location, metadata) # Ensure group is linked. @@ -58,7 +58,7 @@ module Spectator # Note: The metadata will not be merged with the parent metadata. def initialize(@context : Context, @entrypoint : self ->, @name_proc : Example -> String, location : Location? = nil, - @group : ExampleGroup? = nil, metadata = Metadata.new) + @group : ExampleGroup? = nil, metadata = nil) super(nil, location, metadata) # Ensure group is linked. @@ -75,7 +75,7 @@ module Spectator # 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, metadata = Metadata.new, &block : self ->) + @group : ExampleGroup? = nil, metadata = nil, &block : self ->) super(name, location, metadata) @context = NullContext.new @@ -93,9 +93,10 @@ module Spectator # 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, metadata = Metadata.new, reason = nil) + group : ExampleGroup? = nil, metadata = nil, reason = nil) # Add pending tag and reason if they don't exist. - metadata = metadata.merge({:pending => nil, :reason => reason}) { |_, v, _| v } + tags = {:pending => nil, :reason => reason} + metadata = metadata ? metadata.merge(tags) { |_, v, _| v } : tags new(name, location, group, metadata) { nil } end diff --git a/src/spectator/example_builder.cr b/src/spectator/example_builder.cr index bb640df..23398d2 100644 --- a/src/spectator/example_builder.cr +++ b/src/spectator/example_builder.cr @@ -15,7 +15,7 @@ module Spectator # The *entrypoint* indicates the proc used to invoke the test code in the example. # The *name*, *location*, and *metadata* will be applied to the `Example` produced by `#build`. def initialize(@context_builder : -> Context, @entrypoint : Example ->, - @name : String? = nil, @location : Location? = nil, @metadata : Metadata = Metadata.new) + @name : String? = nil, @location : Location? = nil, @metadata : Metadata? = nil) end # Creates the builder. @@ -24,7 +24,7 @@ module Spectator # The *name* is an interpolated string that runs in the context of the example. # *location*, and *metadata* will be applied to the `Example` produced by `#build`. def initialize(@context_builder : -> Context, @entrypoint : Example ->, - @name : Example -> String, @location : Location? = nil, @metadata : Metadata = Metadata.new) + @name : Example -> String, @location : Location? = nil, @metadata : Metadata? = nil) end # Constructs an example with previously defined attributes and context. diff --git a/src/spectator/example_group.cr b/src/spectator/example_group.cr index dc1fa57..277be3c 100644 --- a/src/spectator/example_group.cr +++ b/src/spectator/example_group.cr @@ -79,7 +79,7 @@ module Spectator # This group will be assigned to the parent *group* if it is provided. # A set of *metadata* can be used for filtering and modifying example behavior. def initialize(@name : Label = nil, @location : Location? = nil, - @group : ExampleGroup? = nil, @metadata : Metadata = Metadata.new) + @group : ExampleGroup? = nil, @metadata : Metadata? = nil) # Ensure group is linked. group << self if group end diff --git a/src/spectator/example_group_builder.cr b/src/spectator/example_group_builder.cr index 05c740f..207cb6e 100644 --- a/src/spectator/example_group_builder.cr +++ b/src/spectator/example_group_builder.cr @@ -28,7 +28,7 @@ module Spectator # Creates the builder. # Initially, the builder will have no children and no hooks. # The *name*, *location*, and *metadata* will be applied to the `ExampleGroup` produced by `#build`. - def initialize(@name : Label = nil, @location : Location? = nil, @metadata : Metadata = Metadata.new) + def initialize(@name : Label = nil, @location : Location? = nil, @metadata : Metadata? = nil) end # Constructs an example group with previously defined attributes, children, and hooks. diff --git a/src/spectator/example_group_iteration.cr b/src/spectator/example_group_iteration.cr index d6576d2..0d20a29 100644 --- a/src/spectator/example_group_iteration.cr +++ b/src/spectator/example_group_iteration.cr @@ -18,7 +18,7 @@ module Spectator # This group will be assigned to the parent *group* if it is provided. # A set of *metadata* can be used for filtering and modifying example behavior. def initialize(@item : T, name : Label = nil, location : Location? = nil, - group : ExampleGroup? = nil, metadata : Metadata = Metadata.new) + group : ExampleGroup? = nil, metadata : Metadata? = nil) super(name, location, group, metadata) end end diff --git a/src/spectator/iterative_example_group_builder.cr b/src/spectator/iterative_example_group_builder.cr index 39ad549..fe67c7a 100644 --- a/src/spectator/iterative_example_group_builder.cr +++ b/src/spectator/iterative_example_group_builder.cr @@ -15,7 +15,7 @@ module Spectator # The *collection* is the set of items to create sub-nodes for. # The *iterators* is a list of optional names given to items in the collection. def initialize(@collection : Enumerable(T), name : String? = nil, @iterators : Array(String) = [] of String, - location : Location? = nil, metadata : Metadata = Metadata.new) + location : Location? = nil, metadata : Metadata? = nil) super(name, location, metadata) end diff --git a/src/spectator/node.cr b/src/spectator/node.cr index 807c8df..6a5d068 100644 --- a/src/spectator/node.cr +++ b/src/spectator/node.cr @@ -30,14 +30,16 @@ module Spectator end # User-defined tags and values used for filtering and behavior modification. - getter metadata : Metadata + def metadata : Metadata + @metadata ||= Metadata.new + end # 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 *metadata* can be used for filtering and modifying example behavior. - def initialize(@name : Label = nil, @location : Location? = nil, @metadata : Metadata = Metadata.new) + def initialize(@name : Label = nil, @location : Location? = nil, @metadata : Metadata? = nil) end # Indicates whether the node has completed. @@ -46,17 +48,25 @@ module Spectator # Checks if the node has been marked as pending. # Pending items should be skipped during execution. def pending? - metadata.has_key?(:pending) || metadata.has_key?(:skip) + return false unless md = @metadata + + md.has_key?(:pending) || md.has_key?(:skip) end # Gets the reason the node has been marked as pending. def pending_reason - metadata[:pending]? || metadata[:skip]? || metadata[:reason]? || DEFAULT_PENDING_REASON + return DEFAULT_PENDING_REASON unless md = @metadata + + md[:pending]? || md[:skip]? || md[:reason]? || DEFAULT_PENDING_REASON end # Retrieves just the tag names applied to the node. def tags - Tags.new(metadata.keys) + if md = @metadata + Tags.new(md.keys) + else + Tags.new + end end # Non-nil name used to show the node name. diff --git a/src/spectator/pending_example_builder.cr b/src/spectator/pending_example_builder.cr index a1f0292..434efe5 100644 --- a/src/spectator/pending_example_builder.cr +++ b/src/spectator/pending_example_builder.cr @@ -11,7 +11,7 @@ module Spectator # The *name*, *location*, and *metadata* will be applied to the `Example` produced by `#build`. # A default *reason* can be given in case the user didn't provide one. def initialize(@name : String? = nil, @location : Location? = nil, - @metadata : Metadata = Metadata.new, @reason : String? = nil) + @metadata : Metadata? = nil, @reason : String? = nil) end # Constructs an example with previously defined attributes. diff --git a/src/spectator/spec_builder.cr b/src/spectator/spec_builder.cr index 265b41d..17b0284 100644 --- a/src/spectator/spec_builder.cr +++ b/src/spectator/spec_builder.cr @@ -60,7 +60,7 @@ module Spectator # # 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. - def start_group(name, location = nil, metadata = Metadata.new) : Nil + def start_group(name, location = nil, metadata = nil) : Nil Log.trace { "Start group: #{name.inspect} @ #{location}; metadata: #{metadata}" } builder = ExampleGroupBuilder.new(name, location, metadata) @@ -86,7 +86,7 @@ module Spectator # # 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. - def start_iterative_group(collection, name, iterator = nil, location = nil, metadata = Metadata.new) : Nil + def start_iterative_group(collection, name, iterator = nil, location = nil, metadata = nil) : Nil Log.trace { "Start iterative group: #{name} (#{typeof(collection)}) @ #{location}; metadata: #{metadata}" } builder = IterativeExampleGroupBuilder.new(collection, name, iterator, location, metadata) @@ -127,7 +127,7 @@ module Spectator # It will be yielded two arguments - the example created by this method, and the *context* argument. # The return value of the block is ignored. # It is expected that the test code runs when the block is called. - def add_example(name, location, context_builder, metadata = Metadata.new, &block : Example -> _) : Nil + def add_example(name, location, context_builder, metadata = nil, &block : Example -> _) : Nil Log.trace { "Add example: #{name} @ #{location}; metadata: #{metadata}" } current << ExampleBuilder.new(context_builder, block, name, location, metadata) end @@ -144,7 +144,7 @@ module Spectator # 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. - def add_pending_example(name, location, metadata = Metadata.new, reason = nil) : Nil + def add_pending_example(name, location, metadata = nil, reason = nil) : Nil Log.trace { "Add pending example: #{name} @ #{location}; metadata: #{metadata}" } current << PendingExampleBuilder.new(name, location, metadata, reason) end diff --git a/src/spectator/tag_node_filter.cr b/src/spectator/tag_node_filter.cr index d360712..0dedd59 100644 --- a/src/spectator/tag_node_filter.cr +++ b/src/spectator/tag_node_filter.cr @@ -10,7 +10,9 @@ module Spectator # Checks whether the node satisfies the filter. def includes?(node) : Bool - node.metadata.any? { |key, value| key.to_s == @tag && (!@value || value == @value) } + return false unless metadata = node.metadata + + metadata.any? { |key, value| key.to_s == @tag && (!@value || value == @value) } end end end diff --git a/src/spectator/test_context.cr b/src/spectator/test_context.cr index a68c5b9..e04fe56 100644 --- a/src/spectator/test_context.cr +++ b/src/spectator/test_context.cr @@ -34,7 +34,7 @@ class SpectatorTestContext < SpectatorContext # Initial metadata for tests. # This method should be overridden by example groups and examples. - private def self.metadata - ::Spectator::Metadata.new + private def self.metadata : ::Spectator::Metadata? + nil end end