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