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 module Spectator::Expectations
abstract class Expectation # Min-in for all expectation types.
getter? negated : Bool # 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.
end
abstract def eval : Bool
abstract def message : String 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
# 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
end end

View file

@ -7,7 +7,7 @@ module Spectator::Expectations
private def initialize(@raise_on_failure = true) private def initialize(@raise_on_failure = true)
end end
def report(expectation : Expectation) : Nil def report(result : Expectation::Result) : Nil
raise NotImplementedError.new("ExpectationRegistry#report") raise NotImplementedError.new("ExpectationRegistry#report")
end end
@ -15,11 +15,11 @@ module Spectator::Expectations
raise NotImplementedError.new("ExpectationRegistry.current") raise NotImplementedError.new("ExpectationRegistry.current")
end end
def self.start(example : Example) : ExpectationRegistry def self.start(example : Example) : Nil
raise NotImplementedError.new("ExpectationRegistry.start") raise NotImplementedError.new("ExpectationRegistry.start")
end end
def self.finish : Nil # TODO: Define return type. def self.finish : ExpectationResults
raise NotImplementedError.new("ExpectationRegistry.finish") raise NotImplementedError.new("ExpectationRegistry.finish")
end end
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" require "./expectation"
module Spectator::Expectations module Spectator::Expectations
class ValueExpectation(ActualType, ExpectedType) < Expectation class ValueExpectation(ActualType, ExpectedType)
def initialize(negated, include Expectation
@partial : ValueExpectationPartial(ActualType),
@matcher : ValueMatcher(ExpectedType)) def initialize(@partial : ValueExpectationPartial(ActualType),
super(negated) @matcher : Matchers::ValueMatcher(ExpectedType))
end end
def eval : Bool # Checks whether the expectation is met.
@matcher.match?(@partial) ^ negated? def satisfied? : Bool
@matcher.match?(@partial)
end end
# Describes the condition that must be met for the expectation to be satisifed.
def message : String 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 end
end end

View file

@ -9,11 +9,15 @@ module Spectator::Expectations
end end
def to(matcher : Matchers::ValueMatcher(ExpectedType)) : Nil forall ExpectedType 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 end
def to_not(matcher : Matchers::ValueMatcher(ExpectedType)) : Nil forall ExpectedType 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 end
# ditto # ditto

View file

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

View file

@ -2,15 +2,15 @@ require "./value_matcher"
module Spectator::Matchers module Spectator::Matchers
class EqualityMatcher(ExpectedType) < ValueMatcher(ExpectedType) 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 partial.actual == expected
end 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 ==)" "Expected #{partial.label} to equal #{label} (using ==)"
end 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 ==)" "Expected #{partial.label} to not equal #{label} (using ==)"
end end
end end

View file

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

View file

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