From 4462f27316444a3acb8db611e2fe5896226a70d0 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Sat, 17 Oct 2020 14:56:31 -0600 Subject: [PATCH] Rework result types --- src/spectator/error_result.cr | 18 +++++++++++ src/spectator/errored_result.cr | 50 ------------------------------ src/spectator/example.cr | 14 ++++++--- src/spectator/fail_result.cr | 28 +++++++++++++++++ src/spectator/failed_result.cr | 42 ------------------------- src/spectator/finished_result.cr | 29 ----------------- src/spectator/pass_result.cr | 16 ++++++++++ src/spectator/pending_result.cr | 17 +++++----- src/spectator/result.cr | 40 ++++++------------------ src/spectator/successful_result.cr | 23 -------------- 10 files changed, 89 insertions(+), 188 deletions(-) create mode 100644 src/spectator/error_result.cr delete mode 100644 src/spectator/errored_result.cr create mode 100644 src/spectator/fail_result.cr delete mode 100644 src/spectator/failed_result.cr delete mode 100644 src/spectator/finished_result.cr create mode 100644 src/spectator/pass_result.cr delete mode 100644 src/spectator/successful_result.cr diff --git a/src/spectator/error_result.cr b/src/spectator/error_result.cr new file mode 100644 index 0000000..cb47a4f --- /dev/null +++ b/src/spectator/error_result.cr @@ -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 diff --git a/src/spectator/errored_result.cr b/src/spectator/errored_result.cr deleted file mode 100644 index 00c73ff..0000000 --- a/src/spectator/errored_result.cr +++ /dev/null @@ -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 diff --git a/src/spectator/example.cr b/src/spectator/example.cr index eb6dda1..bcfd96e 100644 --- a/src/spectator/example.cr +++ b/src/spectator/example.cr @@ -1,6 +1,8 @@ require "./example_context_delegate" require "./example_group" require "./example_node" +require "./pass_result" +require "./pending_result" require "./result" require "./source" @@ -11,8 +13,7 @@ module Spectator getter? finished : Bool = false # Retrieves the result of the last time the example ran. - # TODO: Make result not nil and default to pending. - getter! result : Result + getter result : Result = PendingResult.new # Creates the example. # 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`. def run : Result Log.debug { "Running example #{self}" } - @delegate.call(self) - raise NotImplementedError.new("#run") + elapsed = Time.measure do + @delegate.call(self) + end + @finished = true + @result = PassResult.new(elapsed) end # Exposes information about the example useful for debugging. @@ -47,7 +51,7 @@ module Spectator io << s end - # TODO: Add result. + io << result end end end diff --git a/src/spectator/fail_result.cr b/src/spectator/fail_result.cr new file mode 100644 index 0000000..710698a --- /dev/null +++ b/src/spectator/fail_result.cr @@ -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 diff --git a/src/spectator/failed_result.cr b/src/spectator/failed_result.cr deleted file mode 100644 index 43b2ea6..0000000 --- a/src/spectator/failed_result.cr +++ /dev/null @@ -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 diff --git a/src/spectator/finished_result.cr b/src/spectator/finished_result.cr deleted file mode 100644 index 1e3dae8..0000000 --- a/src/spectator/finished_result.cr +++ /dev/null @@ -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 diff --git a/src/spectator/pass_result.cr b/src/spectator/pass_result.cr new file mode 100644 index 0000000..193e3c2 --- /dev/null +++ b/src/spectator/pass_result.cr @@ -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 diff --git a/src/spectator/pending_result.cr b/src/spectator/pending_result.cr index 65512e9..5781edc 100644 --- a/src/spectator/pending_result.cr +++ b/src/spectator/pending_result.cr @@ -5,19 +5,18 @@ module Spectator # 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. class PendingResult < Result - # Calls the `pending` method on *interface*. - def call(interface) - interface.pending + # Creates the result. + # *elapsed* is the length of time it took to run the example. + def initialize(elapsed = Time::Span::ZERO) + super end - # Calls the `pending` method on *interface* - # and passes the yielded value. - def call(interface) - value = yield self - interface.pending(value) + # Calls the `pending` method on the *visitor*. + def accept(visitor) + visitor.pending end - # One-word descriptor of the result. + # One-word description of the result. def to_s(io) io << "pending" end diff --git a/src/spectator/result.cr b/src/spectator/result.cr index c2f0b45..056c979 100644 --- a/src/spectator/result.cr +++ b/src/spectator/result.cr @@ -2,39 +2,19 @@ module Spectator # Base class that represents the outcome of running an example. # Sub-classes contain additional information specific to the type of result. abstract class Result - # Example that was run that this result is for. - getter example : Example + # Length of time it took to run the example. + getter elapsed : Time::Span - # Constructs the base of the result. - # The *example* should refer to the example that was run - # and that this result is for. - def initialize(@example) + # The assertions checked in the example. + # getter assertions : Enumerable(Assertion) # TODO: Implement Assertion type. + + # Creates the result. + # *elapsed* is the length of time it took to run the example. + def initialize(@elapsed) end # 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*. - 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 + # This is the visitor design pattern. + abstract def accept(visitor) end end diff --git a/src/spectator/successful_result.cr b/src/spectator/successful_result.cr deleted file mode 100644 index bd3f0ed..0000000 --- a/src/spectator/successful_result.cr +++ /dev/null @@ -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