mirror of
https://gitea.invidious.io/iv-org/shard-spectator.git
synced 2024-08-15 00:53:35 +00:00
Start rework on capturing expressions
This commit is contained in:
parent
1352110871
commit
fbe9f22e02
10 changed files with 81 additions and 250 deletions
52
src/spectator/abstract_expression.cr
Normal file
52
src/spectator/abstract_expression.cr
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
require "./label"
|
||||||
|
|
||||||
|
module Spectator
|
||||||
|
# Represents an expression from a test.
|
||||||
|
# This is typically captured by an `expect` macro.
|
||||||
|
# It consists of a label and the value of the expression.
|
||||||
|
# The label should be a string recognizable by the user,
|
||||||
|
# or nil if one isn't available.
|
||||||
|
# This base class is provided so that all generic sub-classes can be stored as this one type.
|
||||||
|
# The value of the expression can be retrieved by downcasting to the expected type with `#cast`.
|
||||||
|
abstract class AbstractExpression
|
||||||
|
# User recognizable string for the expression.
|
||||||
|
# This can be something like a variable name or a snippet of Crystal code.
|
||||||
|
getter label : Label
|
||||||
|
|
||||||
|
# Creates the expression.
|
||||||
|
# The *label* is usually the Crystal code evaluating to the `#value`.
|
||||||
|
# It can be nil if it isn't available.
|
||||||
|
def initialize(@label : Label)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Retrieves the real value of the expression.
|
||||||
|
abstract def value
|
||||||
|
|
||||||
|
# Attempts to cast `#value` to the type *T* and return it.
|
||||||
|
def cast(type : T.class) : T forall T
|
||||||
|
value.as(T)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Produces a string representation of the expression.
|
||||||
|
# This consists of the label (if one is available) and the value.
|
||||||
|
def to_s(io)
|
||||||
|
if (label = @label)
|
||||||
|
io << label
|
||||||
|
io << ':'
|
||||||
|
io << ' '
|
||||||
|
end
|
||||||
|
io << value
|
||||||
|
end
|
||||||
|
|
||||||
|
# Produces a detailed string representation of the expression.
|
||||||
|
# This consists of the label (if one is available) and the value.
|
||||||
|
def inspect(io)
|
||||||
|
if (label = @label)
|
||||||
|
io << @label
|
||||||
|
io << ':'
|
||||||
|
io << ' '
|
||||||
|
end
|
||||||
|
value.inspect(io)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
22
src/spectator/expression.cr
Normal file
22
src/spectator/expression.cr
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
require "./abstract_expression"
|
||||||
|
require "./label"
|
||||||
|
|
||||||
|
module Spectator
|
||||||
|
# Represents an expression from a test.
|
||||||
|
# This is typically captured by an `expect` macro.
|
||||||
|
# It consists of a label and the value of the expression.
|
||||||
|
# The label should be a string recognizable by the user,
|
||||||
|
# or nil if one isn't available.
|
||||||
|
class Expression(T) < AbstractExpression
|
||||||
|
# Raw value of the expression.
|
||||||
|
getter value
|
||||||
|
|
||||||
|
# Creates the expression.
|
||||||
|
# Expects the *value* of the expression and a *label* describing it.
|
||||||
|
# The *label* is usually the Crystal code evaluating to the *value*.
|
||||||
|
# It can be nil if it isn't available.
|
||||||
|
def initialize(@value : T, label : Label)
|
||||||
|
super(label)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
7
src/spectator/label.cr
Normal file
7
src/spectator/label.cr
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
module Spectator
|
||||||
|
# Identifier used in the spec.
|
||||||
|
# Signficant to the user.
|
||||||
|
# When a label is a symbol, then it is referencing a type or method.
|
||||||
|
# A label is nil when one can't be provided or captured.
|
||||||
|
alias Label = String | Symbol | Nil
|
||||||
|
end
|
|
@ -1,48 +0,0 @@
|
||||||
require "./test_expression"
|
|
||||||
|
|
||||||
module Spectator
|
|
||||||
# Captures an block from a test and its label.
|
|
||||||
struct TestBlock(ReturnType) < TestExpression(ReturnType)
|
|
||||||
# Calls the block and retrieves the value.
|
|
||||||
def value : ReturnType
|
|
||||||
@proc.call
|
|
||||||
end
|
|
||||||
|
|
||||||
# Creates the block expression with a custom label.
|
|
||||||
# Typically the label is the code in the block/proc.
|
|
||||||
def initialize(@proc : -> ReturnType, label : String)
|
|
||||||
super(label)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.create(proc : -> T, label : String) forall T
|
|
||||||
{% if T.id == "ReturnType".id %}
|
|
||||||
wrapper = ->{ proc.call; nil }
|
|
||||||
TestBlock(Nil).new(wrapper, label)
|
|
||||||
{% else %}
|
|
||||||
TestBlock(T).new(proc, label)
|
|
||||||
{% end %}
|
|
||||||
end
|
|
||||||
|
|
||||||
# Creates the block expression with a generic label.
|
|
||||||
# This is used for the "should" syntax and when the label doesn't matter.
|
|
||||||
def initialize(@proc : -> ReturnType)
|
|
||||||
super("<Proc>")
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.create(proc : -> T) forall T
|
|
||||||
{% if T.id == "ReturnType".id %}
|
|
||||||
wrapper = ->{ proc.call; nil }
|
|
||||||
TestBlock(Nil).new(wrapper)
|
|
||||||
{% else %}
|
|
||||||
TestBlock(T).new(proc)
|
|
||||||
{% end %}
|
|
||||||
end
|
|
||||||
|
|
||||||
# Reports complete information about the expression.
|
|
||||||
def inspect(io)
|
|
||||||
io << label
|
|
||||||
io << " -> "
|
|
||||||
io << value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,82 +0,0 @@
|
||||||
require "./example_hooks"
|
|
||||||
require "./test_values"
|
|
||||||
|
|
||||||
module Spectator
|
|
||||||
class TestContext
|
|
||||||
getter! parent
|
|
||||||
|
|
||||||
getter values
|
|
||||||
|
|
||||||
getter stubs : Hash(String, Deque(Mocks::MethodStub))
|
|
||||||
|
|
||||||
def initialize(@parent : TestContext?,
|
|
||||||
@hooks : ExampleHooks,
|
|
||||||
@conditions : ExampleConditions,
|
|
||||||
@values : TestValues,
|
|
||||||
@stubs : Hash(String, Deque(Mocks::MethodStub)))
|
|
||||||
@before_all_hooks_run = false
|
|
||||||
@after_all_hooks_run = false
|
|
||||||
end
|
|
||||||
|
|
||||||
def run_before_hooks(example : Example)
|
|
||||||
run_before_all_hooks
|
|
||||||
run_before_each_hooks(example)
|
|
||||||
end
|
|
||||||
|
|
||||||
protected def run_before_all_hooks
|
|
||||||
return if @before_all_hooks_run
|
|
||||||
|
|
||||||
@parent.try &.run_before_all_hooks
|
|
||||||
@hooks.run_before_all
|
|
||||||
ensure
|
|
||||||
@before_all_hooks_run = true
|
|
||||||
end
|
|
||||||
|
|
||||||
protected def run_before_each_hooks(example : Example)
|
|
||||||
@parent.try &.run_before_each_hooks(example)
|
|
||||||
@hooks.run_before_each(example.test_wrapper, example)
|
|
||||||
end
|
|
||||||
|
|
||||||
def run_after_hooks(example : Example)
|
|
||||||
run_after_each_hooks(example)
|
|
||||||
run_after_all_hooks(example.group)
|
|
||||||
end
|
|
||||||
|
|
||||||
protected def run_after_all_hooks(group : ExampleGroup, *, ignore_unfinished = false)
|
|
||||||
return if @after_all_hooks_run
|
|
||||||
return unless ignore_unfinished || group.finished?
|
|
||||||
|
|
||||||
@hooks.run_after_all
|
|
||||||
@parent.try do |parent_context|
|
|
||||||
parent_group = group.as(NestedExampleGroup).parent
|
|
||||||
parent_context.run_after_all_hooks(parent_group, ignore_unfinished: ignore_unfinished)
|
|
||||||
end
|
|
||||||
ensure
|
|
||||||
@after_all_hooks_run = true
|
|
||||||
end
|
|
||||||
|
|
||||||
protected def run_after_each_hooks(example : Example)
|
|
||||||
@hooks.run_after_each(example.test_wrapper, example)
|
|
||||||
@parent.try &.run_after_each_hooks(example)
|
|
||||||
end
|
|
||||||
|
|
||||||
def wrap_around_each_hooks(test, &block : ->)
|
|
||||||
wrapper = @hooks.wrap_around_each(test, block)
|
|
||||||
if (parent = @parent)
|
|
||||||
parent.wrap_around_each_hooks(test, &wrapper)
|
|
||||||
else
|
|
||||||
wrapper
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def run_pre_conditions(example)
|
|
||||||
@parent.try &.run_pre_conditions(example)
|
|
||||||
@conditions.run_pre_conditions(example.test_wrapper, example)
|
|
||||||
end
|
|
||||||
|
|
||||||
def run_post_conditions(example)
|
|
||||||
@conditions.run_post_conditions(example.test_wrapper, example)
|
|
||||||
@parent.try &.run_post_conditions(example)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,25 +0,0 @@
|
||||||
module Spectator
|
|
||||||
# Base type for capturing an expression from a test.
|
|
||||||
abstract struct TestExpression(T)
|
|
||||||
# User-friendly string displayed for the actual expression being tested.
|
|
||||||
# For instance, in the expectation:
|
|
||||||
# ```
|
|
||||||
# expect(foo).to eq(bar)
|
|
||||||
# ```
|
|
||||||
# This property will be "foo".
|
|
||||||
# It will be the literal string "foo",
|
|
||||||
# and not the actual value of the foo.
|
|
||||||
getter label : String
|
|
||||||
|
|
||||||
# Creates the common base of the expression.
|
|
||||||
def initialize(@label)
|
|
||||||
end
|
|
||||||
|
|
||||||
abstract def value : T
|
|
||||||
|
|
||||||
# String representation of the expression.
|
|
||||||
def to_s(io)
|
|
||||||
io << label
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,29 +0,0 @@
|
||||||
require "./test_expression"
|
|
||||||
|
|
||||||
module Spectator
|
|
||||||
# Captures a value from a test and its label.
|
|
||||||
struct TestValue(T) < TestExpression(T)
|
|
||||||
# Actual value.
|
|
||||||
getter value : T
|
|
||||||
|
|
||||||
# Creates the expression value with a custom label.
|
|
||||||
def initialize(@value : T, label : String)
|
|
||||||
super(label)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Creates the expression with a stringified value.
|
|
||||||
# This is used for the "should" syntax and when the label doesn't matter.
|
|
||||||
def initialize(@value : T)
|
|
||||||
super(@value.to_s)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Reports complete information about the expression.
|
|
||||||
def inspect(io)
|
|
||||||
io << label
|
|
||||||
io << '='
|
|
||||||
io << @value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
alias LabeledValue = TestValue(String)
|
|
||||||
end
|
|
|
@ -1,42 +0,0 @@
|
||||||
require "../spectator_test"
|
|
||||||
require "./source"
|
|
||||||
|
|
||||||
module Spectator
|
|
||||||
alias TestMethod = ::SpectatorTest ->
|
|
||||||
|
|
||||||
# Stores information about a end-user test.
|
|
||||||
# Used to instantiate tests and run them.
|
|
||||||
struct TestWrapper
|
|
||||||
# Description the user provided for the test.
|
|
||||||
def description
|
|
||||||
@description || @source.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
# Location of the test in source code.
|
|
||||||
getter source
|
|
||||||
|
|
||||||
# Creates a wrapper for the test.
|
|
||||||
def initialize(@description : String?, @source : Source, @test : ::SpectatorTest, @runner : TestMethod)
|
|
||||||
end
|
|
||||||
|
|
||||||
def description?
|
|
||||||
!@description.nil?
|
|
||||||
end
|
|
||||||
|
|
||||||
def run
|
|
||||||
call(@runner)
|
|
||||||
end
|
|
||||||
|
|
||||||
def call(method : TestMethod) : Nil
|
|
||||||
method.call(@test)
|
|
||||||
end
|
|
||||||
|
|
||||||
def call(method, *args) : Nil
|
|
||||||
method.call(@test, *args)
|
|
||||||
end
|
|
||||||
|
|
||||||
def around_hook(context : TestContext)
|
|
||||||
context.wrap_around_each_hooks(@test) { run }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,17 +0,0 @@
|
||||||
require "./value_wrapper"
|
|
||||||
|
|
||||||
module Spectator
|
|
||||||
# Implementation of a value wrapper for a specific type.
|
|
||||||
# Instances of this class should be created to wrap values.
|
|
||||||
# Then the wrapper should be stored as a `ValueWrapper`
|
|
||||||
# so that the type is deferred to runtime.
|
|
||||||
# This trick allows the DSL to store values without explicitly knowing their type.
|
|
||||||
class TypedValueWrapper(T) < ValueWrapper
|
|
||||||
# Wrapped value.
|
|
||||||
getter value : T
|
|
||||||
|
|
||||||
# Creates a new wrapper for a value.
|
|
||||||
def initialize(@value : T)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,7 +0,0 @@
|
||||||
module Spectator
|
|
||||||
# Base class for proxying test values to examples.
|
|
||||||
# This abstraction is required for inferring types.
|
|
||||||
# The DSL makes heavy use of this to defer types.
|
|
||||||
abstract class ValueWrapper
|
|
||||||
end
|
|
||||||
end
|
|
Loading…
Reference in a new issue