Skip to content

Commit

Permalink
feat: #26 check for nullness
Browse files Browse the repository at this point in the history
  • Loading branch information
Tarmil committed Sep 18, 2024
1 parent dadd054 commit e0c9ac2
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 5 deletions.
20 changes: 16 additions & 4 deletions src/Diffract/DiffPrinter.fs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,27 @@ let toStreamImpl (w: TextWriter) param (d: Diff) =
let indentLike str = String.replicate (String.length str) " "
let displayPath path = if path = "" then param.neutralName else path

let printValue indent path x1 x2 =
let printValue indent path (x1: obj) (x2: obj) =
let dpath = if path = "" then "" else path + " "
w.WriteLine($"%s{indent}%s{dpath}%s{param.x1Name} = %A{x1}")
w.WriteLine($"%s{indent}%s{indentLike dpath}%s{param.x2Name} = %A{x2}")

if isNull x1 then
w.WriteLine($"%s{indent}%s{dpath}%s{param.x1Name} is null")
w.WriteLine($"%s{indent}%s{indentLike dpath}%s{param.x2Name} = %A{x2}")
elif isNull x2 then
w.WriteLine($"%s{indent}%s{dpath}%s{param.x1Name} = %A{x1}")
w.WriteLine($"%s{indent}%s{indentLike dpath}%s{param.x2Name} is null")
else
w.WriteLine($"%s{indent}%s{dpath}%s{param.x1Name} = %A{x1}")
w.WriteLine($"%s{indent}%s{indentLike dpath}%s{param.x2Name} = %A{x2}")

let rec loop (indent: string) (path: string) (d: Diff) =
match d with
| Diff.Value (x1, x2) ->
printValue indent path x1 x2
| Diff.Nullness (x1, x2) ->
let dpath = if path = "" then "" else path + " "
let dindent = indentLike dpath
w.WriteLine($"""%s{indent}%s{dpath}%s{param.x1Name} is%s{if isNull x1 then "" else " not"} null""")
w.WriteLine($"""%s{indent}%s{dindent}%s{param.x2Name} is%s{if isNull x2 then "" else " not"} null""")
| Diff.Record fields when fields.Count = 1 ->
loop indent (addPathField path fields.[0].Name) fields.[0].Diff
| Diff.Record fields ->
Expand Down
13 changes: 12 additions & 1 deletion src/Diffract/Differ.fs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ module DifferImpl =
type private Cache = Dictionary<Type, IDifferFactory>
type private CachedDiffer<'T> = Cache -> IDiffer<'T>

let private checkNull<'T> (differ: IDiffer<'T>) =
if typeof<'T>.IsValueType then
differ
else
{ new IDiffer<'T> with
member _.Diff(x1, x2) =
match isNull (box x1), isNull (box x2) with
| false, false -> differ.Diff(x1, x2)
| true, true -> None
| _ -> Some (Nullness(x1, x2)) }

/// Add to the cache a differ for a type that may be recursive (ie have nested fields of its own type).
let private addRecursiveToCache (differ: CachedDiffer<'T>) : CachedDiffer<'T> =
let ty = typeof<'T>
Expand All @@ -25,7 +36,7 @@ module DifferImpl =
{ new IDiffer<'U> with
member _.Diff(x1, x2) =
(unbox<IDiffer<'U>> !r).Diff(x1, x2) } })
let differ = differ cache
let differ = differ cache |> checkNull
r := differ
differ
| true, differFactory ->
Expand Down
2 changes: 2 additions & 0 deletions src/Diffract/Types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ open TypeShape.Core
type Diff =
/// The objects are leaf values and are different.
| Value of x1: obj * x2: obj
/// One of the objects is null and the other isn't.
| Nullness of x1: obj * x2: obj
/// The objects are records or plain objects and some of their fields differ.
| Record of fields: IReadOnlyList<FieldDiff>
/// The objects are F# unions with different cases.
Expand Down
62 changes: 62 additions & 0 deletions tests/Diffract.Tests/Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ type U =
| U2 of x: int * y: int
type Bar = { aaa: Foo; bbb: U }

type Baz = { xx: int; yy: string }

[<AllowNullLiteral>]
type Class(x: int) =
member _.X = x

let assertStr (expected: string, actual: string) =
Assert.Equal(expected.Replace("\r\n", "\n"), actual)

Expand All @@ -27,6 +33,62 @@ let ``Exception for non-equal values`` (x: Bar) (y: Bar) =
Differ.Assert(x, y))
|> ignore

[<Fact>]
let ``Both have null leaf`` () =
let actual = Differ.Diff({ xx = 1; yy = null }, { xx = 1; yy = null })
Assert.Equal(None, actual)

[<Fact>]
let ``Expected has null leaf`` () =
let actual = Differ.Diff({ xx = 1; yy = null }, { xx = 1; yy = "a" })
Assert.Equal(Some(Diff.Record [ { Name = "yy"; Diff = Diff.Value(null, "a") } ]), actual)

let actual = Differ.ToString({ xx = 1; yy = null }, { xx = 1; yy = "a" })
assertStr("\
yy Expect is null
Actual = \"a\"
", actual)

[<Fact>]
let ``Actual has null leaf`` () =
let actual = Differ.Diff({ xx = 1; yy = "a" }, { xx = 1; yy = null })
Assert.Equal(Some(Diff.Record [ { Name = "yy"; Diff = Diff.Value("a", null) } ]), actual)

let actual = Differ.ToString({ xx = 1; yy = "a" }, { xx = 1; yy = null })
assertStr("\
yy Expect = \"a\"
Actual is null
", actual)

[<Fact>]
let ``Both are null`` () =
let actual = Differ.Diff((null: Class), null)
Assert.Equal(None, actual)

[<Fact>]
let ``Expected is null`` () =
let actualValue = Class(3)
let actual = Differ.Diff(null, actualValue)
Assert.Equal(Some(Diff.Nullness(null, actualValue)), actual)

let actual = Differ.ToString(null, actualValue)
assertStr("\
Expect is null
Actual is not null
", actual)

[<Fact>]
let ``Actual is null`` () =
let actualValue = Class(3)
let actual = Differ.Diff(actualValue, null)
Assert.Equal(Some(Diff.Nullness(actualValue, null)), actual)

let actual = Differ.ToString(actualValue, null)
assertStr("\
Expect is not null
Actual is null
", actual)

[<Property>]
let ``List diff`` (l1: int list) (l2: int list) =
let d = Differ.Diff(l1, l2)
Expand Down

0 comments on commit e0c9ac2

Please sign in to comment.