Description
While we're talking about strictness of the eq
expectation in #13389, I'd like to push some other thought that has bugged me for a while.
Equality is usually expected to be commutative (i.e. from a == b
follows b == a
). That's a practical assumption, but not necessarily the case. If a
and b
have different types (say A
and B
), it might be that only one leg, A#==(B)
or B#==(A)
is implemented and the inverse would fall back to the default implementation which always returns false
(ref #10277).
In stdlib there are a couple of cases where this applies. They're related to types that imitate a wrapped type, namely YAML::Any
and Log::Metadata::Value
.
require "log"
a = Log::Metadata::Value.new("foo")
b = "foo"
a == b # => true
b == a # => false
I think that these types that implement custom equality methods should do the same on the types they define equality with.
Having specs ensure equality in both directions should go a long way to provide sanity on this topic.
If type strictness gets implemented as proposed in #13389, this will also cover specs for all such cases because a eq b
requires both operands to have the same type. Otherwise, it would be good for eq
to test reverse equality.
Patch
--- i/src/spec/expectations.cr
+++ w/src/spec/expectations.cr
@@ -16,7 +16,7 @@ module Spec
actual_value.bytesize == expected_value.bytesize &&
actual_value.size == expected_value.size
else
- actual_value == @expected_value
+ (actual_value == @expected_value) && (@expected_value == actual_value)
end
end
Failing examples with this patch:
1) YAML::Schema::Core parses "!!set { 1, 2, 3 }"
Failure/Error: YAML::Schema::Core.parse(string).should eq(expected)
Expected: Set{1, 2, 3} : Set(Int32)
got: Set{1, 2, 3} : YAML::Any
# spec/std/yaml/schema/core_spec.cr:6
2) YAML::Schema::Core parses "!!binary aGVsbG8="
Failure/Error: YAML::Schema::Core.parse(string).should eq(expected)
Expected: Bytes[104, 101, 108, 108, 111] : Slice(UInt8)
got: Bytes[104, 101, 108, 108, 111] : YAML::Any
# spec/std/yaml/schema/core_spec.cr:6
3) YAML::Schema::Core parses "!!timestamp 2010-01-02"
Failure/Error: YAML::Schema::Core.parse(string).should eq(expected)
Expected: 2010-01-02 00:00:00.0 UTC : Time
got: 2010-01-02 00:00:00.0 UTC : YAML::Any
# spec/std/yaml/schema/core_spec.cr:6
4) Log::Metadata []?
Failure/Error: md[:a]?.should eq(3)
Expected: 3 : Int32
got: 3 : Log::Metadata::Value
# spec/std/log/metadata_spec.cr:89
5) Log::Metadata []
Failure/Error: md[:a].should eq(3)
Expected: 3 : Int32
got: 3 : Log::Metadata::Value
# spec/std/log/metadata_spec.cr:81
6) HTTP::Server::RequestProcessor does not bleed Log::Context between requests
Failure/Error: logs.entry.context[:foo].should eq "bar"
Expected: "bar" : String
got: "bar" : Log::Metadata::Value
# spec/std/http/server/request_processor_spec.cr:345
7) HTTP::Server::RequestProcessor catches raised error on handler and retains context from handler
Failure/Error: logs.entry.context[:foo].should eq "bar"
Expected: "bar" : String
got: "bar" : Log::Metadata::Value
# spec/std/http/server/request_processor_spec.cr:289
8) HTTP::Client logging emit logs
Failure/Error: logs.entry.data[:method].should eq("GET")
Expected: "GET" : String
got: "GET" : Log::Metadata::Value
# spec/std/http/client/client_spec.cr:445
9) HTTP::Client logging emit logs with block
Failure/Error: logs.entry.data[:method].should eq("GET")
Expected: "GET" : String
got: "GET" : Log::Metadata::Value
# spec/std/http/client/client_spec.cr:459
Finished in 1:31 minutes
15327 examples, 9 failures, 0 errors, 20 pending
Failed examples:
crystal spec spec/std/yaml/schema/core_spec.cr:184 # YAML::Schema::Core parses "!!set { 1, 2, 3 }"
crystal spec spec/std/yaml/schema/core_spec.cr:192 # YAML::Schema::Core parses "!!binary aGVsbG8="
crystal spec spec/std/yaml/schema/core_spec.cr:235 # YAML::Schema::Core parses "!!timestamp 2010-01-02"
crystal spec spec/std/log/metadata_spec.cr:86 # Log::Metadata []?
crystal spec spec/std/log/metadata_spec.cr:78 # Log::Metadata []
crystal spec spec/std/http/server/request_processor_spec.cr:325 # HTTP::Server::RequestProcessor does not bleed Log::Context between requests
crystal spec spec/std/http/server/request_processor_spec.cr:271 # HTTP::Server::RequestProcessor catches raised error on handler and retains context from handler
crystal spec spec/std/http/client/client_spec.cr:438 # HTTP::Client logging emit logs
crystal spec spec/std/http/client/client_spec.cr:453 # HTTP::Client logging emit logs with block