mirror of
https://gitea.invidious.io/iv-org/shard-spectator.git
synced 2024-08-15 00:53:35 +00:00
Gut factories and example structure code
This commit is contained in:
parent
b8e125e38f
commit
19913a28d1
6 changed files with 2 additions and 328 deletions
|
@ -1,15 +0,0 @@
|
|||
module Spectator::DSL
|
||||
# Creates instances of examples from a specified class.
|
||||
class ExampleFactory
|
||||
# Creates the factory.
|
||||
# The type passed to this constructor must be a sub-type of `Example`.
|
||||
def initialize(@example_type : Example.class)
|
||||
end
|
||||
|
||||
# Constructs a new example instance and returns it.
|
||||
# The *group* is passed to `Example#initialize`.
|
||||
def build(group : ExampleGroup, _sample_values : Internals::SampleValues) : Example
|
||||
@example_type.new(group)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,87 +0,0 @@
|
|||
module Spectator::DSL
|
||||
# Base class for building all example groups.
|
||||
abstract class ExampleGroupBuilder
|
||||
# Type alias for valid children of example groups.
|
||||
# NOTE: `NestedExampleGroupBuilder` is used instead of `ExampleGroupBuilder`.
|
||||
# That is because `RootExampleGroupBuilder` also inherits from this class,
|
||||
# and the root example group can't be a child.
|
||||
alias Child = ExampleFactory | NestedExampleGroupBuilder
|
||||
|
||||
# Factories and builders for all examples and groups.
|
||||
@children = [] of Child
|
||||
|
||||
# Hooks added to the group so far.
|
||||
@before_all_hooks = [] of ->
|
||||
@before_each_hooks = [] of ->
|
||||
@after_all_hooks = [] of ->
|
||||
@after_each_hooks = [] of ->
|
||||
@around_each_hooks = [] of Proc(Nil) ->
|
||||
|
||||
# Pre and post conditions so far.
|
||||
@pre_conditions = [] of ->
|
||||
@post_conditions = [] of ->
|
||||
|
||||
# Adds a new example factory or group builder to this group.
|
||||
def add_child(child : Child)
|
||||
@children << child
|
||||
end
|
||||
|
||||
# Adds a hook to run before all examples (and nested examples) in this group.
|
||||
def add_before_all_hook(block : ->) : Nil
|
||||
@before_all_hooks << block
|
||||
end
|
||||
|
||||
# Adds a hook to run before each example (and nested example) in this group.
|
||||
def add_before_each_hook(block : ->) : Nil
|
||||
@before_each_hooks << block
|
||||
end
|
||||
|
||||
# Adds a hook to run after all examples (and nested examples) in this group.
|
||||
def add_after_all_hook(block : ->) : Nil
|
||||
@after_all_hooks << block
|
||||
end
|
||||
|
||||
# Adds a hook to run after each example (and nested example) in this group.
|
||||
def add_after_each_hook(block : ->) : Nil
|
||||
@after_each_hooks << block
|
||||
end
|
||||
|
||||
# Adds a hook to run around each example (and nested example) in this group.
|
||||
# The block of code will be given another proc as an argument.
|
||||
# It is expected that the block will call the proc.
|
||||
def add_around_each_hook(block : Proc(Nil) ->) : Nil
|
||||
@around_each_hooks << block
|
||||
end
|
||||
|
||||
# Adds a pre-condition to run at the start of every example in this group.
|
||||
def add_pre_condition(block : ->) : Nil
|
||||
@pre_conditions << block
|
||||
end
|
||||
|
||||
# Adds a post-condition to run at the end of every example in this group.
|
||||
def add_post_condition(block : ->) : Nil
|
||||
@post_conditions << block
|
||||
end
|
||||
|
||||
# Constructs an `ExampleHooks` instance with all the hooks defined for this group.
|
||||
# This method should be called only when the group is being built,
|
||||
# otherwise some hooks may be missing.
|
||||
private def hooks
|
||||
ExampleHooks.new(
|
||||
@before_all_hooks,
|
||||
@before_each_hooks,
|
||||
@after_all_hooks,
|
||||
@after_each_hooks,
|
||||
@around_each_hooks
|
||||
)
|
||||
end
|
||||
|
||||
# Constructs an `ExampleConditions` instance
|
||||
# with all the pre- and post-conditions defined for this group.
|
||||
# This method should be called only when the group is being built,
|
||||
# otherwise some conditions may be missing.
|
||||
private def conditions
|
||||
ExampleConditions.new(@pre_conditions, @post_conditions)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,38 +0,0 @@
|
|||
module Spectator::DSL
|
||||
# Standard example group builder.
|
||||
# Creates groups of examples and nested groups.
|
||||
class NestedExampleGroupBuilder < ExampleGroupBuilder
|
||||
# Creates a new group builder.
|
||||
# The value for *what* should be the context for the group.
|
||||
#
|
||||
# For example, in these samples:
|
||||
# ```
|
||||
# describe String do
|
||||
# # ...
|
||||
# context "with an empty string" do
|
||||
# # ...
|
||||
# end
|
||||
# end
|
||||
# ```
|
||||
# The value would be "String" for the describe block
|
||||
# and "with an empty string" for the context block.
|
||||
# Use a `Symbol` when referencing a type name.
|
||||
def initialize(@what : Symbol | String)
|
||||
end
|
||||
|
||||
# Builds the example group.
|
||||
# A new `NestedExampleGroup` will be returned
|
||||
# which can have instances of `Example` and `ExampleGroup` nested in it.
|
||||
# The *parent* should be the group that contains this group.
|
||||
# The *sample_values* will be given to all of the examples (and groups) nested in this group.
|
||||
def build(parent : ExampleGroup, sample_values : Internals::SampleValues) : NestedExampleGroup
|
||||
NestedExampleGroup.new(@what, parent, hooks, conditions, sample_values).tap do |group|
|
||||
# Set the group's children to built versions of the children from this instance.
|
||||
group.children = @children.map do |child|
|
||||
# Build the child and up-cast to prevent type errors.
|
||||
child.build(group, sample_values).as(ExampleComponent)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,18 +0,0 @@
|
|||
module Spectator::DSL
|
||||
# Top-level example group builder.
|
||||
# There should only be one instance of this class,
|
||||
# and it should be at the top of the spec "tree".
|
||||
class RootExampleGroupBuilder < ExampleGroupBuilder
|
||||
# Creates a `RootExampleGroup` which can have instances of `Example` and `ExampleGroup` nested in it.
|
||||
# The *sample_values* will be given to all of the examples (and groups) nested in this group.
|
||||
def build(sample_values : Internals::SampleValues) : RootExampleGroup
|
||||
RootExampleGroup.new(hooks, conditions, sample_values).tap do |group|
|
||||
# Set the group's children to built versions of the children from this instance.
|
||||
group.children = @children.map do |child|
|
||||
# Build the child and up-cast to prevent type errors.
|
||||
child.build(group, sample_values).as(ExampleComponent)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,73 +0,0 @@
|
|||
require "./nested_example_group_builder"
|
||||
|
||||
module Spectator::DSL
|
||||
# Specialized example group builder for "sample" groups.
|
||||
# The type parameter `C` is the type to instantiate to create the collection.
|
||||
# The type parameter `T` should be the type of each element in the sample collection.
|
||||
# This builder creates a container group with groups inside for each item in the collection.
|
||||
# The hooks are only defined for the container group.
|
||||
# By doing so, the hooks are defined once, are inherited, and use less memory.
|
||||
class SampleExampleGroupBuilder(C, T) < NestedExampleGroupBuilder
|
||||
# Creates a new group builder.
|
||||
# The value for *what* should be the text the user specified for the collection.
|
||||
# The *collection_type* is the type to create that will produce the items.
|
||||
# The *collection_builder* is a proc that takes an instance of *collection_type*
|
||||
# and returns an actual array of items to create examples for.
|
||||
# The *name* is the variable name that the user accesses the current collection item with.
|
||||
#
|
||||
# In this code:
|
||||
# ```
|
||||
# sample random_integers do |integer|
|
||||
# # ...
|
||||
# end
|
||||
# ```
|
||||
# The *what* would be "random_integers"
|
||||
# and the collection would contain the items returned by calling *random_integers*.
|
||||
# The *name* would be "integer".
|
||||
#
|
||||
# The *symbol* is passed along to the sample values
|
||||
# so that the example code can retrieve the current item from the collection.
|
||||
# The symbol should be unique.
|
||||
def initialize(what : String, @collection_type : C.class, @collection_builder : C -> Array(T),
|
||||
@name : String, @symbol : Symbol)
|
||||
super(what)
|
||||
end
|
||||
|
||||
# Builds the example group.
|
||||
# A new `NestedExampleGroup` will be returned
|
||||
# which can have instances of `Example` and `ExampleGroup` nested in it.
|
||||
# The *parent* should be the group that contains this group.
|
||||
# The *sample_values* will be given to all of the examples (and groups) nested in this group.
|
||||
def build(parent : ExampleGroup, sample_values : Internals::SampleValues) : NestedExampleGroup
|
||||
collection = @collection_builder.call(@collection_type.new)
|
||||
|
||||
# This creates the container for the sub-groups.
|
||||
# The hooks are defined here, instead of repeating for each sub-group.
|
||||
NestedExampleGroup.new(@what, parent, hooks, conditions, sample_values).tap do |group|
|
||||
# Set the container group's children to be sub-groups for each item in the collection.
|
||||
group.children = collection.map do |value|
|
||||
# Create a sub-group for each item in the collection.
|
||||
build_sub_group(group, sample_values, value).as(ExampleComponent)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Builds a sub-group for one item in the collection.
|
||||
# The *parent* should be the container group currently being built by the `#build` call.
|
||||
# The *sample_values* should be the same as what was passed to the `#build` call.
|
||||
# The *value* is the current item in the collection.
|
||||
# The value will be added to the sample values for the sub-group,
|
||||
# so it shouldn't be added prior to calling this method.
|
||||
private def build_sub_group(parent : ExampleGroup, sample_values : Internals::SampleValues, value : T) : NestedExampleGroup
|
||||
# Add the value to sample values for this sub-group.
|
||||
sub_values = sample_values.add(@symbol, @name, value)
|
||||
NestedExampleGroup.new(value.to_s, parent, ExampleHooks.empty, ExampleConditions.empty, sub_values).tap do |group|
|
||||
# Set the sub-group's children to built versions of the children from this instance.
|
||||
group.children = @children.map do |child|
|
||||
# Build the child and up-cast to prevent type errors.
|
||||
child.build(group, sub_values).as(ExampleComponent)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1426,21 +1426,7 @@ module Spectator::DSL
|
|||
_spectator_test(Test%example, %run) {{block}}
|
||||
{% end %}
|
||||
|
||||
# Create a class derived from `RunnableExample` to run the test code.
|
||||
_spectator_example(Example%example, Test%example, ::Spectator::RunnableExample, {{what}}) do
|
||||
# Source where the example originated from.
|
||||
def source
|
||||
::Spectator::Source.new({{_source_file}}, {{_source_line}})
|
||||
end
|
||||
|
||||
# Implement abstract method to run the wrapped example block.
|
||||
protected def run_instance
|
||||
@instance.%run
|
||||
end
|
||||
end
|
||||
|
||||
# Add the example to the current group.
|
||||
::Spectator::DSL::Builder.add_example(Example%example)
|
||||
# TODO
|
||||
end
|
||||
|
||||
# Creates an example, or a test case.
|
||||
|
@ -1500,19 +1486,7 @@ module Spectator::DSL
|
|||
# By creating a `#pending` test, the code will be referenced.
|
||||
# Thus, forcing the compiler to at least process the code, even if it isn't run.
|
||||
macro pending(what, _source_file = __FILE__, _source_line = __LINE__, &block)
|
||||
# Create the wrapper class for the test code.
|
||||
_spectator_test(Test%example, %run) {{block}}
|
||||
|
||||
# Create a class derived from `PendingExample` to skip the test code.
|
||||
_spectator_example(Example%example, Test%example, ::Spectator::PendingExample, {{what}}) do
|
||||
# Source where the example originated from.
|
||||
def source
|
||||
::Spectator::Source.new({{_source_file}}, {{_source_line}})
|
||||
end
|
||||
end
|
||||
|
||||
# Add the example to the current group.
|
||||
::Spectator::DSL::Builder.add_example(Example%example)
|
||||
# TODO
|
||||
end
|
||||
|
||||
# Creates an example, or a test case, that does not run.
|
||||
|
@ -1553,74 +1527,5 @@ module Spectator::DSL
|
|||
macro xit(&block)
|
||||
pending({{block.body.stringify}}) {{block}}
|
||||
end
|
||||
|
||||
# Creates a wrapper class for test code.
|
||||
# The class serves multiple purposes, mostly dealing with scope.
|
||||
# 1. Include the parent modules as mix-ins.
|
||||
# 2. Enable DSL specific to examples.
|
||||
# 3. Isolate methods in `Example` from the test code.
|
||||
#
|
||||
# Since the names are generated, and macros can't return values,
|
||||
# the names for everything must be passed in as arguments.
|
||||
# The *class_name* argument is the name of the class to define.
|
||||
# The *run_method_name* argument is the name of the method in the wrapper class
|
||||
# that will actually run the test code.
|
||||
# The block passed to this macro is the actual test code.
|
||||
private macro _spectator_test(class_name, run_method_name)
|
||||
# Wrapper class for isolating the test code.
|
||||
class {{class_name.id}} < {{@type.id}}
|
||||
# Generated method for actually running the test code.
|
||||
def {{run_method_name.id}}
|
||||
{{yield}}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Creates an example class.
|
||||
# Since the names are generated, and macros can't return values,
|
||||
# the names for everything must be passed in as arguments.
|
||||
# The *example_class_name* argument is the name of the class to define.
|
||||
# The *test_class_name* argument is the name of the wrapper class to reference.
|
||||
# This must be the same as `class_name` for `#_spectator_example_wrapper`.
|
||||
# The *base_class* argument specifies which type of example class the new class should derive from.
|
||||
# This should typically be `RunnableExample` or `PendingExample`.
|
||||
# The *what* argument is the description passed to the `#it` or `#pending` block.
|
||||
# And lastly, the block specified is additional content to put in the class.
|
||||
# For instance, to define a method in the class, do it in the block.
|
||||
# ```
|
||||
# _spectator_example(Example123, Test123, RunnableExample, "does something") do
|
||||
# def something
|
||||
# # This method is defined in the Example123 class.
|
||||
# end
|
||||
# end
|
||||
# ```
|
||||
private macro _spectator_example(example_class_name, test_class_name, base_class, what, &block)
|
||||
# Example class containing meta information and instructions for running the test.
|
||||
class {{example_class_name.id}} < {{base_class.id}}
|
||||
# Stores the group the example belongs to
|
||||
# and sample values specific to this instance of the test.
|
||||
# This method's signature must match the one used in `ExampleFactory#build`.
|
||||
def initialize(group : ::Spectator::ExampleGroup)
|
||||
super
|
||||
@instance = {{test_class_name.id}}.new
|
||||
end
|
||||
|
||||
# Retrieves the underlying, wrapped test code.
|
||||
getter instance
|
||||
|
||||
# Indicates whether the example references a method.
|
||||
def symbolic?
|
||||
{{what.is_a?(StringLiteral) && what.starts_with?('#') ? true : false}}
|
||||
end
|
||||
|
||||
# Add the block's content.
|
||||
{{block.body}}
|
||||
|
||||
# Description for the test.
|
||||
def what
|
||||
{{what.is_a?(StringLiteral) ? what : what.stringify}}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue