More structure around exceptions and matching

This commit is contained in:
Michael Miller 2018-10-05 16:30:19 -06:00
parent 4c2f6157af
commit 79886e9efb
9 changed files with 95 additions and 30 deletions

View file

@ -1,11 +1,50 @@
module Spectator::Expectations
abstract class Expectation
getter? negated : Bool
# Min-in for all expectation types.
# Classes that include this must implement
# the `#satisfied?`, `#message`, and `#negated_message` methods.
module Expectation
# Checks whether the expectation is met.
abstract def satisfied? : Bool
private def initialize(@negated)
# Describes the condition that must be met for the expectation to be satisifed.
abstract def message : String
# Describes the condition under which the expectation won't be satisifed.
abstract def negated_message : String
# Evaulates the expectation and produces a result.
# The `negated` flag should be set to true to invert the result.
def eval(negated = false) : Result
success = satisfied? ^ negated
Result.new(success, negated, self)
end
abstract def eval : Bool
abstract def message : String
# Information regarding the outcome of an expectation.
class Result
# Indicates whether the expectation was satisifed or not.
getter? successful : Bool
# Creates the result.
# The expectation is stored so that information from it may be lazy-loaded.
protected def initialize(@successful, @negated : Bool, @expectation : Expectation)
end
# Description of the condition that satisfies, or meets, the expectation.
def satisfy_message
message(@negated)
end
# Description of what actually happened when the expectation was evaluated.
def result_message
message(@successful)
end
# Retrieves the message or negated message from an expectation.
# Set `negated` to true to get the negated message,
# or to false to get the regular message.
private def message(negated)
negated ? @expectation.negated_message : @expectation.message
end
end
end
end

View file

@ -7,7 +7,7 @@ module Spectator::Expectations
private def initialize(@raise_on_failure = true)
end
def report(expectation : Expectation) : Nil
def report(result : Expectation::Result) : Nil
raise NotImplementedError.new("ExpectationRegistry#report")
end
@ -15,11 +15,11 @@ module Spectator::Expectations
raise NotImplementedError.new("ExpectationRegistry.current")
end
def self.start(example : Example) : ExpectationRegistry
def self.start(example : Example) : Nil
raise NotImplementedError.new("ExpectationRegistry.start")
end
def self.finish : Nil # TODO: Define return type.
def self.finish : ExpectationResults
raise NotImplementedError.new("ExpectationRegistry.finish")
end
end

View file

@ -0,0 +1,6 @@
module Spectator::Expectations
class ExpectationResults
def initialize(@results : Enumerable(ExpectationResult))
end
end
end

View file

@ -1,19 +1,26 @@
require "./expectation"
module Spectator::Expectations
class ValueExpectation(ActualType, ExpectedType) < Expectation
def initialize(negated,
@partial : ValueExpectationPartial(ActualType),
@matcher : ValueMatcher(ExpectedType))
super(negated)
class ValueExpectation(ActualType, ExpectedType)
include Expectation
def initialize(@partial : ValueExpectationPartial(ActualType),
@matcher : Matchers::ValueMatcher(ExpectedType))
end
def eval : Bool
@matcher.match?(@partial) ^ negated?
# Checks whether the expectation is met.
def satisfied? : Bool
@matcher.match?(@partial)
end
# Describes the condition that must be met for the expectation to be satisifed.
def message : String
negated? ? @matcher.negated_message(@partial) : @matcher.message(@partial)
@matcher.message(@partial)
end
# Describes the condition under which the expectation won't be satisifed.
def negated_message : String
@matcher.negated_message(@partial)
end
end
end

View file

@ -9,11 +9,15 @@ module Spectator::Expectations
end
def to(matcher : Matchers::ValueMatcher(ExpectedType)) : Nil forall ExpectedType
raise NotImplementedError.new("ValueExpectationPartial#to")
expectation = ValueExpectation.new(self, matcher)
result = expectation.eval
ExpectationRegistry.current.report(result)
end
def to_not(matcher : Matchers::ValueMatcher(ExpectedType)) : Nil forall ExpectedType
raise NotImplementedError.new("ValueExpectationPartial#to_not")
expectation = ValueExpectation.new(self, matcher)
result = expectation.eval(true)
ExpectationRegistry.current.report(result)
end
# ditto

View file

@ -3,6 +3,11 @@ require "./result"
module Spectator
class FailedResult < Result
getter error : Exception
getter expectations : Expectations::ExpectationResults
def initialize(example, elapsed, @expectations, @error)
super(example, elapsed)
end
def passed?
false
@ -19,9 +24,5 @@ module Spectator
def pending?
false
end
def initialize(example, elapsed, @error)
super(example, elapsed)
end
end
end

View file

@ -2,15 +2,15 @@ require "./value_matcher"
module Spectator::Matchers
class EqualityMatcher(ExpectedType) < ValueMatcher(ExpectedType)
def match?(partial : ValueExpectationPartial(ActualType)) : Bool forall ActualType
def match?(partial : Expectations::ValueExpectationPartial(ActualType)) : Bool forall ActualType
partial.actual == expected
end
def message(partial : ValueExpectationPartial(ActualType)) : String forall ActualType
def message(partial : Expectations::ValueExpectationPartial(ActualType)) : String forall ActualType
"Expected #{partial.label} to equal #{label} (using ==)"
end
def negated_message(partial : ValueExpectationPartial(ActualType)) : String forall ActualType
def negated_message(partial : Expectations::ValueExpectationPartial(ActualType)) : String forall ActualType
"Expected #{partial.label} to not equal #{label} (using ==)"
end
end

View file

@ -3,6 +3,7 @@ require "./example"
module Spectator
abstract class RunnableExample < Example
def run
Expectations::ExpectationRegistry.start(self)
result = ResultCapture.new
group.run_before_all_hooks
group.run_before_each_hooks
@ -13,7 +14,8 @@ module Spectator
group.run_after_each_hooks
group.run_after_all_hooks
end
translate_result(result)
expectations = Expectations::ExpectationRegistry.finish
translate_result(result, expectations)
end
private def wrapped_capture_result(result)
@ -32,14 +34,14 @@ module Spectator
end
end
private def translate_result(result)
private def translate_result(result, expectations)
case (error = result.error)
when Nil
SuccessfulResult.new(self, result.elapsed)
SuccessfulResult.new(self, result.elapsed, expectations)
when ExpectationFailed
FailedResult.new(self, result.elapsed, error)
FailedResult.new(self, result.elapsed, expectations, error)
else
ErroredResult.new(self, result.elapsed, error)
ErroredResult.new(self, result.elapsed, expectations, error)
end
end

View file

@ -2,6 +2,12 @@ require "./result"
module Spectator
class SuccessfulResult < Result
getter expectations : Expectations::ExpectationResults
def initialize(example, elapsed, @expectations)
super(example, elapsed)
end
def passed?
true
end