Rework result types

This commit is contained in:
Michael Miller 2020-10-17 14:56:31 -06:00
parent 788b12a8bc
commit 4462f27316
No known key found for this signature in database
GPG key ID: FB9F12F7C646A4AD
10 changed files with 89 additions and 188 deletions

View file

@ -0,0 +1,18 @@
require "./fail_result"
module Spectator
# Outcome that indicates running an example generated an error.
# This occurs when an unexpected exception was raised while running an example.
# This is different from a "failed" result in that the error was not from a failed assertion.
class ErrorResult < FailResult
# Calls the `error` method on *visitor*.
def accept(visitor)
visitor.error
end
# One-word description of the result.
def to_s(io)
io << "error"
end
end
end

View file

@ -1,50 +0,0 @@
require "./failed_result"
module Spectator
# Outcome that indicates running an example generated an error.
# This type of result occurs when an exception was raised.
# This is different from a "failed" result
# in that the error was not from a failed expectation.
class ErroredResult < FailedResult
# Calls the `error` method on *interface*.
def call(interface)
interface.error
end
# Calls the `error` method on *interface*
# and passes the yielded value.
def call(interface)
value = yield self
interface.error(value)
end
# One-word descriptor of the result.
def to_s(io)
io << "error"
end
# Adds the common JSON fields for all result types
# and fields specific to errored results.
private def add_json_fields(json : ::JSON::Builder)
super
json.field("exceptions") do
exception = error
json.array do
while exception
error_to_json(exception, json) if exception
exception = error.cause
end
end
end
end
# Adds a single exception to a JSON builder.
private def error_to_json(error : Exception, json : ::JSON::Builder)
json.object do
json.field("type", error.class.to_s)
json.field("message", error.message)
json.field("backtrace", error.backtrace)
end
end
end
end

View file

@ -1,6 +1,8 @@
require "./example_context_delegate" require "./example_context_delegate"
require "./example_group" require "./example_group"
require "./example_node" require "./example_node"
require "./pass_result"
require "./pending_result"
require "./result" require "./result"
require "./source" require "./source"
@ -11,8 +13,7 @@ module Spectator
getter? finished : Bool = false getter? finished : Bool = false
# Retrieves the result of the last time the example ran. # Retrieves the result of the last time the example ran.
# TODO: Make result not nil and default to pending. getter result : Result = PendingResult.new
getter! result : Result
# Creates the example. # Creates the example.
# The *delegate* contains the test context and method that runs the test case. # The *delegate* contains the test context and method that runs the test case.
@ -30,8 +31,11 @@ module Spectator
# The result will also be stored in `#result`. # The result will also be stored in `#result`.
def run : Result def run : Result
Log.debug { "Running example #{self}" } Log.debug { "Running example #{self}" }
@delegate.call(self) elapsed = Time.measure do
raise NotImplementedError.new("#run") @delegate.call(self)
end
@finished = true
@result = PassResult.new(elapsed)
end end
# Exposes information about the example useful for debugging. # Exposes information about the example useful for debugging.
@ -47,7 +51,7 @@ module Spectator
io << s io << s
end end
# TODO: Add result. io << result
end end
end end
end end

View file

@ -0,0 +1,28 @@
require "./result"
module Spectator
# Outcome that indicates an example failed.
# This typically means an assertion did not pass.
class FailResult < Result
# Error that occurred while running the example.
# This describes the primary reason for the failure.
getter error : Exception
# Creates a failure result.
# The *elapsed* argument is the length of time it took to run the example.
# The *error* is the exception raised that caused the failure.
def initialize(elapsed, @error)
super(elapsed)
end
# Calls the `failure` method on *visitor*.
def accept(visitor)
visitor.failure
end
# One-word description of the result.
def to_s(io)
io << "fail"
end
end
end

View file

@ -1,42 +0,0 @@
require "./finished_result"
module Spectator
# Outcome that indicates running an example was a failure.
class FailedResult < FinishedResult
# Error that occurred while running the example.
getter error : Exception
# Creates a failed result.
# The *example* should refer to the example that was run
# and that this result is for.
# The *elapsed* argument is the length of time it took to run the example.
# The *expectations* references the expectations that were checked in the example.
# The *error* is the exception that was raised to cause the failure.
def initialize(example, elapsed, expectations, @error)
super(example, elapsed, expectations)
end
# Calls the `failure` method on *interface*.
def call(interface)
interface.failure
end
# Calls the `failure` method on *interface*
# and passes the yielded value.
def call(interface)
value = yield self
interface.failure(value)
end
# One-word descriptor of the result.
def to_s(io)
io << "fail"
end
# Adds all of the JSON fields for finished results and failed results.
private def add_json_fields(json : ::JSON::Builder)
super
json.field("error", error.message)
end
end
end

View file

@ -1,29 +0,0 @@
require "./result"
module Spectator
# Abstract class for all results by examples
abstract class FinishedResult < Result
# Length of time it took to run the example.
getter elapsed : Time::Span
# The expectations that were run in the example.
getter expectations : Expectations::ExampleExpectations
# Creates a successful result.
# The *example* should refer to the example that was run
# and that this result is for.
# The *elapsed* argument is the length of time it took to run the example.
# The *expectations* references the expectations that were checked in the example.
def initialize(example, @elapsed, @expectations)
super(example)
end
# Adds the common JSON fields for all result types
# and fields specific to finished results.
private def add_json_fields(json : ::JSON::Builder)
super
json.field("time", elapsed.total_seconds)
json.field("expectations", expectations)
end
end
end

View file

@ -0,0 +1,16 @@
require "./result"
module Spectator
# Outcome that indicates running an example was successful.
class PassResult < Result
# Calls the `pass` method on *visitor*.
def accept(visitor)
visitor.pass
end
# One-word description of the result.
def to_s(io)
io << "pass"
end
end
end

View file

@ -5,19 +5,18 @@ module Spectator
# A pending result means the example is not ready to run yet. # A pending result means the example is not ready to run yet.
# This can happen when the functionality to be tested is not implemented yet. # This can happen when the functionality to be tested is not implemented yet.
class PendingResult < Result class PendingResult < Result
# Calls the `pending` method on *interface*. # Creates the result.
def call(interface) # *elapsed* is the length of time it took to run the example.
interface.pending def initialize(elapsed = Time::Span::ZERO)
super
end end
# Calls the `pending` method on *interface* # Calls the `pending` method on the *visitor*.
# and passes the yielded value. def accept(visitor)
def call(interface) visitor.pending
value = yield self
interface.pending(value)
end end
# One-word descriptor of the result. # One-word description of the result.
def to_s(io) def to_s(io)
io << "pending" io << "pending"
end end

View file

@ -2,39 +2,19 @@ module Spectator
# Base class that represents the outcome of running an example. # Base class that represents the outcome of running an example.
# Sub-classes contain additional information specific to the type of result. # Sub-classes contain additional information specific to the type of result.
abstract class Result abstract class Result
# Example that was run that this result is for. # Length of time it took to run the example.
getter example : Example getter elapsed : Time::Span
# Constructs the base of the result. # The assertions checked in the example.
# The *example* should refer to the example that was run # getter assertions : Enumerable(Assertion) # TODO: Implement Assertion type.
# and that this result is for.
def initialize(@example) # Creates the result.
# *elapsed* is the length of time it took to run the example.
def initialize(@elapsed)
end end
# Calls the corresponding method for the type of result. # Calls the corresponding method for the type of result.
# This is used to avoid placing if or case-statements everywhere based on type. # This is the visitor design pattern.
# Each sub-class implements this method by calling the correct method on *interface*. abstract def accept(visitor)
abstract def call(interface)
# Calls the corresponding method for the type of result.
# This is used to avoid placing if or case-statements everywhere based on type.
# Each sub-class implements this method by calling the correct method on *interface*.
# This variation takes a block, which is passed the result.
# The value returned from the block will be returned by this method.
abstract def call(interface, &block : Result -> _)
# Creates a JSON object from the result information.
def to_json(json : ::JSON::Builder)
json.object do
add_json_fields(json)
end
end
# Adds the common fields for a result to a JSON builder.
private def add_json_fields(json : ::JSON::Builder)
json.field("name", example)
json.field("location", example.source)
json.field("result", to_s)
end
end end
end end

View file

@ -1,23 +0,0 @@
require "./finished_result"
module Spectator
# Outcome that indicates running an example was successful.
class SuccessfulResult < FinishedResult
# Calls the `success` method on *interface*.
def call(interface)
interface.success
end
# Calls the `success` method on *interface*
# and passes the yielded value.
def call(interface)
value = yield self
interface.success(value)
end
# One-word descriptor of the result.
def to_s(io)
io << "success"
end
end
end