shard-spectator/src/spectator/harness.cr

114 lines
3.7 KiB
Crystal
Raw Normal View History

2020-11-08 03:56:30 +00:00
require "./error_result"
2021-01-21 07:03:57 +00:00
require "./expectation"
2021-01-31 00:27:02 +00:00
require "./mocks"
2020-11-08 03:56:30 +00:00
require "./pass_result"
require "./result"
2019-11-10 06:22:21 +00:00
2019-09-26 21:11:54 +00:00
module Spectator
2020-11-08 03:56:30 +00:00
# Helper class that acts as a gateway between test code and the framework.
# This is essentially an "example runner."
#
# Test code should be wrapped with a call to `.run`.
# This class will catch all errors raised by the test code.
# Errors caused by failed assertions (`AssertionFailed`) are translated to failed results (`FailResult`).
# Errors not caused by assertions are translated to error results (`ErrorResult`).
#
# Every runnable example should invoke the test code by calling `.run`.
# This sets up the harness so that the test code can use it.
# The framework does the following:
2018-10-09 20:25:39 +00:00
# ```
2021-01-16 18:03:05 +00:00
# result = Harness.run { run_example_code }
2018-10-09 20:25:39 +00:00
# # Do something with the result.
# ```
2021-01-16 18:03:05 +00:00
#
2020-11-08 03:56:30 +00:00
# Then from the test code, the harness can be accessed via `.current` like so:
2018-10-09 20:25:39 +00:00
# ```
2019-09-26 21:11:54 +00:00
# harness = ::Spectator::Harness.current
2018-10-09 20:25:39 +00:00
# # Do something with the harness.
# ```
2021-01-16 18:03:05 +00:00
#
2018-10-09 20:25:39 +00:00
# Of course, the end-user shouldn't see this or work directly with the harness.
2021-01-16 18:03:05 +00:00
# Instead, methods the test calls can access it.
# For instance, an expectation reporting a result.
class Harness
2021-01-21 07:03:57 +00:00
Log = ::Spectator::Log.for(self)
2018-10-09 20:25:39 +00:00
# Retrieves the harness for the current running example.
class_getter! current : self
2021-01-31 00:27:02 +00:00
getter mocks = Mocks::Registry.new
2020-11-08 03:56:30 +00:00
# Wraps an example with a harness and runs test code.
# A block provided to this method is considered to be the test code.
# The value of `.current` is set to the harness for the duration of the test.
# It will be reset after the test regardless of the outcome.
# The result of running the test code will be returned.
def self.run : Result
harness = new
previous = @@current
@@current = harness
result = harness.run { yield }
@@current = previous
result
2018-10-09 20:25:39 +00:00
end
2020-11-08 03:56:30 +00:00
@deferred = Deque(->).new
2021-01-31 03:07:45 +00:00
@expectations = [] of Expectation
2018-10-09 20:25:39 +00:00
2020-11-08 03:56:30 +00:00
# Runs test code and produces a result based on the outcome.
# The test code should be called from within the block given to this method.
def run : Result
outcome = capture { yield }
run_deferred # TODO: Handle errors in deferred blocks.
translate(*outcome)
end
2021-01-21 07:03:57 +00:00
def report(expectation : Expectation) : Nil
Log.debug { "Reporting expectation #{expectation}" }
2021-01-31 03:07:45 +00:00
@expectations << expectation
2021-01-21 07:03:57 +00:00
raise ExpectationFailed.new(expectation) if expectation.failed?
2021-01-16 18:03:05 +00:00
end
2020-11-08 03:56:30 +00:00
# Stores a block of code to be executed later.
# All deferred blocks run just before the `#run` method completes.
def defer(&block) : Nil
@deferred << block
2018-10-09 20:41:22 +00:00
end
2020-11-08 03:56:30 +00:00
# Yields to run the test code and returns information about the outcome.
# Returns a tuple with the elapsed time and an error if one occurred (otherwise nil).
private def capture
error = nil.as(Exception?)
elapsed = Time.measure do
begin
yield
rescue e
error = e
end
end
{elapsed, error}
2018-10-09 20:41:22 +00:00
end
2018-10-09 20:25:39 +00:00
2020-11-08 03:56:30 +00:00
# Translates the outcome of running a test to a result.
# Takes the *elapsed* time and a possible *error* from the test.
# Returns a type of `Result`.
private def translate(elapsed, error) : Result
2021-01-31 02:42:46 +00:00
example = Example.current # TODO: Remove this.
2021-01-21 07:03:57 +00:00
case error
when nil
2021-01-31 03:07:45 +00:00
PassResult.new(example, elapsed, @expectations)
2021-01-21 07:03:57 +00:00
when ExpectationFailed
2021-01-31 03:07:45 +00:00
FailResult.new(example, elapsed, error, @expectations)
2021-01-21 07:03:57 +00:00
else
2021-01-31 03:07:45 +00:00
ErrorResult.new(example, elapsed, error, @expectations)
2020-11-08 03:56:30 +00:00
end
2019-11-15 01:30:48 +00:00
end
# Runs all deferred blocks.
2020-11-08 03:56:30 +00:00
private def run_deferred : Nil
2019-11-15 01:30:48 +00:00
@deferred.each(&.call)
@deferred.clear
end
2018-10-09 20:25:39 +00:00
end
end