mirror of
https://gitea.invidious.io/iv-org/shard-spectator.git
synced 2024-08-15 00:53:35 +00:00
Merge branch 'master' of gitlab.com:arctic-fox/spectator
This commit is contained in:
commit
297701c463
11 changed files with 165 additions and 35 deletions
59
src/spectator/example_status.cr
Normal file
59
src/spectator/example_status.cr
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
module Spectator
|
||||||
|
# Various outcomes that an example can be in.
|
||||||
|
enum ExampleStatus
|
||||||
|
# The example was intentionally not run.
|
||||||
|
# This is different than `Pending`.
|
||||||
|
# This value means the example was not pending
|
||||||
|
# and was not run due to filtering or other means.
|
||||||
|
Skipped,
|
||||||
|
|
||||||
|
# The example finished successfully.
|
||||||
|
Successful,
|
||||||
|
|
||||||
|
# The example ran, but failed.
|
||||||
|
Failed,
|
||||||
|
|
||||||
|
# An error occurred while executing the example.
|
||||||
|
Errored,
|
||||||
|
|
||||||
|
# The example is marked as pending and was not run.
|
||||||
|
Pending,
|
||||||
|
|
||||||
|
# The example finished successfully,
|
||||||
|
# but something may be wrong with it.
|
||||||
|
Warning
|
||||||
|
|
||||||
|
# Indicates whether an example was skipped.
|
||||||
|
def skipped?
|
||||||
|
self == Skipped
|
||||||
|
end
|
||||||
|
|
||||||
|
# Indicates that an example was run and it was successful.
|
||||||
|
# NOTE: Examples with warnings count as successful.
|
||||||
|
def successful?
|
||||||
|
!failed? && !pending?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Indicates that an example was run, but it failed.
|
||||||
|
# Errors count as failures.
|
||||||
|
def failed?
|
||||||
|
self == Failed || errored?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Indicates whether an error was encountered while running the example.
|
||||||
|
def errored?
|
||||||
|
self == Errored
|
||||||
|
end
|
||||||
|
|
||||||
|
# Indicates that an example was marked as pending.
|
||||||
|
def pending?
|
||||||
|
self == Pending
|
||||||
|
end
|
||||||
|
|
||||||
|
# Indicates that the example finished successfully,
|
||||||
|
# but something may be wrong with it.
|
||||||
|
def warning?
|
||||||
|
self == Warning
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,4 +1,14 @@
|
||||||
module Spectator
|
module Spectator
|
||||||
|
# Exception that indicates a required expectation was not met in an example.
|
||||||
class ExpectationFailed < Exception
|
class ExpectationFailed < Exception
|
||||||
|
# Outcome of the expectation.
|
||||||
|
# Additional information can be retrieved through this.
|
||||||
|
getter result : Expectations::Expectation::Result
|
||||||
|
|
||||||
|
# Creates the exception.
|
||||||
|
# The exception string is generated from the expecation message.
|
||||||
|
def initialize(@result)
|
||||||
|
super(@result.actual_message)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,6 +2,8 @@ module Spectator::Expectations
|
||||||
# Min-in for all expectation types.
|
# Min-in for all expectation types.
|
||||||
# Classes that include this must implement
|
# Classes that include this must implement
|
||||||
# the `#satisfied?`, `#message`, and `#negated_message` methods.
|
# the `#satisfied?`, `#message`, and `#negated_message` methods.
|
||||||
|
# Typically, expectation classes/structs store an `ExpectationPartial`
|
||||||
|
# and a `Matchers::Matcher` and then proxy calls to those instances.
|
||||||
module Expectation
|
module Expectation
|
||||||
# Checks whether the expectation is met.
|
# Checks whether the expectation is met.
|
||||||
abstract def satisfied? : Bool
|
abstract def satisfied? : Bool
|
||||||
|
@ -20,22 +22,27 @@ module Spectator::Expectations
|
||||||
end
|
end
|
||||||
|
|
||||||
# Information regarding the outcome of an expectation.
|
# Information regarding the outcome of an expectation.
|
||||||
class Result
|
struct Result
|
||||||
# Indicates whether the expectation was satisifed or not.
|
# Indicates whether the expectation was satisifed or not.
|
||||||
getter? successful : Bool
|
getter? successful : Bool
|
||||||
|
|
||||||
|
# Indicates whether the expectation failed.
|
||||||
|
def failure? : Bool
|
||||||
|
!@successful
|
||||||
|
end
|
||||||
|
|
||||||
# Creates the result.
|
# Creates the result.
|
||||||
# The expectation is stored so that information from it may be lazy-loaded.
|
# The expectation is stored so that information from it may be lazy-loaded.
|
||||||
protected def initialize(@successful, @negated : Bool, @expectation : Expectation)
|
protected def initialize(@successful, @negated : Bool, @expectation : Expectation)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Description of the condition that satisfies, or meets, the expectation.
|
# Description of the condition that satisfies, or meets, the expectation.
|
||||||
def satisfy_message
|
def expected_message
|
||||||
message(@negated)
|
message(@negated)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Description of what actually happened when the expectation was evaluated.
|
# Description of what actually happened when the expectation was evaluated.
|
||||||
def result_message
|
def actual_message
|
||||||
message(@successful)
|
message(@successful)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,9 @@ module Spectator::Expectations
|
||||||
# The part of the expectation this class covers is the actual value.
|
# The part of the expectation this class covers is the actual value.
|
||||||
# This can also cover a block's behavior.
|
# This can also cover a block's behavior.
|
||||||
# Sub-types of this class are returned by the `DSL::ExampleDSL.expect` call.
|
# Sub-types of this class are returned by the `DSL::ExampleDSL.expect` call.
|
||||||
abstract class ExpectationPartial
|
# Sub-types are expected to implement their own variation
|
||||||
|
# of the `#to` and `#not_to` methods.
|
||||||
|
abstract struct ExpectationPartial
|
||||||
# User-friendly string displayed for the actual expression being tested.
|
# User-friendly string displayed for the actual expression being tested.
|
||||||
# For instance, in the expectation:
|
# For instance, in the expectation:
|
||||||
# ```
|
# ```
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
module Spectator::Expectations
|
|
||||||
# Tracks the expectations and their outcomes in an example.
|
|
||||||
# A single instance of this class should be associated with one example.
|
|
||||||
class ExpectationRegistry
|
|
||||||
private getter? raise_on_failure : Bool
|
|
||||||
|
|
||||||
private def initialize(@raise_on_failure = true)
|
|
||||||
end
|
|
||||||
|
|
||||||
def report(result : Expectation::Result) : Nil
|
|
||||||
raise NotImplementedError.new("ExpectationRegistry#report")
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.current : ExpectationRegistry
|
|
||||||
raise NotImplementedError.new("ExpectationRegistry.current")
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.start(example : Example) : Nil
|
|
||||||
raise NotImplementedError.new("ExpectationRegistry.start")
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.finish : ExpectationResults
|
|
||||||
raise NotImplementedError.new("ExpectationRegistry.finish")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
28
src/spectator/expectations/expectation_reporter.cr
Normal file
28
src/spectator/expectations/expectation_reporter.cr
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
module Spectator::Expectations
|
||||||
|
# Tracks the expectations and their outcomes in an example.
|
||||||
|
# A single instance of this class should be associated with one example.
|
||||||
|
class ExpectationReporter
|
||||||
|
# All results are stored in this array.
|
||||||
|
# The initial capacity is set to one,
|
||||||
|
# as that is the typical (and recommended)
|
||||||
|
# number of expectations per example.
|
||||||
|
@results = Array(Expectation::Result).new(1)
|
||||||
|
|
||||||
|
# Creates the reporter.
|
||||||
|
# When the `raise_on_failure` flag is set to true,
|
||||||
|
# which is the default, an exception will be raised
|
||||||
|
# on the first failure that is reported.
|
||||||
|
# To store failures and continue, set the flag to false.
|
||||||
|
def initialize(@raise_on_failure = true)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Stores the outcome of an expectation.
|
||||||
|
# If the raise on failure flag is set to true,
|
||||||
|
# then this method will raise an exception
|
||||||
|
# when a failing result is given.
|
||||||
|
def report(result : Expectation::Result) : Nil
|
||||||
|
@results << result
|
||||||
|
raise ExpectationFailed.new(result) if result.failure? && @raise_on_failure
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,9 +1,15 @@
|
||||||
require "./expectation"
|
require "./expectation"
|
||||||
|
|
||||||
module Spectator::Expectations
|
module Spectator::Expectations
|
||||||
class ValueExpectation(ActualType, ExpectedType)
|
# Expectation that operates on values.
|
||||||
|
# There are two values - the actual and expected.
|
||||||
|
# The actual value is what the SUT returned.
|
||||||
|
# The expected value is what the test wants to see.
|
||||||
|
struct ValueExpectation(ActualType, ExpectedType)
|
||||||
include Expectation
|
include Expectation
|
||||||
|
|
||||||
|
# Creates the expectation.
|
||||||
|
# This simply takes in the expectation partial and the matcher.
|
||||||
def initialize(@partial : ValueExpectationPartial(ActualType),
|
def initialize(@partial : ValueExpectationPartial(ActualType),
|
||||||
@matcher : Matchers::ValueMatcher(ExpectedType))
|
@matcher : Matchers::ValueMatcher(ExpectedType))
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,19 +1,33 @@
|
||||||
require "./expectation_partial"
|
require "./expectation_partial"
|
||||||
|
|
||||||
module Spectator::Expectations
|
module Spectator::Expectations
|
||||||
class ValueExpectationPartial(ActualType) < ExpectationPartial
|
# Expectation partial variation that operates on a value.
|
||||||
|
struct ValueExpectationPartial(ActualType) < ExpectationPartial
|
||||||
|
# Actual value produced by the test.
|
||||||
|
# This is the value passed to the `#expect` call.
|
||||||
getter actual
|
getter actual
|
||||||
|
|
||||||
|
# Creates the expectation partial.
|
||||||
protected def initialize(label : String, @actual : ActualType)
|
protected def initialize(label : String, @actual : ActualType)
|
||||||
super(label)
|
super(label)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Returns the actual value as a string
|
||||||
|
# if there's no label available.
|
||||||
|
def label
|
||||||
|
super.empty? ? actual.to_s : super
|
||||||
|
end
|
||||||
|
|
||||||
|
# Asserts that the `#actual` value matches some criteria.
|
||||||
|
# The criteria is defined by the matcher passed to this method.
|
||||||
def to(matcher : Matchers::ValueMatcher(ExpectedType)) : Nil forall ExpectedType
|
def to(matcher : Matchers::ValueMatcher(ExpectedType)) : Nil forall ExpectedType
|
||||||
expectation = ValueExpectation.new(self, matcher)
|
expectation = ValueExpectation.new(self, matcher)
|
||||||
result = expectation.eval
|
result = expectation.eval
|
||||||
ExpectationRegistry.current.report(result)
|
ExpectationRegistry.current.report(result)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Asserts that the `#actual` value *does not* match some criteria.
|
||||||
|
# This is effectively the opposite of `#to`.
|
||||||
def to_not(matcher : Matchers::ValueMatcher(ExpectedType)) : Nil forall ExpectedType
|
def to_not(matcher : Matchers::ValueMatcher(ExpectedType)) : Nil forall ExpectedType
|
||||||
expectation = ValueExpectation.new(self, matcher)
|
expectation = ValueExpectation.new(self, matcher)
|
||||||
result = expectation.eval(true)
|
result = expectation.eval(true)
|
||||||
|
|
|
@ -1,15 +1,23 @@
|
||||||
require "./value_matcher"
|
require "./value_matcher"
|
||||||
|
|
||||||
module Spectator::Matchers
|
module Spectator::Matchers
|
||||||
class EqualityMatcher(ExpectedType) < ValueMatcher(ExpectedType)
|
# Common matcher that tests whether two values equal each other.
|
||||||
|
# The values are compared with the `==` operator.
|
||||||
|
struct EqualityMatcher(ExpectedType) < ValueMatcher(ExpectedType)
|
||||||
|
# Determines whether the matcher is satisfied with the value given to it.
|
||||||
|
# True is returned if the match was successful, false otherwise.
|
||||||
def match?(partial : Expectations::ValueExpectationPartial(ActualType)) : Bool forall ActualType
|
def match?(partial : Expectations::ValueExpectationPartial(ActualType)) : Bool forall ActualType
|
||||||
partial.actual == expected
|
partial.actual == expected
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Describes the condition that satisfies the matcher.
|
||||||
|
# This is informational and displayed to the end-user.
|
||||||
def message(partial : Expectations::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
|
||||||
|
|
||||||
|
# Describes the condition that won't satsify the matcher.
|
||||||
|
# This is informational and displayed to the end-user.
|
||||||
def negated_message(partial : Expectations::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
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
module Spectator::Matchers
|
module Spectator::Matchers
|
||||||
abstract class Matcher
|
# Common base class for all expectation conditions.
|
||||||
|
# A matcher looks at something produced by the SUT
|
||||||
|
# and evaluates whether it is correct or not.
|
||||||
|
abstract struct Matcher
|
||||||
|
# Textual representation of what the matcher expects.
|
||||||
|
# This shouldn't be used in the conditional logic,
|
||||||
|
# but for verbose output to help the end-user.
|
||||||
private getter label : String
|
private getter label : String
|
||||||
|
|
||||||
|
# Creates the base of the matcher.
|
||||||
private def initialize(@label)
|
private def initialize(@label)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,17 +1,32 @@
|
||||||
require "./matcher"
|
require "./matcher"
|
||||||
|
|
||||||
module Spectator::Matchers
|
module Spectator::Matchers
|
||||||
abstract class ValueMatcher(ExpectedType) < Matcher
|
# Category of matcher that uses a value.
|
||||||
|
# Matchers of this type expect that a SUT applies to the value in some way.
|
||||||
|
# Sub-types must implement `#match`, `#message`, and `#negated_message`.
|
||||||
|
# Those methods accept a `ValueExpectationPartial` to work with.
|
||||||
|
abstract struct ValueMatcher(ExpectedType) < Matcher
|
||||||
|
# Expected value.
|
||||||
|
# Sub-types may use this value to test the expectation and generate message strings.
|
||||||
private getter expected
|
private getter expected
|
||||||
|
|
||||||
|
# Creates the value matcher.
|
||||||
|
# The label should be a string representation of the expectation.
|
||||||
|
# The expected value is stored for later use.
|
||||||
def initialize(label : String, @expected : ExpectedType)
|
def initialize(label : String, @expected : ExpectedType)
|
||||||
super(label)
|
super(label)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Determines whether the matcher is satisfied with the value given to it.
|
||||||
|
# True is returned if the match was successful, false otherwise.
|
||||||
abstract def match?(partial : ValueExpectationPartial(ActualType)) : Bool forall ActualType
|
abstract def match?(partial : ValueExpectationPartial(ActualType)) : Bool forall ActualType
|
||||||
|
|
||||||
|
# Describes the condition that satisfies the matcher.
|
||||||
|
# This is informational and displayed to the end-user.
|
||||||
abstract def message(partial : ValueExpectationPartial(ActualType)) : String forall ActualType
|
abstract def message(partial : ValueExpectationPartial(ActualType)) : String forall ActualType
|
||||||
|
|
||||||
|
# Describes the condition that won't satsify the matcher.
|
||||||
|
# This is informational and displayed to the end-user.
|
||||||
abstract def negated_message(partial : ValueExpectationPartial(ActualType)) : String forall ActualType
|
abstract def negated_message(partial : ValueExpectationPartial(ActualType)) : String forall ActualType
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue