Skip to content

Instantly share code, notes, and snippets.

@moodmosaic
Last active December 30, 2015 13:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save moodmosaic/7838293 to your computer and use it in GitHub Desktop.
Save moodmosaic/7838293 to your computer and use it in GitHub Desktop.
Compare complex object graphs with SemanticComparer<T>.
module SemanticEqualityComparisonInFSharp
open System
open System.Reflection
open Ploeh.SemanticComparison
open Xunit
open Xunit.Extensions
[<CustomEquality; NoComparison>]
type StructuralType =
{ Value: int;
Other: string }
override this.Equals(y) =
match y with
| :? StructuralType as other -> (this.Value = other.Value)
| _ -> false
override x.GetHashCode() = hash x.Value
type ValueObject(x: int, y: int) =
member this.X = x
member this.Y = y
override this.Equals(other) =
match other with
| :? ValueObject as other ->
this.X = other.X &&
this.Y = other.Y
| _ -> Object.Equals(this, other)
override this.GetHashCode() =
hash this.X ^^^
hash this.Y
type Entity(name: string) =
member this.Name = name
member this.Id = Guid.NewGuid()
override this.Equals(other) =
match other with
| :? Entity as other -> this.Id = other.Id
| _ -> Object.Equals(this, other)
override this.GetHashCode() = hash this.Id
type ComplexType(entity, value, record, number, text, version, os) =
member this.Entity = entity
member this.Value = value
member this.Record = record
member this.Number = number
member this.Text = text
member this.Version = version
member this.OS = os
[<Theory; PropertyData("RecursiveComparisonTestCases")>]
let ``Equals returns correct result for ComplexType`` value other expected =
// Fixture setup
let valueObjectComparer() = {
new IMemberComparer with
member this.IsSatisfiedBy(request: PropertyInfo) = true
member this.IsSatisfiedBy(request: FieldInfo) = true
member this.GetHashCode(obj) = hash obj
member this.Equals(x, y) = x.Equals(y) }
let entityComparer() = {
new IMemberComparer with
member this.IsSatisfiedBy(request: PropertyInfo) =
request.PropertyType = typedefof<Entity>
member this.IsSatisfiedBy(request: FieldInfo) =
request.FieldType = typedefof<Entity>
member this.GetHashCode(obj) = hash obj
member this.Equals(x, y) =
StringComparer.OrdinalIgnoreCase.Equals(
(x :?> Entity).Name,
(y :?> Entity).Name) }
let osComparer() = {
new IMemberComparer with
member this.IsSatisfiedBy(request: PropertyInfo) =
request.PropertyType = typedefof<OperatingSystem>
member this.IsSatisfiedBy(request: FieldInfo) =
request.FieldType = typedefof<OperatingSystem>
member this.GetHashCode(obj) = hash obj
member this.Equals(x, y) =
(x :?> OperatingSystem).Version.Equals(
(y :?> OperatingSystem).Version) }
let sut =
SemanticComparer<ComplexType>(
valueObjectComparer(),
entityComparer(),
osComparer())
// Exercise system
let actual = sut.Equals(value, other)
// Verify outcome
Assert.Equal(expected, actual)
// Teardown
[<Theory; PropertyData("RecursiveComparisonTestCases")>]
let ``Likeness returns correct result for ComplexType`` value other expected =
// Fixture setup
let valueObjectComparer() = {
new IMemberComparer with
member this.IsSatisfiedBy(request: PropertyInfo) = true
member this.IsSatisfiedBy(request: FieldInfo) = true
member this.GetHashCode(obj) = hash obj
member this.Equals(x, y) = x.Equals(y) }
let entityComparer() = {
new IMemberComparer with
member this.IsSatisfiedBy(request: PropertyInfo) =
request.PropertyType = typedefof<Entity>
member this.IsSatisfiedBy(request: FieldInfo) =
request.FieldType = typedefof<Entity>
member this.GetHashCode(obj) = hash obj
member this.Equals(x, y) =
StringComparer.OrdinalIgnoreCase.Equals(
(x :?> Entity).Name,
(y :?> Entity).Name) }
let osComparer() = {
new IMemberComparer with
member this.IsSatisfiedBy(request: PropertyInfo) =
request.PropertyType = typedefof<OperatingSystem>
member this.IsSatisfiedBy(request: FieldInfo) =
request.FieldType = typedefof<OperatingSystem>
member this.GetHashCode(obj) = hash obj
member this.Equals(x, y) =
(x :?> OperatingSystem).Version.Equals(
(y :?> OperatingSystem).Version) }
let likeness =
Likeness<ComplexType>(
value,
SemanticComparer<ComplexType>(
valueObjectComparer(),
entityComparer(),
osComparer()))
let sut = likeness.ToResemblance()
// Exercise system
let actual = sut.Equals(other)
// Verify outcome
Assert.Equal(expected, actual)
// Teardown
let RecursiveComparisonTestCases : seq<obj[]> =
seq {
yield
[|
ComplexType(
Entity("abc"),
ValueObject(1, 2),
{ Value = 1;
Other = "foo" },
1,
"Anonymous Text",
Version(4, 0, 0),
OperatingSystem(
PlatformID.Unix,
Version(3, 9, 8)))
ComplexType(
Entity("abc"),
ValueObject(1, 2),
{ Value = 1;
Other = "bar" }, // Difference
1,
"Anonymous Text",
Version(4, 0, 0),
OperatingSystem(
PlatformID.Xbox, // Difference
Version(3, 9, 8)))
true // Expected result
|]
yield
[|
ComplexType(
Entity("abc"),
ValueObject(1, 2),
{ Value = 2;
Other = "foo" },
1,
"123",
Version(4, 0, 0),
OperatingSystem(
PlatformID.Unix,
Version(3, 9, 8)))
ComplexType(
Entity("ABC"), // Difference
ValueObject(1, 2),
{ Value = 2;
Other = "foo" },
1,
"123",
Version(4, 0, 0),
OperatingSystem(
PlatformID.Xbox, // Difference
Version(3, 9, 8)))
true // Expected result
|]
yield
[|
ComplexType(
Entity("abc"),
ValueObject(1, 2),
{ Value = 3;
Other = "foo" },
1,
"Anonymous Text",
Version(4, 0, 0),
OperatingSystem(
PlatformID.Unix,
Version(3, 9, 8)))
ComplexType(
Entity("abc"),
ValueObject(1, 2),
{ Value = 4; // Difference
Other = "foo" },
1,
"Anonymous Text",
Version(4, 0, 0),
OperatingSystem(
PlatformID.Xbox, // Difference
Version(0, 0, 0))) // Difference
false // Expected result
|] }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment