From 679b78b18dcaf8a7d50ef0b3ac49a5dd91f84b84 Mon Sep 17 00:00:00 2001 From: HLWeil Date: Mon, 27 Jan 2025 16:29:38 +0100 Subject: [PATCH 1/4] add deepHashing function --- src/DynamicObj/DynamicObj.fs | 35 +++++++++++++++++++++++++++++++---- src/DynamicObj/HashCodes.fs | 8 ++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/DynamicObj/DynamicObj.fs b/src/DynamicObj/DynamicObj.fs index ea05127..1fdaa88 100644 --- a/src/DynamicObj/DynamicObj.fs +++ b/src/DynamicObj/DynamicObj.fs @@ -389,10 +389,7 @@ type DynamicObj() = lookup.SetProperty (name,value) override this.GetHashCode () = - this.GetProperties(true) - |> Seq.sortBy (fun pair -> pair.Key) - |> HashCodes.boxHashKeyValSeq - |> fun x -> x :?> int + HashUtils.deepHash this override this.Equals o = match o with @@ -400,6 +397,36 @@ type DynamicObj() = this.GetHashCode() = other.GetHashCode() | _ -> false +and HashUtils = + + static member deepHash (o:obj) = + match o with + | :? DynamicObj as o -> + o.GetProperties(true) + |> Seq.sortBy (fun pair -> pair.Key) + |> HashCodes.boxHashKeyValSeqBy HashUtils.deepHash + |> fun x -> x :?> int + | :? string as s -> DynamicObj.HashCodes.hash s + #if !FABLE_COMPILER + | :? System.Collections.IDictionary as d -> + let mutable en = d.GetEnumerator() + [ + while en.MoveNext() do + let c = en.Current :?> System.Collections.DictionaryEntry + HashCodes.mergeHashes (hash c.Key) (HashUtils.deepHash c.Value) + ] + |> List.fold (fun acc h -> HashCodes.mergeHashes acc h) 0 + #endif + | :? System.Collections.IEnumerable as e -> + let en = e.GetEnumerator() + [ + while en.MoveNext() do + + HashUtils.deepHash en.Current + ] + |> List.fold (fun acc h -> HashCodes.mergeHashes acc h) 0 + | _ -> DynamicObj.HashCodes.hash o + and CopyUtils = /// diff --git a/src/DynamicObj/HashCodes.fs b/src/DynamicObj/HashCodes.fs index 9ff93d3..16e77a8 100644 --- a/src/DynamicObj/HashCodes.fs +++ b/src/DynamicObj/HashCodes.fs @@ -41,6 +41,14 @@ let boxHashSeq (a: seq<'a>) : obj = |> box let boxHashKeyValSeq (a: seq>) : obj = + a + // from https://stackoverflow.com/a/53507559 + |> Seq.fold (fun acc o -> + mergeHashes (hash o.Key) (hash o.Value) + |> mergeHashes acc) 0 + |> box + +let boxHashKeyValSeqBy (f : 'b -> int) (a: seq>) : obj = a // from https://stackoverflow.com/a/53507559 |> Seq.fold (fun acc o -> From 1ab0bf405d48d977363e375fe292cdd934a0f1cb Mon Sep 17 00:00:00 2001 From: HLWeil Date: Mon, 27 Jan 2025 16:29:47 +0100 Subject: [PATCH 2/4] add tests for deep Hashing --- .../DynamicObject.Tests.fsproj | 1 + tests/DynamicObject.Tests/HashUtils.fs | 129 ++++++++++++++++++ tests/DynamicObject.Tests/Main.fs | 1 + 3 files changed, 131 insertions(+) create mode 100644 tests/DynamicObject.Tests/HashUtils.fs diff --git a/tests/DynamicObject.Tests/DynamicObject.Tests.fsproj b/tests/DynamicObject.Tests/DynamicObject.Tests.fsproj index b81ae5f..30c418e 100644 --- a/tests/DynamicObject.Tests/DynamicObject.Tests.fsproj +++ b/tests/DynamicObject.Tests/DynamicObject.Tests.fsproj @@ -33,6 +33,7 @@ + diff --git a/tests/DynamicObject.Tests/HashUtils.fs b/tests/DynamicObject.Tests/HashUtils.fs new file mode 100644 index 0000000..dfaf172 --- /dev/null +++ b/tests/DynamicObject.Tests/HashUtils.fs @@ -0,0 +1,129 @@ +module HashUtils.Tests + +open System +open Fable.Pyxpecto +open DynamicObj +open Fable.Core + +let int1 = box 1 +let int2 = box 2 +let int3 = box 3 +let int4 = box 4 + +let intDict1 = + let d = System.Collections.Generic.Dictionary() + d.Add(int1, int2) + d.Add(int3, int4) + d + +let intDict1' = + let d = System.Collections.Generic.Dictionary() + d.Add(int1, int2) + d.Add(int3, int4) + d + +let intDict2 = + let d = System.Collections.Generic.Dictionary() + d.Add(int1, int4) + d.Add(int3, int2) + d + +let intDict3 = + let d = System.Collections.Generic.Dictionary() + d.Add(int2, int1) + d.Add(int4, int3) + d + +let intDict4 = + let d = System.Collections.Generic.Dictionary() + d.Add(int1, int3) + d.Add(int2, int4) + d + +let intList1 = [int1;int2;int3;int4] +let intList1' = [int1;int2;int3;int4] +let intList2 = [int1;int4;int3;int2] + +let nestedList1 = [intList1;intList2] +let nestedList1' = [intList1';intList2] +let nestedList2 = [intList2;intList1] + +let intArray1 = [|int1;int2;int3;int4|] +let intArray1' = [|int1;int2;int3;int4|] +let intArray2 = [|int1;int4;int3;int2|] + +let intSeq1 = seq { yield int1; yield int2; yield int3; yield int4 } +let intSeq1' = seq { yield int1; yield int2; yield int3; yield int4 } +let intSeq2 = seq { yield int1; yield int4; yield int3; yield int2 } + +let resizeArray1 = ResizeArray [int1;int2;int3;int4] +let resizeArray1' = ResizeArray [int1;int2;int3;int4] +let resizeArray2 = ResizeArray [int1;int4;int3;int2] + +let tests_Dictionary = + testList "Dictionary" [ + testCase "Shuffled Int" <| fun _ -> + Expect.equal (HashUtils.deepHash intDict1) (HashUtils.deepHash intDict2) "Same Dictionary should return consistent Hash" + Expect.equal (HashUtils.deepHash intDict1) (HashUtils.deepHash intDict1') "Strucutally equal Dictionary should return consistent Hash" + Expect.notEqual (HashUtils.deepHash intDict1) (HashUtils.deepHash intDict2) "Different Dictionary should return different Hash (1vs2)" + Expect.notEqual (HashUtils.deepHash intDict1) (HashUtils.deepHash intDict3) "Different Dictionary should return different Hash (1vs3)" + Expect.notEqual (HashUtils.deepHash intDict1) (HashUtils.deepHash intDict4) "Different Dictionary should return different Hash (1vs4)" + Expect.notEqual (HashUtils.deepHash intDict2) (HashUtils.deepHash intDict3) "Different Dictionary should return different Hash (2vs3)" + Expect.notEqual (HashUtils.deepHash intDict2) (HashUtils.deepHash intDict4) "Different Dictionary should return different Hash (2vs4)" + Expect.notEqual (HashUtils.deepHash intDict3) (HashUtils.deepHash intDict4) "Different Dictionary should return different Hash (3vs4)" + ] + +let tests_Lists = + testList "Lists" [ + testCase "Shuffled Int" <| fun _ -> + Expect.equal (HashUtils.deepHash intList1) (HashUtils.deepHash intList1) "Same List should return consistent Hash" + Expect.equal (HashUtils.deepHash intList1) (HashUtils.deepHash intList1') "Strucutally equal List should return consistent Hash" + Expect.notEqual (HashUtils.deepHash intList1) (HashUtils.deepHash intList2) "Different List should return different Hash" + testCase "Shuffled Nested" <| fun _ -> + Expect.equal (HashUtils.deepHash nestedList1) (HashUtils.deepHash nestedList1) "Same Nested List should return consistent Hash" + Expect.equal (HashUtils.deepHash nestedList1) (HashUtils.deepHash nestedList1') "Strucutally equal Nested List should return consistent Hash" + Expect.notEqual (HashUtils.deepHash nestedList1) (HashUtils.deepHash nestedList2) "Different Nested List should return different Hash" + ] + +let tests_Array = + testList "Array" [ + testCase "Shuffled Int" <| fun _ -> + Expect.equal (HashUtils.deepHash intArray1) (HashUtils.deepHash intArray1) "Same Array should return consistent Hash" + Expect.equal (HashUtils.deepHash intArray1) (HashUtils.deepHash intArray1') "Strucutally equal Array should return consistent Hash" + Expect.notEqual (HashUtils.deepHash intArray1) (HashUtils.deepHash intArray2) "Different Array should return different Hash" + ] + +let tests_Seq = + testList "Seq" [ + testCase "Shuffled Int" <| fun _ -> + Expect.equal (HashUtils.deepHash intSeq1) (HashUtils.deepHash intSeq1) "Same Seq should return consistent Hash" + Expect.equal (HashUtils.deepHash intSeq1) (HashUtils.deepHash intSeq1') "Strucutally equal Seq should return consistent Hash" + Expect.notEqual (HashUtils.deepHash intSeq1) (HashUtils.deepHash intSeq2) "Different Seq should return different Hash" + ] + +let tests_ResizeArray = + testList "ResizeArray" [ + testCase "Shuffled Int" <| fun _ -> + Expect.equal (HashUtils.deepHash resizeArray1) (HashUtils.deepHash resizeArray1) "Same ResizeArray should return consistent Hash" + Expect.equal (HashUtils.deepHash resizeArray1) (HashUtils.deepHash resizeArray1') "Strucutally equal ResizeArray should return consistent Hash" + Expect.notEqual (HashUtils.deepHash resizeArray1) (HashUtils.deepHash resizeArray2) "Different ResizeArray should return different Hash" + ] + + +let tests_Mixed = + testList "Mixed" [ + testCase "List vs Dict" <| fun _ -> + Expect.notEqual (HashUtils.deepHash intList1) (HashUtils.deepHash intDict1) "List vs Dict with same values should return different Hash" + ] + + + + +let main = testList "DeepHash" [ + tests_Dictionary + tests_Lists + tests_Array + tests_Seq + tests_ResizeArray + tests_Mixed +] \ No newline at end of file diff --git a/tests/DynamicObject.Tests/Main.fs b/tests/DynamicObject.Tests/Main.fs index 0f2cf55..beed87f 100644 --- a/tests/DynamicObject.Tests/Main.fs +++ b/tests/DynamicObject.Tests/Main.fs @@ -4,6 +4,7 @@ open Fable.Pyxpecto let all = testSequenced <| testList "DynamicObj" [ ReflectionUtils.Tests.main + HashUtils.Tests.main CopyUtils.Tests.main DynamicObj.Tests.main DynObj.Tests.main From eb07ce958bade3c94a3c5680b41c13f5235bbb51 Mon Sep 17 00:00:00 2001 From: HLWeil Date: Tue, 28 Jan 2025 12:52:19 +0100 Subject: [PATCH 3/4] add tests and fixes for deepHash function --- src/DynamicObj/DynamicObj.fs | 5 +- src/DynamicObj/HashCodes.fs | 9 +- tests/DynamicObject.Tests/HashUtils.fs | 208 +++++++++++++++++++++---- 3 files changed, 187 insertions(+), 35 deletions(-) diff --git a/src/DynamicObj/DynamicObj.fs b/src/DynamicObj/DynamicObj.fs index 1fdaa88..d581471 100644 --- a/src/DynamicObj/DynamicObj.fs +++ b/src/DynamicObj/DynamicObj.fs @@ -415,16 +415,15 @@ and HashUtils = let c = en.Current :?> System.Collections.DictionaryEntry HashCodes.mergeHashes (hash c.Key) (HashUtils.deepHash c.Value) ] - |> List.fold (fun acc h -> HashCodes.mergeHashes acc h) 0 + |> List.reduce HashCodes.mergeHashes #endif | :? System.Collections.IEnumerable as e -> let en = e.GetEnumerator() [ while en.MoveNext() do - HashUtils.deepHash en.Current ] - |> List.fold (fun acc h -> HashCodes.mergeHashes acc h) 0 + |> List.reduce HashCodes.mergeHashes | _ -> DynamicObj.HashCodes.hash o and CopyUtils = diff --git a/src/DynamicObj/HashCodes.fs b/src/DynamicObj/HashCodes.fs index 16e77a8..d551e32 100644 --- a/src/DynamicObj/HashCodes.fs +++ b/src/DynamicObj/HashCodes.fs @@ -1,5 +1,12 @@ module DynamicObj.HashCodes +// Taken from +//https://softwareengineering.stackexchange.com/a/402543 +// Which points to a no-longer existing source in FSharp Core Compiler +// But can be found in +// https://github.com/dotnet/fsharp/blob/2edab1216843f20a00a7d8f171aca52cbc35d7fd/src/Compiler/Checking/AugmentWithHashCompare.fs#L171 +// Or Fables mirror +// https://github.com/fable-compiler/Fable/blob/b0e640763fd90bd084f72531cb119d49a91ec077/src/fcs-fable/src/Compiler/Checking/AugmentWithHashCompare.fs#L171 let mergeHashes (hash1 : int) (hash2 : int) : int = 0x9e3779b9 + hash2 + (hash1 <<< 6) + (hash1 >>> 2) @@ -52,6 +59,6 @@ let boxHashKeyValSeqBy (f : 'b -> int) (a: seq Seq.fold (fun acc o -> - mergeHashes (hash o.Key) (hash o.Value) + mergeHashes (hash o.Key) (f o.Value) |> mergeHashes acc) 0 |> box \ No newline at end of file diff --git a/tests/DynamicObject.Tests/HashUtils.fs b/tests/DynamicObject.Tests/HashUtils.fs index dfaf172..99a46a0 100644 --- a/tests/DynamicObject.Tests/HashUtils.fs +++ b/tests/DynamicObject.Tests/HashUtils.fs @@ -60,58 +60,203 @@ let resizeArray1 = ResizeArray [int1;int2;int3;int4] let resizeArray1' = ResizeArray [int1;int2;int3;int4] let resizeArray2 = ResizeArray [int1;int4;int3;int2] + +let dynamicObjectWithInt1 = + + let d = DynamicObj() + d.SetProperty("a", int1) + d.SetProperty("b", int2) + d + +let dynamicObjectWithInt1DiffKey = + let d = DynamicObj() + d.SetProperty("a", int1) + d.SetProperty("c", int2) + d + +let dynamicObjectWithInt1' = + let d = DynamicObj() + d.SetProperty("a", int1) + d.SetProperty("b", int2) + d + +let dynamicObjectWithInt2 = + let d = DynamicObj() + d.SetProperty("a", int2) + d.SetProperty("b", int1) + d + +let dynamicObjectWithDict1 = + let d = DynamicObj() + d.SetProperty("a", intDict1) + d.SetProperty("b", intDict2) + d + +let dynamicObjectWithDict1' = + let d = DynamicObj() + d.SetProperty("a", intDict1) + d.SetProperty("b", intDict2) + d + +let dynamicObjectWithDict2 = + let d = DynamicObj() + d.SetProperty("a", intDict2) + d.SetProperty("b", intDict1) + d + +let dynamicObjectWithDynamicObject1 = + let d = DynamicObj() + d.SetProperty("a", dynamicObjectWithInt1) + d.SetProperty("b", dynamicObjectWithInt2) + d + +let dynamicObjectWithDynamicObject1' = + let d = DynamicObj() + d.SetProperty("a", dynamicObjectWithInt1) + d.SetProperty("b", dynamicObjectWithInt2) + d + +let dynamicObjectWithDynamicObject2 = + let d = DynamicObj() + d.SetProperty("a", dynamicObjectWithInt2) + d.SetProperty("b", dynamicObjectWithDict1) + d + + let tests_Dictionary = testList "Dictionary" [ - testCase "Shuffled Int" <| fun _ -> - Expect.equal (HashUtils.deepHash intDict1) (HashUtils.deepHash intDict2) "Same Dictionary should return consistent Hash" - Expect.equal (HashUtils.deepHash intDict1) (HashUtils.deepHash intDict1') "Strucutally equal Dictionary should return consistent Hash" - Expect.notEqual (HashUtils.deepHash intDict1) (HashUtils.deepHash intDict2) "Different Dictionary should return different Hash (1vs2)" - Expect.notEqual (HashUtils.deepHash intDict1) (HashUtils.deepHash intDict3) "Different Dictionary should return different Hash (1vs3)" - Expect.notEqual (HashUtils.deepHash intDict1) (HashUtils.deepHash intDict4) "Different Dictionary should return different Hash (1vs4)" - Expect.notEqual (HashUtils.deepHash intDict2) (HashUtils.deepHash intDict3) "Different Dictionary should return different Hash (2vs3)" - Expect.notEqual (HashUtils.deepHash intDict2) (HashUtils.deepHash intDict4) "Different Dictionary should return different Hash (2vs4)" - Expect.notEqual (HashUtils.deepHash intDict3) (HashUtils.deepHash intDict4) "Different Dictionary should return different Hash (3vs4)" + testList "Shuffled Int" [ + testCase "1v1" <| fun _ -> + Expect.equal (HashUtils.deepHash intDict1) (HashUtils.deepHash intDict1) "Same Dictionary should return consistent Hash" + testCase "1v1'" <| fun _ -> + Expect.equal (HashUtils.deepHash intDict1) (HashUtils.deepHash intDict1') "Structurally equal Dictionary should return consistent Hash" + testCase "1v2" <| fun _ -> + Expect.notEqual (HashUtils.deepHash intDict1) (HashUtils.deepHash intDict2) "Different Dictionary should return different Hash (1vs2)" + testCase "1v3" <| fun _ -> + Expect.notEqual (HashUtils.deepHash intDict1) (HashUtils.deepHash intDict3) "Different Dictionary should return different Hash (1vs3)" + testCase "1v4" <| fun _ -> + Expect.notEqual (HashUtils.deepHash intDict1) (HashUtils.deepHash intDict4) "Different Dictionary should return different Hash (1vs4)" + testCase "2v3" <| fun _ -> + Expect.notEqual (HashUtils.deepHash intDict2) (HashUtils.deepHash intDict3) "Different Dictionary should return different Hash (2vs3)" + testCase "2v4" <| fun _ -> + Expect.notEqual (HashUtils.deepHash intDict2) (HashUtils.deepHash intDict4) "Different Dictionary should return different Hash (2vs4)" + + ] ] let tests_Lists = testList "Lists" [ - testCase "Shuffled Int" <| fun _ -> - Expect.equal (HashUtils.deepHash intList1) (HashUtils.deepHash intList1) "Same List should return consistent Hash" - Expect.equal (HashUtils.deepHash intList1) (HashUtils.deepHash intList1') "Strucutally equal List should return consistent Hash" - Expect.notEqual (HashUtils.deepHash intList1) (HashUtils.deepHash intList2) "Different List should return different Hash" - testCase "Shuffled Nested" <| fun _ -> - Expect.equal (HashUtils.deepHash nestedList1) (HashUtils.deepHash nestedList1) "Same Nested List should return consistent Hash" - Expect.equal (HashUtils.deepHash nestedList1) (HashUtils.deepHash nestedList1') "Strucutally equal Nested List should return consistent Hash" - Expect.notEqual (HashUtils.deepHash nestedList1) (HashUtils.deepHash nestedList2) "Different Nested List should return different Hash" + testList "Shuffled Int" [ + testCase "1v1" <| fun _ -> + Expect.equal (HashUtils.deepHash intList1) (HashUtils.deepHash intList1) "Same List should return consistent Hash" + testCase "1v1'" <| fun _ -> + Expect.equal (HashUtils.deepHash intList1) (HashUtils.deepHash intList1') "Structurally equal List should return consistent Hash" + testCase "1v2" <| fun _ -> + Expect.notEqual (HashUtils.deepHash intList1) (HashUtils.deepHash intList2) "Different List should return different Hash" + ] + testList "Shuffled Nested" [ + testCase "1v1" <| fun _ -> + Expect.equal (HashUtils.deepHash nestedList1) (HashUtils.deepHash nestedList1) "Same Nested List should return consistent Hash" + testCase "1v1'" <| fun _ -> + Expect.equal (HashUtils.deepHash nestedList1) (HashUtils.deepHash nestedList1') "Structurally equal Nested List should return consistent Hash" + testCase "1v2" <| fun _ -> + Expect.notEqual (HashUtils.deepHash nestedList1) (HashUtils.deepHash nestedList2) "Different Nested List should return different Hash" + + ] ] -let tests_Array = +let tests_Array = testList "Array" [ - testCase "Shuffled Int" <| fun _ -> - Expect.equal (HashUtils.deepHash intArray1) (HashUtils.deepHash intArray1) "Same Array should return consistent Hash" - Expect.equal (HashUtils.deepHash intArray1) (HashUtils.deepHash intArray1') "Strucutally equal Array should return consistent Hash" - Expect.notEqual (HashUtils.deepHash intArray1) (HashUtils.deepHash intArray2) "Different Array should return different Hash" + testList "Shuffled Int" [ + testCase "1v1" <| fun _ -> + Expect.equal (HashUtils.deepHash intArray1) (HashUtils.deepHash intArray1) "Same Array should return consistent Hash" + testCase "1v1'" <| fun _ -> + Expect.equal (HashUtils.deepHash intArray1) (HashUtils.deepHash intArray1') "Structurally equal Array should return consistent Hash" + testCase "1v2" <| fun _ -> + Expect.notEqual (HashUtils.deepHash intArray1) (HashUtils.deepHash intArray2) "Different Array should return different Hash" + ] ] -let tests_Seq = +let tests_Seq = testList "Seq" [ - testCase "Shuffled Int" <| fun _ -> - Expect.equal (HashUtils.deepHash intSeq1) (HashUtils.deepHash intSeq1) "Same Seq should return consistent Hash" - Expect.equal (HashUtils.deepHash intSeq1) (HashUtils.deepHash intSeq1') "Strucutally equal Seq should return consistent Hash" - Expect.notEqual (HashUtils.deepHash intSeq1) (HashUtils.deepHash intSeq2) "Different Seq should return different Hash" + testList "Shuffled Int" [ + testCase "1v1" <| fun _ -> + Expect.equal (HashUtils.deepHash intSeq1) (HashUtils.deepHash intSeq1) "Same Seq should return consistent Hash" + testCase "1v1'" <| fun _ -> + Expect.equal (HashUtils.deepHash intSeq1) (HashUtils.deepHash intSeq1') "Structurally equal Seq should return consistent Hash" + testCase "1v2" <| fun _ -> + Expect.notEqual (HashUtils.deepHash intSeq1) (HashUtils.deepHash intSeq2) "Different Seq should return different Hash" + ] ] let tests_ResizeArray = testList "ResizeArray" [ - testCase "Shuffled Int" <| fun _ -> - Expect.equal (HashUtils.deepHash resizeArray1) (HashUtils.deepHash resizeArray1) "Same ResizeArray should return consistent Hash" - Expect.equal (HashUtils.deepHash resizeArray1) (HashUtils.deepHash resizeArray1') "Strucutally equal ResizeArray should return consistent Hash" - Expect.notEqual (HashUtils.deepHash resizeArray1) (HashUtils.deepHash resizeArray2) "Different ResizeArray should return different Hash" + testList "Shuffled Int" [ + testCase "1v1" <| fun _ -> + + Expect.equal (HashUtils.deepHash resizeArray1) (HashUtils.deepHash resizeArray1) "Same ResizeArray should return consistent Hash" + testCase "1v1'" <| fun _ -> + Expect.equal (HashUtils.deepHash resizeArray1) (HashUtils.deepHash resizeArray1') "Structurally equal ResizeArray should return consistent Hash" + testCase "1v2" <| fun _ -> + Expect.notEqual (HashUtils.deepHash resizeArray1) (HashUtils.deepHash resizeArray2) "Different ResizeArray should return different Hash" + ] + ] + + +let tests_DynamicObject = + testList "DynamicObj" [ + testList "Shuffled Int" [ + testCase "1v1" <| fun _ -> + Expect.equal (HashUtils.deepHash dynamicObjectWithInt1) (HashUtils.deepHash dynamicObjectWithInt1) "Same DynamicObject should return consistent Hash" + testCase "1v1'" <| fun _ -> + Expect.equal (HashUtils.deepHash dynamicObjectWithInt1) (HashUtils.deepHash dynamicObjectWithInt1') "Structurally equal DynamicObject should return consistent Hash" + testCase "1v1DiffKey" <| fun _ -> + Expect.notEqual (HashUtils.deepHash dynamicObjectWithInt1) (HashUtils.deepHash dynamicObjectWithInt1DiffKey) "Different DynamicObject should return different Hash" + testCase "1v2" <| fun _ -> + Expect.notEqual (HashUtils.deepHash dynamicObjectWithInt1) (HashUtils.deepHash dynamicObjectWithInt2) "Different DynamicObject should return different Hash" + ] + testList "Shuffled Dict" [ + testCase "1v1" <| fun _ -> + Expect.equal (HashUtils.deepHash dynamicObjectWithDict1) (HashUtils.deepHash dynamicObjectWithDict1) "Same DynamicObject should return consistent Hash" + testCase "1v1'" <| fun _ -> + Expect.equal (HashUtils.deepHash dynamicObjectWithDict1) (HashUtils.deepHash dynamicObjectWithDict1') "Structurally equal DynamicObject should return consistent Hash" + testCase "1v2" <| fun _ -> + Expect.notEqual (HashUtils.deepHash dynamicObjectWithDict1) (HashUtils.deepHash dynamicObjectWithDict2) "Different DynamicObject should return different Hash" + ] + testList "Shuffled DynamicObject" [ + testCase "1v1" <| fun _ -> + Expect.equal (HashUtils.deepHash dynamicObjectWithDynamicObject1) (HashUtils.deepHash dynamicObjectWithDynamicObject1) "Same DynamicObject should return consistent Hash" + testCase "1v1'" <| fun _ -> + Expect.equal (HashUtils.deepHash dynamicObjectWithDynamicObject1) (HashUtils.deepHash dynamicObjectWithDynamicObject1') "Structurally equal DynamicObject should return consistent Hash" + testCase "1v2" <| fun _ -> + Expect.notEqual (HashUtils.deepHash dynamicObjectWithDynamicObject1) (HashUtils.deepHash dynamicObjectWithDynamicObject2) "Different DynamicObject should return different Hash" + ] + testList "Shuffled Int AsOption" [ + testCase "1v1" <| fun _ -> + Expect.equal (HashUtils.deepHash (Some dynamicObjectWithInt1)) (HashUtils.deepHash (Some dynamicObjectWithInt1)) "Same DynamicObject should return consistent Hash" + testCase "1v1'" <| fun _ -> + Expect.equal (HashUtils.deepHash (Some dynamicObjectWithInt1)) (HashUtils.deepHash (Some dynamicObjectWithInt1')) "Structurally equal DynamicObject should return consistent Hash" + testCase "1v1DiffKey" <| fun _ -> + Expect.notEqual (HashUtils.deepHash (Some dynamicObjectWithInt1)) (HashUtils.deepHash (Some dynamicObjectWithInt1DiffKey)) "Different DynamicObject should return different Hash" + testCase "1v2" <| fun _ -> + Expect.notEqual (HashUtils.deepHash (Some dynamicObjectWithInt1)) (HashUtils.deepHash (Some dynamicObjectWithInt2)) "Different DynamicObject should return different Hash" + testCase "1 v None" <| fun _ -> + Expect.notEqual (HashUtils.deepHash (Some dynamicObjectWithInt1)) (HashUtils.deepHash None) "Different DynamicObject should return different Hash" + + ] + testList "Mixed" [ + testCase "Int vs Dict" <| fun _ -> + Expect.notEqual (HashUtils.deepHash dynamicObjectWithInt1) (HashUtils.deepHash dynamicObjectWithDict1) "Int vs Dict with same values should return different Hash" + testCase "Dict vs DynObj" <| fun _ -> + Expect.notEqual (HashUtils.deepHash dynamicObjectWithDict1) (HashUtils.deepHash dynamicObjectWithDynamicObject1) "Dict vs DynObj with same values should return different Hash" + ] ] let tests_Mixed = testList "Mixed" [ + testCase "Int vs Dict" <| fun _ -> + Expect.notEqual (HashUtils.deepHash int1) (HashUtils.deepHash intDict1) "Int vs Dict with same values should return different Hash" testCase "List vs Dict" <| fun _ -> Expect.notEqual (HashUtils.deepHash intList1) (HashUtils.deepHash intDict1) "List vs Dict with same values should return different Hash" ] @@ -125,5 +270,6 @@ let main = testList "DeepHash" [ tests_Array tests_Seq tests_ResizeArray + tests_DynamicObject tests_Mixed ] \ No newline at end of file From 90fd64c482285fdb832393e5abe0ed20c7fd5b18 Mon Sep 17 00:00:00 2001 From: HLWeil Date: Tue, 28 Jan 2025 13:08:57 +0100 Subject: [PATCH 4/4] add reference and structural equality function for dynamicObj --- src/DynamicObj/DynamicObj.fs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/DynamicObj/DynamicObj.fs b/src/DynamicObj/DynamicObj.fs index d581471..7c19ec8 100644 --- a/src/DynamicObj/DynamicObj.fs +++ b/src/DynamicObj/DynamicObj.fs @@ -388,13 +388,18 @@ type DynamicObj() = static member (?<-) (lookup:#DynamicObj,name:string,value:'v) = lookup.SetProperty (name,value) + member this.ReferenceEquals (other: DynamicObj) = System.Object.ReferenceEquals(this,other) + + member this.StructurallyEquals (other: DynamicObj) = + this.GetHashCode() = other.GetHashCode() + override this.GetHashCode () = HashUtils.deepHash this override this.Equals o = match o with | :? DynamicObj as other -> - this.GetHashCode() = other.GetHashCode() + this.StructurallyEquals(other) | _ -> false and HashUtils =