2021-02-13 05:46:22 +00:00
|
|
|
require "../location"
|
2020-09-13 00:40:56 +00:00
|
|
|
require "./builder"
|
2021-02-11 00:07:49 +00:00
|
|
|
require "./memoize"
|
2021-06-12 22:45:45 +00:00
|
|
|
require "./metadata"
|
2020-09-13 00:40:56 +00:00
|
|
|
|
2020-09-12 22:01:29 +00:00
|
|
|
module Spectator::DSL
|
|
|
|
# DSL methods and macros for creating example groups.
|
|
|
|
# This module should be included as a mix-in.
|
|
|
|
module Groups
|
2021-06-12 22:45:45 +00:00
|
|
|
include Metadata
|
2021-01-30 22:34:17 +00:00
|
|
|
|
2021-01-30 22:39:29 +00:00
|
|
|
# Defines a macro to generate code for an example group.
|
|
|
|
# The *name* is the name given to the macro.
|
|
|
|
#
|
|
|
|
# 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.
|
2021-01-30 19:32:13 +00:00
|
|
|
macro define_example_group(name, *tags, **metadata)
|
2020-09-26 03:44:17 +00:00
|
|
|
# Defines a new example group.
|
|
|
|
# The *what* argument is a name or description of the group.
|
2020-09-27 01:12:42 +00:00
|
|
|
#
|
2021-01-30 22:39:29 +00:00
|
|
|
# The first argument names the example (test).
|
|
|
|
# Typically, this specifies what is being tested.
|
|
|
|
# This argument is also used as the subject.
|
|
|
|
# When it is a type name, it becomes an explicit, which overrides any previous subjects.
|
|
|
|
# Otherwise it becomes an implicit subject, which doesn't override explicitly defined subjects.
|
|
|
|
#
|
|
|
|
# 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.
|
|
|
|
#
|
2020-09-27 01:12:42 +00:00
|
|
|
# TODO: Handle string interpolation in example and group names.
|
2021-01-30 07:40:59 +00:00
|
|
|
macro {{name.id}}(what, *tags, **metadata, &block)
|
2020-09-27 01:23:57 +00:00
|
|
|
\{% raise "Cannot use '{{name.id}}' inside of a test block" if @def %}
|
|
|
|
|
2020-10-17 18:12:04 +00:00
|
|
|
class Group\%group < \{{@type.id}}
|
2020-09-27 01:12:42 +00:00
|
|
|
_spectator_group_subject(\{{what}})
|
|
|
|
|
2021-06-12 22:45:45 +00:00
|
|
|
_spectator_metadata(:metadata, :super, {{tags.splat(", ")}} {{metadata.double_splat}})
|
|
|
|
_spectator_metadata(:metadata, :previous_def, \{{tags.splat(", ")}} \{{metadata.double_splat}})
|
2021-01-30 07:40:59 +00:00
|
|
|
|
2020-09-26 03:44:17 +00:00
|
|
|
::Spectator::DSL::Builder.start_group(
|
2020-09-27 01:11:52 +00:00
|
|
|
_spectator_group_name(\{{what}}),
|
2021-08-17 19:49:58 +00:00
|
|
|
::Spectator::Location.new(\{{block.filename}}, \{{block.line_number}}, \{{block.end_line_number}}),
|
2021-06-12 22:45:45 +00:00
|
|
|
metadata
|
2020-09-26 03:44:17 +00:00
|
|
|
)
|
2019-09-26 22:02:24 +00:00
|
|
|
|
2021-07-17 18:36:21 +00:00
|
|
|
\{{block.body if block}}
|
2019-09-15 15:15:33 +00:00
|
|
|
|
2020-09-26 03:44:17 +00:00
|
|
|
::Spectator::DSL::Builder.end_group
|
|
|
|
end
|
|
|
|
end
|
2019-09-15 15:15:33 +00:00
|
|
|
end
|
|
|
|
|
2021-07-17 19:25:38 +00:00
|
|
|
# Defines a macro to generate code for an iterative example group.
|
|
|
|
# The *name* is the name given to the macro.
|
|
|
|
#
|
|
|
|
# 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.
|
|
|
|
#
|
|
|
|
# If provided, a block can be used to modify collection that will be iterated.
|
|
|
|
# It takes a single argument - the original collection from the user.
|
|
|
|
# The modified collection should be returned.
|
|
|
|
#
|
|
|
|
# TODO: Handle string interpolation in example and group names.
|
|
|
|
macro define_iterative_group(name, *tags, **metadata, &block)
|
|
|
|
macro {{name.id}}(collection, *tags, count = nil, **metadata, &block)
|
|
|
|
\{% raise "Cannot use 'sample' inside of a test block" if @def %}
|
|
|
|
|
|
|
|
class Group\%group < \{{@type.id}}
|
|
|
|
_spectator_metadata(:metadata, :super, {{tags.splat(", ")}} {{metadata.double_splat}})
|
|
|
|
_spectator_metadata(:metadata, :previous_def, \{{tags.splat(", ")}} \{{metadata.double_splat}})
|
|
|
|
|
|
|
|
def self.\%collection
|
|
|
|
\{{collection}}
|
|
|
|
end
|
|
|
|
|
|
|
|
{% if block %}
|
|
|
|
def self.%mutate({{block.args.splat}})
|
|
|
|
{{block.body}}
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.\%collection
|
|
|
|
%mutate(previous_def)
|
|
|
|
end
|
|
|
|
{% end %}
|
|
|
|
|
|
|
|
\{% if count %}
|
|
|
|
def self.\%collection
|
|
|
|
previous_def.first(\{{count}})
|
|
|
|
end
|
|
|
|
\{% end %}
|
|
|
|
|
|
|
|
::Spectator::DSL::Builder.start_iterative_group(
|
|
|
|
\%collection,
|
|
|
|
\{{collection.stringify}},
|
2022-01-11 22:53:53 +00:00
|
|
|
[\{{block.args.empty? ? "".id : block.args.map(&.stringify).splat}}] of String,
|
2021-08-17 19:49:58 +00:00
|
|
|
::Spectator::Location.new(\{{block.filename}}, \{{block.line_number}}, \{{block.end_line_number}}),
|
2021-07-17 19:25:38 +00:00
|
|
|
metadata
|
|
|
|
)
|
|
|
|
|
|
|
|
\{% if block %}
|
2022-01-26 20:14:30 +00:00
|
|
|
\{% if block.args.size > 1 %}
|
|
|
|
\{% for arg, i in block.args %}
|
|
|
|
let(\{{arg}}) do |example|
|
|
|
|
example.group.as(::Spectator::ExampleGroupIteration(typeof(Group\%group.\%collection.first))).item[\{{i}}]
|
|
|
|
end
|
|
|
|
\{% end %}
|
|
|
|
\{% else %}
|
|
|
|
let(\{{block.args[0]}}) do |example|
|
|
|
|
example.group.as(::Spectator::ExampleGroupIteration(typeof(Group\%group.\%collection.first))).item
|
2021-07-17 19:25:38 +00:00
|
|
|
end
|
|
|
|
\{% end %}
|
|
|
|
|
|
|
|
\{{block.body}}
|
|
|
|
\{% end %}
|
|
|
|
|
|
|
|
::Spectator::DSL::Builder.end_group
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-09-27 01:11:52 +00:00
|
|
|
# Inserts the correct representation of a group's name.
|
|
|
|
# If *what* appears to be a type name, it will be symbolized.
|
|
|
|
# If it's a string, then it is dropped in as-is.
|
|
|
|
# For anything else, it is stringified.
|
2021-05-08 02:09:33 +00:00
|
|
|
# This is intended to be used to convert a description from the spec DSL to `Node#name`.
|
2020-09-27 01:11:52 +00:00
|
|
|
private macro _spectator_group_name(what)
|
|
|
|
{% if (what.is_a?(Generic) ||
|
|
|
|
what.is_a?(Path) ||
|
|
|
|
what.is_a?(TypeNode) ||
|
|
|
|
what.is_a?(Union)) &&
|
2020-10-20 02:34:18 +00:00
|
|
|
what.resolve?.is_a?(TypeNode) %}
|
2020-09-27 01:11:52 +00:00
|
|
|
{{what.symbolize}}
|
|
|
|
{% elsif what.is_a?(StringLiteral) ||
|
|
|
|
what.is_a?(NilLiteral) %}
|
|
|
|
{{what}}
|
2022-01-11 04:11:28 +00:00
|
|
|
{% elsif what.is_a?(StringInterpolation) %}
|
2022-12-21 04:40:47 +00:00
|
|
|
{{@type.name}}.new.eval do
|
|
|
|
{{what}}
|
|
|
|
rescue e
|
|
|
|
"<Failed to evaluate context label - #{e.class}: #{e}>"
|
|
|
|
end
|
2020-09-27 01:11:52 +00:00
|
|
|
{% else %}
|
|
|
|
{{what.stringify}}
|
|
|
|
{% end %}
|
|
|
|
end
|
|
|
|
|
2020-09-12 22:01:29 +00:00
|
|
|
# Defines the implicit subject for the test context.
|
|
|
|
# If *what* is a type, then the `described_class` method will be defined.
|
|
|
|
# Additionally, the implicit subject is set to an instance of *what* if it's not a module.
|
|
|
|
#
|
|
|
|
# There is no common macro type that has the `#resolve?` method.
|
|
|
|
# Also, `#responds_to?` can't be used in macros.
|
|
|
|
# So the large if statement in this macro is used to look for type signatures.
|
|
|
|
private macro _spectator_group_subject(what)
|
|
|
|
{% if (what.is_a?(Generic) ||
|
|
|
|
what.is_a?(Path) ||
|
|
|
|
what.is_a?(TypeNode) ||
|
|
|
|
what.is_a?(Union)) &&
|
|
|
|
(described_type = what.resolve?).is_a?(TypeNode) %}
|
2021-01-31 07:38:17 +00:00
|
|
|
private macro described_class
|
|
|
|
{{what}}
|
2020-09-12 22:01:29 +00:00
|
|
|
end
|
|
|
|
|
2021-01-31 07:38:17 +00:00
|
|
|
subject do
|
|
|
|
{% if described_type.class? || described_type.struct? %}
|
|
|
|
described_class.new
|
|
|
|
{% else %}
|
2020-09-12 22:01:29 +00:00
|
|
|
described_class
|
2021-01-31 07:38:17 +00:00
|
|
|
{% end %}
|
|
|
|
end
|
2020-09-12 22:01:29 +00:00
|
|
|
{% else %}
|
|
|
|
private def _spectator_implicit_subject
|
|
|
|
{{what}}
|
|
|
|
end
|
|
|
|
{% end %}
|
|
|
|
end
|
2020-09-27 01:15:34 +00:00
|
|
|
|
|
|
|
define_example_group :example_group
|
|
|
|
|
|
|
|
define_example_group :describe
|
|
|
|
|
|
|
|
define_example_group :context
|
2019-09-26 21:56:48 +00:00
|
|
|
|
2021-06-12 18:14:19 +00:00
|
|
|
define_example_group :xexample_group, skip: "Temporarily skipped with xexample_group"
|
2021-01-30 19:32:13 +00:00
|
|
|
|
2021-06-12 18:14:19 +00:00
|
|
|
define_example_group :xdescribe, skip: "Temporarily skipped with xdescribe"
|
2021-01-30 19:32:13 +00:00
|
|
|
|
2021-06-12 18:14:19 +00:00
|
|
|
define_example_group :xcontext, skip: "Temporarily skipped with xcontext"
|
2021-01-30 19:32:13 +00:00
|
|
|
|
2021-08-18 19:55:21 +00:00
|
|
|
define_example_group :fexample_group, focus: true
|
|
|
|
|
|
|
|
define_example_group :fdescribe, focus: true
|
|
|
|
|
|
|
|
define_example_group :fcontext, focus: true
|
|
|
|
|
2021-06-19 17:57:56 +00:00
|
|
|
# Defines a new iterative example group.
|
|
|
|
# This type of group duplicates its contents for each element in *collection*.
|
|
|
|
#
|
|
|
|
# The first argument is the collection of elements to iterate over.
|
|
|
|
#
|
|
|
|
# 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.
|
|
|
|
#
|
2021-07-17 18:49:11 +00:00
|
|
|
# The number of items iterated can be restricted by specifying a *count* argument.
|
|
|
|
# The first *count* items will be used if specified, otherwise all items will be used.
|
2021-07-17 19:25:38 +00:00
|
|
|
define_iterative_group :sample
|
2021-06-19 17:57:56 +00:00
|
|
|
|
2021-07-17 19:25:38 +00:00
|
|
|
# :ditto:
|
|
|
|
define_iterative_group :xsample, skip: "Temporarily skipped with xsample"
|
2021-07-17 19:05:03 +00:00
|
|
|
|
2021-09-20 17:01:00 +00:00
|
|
|
# :ditto:
|
2021-08-18 19:55:21 +00:00
|
|
|
define_iterative_group :fsample, focus: true
|
|
|
|
|
2021-07-17 19:05:03 +00:00
|
|
|
# Defines a new iterative example group.
|
|
|
|
# This type of group duplicates its contents for each element in *collection*.
|
|
|
|
# This is the same as `#sample` except that the items are shuffled.
|
|
|
|
# The items are selected with a RNG based on the seed.
|
|
|
|
#
|
|
|
|
# The first argument is the collection of elements to iterate over.
|
|
|
|
#
|
|
|
|
# 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.
|
|
|
|
#
|
|
|
|
# The number of items iterated can be restricted by specifying a *count* argument.
|
|
|
|
# The first *count* items will be used if specified, otherwise all items will be used.
|
2021-07-17 19:25:38 +00:00
|
|
|
define_iterative_group :random_sample do |collection|
|
|
|
|
collection.to_a.shuffle(::Spectator.random)
|
|
|
|
end
|
2021-07-17 19:05:03 +00:00
|
|
|
|
2021-07-17 19:25:38 +00:00
|
|
|
# :ditto:
|
|
|
|
define_iterative_group :xrandom_sample, skip: "Temporarily skipped with xrandom_sample" do |collection|
|
|
|
|
collection.to_a.shuffle(::Spectator.random)
|
2021-07-17 19:05:03 +00:00
|
|
|
end
|
2021-08-18 19:55:21 +00:00
|
|
|
|
|
|
|
# :ditto:
|
|
|
|
define_iterative_group :frandom_sample, focus: true do |collection|
|
|
|
|
collection.to_a.shuffle(::Spectator.random)
|
|
|
|
end
|
2021-01-09 20:17:42 +00:00
|
|
|
end
|
2019-09-15 15:15:33 +00:00
|
|
|
end
|