From 5520999b6d7619985a93935faf546667f54289ad Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Sat, 27 Jan 2024 11:16:57 -0700 Subject: [PATCH 1/8] Add spec for GitHub issue 55 https://github.com/icy-arctic-fox/spectator/issues/55 --- spec/issues/github_issue_55_spec.cr | 48 +++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 spec/issues/github_issue_55_spec.cr diff --git a/spec/issues/github_issue_55_spec.cr b/spec/issues/github_issue_55_spec.cr new file mode 100644 index 0000000..92c2b42 --- /dev/null +++ b/spec/issues/github_issue_55_spec.cr @@ -0,0 +1,48 @@ +require "../spec_helper" + +Spectator.describe "GitHub Issue #55" do + GROUP_NAME = "CallCenter" + + let(name) { "TimeTravel" } + let(source) { "my.time.travel.experiment" } + + class Analytics(T) + property start_time = Time.local + property end_time = Time.local + + def initialize(@brain_talker : T) + end + + def instrument(*, name, source, &) + @brain_talker.send(payload: { + :group => GROUP_NAME, + :name => name, + :source => source, + :start => start_time, + :end => end_time, + }, action: "analytics") + end + end + + double(:brain_talker, send: nil) + + let(brain_talker) { double(:brain_talker) } + let(analytics) { Analytics.new(brain_talker) } + + it "tracks the time it takes to run the block" do + analytics.start_time = expected_start_time = Time.local + expected_end_time = expected_start_time + 10.seconds + analytics.end_time = expected_end_time + 0.5.seconds # Offset to ensure non-exact match. + + analytics.instrument(name: name, source: source) do + end + + expect(brain_talker).to have_received(:send).with(payload: { + :group => GROUP_NAME, + :name => name, + :source => source, + :start => expected_start_time, + :end => be_within(1.second).of(expected_end_time), + }, action: "analytics") + end +end From b5fbc96195031cdcb2658dd0a324e4e73c0b6f9a Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Sat, 27 Jan 2024 11:17:19 -0700 Subject: [PATCH 2/8] Allow matchers to be used in case equality --- src/spectator/matchers/matcher.cr | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/spectator/matchers/matcher.cr b/src/spectator/matchers/matcher.cr index e54e55e..05adb81 100644 --- a/src/spectator/matchers/matcher.cr +++ b/src/spectator/matchers/matcher.cr @@ -1,3 +1,4 @@ +require "../value" require "./match_data" module Spectator::Matchers @@ -22,6 +23,19 @@ module Spectator::Matchers # A successful match with `#match` should normally fail for this method, and vice-versa. abstract def negated_match(actual : Expression(T)) : MatchData forall T + # Compares a matcher against a value. + # Enables composable matchers. + def ===(actual : Expression(T)) : Bool + match(actual).matched? + end + + # Compares a matcher against a value. + # Enables composable matchers. + def ===(other) : Bool + expression = Value.new(other) + match(expression).matched? + end + private def match_data_description(actual : Expression(T)) : String forall T match_data_description(actual.label) end From 556d4783bf3669cba6fab53db3ea42edba7981f4 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Sat, 27 Jan 2024 11:18:10 -0700 Subject: [PATCH 3/8] Support case equality of tuples, arrays, named tuples, and hashes in stub argument matching --- src/spectator/mocks/abstract_arguments.cr | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/spectator/mocks/abstract_arguments.cr b/src/spectator/mocks/abstract_arguments.cr index 442c1d2..fd9bac9 100644 --- a/src/spectator/mocks/abstract_arguments.cr +++ b/src/spectator/mocks/abstract_arguments.cr @@ -7,7 +7,7 @@ module Spectator end # Utility method for comparing two tuples considering special types. - private def compare_tuples(a : Tuple, b : Tuple) + private def compare_tuples(a : Tuple | Array, b : Tuple | Array) return false if a.size != b.size a.zip(b) do |a_value, b_value| @@ -18,14 +18,14 @@ module Spectator # Utility method for comparing two tuples considering special types. # Supports nilable tuples (ideal for splats). - private def compare_tuples(a : Tuple?, b : Tuple?) + private def compare_tuples(a : Tuple? | Array?, b : Tuple? | Array?) return false if a.nil? ^ b.nil? compare_tuples(a.not_nil!, b.not_nil!) end # Utility method for comparing two named tuples ignoring order. - private def compare_named_tuples(a : NamedTuple, b : NamedTuple) + private def compare_named_tuples(a : NamedTuple | Hash, b : NamedTuple | Hash) a.each do |k, v1| v2 = b.fetch(k) { return false } return false unless compare_values(v1, v2) @@ -50,6 +50,18 @@ module Spectator else a == b end + when Tuple, Array + if b.is_a?(Tuple) || b.is_a?(Array) + compare_tuples(a, b) + else + a === b + end + when NamedTuple, Hash + if b.is_a?(NamedTuple) || b.is_a?(Hash) + compare_named_tuples(a, b) + else + a === b + end else a === b end From 526a998e4183e3d24621d7413d44eae91003e8be Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Sat, 27 Jan 2024 11:25:25 -0700 Subject: [PATCH 4/8] Shorten compare_values case statements --- src/spectator/mocks/abstract_arguments.cr | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/src/spectator/mocks/abstract_arguments.cr b/src/spectator/mocks/abstract_arguments.cr index fd9bac9..4a6f75f 100644 --- a/src/spectator/mocks/abstract_arguments.cr +++ b/src/spectator/mocks/abstract_arguments.cr @@ -45,23 +45,14 @@ module Spectator when Range # Ranges can only be matched against if their right side is comparable. # Ensure the right side is comparable, otherwise compare directly. - if b.is_a?(Comparable(typeof(b))) - a === b - else - a == b - end + return a === b if b.is_a?(Comparable(typeof(b))) + a == b when Tuple, Array - if b.is_a?(Tuple) || b.is_a?(Array) - compare_tuples(a, b) - else - a === b - end + return compare_tuples(a, b) if b.is_a?(Tuple) || b.is_a?(Array) + a === b when NamedTuple, Hash - if b.is_a?(NamedTuple) || b.is_a?(Hash) - compare_named_tuples(a, b) - else - a === b - end + return compare_named_tuples(a, b) if b.is_a?(NamedTuple) || b.is_a?(Hash) + a === b else a === b end From edb20e5b2f2e57c4326bad7a0fca374342adc8c5 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Sat, 27 Jan 2024 11:25:59 -0700 Subject: [PATCH 5/8] Additional handling when comparing ranges against unexpected types --- src/spectator/matchers/range_matcher.cr | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/spectator/matchers/range_matcher.cr b/src/spectator/matchers/range_matcher.cr index 8c31810..8a9a307 100644 --- a/src/spectator/matchers/range_matcher.cr +++ b/src/spectator/matchers/range_matcher.cr @@ -29,7 +29,26 @@ module Spectator::Matchers # Checks whether the matcher is satisfied with the expression given to it. private def match?(actual : Expression(T)) : Bool forall T - expected.value.includes?(actual.value) + actual_value = actual.value + expected_value = expected.value + if expected_value.is_a?(Range) && actual_value.is_a?(Comparable) + return match_impl?(expected_value, actual_value) + end + return false unless actual_value.is_a?(Comparable(typeof(expected_value.begin))) + expected_value.includes?(actual_value) + end + + private def match_impl?(expected_value : Range(B, E), actual_value : Comparable(B)) : Bool forall B, E + expected_value.includes?(actual_value) + end + + private def match_impl?(expected_value : Range(B, E), actual_value : T) : Bool forall B, E, T + return false unless actual_value.is_a?(B) || actual_value.is_a?(Comparable(B)) + expected_value.includes?(actual_value) + end + + private def match_impl?(expected_value : Range(Number, Number), actual_value : Number) : Bool + expected_value.includes?(actual_value) end # Message displayed when the matcher isn't satisfied. From 9b1d400ee1f0edf0b979b0d113ea618b16e0deed Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Sat, 27 Jan 2024 11:29:11 -0700 Subject: [PATCH 6/8] Update CHANGELOG --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e23272..7f7943a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] +### Added +- Added ability to use matchers for case equality. [#55](https://github.com/icy-arctic-fox/spectator/issues/55) +- Added support for nested case equality when checking arguments with Array, Tuple, Hash, and NamedTuple. + +### Fixed +- Fixed some issues with the `be_within` matcher when used with expected and union types. + ## [0.11.7] - 2023-10-16 ### Fixed - Fix memoized value (`let`) with a union type causing segfault. [#81](https://gitlab.com/arctic-fox/spectator/-/issues/81) From f39ceb8eba897cf634fee1922543e8a7e7125f02 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Sat, 3 Feb 2024 07:56:57 -0700 Subject: [PATCH 7/8] Release v0.12.0 --- CHANGELOG.md | 5 +++-- shard.yml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f7943a..278c53c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [0.12.0] - 2024-02-03 ### Added - Added ability to use matchers for case equality. [#55](https://github.com/icy-arctic-fox/spectator/issues/55) - Added support for nested case equality when checking arguments with Array, Tuple, Hash, and NamedTuple. @@ -458,7 +458,8 @@ This has been changed so that it compiles and raises an error at runtime with a First version ready for public use. -[Unreleased]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.11.7...master +[Unreleased]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.12.0...master +[0.12.0]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.11.7...v0.12.0 [0.11.7]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.11.6...v0.11.7 [0.11.6]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.11.5...v0.11.6 [0.11.5]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.11.4...v0.11.5 diff --git a/shard.yml b/shard.yml index 78ab190..559754f 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: spectator -version: 0.11.7 +version: 0.12.0 description: | Feature-rich testing framework for Crystal inspired by RSpec. From 287758e6af784200a4a51a60f364211fbd1ff6c4 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Sat, 3 Feb 2024 08:05:02 -0700 Subject: [PATCH 8/8] Update README to point at v0.12.0 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 323b4ca..00b5eb9 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Add this to your application's `shard.yml`: development_dependencies: spectator: gitlab: arctic-fox/spectator - version: ~> 0.11.0 + version: ~> 0.12.0 ``` Usage