Skip to content

Instantly share code, notes, and snippets.

@mdiep
Created February 4, 2020 13:02
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mdiep/fa69bd35339974d4d4e7b57009a9d0a1 to your computer and use it in GitHub Desktop.
Save mdiep/fa69bd35339974d4d4e7b57009a9d0a1 to your computer and use it in GitHub Desktop.
Diff values with Mirror and AnyHashable
import Foundation
// Diff values for better test assertions.
//
// Enums and collections left as an exercise for the reader.
// A difference between two values
struct Difference: CustomStringConvertible {
let path: String
let actual: String
let expected: String
init(path: String, actual: String, expected: String) {
self.path = path
self.actual = actual
self.expected = expected
}
init<V>(actual: V, expected: V) {
path = ""
self.actual = "\(actual)"
self.expected = "\(expected)"
}
// Add a key to the front of the key path
func prefix(_ key: String) -> Difference {
return Difference(
path: path == "" ? key : "\(key).\(path)",
actual: actual,
expected: expected
)
}
var description: String {
"""
\(path) doesn't match:
\tActual:
\t\t\(actual)
\tExpected:
\t\t\(expected)
"""
}
}
func differences<V>(_ a: V, _ b: V) -> [Difference] {
guard
let aHashable = a as? AnyHashable,
let bHashable = b as? AnyHashable
else {
fatalError("\(V.self) must conform to Hashable")
}
let areEqual = aHashable == bHashable
let mirrorA = Mirror(reflecting: a)
let mirrorB = Mirror(reflecting: b)
// If there are no children, then must be a primitive value.
// Compare in directly.
guard !mirrorA.children.isEmpty else {
return areEqual
? []
: [ Difference(actual: a, expected: b) ]
}
let diffs = zip(mirrorA.children, mirrorB.children)
.flatMap { pair -> [Difference] in
let (a, b) = pair
return differences(a.value, b.value)
.map { $0.prefix(a.label!) }
}
// Make sure no mistakes were made
precondition(diffs.isEmpty == areEqual)
return diffs
}
func diff<V>(_ a: V, _ b: V) -> String? {
let diffs = differences(a, b)
if diffs.isEmpty { return nil }
return diffs
.map { "\($0)" }
.joined(separator: "\n")
}
struct M: Hashable {
struct Foo: Hashable {
var baz: String
}
let id: Int
let foo: Foo
}
let a = M(id: 1, foo: M.Foo(baz: "bar"))
let b = M(id: 2, foo: M.Foo(baz: "baz"))
print(diff(a, b) ?? "==")
// Prints:
//
// id doesn't match:
// Actual:
// 1
// Expected:
// 2
// foo.baz doesn't match:
// Actual:
// bar
// Expected:
// baz
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment