Skip to content

Commit e106f48

Browse files
xperiandriclaude
andcommitted
fixup! test: shared contracts of the map modules, run for HashMap and IMap
The sequence inputs have a test per facet with a row per function, for `HashMap`, `IMap`, the members of the comparer struct and `hashMap { }`: a default flat list that is given as a sequence throws `ArgumentNullException` that names the parameter of the function, and the struct and the expression get the tests of the null sequence too. The extension overloads run through the module and the struct. A default flat list of strings given as `seq<obj>` has a row per module, with `FlatListOracles.defaultStringsAsObjects`, and a flat list that is not default is checked to pass, also one of strings as objects. The rejections use `FlatListAssert.RejectsNull`. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 parent 7e604d7 commit e106f48

4 files changed

Lines changed: 436 additions & 0 deletions

File tree

‎tests/FSharp.Collections.Immutable.Tests/HashMap/CustomComparersHashMapTests.fs‎

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,60 @@ type CustomComparersHashMapTests () =
7979
"ofSpan", (fun comparers -> comparers.ofSpan (Span [| KeyValuePair ("one", "uno"); KeyValuePair ("ONE", "abc") |]))
8080
|]
8181

82+
/// The members of the struct that take a sequence, by name, with the name of the sequence parameter and the member on a
83+
/// null sequence. The struct has comparers that differ from the default ones.
84+
static let nullSequenceMembers : Map<string, struct (string * (unit -> unit))> =
85+
let comparers = HashMap.forComparers StringComparer.OrdinalIgnoreCase byLength
86+
87+
Map.ofArray [|
88+
"ofSeq of key/value pairs",
89+
struct ("elements",
90+
fun () ->
91+
comparers.ofSeq (Unchecked.defaultof<seq<KeyValuePair<string, string>>>)
92+
|> ignore)
93+
"ofSeq of reference tuples",
94+
struct ("elements",
95+
fun () ->
96+
comparers.ofSeq (Unchecked.defaultof<seq<string * string>>)
97+
|> ignore)
98+
"ofSeq of struct tuples",
99+
struct ("elements",
100+
fun () ->
101+
comparers.ofSeq (Unchecked.defaultof<seq<struct (string * string)>>)
102+
|> ignore)
103+
"ofSeqBy",
104+
struct ("source",
105+
fun () ->
106+
comparers.ofSeqBy id (Unchecked.defaultof<seq<string>>)
107+
|> ignore)
108+
|]
109+
110+
/// The members of the struct that take a sequence, by name, with the name of the sequence parameter and the member on a
111+
/// default flat list that is given as the sequence. A default flat list converts to a sequence without a check and fails
112+
/// with InvalidOperationException when the sequence is enumerated. The last row gives a sequence of objects, which a
113+
/// default flat list of strings is by the covariance of seq: the element type of the sequence is not the one of the
114+
/// flat list.
115+
static let defaultSequenceMembers : Map<string, struct (string * (unit -> unit))> =
116+
let comparers = HashMap.forComparers StringComparer.OrdinalIgnoreCase byLength
117+
let objectComparers : CustomComparersHashMap<obj, obj> =
118+
HashMap.forKeyComparer EqualityComparer<obj>.Default
119+
let defaultPairs = Unchecked.defaultof<FlatList<KeyValuePair<string, string>>> :> seq<_>
120+
let defaultTuples = Unchecked.defaultof<FlatList<string * string>> :> seq<_>
121+
let defaultStructTuples = Unchecked.defaultof<FlatList<struct (string * string)>> :> seq<_>
122+
let defaultValues = Unchecked.defaultof<FlatList<string>> :> seq<_>
123+
124+
Map.ofArray [|
125+
"ofSeq of key/value pairs", struct ("elements", fun () -> comparers.ofSeq defaultPairs |> ignore)
126+
"ofSeq of reference tuples", struct ("elements", fun () -> comparers.ofSeq defaultTuples |> ignore)
127+
"ofSeq of struct tuples", struct ("elements", fun () -> comparers.ofSeq defaultStructTuples |> ignore)
128+
"ofSeqBy", struct ("source", fun () -> comparers.ofSeqBy id defaultValues |> ignore)
129+
"ofSeqBy (default flat list of strings as objects)",
130+
struct ("source",
131+
fun () ->
132+
objectComparers.ofSeqBy id FlatListOracles.defaultStringsAsObjects
133+
|> ignore)
134+
|]
135+
82136
/// The comparer structs of the two paths of the span members, by name: the default comparer of the values wraps
83137
/// CreateRangeWithOverwrite, and any other comparer, even one that behaves like the default one, sets the bindings in a
84138
/// builder.
@@ -145,6 +199,12 @@ type CustomComparersHashMapTests () =
145199
/// test.
146200
static member EqualValueCreations = equalValueCreations.Keys
147201

202+
/// The names of the members that take a sequence and are given a null one, as the rows of their test.
203+
static member NullSequenceMembers = nullSequenceMembers.Keys
204+
205+
/// The names of the members that take a sequence and are given a default flat list, as the rows of their test.
206+
static member DefaultSequenceMembers = defaultSequenceMembers.Keys
207+
148208
[<TestMethod; DataRow "keyComparer"; DataRow "valueComparer">]
149209
member _.``the constructor with both comparers rejects a null comparer`` (nullParameter : string) =
150210
let struct (keyComparer, valueComparer) = comparersWithNull nullParameter
@@ -198,6 +258,62 @@ type CustomComparersHashMapTests () =
198258

199259
Assert.AreEqual<string | null>("keyComparer", ex.ParamName, "HashMap.forKeyComparer must name the key comparer")
200260

261+
[<TestMethod; DynamicData(nameof CustomComparersHashMapTests.NullSequenceMembers)>]
262+
member _.``members reject a null sequence`` (name : string) =
263+
let struct (paramName, call) = nullSequenceMembers[name]
264+
FlatListAssert.RejectsNull ($"CustomComparersHashMap.%s{name}", paramName, call)
265+
266+
[<TestMethod; DynamicData(nameof CustomComparersHashMapTests.DefaultSequenceMembers)>]
267+
member _.``members reject a default flat list given as a sequence`` (name : string) =
268+
// The exception names the parameter of the member, not the one of the BCL member that it would have called
269+
let struct (paramName, call) = defaultSequenceMembers[name]
270+
FlatListAssert.RejectsNull ($"CustomComparersHashMap.%s{name}", paramName, call)
271+
272+
[<TestMethod>]
273+
member _.``members take a flat list that is not default as a sequence, also one of strings as a sequence of objects`` () =
274+
let flatList (elements : 'T array) = FlatList.ofArray elements :> seq<'T>
275+
let comparers = HashMap.forKeyComparer StringComparer.OrdinalIgnoreCase
276+
277+
let sortedBindings (table : HashMap<string, string>) =
278+
HashMap.toArray table
279+
|> Array.sortBy (fun struct (key, _) -> key)
280+
281+
CollectionAssert.AreEqual (
282+
[| struct ("a", "b") |],
283+
sortedBindings (comparers.ofSeq (flatList [| KeyValuePair ("a", "b") |])),
284+
"ofSeq must take the key/value pairs of a flat list"
285+
)
286+
287+
CollectionAssert.AreEqual (
288+
[| struct ("a", "b") |],
289+
sortedBindings (comparers.ofSeq (flatList [| ("a", "b") |])),
290+
"ofSeq must take the reference tuples of a flat list"
291+
)
292+
293+
CollectionAssert.AreEqual (
294+
[| struct ("a", "b") |],
295+
sortedBindings (comparers.ofSeq (flatList [| struct ("a", "b") |])),
296+
"ofSeq must take the struct tuples of a flat list"
297+
)
298+
299+
CollectionAssert.AreEqual (
300+
[| struct ("ab", "ab") |],
301+
sortedBindings (comparers.ofSeqBy id (flatList [| "ab" |])),
302+
"ofSeqBy must take the elements of a flat list"
303+
)
304+
305+
// A flat list of strings is a sequence of objects, which a type test for the flat list of objects does not see
306+
let objectComparers : CustomComparersHashMap<obj, obj> =
307+
HashMap.forKeyComparer EqualityComparer<obj>.Default
308+
let widened = FlatListOracles.stringsAsObjects (FlatList.ofArray [| "ab" |])
309+
310+
CollectionAssert.AreEqual (
311+
[| "ab" :> obj |],
312+
HashMap.keys (objectComparers.ofSeqBy id widened)
313+
|> Seq.toArray,
314+
"ofSeqBy must take the elements of a flat list of strings as objects"
315+
)
316+
201317
[<TestMethod>]
202318
member _.``forComparers holds both comparers`` () =
203319
let comparers = HashMap.forComparers StringComparer.OrdinalIgnoreCase byLength

‎tests/FSharp.Collections.Immutable.Tests/HashMap/HashMapComputationExpressionTests.fs‎

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,71 @@ type HashMapComputationExpressionTests () =
5050
})
5151
|]
5252

53+
/// The expressions that take a sequence, by name, with the name of the sequence parameter and the expression on a null
54+
/// sequence. The expression rejects the sequence when the member runs, and not when the buffer does.
55+
static let nullSequenceExpressions : Map<string, struct (string * (unit -> unit))> =
56+
Map.ofArray [|
57+
"yield! of a sequence of struct pairs",
58+
struct ("pairs",
59+
fun () ->
60+
hashMap { yield! Unchecked.defaultof<seq<struct (int * string)>> }
61+
|> ignore)
62+
"yield! of a sequence of key/value pairs",
63+
struct ("pairs",
64+
fun () ->
65+
hashMap { yield! Unchecked.defaultof<seq<KeyValuePair<int, string>>> }
66+
|> ignore)
67+
"for",
68+
struct ("source",
69+
fun () ->
70+
hashMap {
71+
for item in Unchecked.defaultof<seq<int>> do
72+
yield struct (item, "x")
73+
}
74+
|> ignore)
75+
|]
76+
77+
/// The expressions that take a sequence, by name, with the name of the sequence parameter and the expression on a
78+
/// default flat list that is given as the sequence. A default flat list converts to a sequence without a check and fails
79+
/// with InvalidOperationException when the sequence is enumerated. The last row gives a sequence of objects, which a
80+
/// default flat list of strings is by the covariance of seq: the element type of the sequence is not the one of the
81+
/// flat list.
82+
static let defaultSequenceExpressions : Map<string, struct (string * (unit -> unit))> =
83+
let defaultStructPairs = Unchecked.defaultof<FlatList<struct (int * string)>> :> seq<_>
84+
let defaultPairs = Unchecked.defaultof<FlatList<KeyValuePair<int, string>>> :> seq<_>
85+
let defaultKeys = Unchecked.defaultof<FlatList<int>> :> seq<_>
86+
87+
Map.ofArray [|
88+
"yield! of a sequence of struct pairs", struct ("pairs", fun () -> hashMap { yield! defaultStructPairs } |> ignore)
89+
"yield! of a sequence of key/value pairs", struct ("pairs", fun () -> hashMap { yield! defaultPairs } |> ignore)
90+
"for",
91+
struct ("source",
92+
fun () ->
93+
hashMap {
94+
for item in defaultKeys do
95+
yield struct (item, "x")
96+
}
97+
|> ignore)
98+
"for (default flat list of strings as objects)",
99+
struct ("source",
100+
fun () ->
101+
hashMap {
102+
for item in FlatListOracles.defaultStringsAsObjects do
103+
yield struct (item, "x")
104+
}
105+
|> ignore)
106+
|]
107+
53108
/// The names of the members that set a binding, as the rows of the test of the values that the default comparer finds
54109
/// equal.
55110
static member EqualValueExpressions = equalValueExpressions.Keys
56111

112+
/// The names of the expressions that take a sequence and are given a null one, as the rows of their test.
113+
static member NullSequenceExpressions = nullSequenceExpressions.Keys
114+
115+
/// The names of the expressions that take a sequence and are given a default flat list, as the rows of their test.
116+
static member DefaultSequenceExpressions = defaultSequenceExpressions.Keys
117+
57118
[<TestMethod>]
58119
member _.``an expression without bindings is empty`` () =
59120
let table : HashMap<int, string> = hashMap { () }
@@ -258,6 +319,51 @@ type HashMapComputationExpressionTests () =
258319
Assert.AreEqual<int>(2, HashMap.count table, "HashMapCE must merge the keys that the comparer considers equal")
259320
Assert.AreEqual<int>(2, HashMap.find "One" table, "HashMapCE must keep the last binding of equal keys")
260321

322+
[<TestMethod; DynamicData(nameof HashMapComputationExpressionTests.NullSequenceExpressions)>]
323+
member _.``yield! and for reject a null sequence`` (name : string) =
324+
let struct (paramName, run) = nullSequenceExpressions[name]
325+
FlatListAssert.RejectsNull ($"hashMap %s{name}", paramName, run)
326+
327+
[<TestMethod; DynamicData(nameof HashMapComputationExpressionTests.DefaultSequenceExpressions)>]
328+
member _.``yield! and for reject a default flat list given as a sequence`` (name : string) =
329+
// The member rejects it as the functions of HashMap do, when the expression runs it and not when the buffer does
330+
let struct (paramName, run) = defaultSequenceExpressions[name]
331+
FlatListAssert.RejectsNull ($"hashMap %s{name}", paramName, run)
332+
333+
[<TestMethod>]
334+
member _.``yield! and for take a flat list that is not default as a sequence, also one of strings as objects`` () =
335+
let structPairs = FlatList.ofArray [| struct (1, "one"); struct (2, "two") |] :> seq<_>
336+
let pairs = FlatList.ofArray [| KeyValuePair (3, "three") |] :> seq<_>
337+
let keys = FlatList.ofArray [| 4 |] :> seq<_>
338+
339+
let table = hashMap {
340+
yield! structPairs
341+
yield! pairs
342+
343+
for key in keys do
344+
yield struct (key, "four")
345+
}
346+
347+
CollectionAssert.AreEqual (
348+
[| struct (1, "one"); struct (2, "two"); struct (3, "three"); struct (4, "four") |],
349+
bindings table,
350+
"hashMap must take the elements of a flat list in yield! and for"
351+
)
352+
353+
// A flat list of strings is a sequence of objects, which a type test for the flat list of objects does not see
354+
let widened = FlatListOracles.stringsAsObjects (FlatList.ofArray [| "a"; "b" |])
355+
356+
let objectTable = hashMap {
357+
for item in widened do
358+
yield struct (item, 1)
359+
}
360+
361+
CollectionAssert.AreEquivalent (
362+
[| "a" :> obj; "b" :> obj |],
363+
HashMap.keys objectTable |> Seq.toArray,
364+
"hashMap must take the elements of a flat list of strings as objects in for"
365+
)
366+
261367
[<TestMethod>]
262368
member _.``HashMapCE rejects a null key comparer`` () =
263369
let ex =

0 commit comments

Comments
 (0)