Skip to content

Commutative equality #13409

Open
Open
@straight-shoota

Description

@straight-shoota

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions