Skip to content

Commit 6aa1c13

Browse files
xperiandriclaude
andcommitted
fixup! test: shared contracts of the set modules, run for HashSet and ISet
Pin the recipe that the changelog gives for passing a sequence to a relation or to an operation of the set algebra, which take sets, not sequences, in `HashSet` and `ISet`. The strings, the comparers and the sets and sequences of each case are in `SetRelationInputs`, which the two test classes share: - 24 rows each: the six relations and `intersect` and `symmetricDifference`, with the default comparer, a comparer that ignores case and a reference comparer (`HashIdentity.Reference`) as the comparer of the first set. The strings are two instances of "a", "A" and "b". The conversion with the comparer of the first set, `(HashSet.forComparer (HashSet.comparer set)).ofSeq` for `HashSet` and `ISet.addRange elements (ISet.clear set)` for `ISet`, answers as the member of the BCL type (of the interface for `ISet`) in every row. The conversion with the default comparer, `HashSet.ofSeq`, answers as the member for the first two comparers, which find equal what the default comparer finds equal, and differently for the reference comparer, which tells the two instances of "a" apart where the default comparer keeps one of them. - 6 rows each: `overlaps` and `isSuperset` as `Seq.exists` and `Seq.forall` with `contains` answer as the members with all three comparers. Each pinned behavior was broken once in the code, 11 mutations (`ofSeq` of the struct converting with the default comparer, `isSubset` of each module comparing in the comparer of the second set, `contains` of each module comparing in the default comparer, `intersect` and `symmetricDifference` of each module converting the second set with the default comparer, `ISet.clear` without the comparer, and the reference comparer of the test data comparing by the default comparer), and a new test failed each time. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 parent d24fdc1 commit 6aa1c13

3 files changed

Lines changed: 372 additions & 0 deletions

File tree

‎tests/FSharp.Collections.Immutable.Tests/Contracts/SetInputs.fs‎

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
namespace FSharp.Collections.Immutable.Tests
22

3+
open System
34
open System.Collections.Generic
45

56
/// <summary>
@@ -109,3 +110,105 @@ type SetInputs =
109110

110111
/// The comparer of the structs that create sorted sets: it orders numbers by their remainders.
111112
static member ByRemainder = byRemainder
113+
114+
/// <summary>
115+
/// The inputs of the tests that pass a sequence to a relation or to an operation of the set algebra. The functions of the
116+
/// set modules take sets, where the members of the BCL types take sequences, so a sequence is converted to a set first
117+
/// (ADR 0001, D14).
118+
/// </summary>
119+
/// <remarks>
120+
/// <para>
121+
/// A test builds the first set from the strings of a relation with one of the comparers, builds the sequence from the
122+
/// other strings, and compares the answer of the member of the BCL type for the sequence with the answer of the function of
123+
/// the module for the set that the sequence is converted to.
124+
/// </para>
125+
/// <para>
126+
/// The strings tell the comparers apart: when the first set compares by reference, each relation and each operation answers
127+
/// differently for the sequence that the default comparer converts, which keeps one of "a1" and "a2", than for the sequence
128+
/// that the comparer of the first set converts.
129+
/// </para>
130+
/// </remarks>
131+
[<AbstractClass; Sealed>]
132+
type SetRelationInputs =
133+
134+
/// <summary>
135+
/// The strings by name: "a1" and "a2" have the text "a" and are different instances, "A" differs from them in case, and
136+
/// "b" has other text.
137+
/// </summary>
138+
/// <remarks>
139+
/// The instances are created once: a comparer by reference tells "a1" from "a2", so the first set and the sequence of a
140+
/// test must hold the same two instances.
141+
/// </remarks>
142+
static let pool : Map<string, string> =
143+
Map.ofArray [| "a1", String [| 'a' |]; "a2", String [| 'a' |]; "A", "A"; "b", "b" |]
144+
145+
/// <summary>
146+
/// The comparers of the first set, by name, each with whether it finds equal every pair of strings that the default
147+
/// comparer finds equal.
148+
/// </summary>
149+
static let comparers : Map<string, struct (IEqualityComparer<string> * bool)> =
150+
Map.ofArray [|
151+
"default comparer", struct (EqualityComparer<string>.Default :> IEqualityComparer<string>, true)
152+
"comparer that ignores case", struct (StringComparer.OrdinalIgnoreCase :> IEqualityComparer<string>, true)
153+
// It tells "a1" from "a2", which the default comparer finds equal
154+
"reference comparer", struct (HashIdentity.Reference, false)
155+
|]
156+
157+
/// <summary>
158+
/// The names of the strings of the first set and of the sequence of each relation, by the name of the relation.
159+
/// </summary>
160+
static let items : Map<string, struct (string array * string array)> =
161+
Map.ofArray [|
162+
"isProperSubset", struct ([| "a1"; "a2" |], [| "a1"; "a2"; "b" |])
163+
"isProperSuperset", struct ([| "a1"; "a2"; "A" |], [| "a1"; "a2"; "A" |])
164+
"isSubset", struct ([| "a1"; "a2"; "A" |], [| "a1"; "a2"; "A" |])
165+
"isSuperset", struct ([| "a1" |], [| "a1"; "a2" |])
166+
// The default comparer keeps "a2", the first string of the sequence
167+
"overlaps", struct ([| "a1" |], [| "a2"; "a1" |])
168+
"setEquals", struct ([| "a1"; "a2"; "A" |], [| "a1"; "a2"; "A" |])
169+
|]
170+
171+
/// <summary>
172+
/// The names of the strings of the first set and of the sequence of each operation of the set algebra that takes a
173+
/// sequence in the BCL types and returns a set, by the name of the operation.
174+
/// </summary>
175+
static let operationItems : Map<string, struct (string array * string array)> =
176+
Map.ofArray [|
177+
"intersect", struct ([| "a1"; "a2" |], [| "a1"; "a2" |])
178+
"symmetricDifference", struct ([| "a1"; "a2" |], [| "a1"; "a2" |])
179+
|]
180+
181+
/// The strings for their names in the pool.
182+
static member Strings (names : string array) = names |> Array.map (fun name -> pool[name])
183+
184+
/// The names of the strings of the first set and of the sequence of each relation, by the name of the relation.
185+
static member Items = items
186+
187+
/// The comparers of the first set, by name, each with whether it finds equal every pair of strings that the default
188+
/// comparer finds equal.
189+
static member Comparers = comparers
190+
191+
/// The relations of a set with a sequence, each with every comparer of the first set, as rows.
192+
static member Rows = seq {
193+
for relation in items.Keys do
194+
for comparer in comparers.Keys do
195+
struct (relation, comparer)
196+
}
197+
198+
/// The relations that need no conversion, each with every comparer of the first set, as rows.
199+
static member StreamingRows = seq {
200+
for relation in [ "isSuperset"; "overlaps" ] do
201+
for comparer in comparers.Keys do
202+
struct (relation, comparer)
203+
}
204+
205+
/// The names of the strings of the first set and of the sequence of each operation of the set algebra, by the name of
206+
/// the operation.
207+
static member OperationItems = operationItems
208+
209+
/// The operations of the set algebra with a sequence, each with every comparer of the first set, as rows.
210+
static member OperationRows = seq {
211+
for operation in operationItems.Keys do
212+
for comparer in comparers.Keys do
213+
struct (operation, comparer)
214+
}

‎tests/FSharp.Collections.Immutable.Tests/HashSet/HashSetTests.fs‎

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,45 @@ type HashSetTests () =
238238
struct ("elements", (fun () -> ints.ofSeq defaultElements |> ignore))
239239
|]
240240

241+
/// <summary>
242+
/// The relations of a hash set with a sequence, by name: the member of the BCL type, which takes the sequence, and the
243+
/// function of the module, which takes a hash set.
244+
/// </summary>
245+
static let relationFunctions
246+
: Map<string, struct ((HashSet<string> -> seq<string> -> bool) * (HashSet<string> -> HashSet<string> -> bool))> =
247+
Map.ofArray [|
248+
nameof HashSet.isProperSubset, struct ((fun set elements -> set.IsProperSubsetOf elements), HashSet.isProperSubset)
249+
nameof HashSet.isProperSuperset,
250+
struct ((fun set elements -> set.IsProperSupersetOf elements), HashSet.isProperSuperset)
251+
nameof HashSet.isSubset, struct ((fun set elements -> set.IsSubsetOf elements), HashSet.isSubset)
252+
nameof HashSet.isSuperset, struct ((fun set elements -> set.IsSupersetOf elements), HashSet.isSuperset)
253+
nameof HashSet.overlaps, struct ((fun set elements -> set.Overlaps elements), HashSet.overlaps)
254+
nameof HashSet.setEquals, struct ((fun set elements -> set.SetEquals elements), HashSet.setEquals)
255+
|]
256+
257+
/// <summary>
258+
/// The operations of the set algebra with a sequence, by name: the member of the BCL type, which takes the sequence, and
259+
/// the function of the module, which takes a hash set.
260+
/// </summary>
261+
static let operationFunctions
262+
: Map<
263+
string,
264+
struct ((HashSet<string> -> seq<string> -> HashSet<string>) *
265+
(HashSet<string> -> HashSet<string> -> HashSet<string>))
266+
> =
267+
Map.ofArray [|
268+
nameof HashSet.intersect, struct ((fun set elements -> set.Intersect elements), HashSet.intersect)
269+
nameof HashSet.symmetricDifference,
270+
struct ((fun set elements -> set.SymmetricExcept elements), HashSet.symmetricDifference)
271+
|]
272+
273+
/// The relations that need no conversion, by name, as the changelog gives them: each tests the elements with contains.
274+
static let streamingRecipes : Map<string, HashSet<string> -> seq<string> -> bool> =
275+
Map.ofArray [|
276+
nameof HashSet.isSuperset, (fun set elements -> Seq.forall (fun item -> HashSet.contains item set) elements)
277+
nameof HashSet.overlaps, (fun set elements -> Seq.exists (fun item -> HashSet.contains item set) elements)
278+
|]
279+
241280
/// The names of the creation functions without a comparer, as rows.
242281
static member DefaultCreations = defaultCreations.Keys
243282

@@ -555,6 +594,97 @@ type HashSetTests () =
555594
"HashSet.contains must compare with the default comparer"
556595
)
557596

597+
// ---- Relations with a sequence ----
598+
599+
// The way to pass a sequence to a relation, as the changelog gives it: convert the sequence with the comparer of the
600+
// first set. The default comparer gives the same answer when the comparer of the first set finds equal what the default
601+
// comparer finds equal, and another answer when it tells such strings apart, as the reference comparer does
602+
[<TestMethod; DynamicData(nameof SetRelationInputs.Rows, typeof<SetRelationInputs>)>]
603+
member _.``a relation with a sequence takes the sequence that the comparer of the first set converts``
604+
(relation : string, comparer : string)
605+
=
606+
let instances = SetRelationInputs.Strings [| "a1"; "a2" |]
607+
Assert.AreNotSame (
608+
instances[0],
609+
instances[1],
610+
"The test needs two strings with the same text that are different instances"
611+
)
612+
613+
let struct (setItems, sequenceItems) = SetRelationInputs.Items[relation]
614+
let struct (bclMember, moduleFunction) = relationFunctions[relation]
615+
let struct (firstComparer, defaultFindsEqualWhatItFindsEqual) = SetRelationInputs.Comparers[comparer]
616+
let set = (HashSet.forComparer firstComparer).ofArray(SetRelationInputs.Strings setItems)
617+
let elements = SetRelationInputs.Strings sequenceItems
618+
let expected = bclMember set elements
619+
620+
Assert.AreEqual<bool>(
621+
expected,
622+
moduleFunction set ((HashSet.forComparer (HashSet.comparer set)).ofSeq elements),
623+
$"HashSet.%s{relation} must answer as the member of the BCL type for the sequence that the comparer of the first set converts"
624+
)
625+
626+
let withDefaultComparer = moduleFunction set (HashSet.ofSeq elements)
627+
628+
if defaultFindsEqualWhatItFindsEqual then
629+
Assert.AreEqual<bool>(
630+
expected,
631+
withDefaultComparer,
632+
$"HashSet.%s{relation} must answer as the member of the BCL type for the sequence that the default comparer converts, because the comparer of the first set finds equal what the default comparer finds equal"
633+
)
634+
else
635+
Assert.AreNotEqual<bool>(
636+
expected,
637+
withDefaultComparer,
638+
$"HashSet.%s{relation} must answer differently for the sequence that the default comparer converts, because the comparer of the first set tells apart strings that the default comparer finds equal"
639+
)
640+
641+
// The same recipe for the operations of the set algebra that take a sequence in the BCL type
642+
[<TestMethod; DynamicData(nameof SetRelationInputs.OperationRows, typeof<SetRelationInputs>)>]
643+
member _.``an operation with a sequence takes the sequence that the comparer of the first set converts``
644+
(operation : string, comparer : string)
645+
=
646+
let struct (setItems, sequenceItems) = SetRelationInputs.OperationItems[operation]
647+
let struct (bclMember, moduleFunction) = operationFunctions[operation]
648+
let struct (firstComparer, defaultFindsEqualWhatItFindsEqual) = SetRelationInputs.Comparers[comparer]
649+
let set = (HashSet.forComparer firstComparer).ofArray(SetRelationInputs.Strings setItems)
650+
let elements = SetRelationInputs.Strings sequenceItems
651+
let expected = HashSet.toArray (bclMember set elements)
652+
653+
CollectionAssert.AreEquivalent (
654+
expected,
655+
HashSet.toArray (moduleFunction set ((HashSet.forComparer (HashSet.comparer set)).ofSeq elements)),
656+
$"HashSet.%s{operation} must return the result of the member of the BCL type for the sequence that the comparer of the first set converts"
657+
)
658+
659+
let withDefaultComparer = HashSet.toArray (moduleFunction set (HashSet.ofSeq elements))
660+
661+
if defaultFindsEqualWhatItFindsEqual then
662+
CollectionAssert.AreEquivalent (
663+
expected,
664+
withDefaultComparer,
665+
$"HashSet.%s{operation} must return the result of the member of the BCL type for the sequence that the default comparer converts, because the comparer of the first set finds equal what the default comparer finds equal"
666+
)
667+
else
668+
CollectionAssert.AreNotEquivalent (
669+
expected,
670+
withDefaultComparer,
671+
$"HashSet.%s{operation} must return another result for the sequence that the default comparer converts, because the comparer of the first set tells apart strings that the default comparer finds equal"
672+
)
673+
674+
[<TestMethod; DynamicData(nameof SetRelationInputs.StreamingRows, typeof<SetRelationInputs>)>]
675+
member _.``overlaps and isSuperset with a sequence test each element with contains`` (relation : string, comparer : string) =
676+
let struct (setItems, sequenceItems) = SetRelationInputs.Items[relation]
677+
let struct (bclMember, _) = relationFunctions[relation]
678+
let struct (firstComparer, _) = SetRelationInputs.Comparers[comparer]
679+
let set = (HashSet.forComparer firstComparer).ofArray(SetRelationInputs.Strings setItems)
680+
let elements = SetRelationInputs.Strings sequenceItems
681+
682+
Assert.AreEqual<bool>(
683+
bclMember set elements,
684+
streamingRecipes[relation] set elements,
685+
$"%s{relation} with a sequence must test each element with HashSet.contains, in the comparer of the first set"
686+
)
687+
558688
// ---- Null inputs ----
559689

560690
[<TestMethod; DynamicData(nameof HashSetTests.NullRejections)>]

0 commit comments

Comments
 (0)