From bd0396c001aed4628f0b4e6b1e9b07e3b5823eaf Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Thu, 14 Nov 2019 18:19:37 -0700 Subject: [PATCH 1/5] Add to_eventually and to_never operations This defines what they should look like. --- .../expectations/expectation_partial.cr | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/spectator/expectations/expectation_partial.cr b/src/spectator/expectations/expectation_partial.cr index ed2e33c..8aa3709 100644 --- a/src/spectator/expectations/expectation_partial.cr +++ b/src/spectator/expectations/expectation_partial.cr @@ -37,6 +37,24 @@ module Spectator::Expectations to_not(matcher) end + # Asserts that some criteria defined by the matcher is eventually satisfied. + # The expectation is checked after the example finishes and all hooks have run. + def to_eventually(matcher) : Nil + Harness.current.defer { to(matcher) } + end + + # Asserts that some criteria defined by the matcher is never satisfied. + # The expectation is checked after the example finishes and all hooks have run. + def to_never(matcher) : Nil + Harness.current.defer { to_not(matcher) } + end + + # ditto + @[AlwaysInline] + def never_to(matcher) : Nil + to_never(matcher) + end + # Reports an expectation to the current harness. private def report(match_data : Matchers::MatchData) expectation = Expectation.new(match_data, @source) From a80d018ff6d7f088c42c198947b0d8817502251a Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Thu, 14 Nov 2019 18:23:45 -0700 Subject: [PATCH 2/5] Remove type annotation/requirement --- src/spectator/should.cr | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/spectator/should.cr b/src/spectator/should.cr index de75c85..bfa0941 100644 --- a/src/spectator/should.cr +++ b/src/spectator/should.cr @@ -16,7 +16,7 @@ class Object # ``` # require "spectator/should" # ``` - def should(matcher : ::Spectator::Matchers::Matcher) + def should(matcher) # First argument of the `Expectation` initializer is the expression label. # However, since this isn't a macro and we can't "look behind" this method call # to see what it was invoked on, the argument is an empty string. @@ -28,7 +28,7 @@ class Object # Works the same as `#should` except the condition is inverted. # When `#should` succeeds, this method will fail, and vice-versa. - def should_not(matcher : ::Spectator::Matchers::Matcher) + def should_not(matcher) actual = ::Spectator::TestValue.new(self) source = ::Spectator::Source.new(__FILE__, __LINE__) ::Spectator::Expectations::ExpectationPartial.new(actual, source).to_not(matcher) @@ -38,7 +38,7 @@ end struct Proc(*T, R) # Extension method to create an expectation for a block of code (proc). # Depending on the matcher, the proc may be executed multiple times. - def should(matcher : ::Spectator::Matchers::Matcher) + def should(matcher) actual = ::Spectator::TestBlock.new(self) source = ::Spectator::Source.new(__FILE__, __LINE__) ::Spectator::Expectations::ExpectationPartial.new(actual, source).to(matcher) @@ -46,7 +46,7 @@ struct Proc(*T, R) # Works the same as `#should` except the condition is inverted. # When `#should` succeeds, this method will fail, and vice-versa. - def should_not(matcher : ::Spectator::Matchers::Matcher) + def should_not(matcher) actual = ::Spectator::TestBlock.new(self) source = ::Spectator::Source.new(__FILE__, __LINE__) ::Spectator::Expectations::BlockExpectationPartial.new(actual, source).to_not(matcher) From 173f7bfa13f8207011361c7865865c0dc18f00c0 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Thu, 14 Nov 2019 18:26:24 -0700 Subject: [PATCH 3/5] Add should_eventually and should_never methods --- src/spectator/should.cr | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/spectator/should.cr b/src/spectator/should.cr index bfa0941..b8cda4a 100644 --- a/src/spectator/should.cr +++ b/src/spectator/should.cr @@ -33,6 +33,18 @@ class Object source = ::Spectator::Source.new(__FILE__, __LINE__) ::Spectator::Expectations::ExpectationPartial.new(actual, source).to_not(matcher) end + + # Works the same as `#should` except that the condition check is postphoned. + # The expectation is checked after the example finishes and all hooks have run. + def should_eventually(matcher) + ::Spectator::Harness.current.defer { should(matcher) } + end + + # Works the same as `#should_not` except that the condition check is postphoned. + # The expectation is checked after the example finishes and all hooks have run. + def should_never(matcher) + ::Spectator::Harness.current.defer { should_not(matcher) } + end end struct Proc(*T, R) From 2128184659eb998e87710b86dd37b773c514ddd6 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Thu, 14 Nov 2019 18:30:48 -0700 Subject: [PATCH 4/5] Implement defer logic in harness --- src/spectator/harness.cr | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/spectator/harness.cr b/src/spectator/harness.cr index 8fe0070..caa43a0 100644 --- a/src/spectator/harness.cr +++ b/src/spectator/harness.cr @@ -51,10 +51,22 @@ module Spectator @reporter.expectations end + # Marks a block of code to run later. + def defer(&block : ->) : Nil + @deferred << block + end + + # Runs all deferred blocks. + def run_deferred : Nil + @deferred.each(&.call) + @deferred.clear + end + # Creates a new harness. # The example the harness is for should be passed in. private def initialize(@example) @reporter = Expectations::ExpectationReporter.new + @deferred = Deque(->).new end end end From 6b3885af309c2da217e77e6daaeaba033c7be50e Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Thu, 14 Nov 2019 18:41:26 -0700 Subject: [PATCH 5/5] Run deferred blocks --- src/spectator/runnable_example.cr | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/spectator/runnable_example.cr b/src/spectator/runnable_example.cr index d981689..9bbbbed 100644 --- a/src/spectator/runnable_example.cr +++ b/src/spectator/runnable_example.cr @@ -20,6 +20,7 @@ module Spectator context.run_before_hooks(self) run_example(result) context.run_after_hooks(self) + run_deferred(result) end end @@ -40,6 +41,17 @@ module Spectator end end + # Runs the deferred blocks of code and captures the result. + private def run_deferred(result) + result.elapsed += Time.measure do + begin + Harness.current.run_deferred + rescue ex # Catch all errors and handle them later. + result.error = ex + end + end + end + # Creates a result instance from captured result information. private def translate_result(result, expectations) case (error = result.error)