Skip to content

Commit 517e98d

Browse files
xperiandriclaude
andcommitted
test: shared contracts of the sequence-like modules, run for FlatList
- `SeqOps`, `SeqCreationOps` and `IndexedOps` record the functions of a module; the `SeqLike` facet is a chain of abstract classes, one file per category, and `SeqCreation` and `Indexed` complete it. - Expected values come from the FSharp.Core `List` module over the enumerated elements, expected exceptions from the mirrored module. - `FlatList` derives one test class per facet and adds its own tests: default values, shared storage, the builder, the computation expression and the `Option` and `Tuple` namespaces. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 1d77102 commit 517e98d

20 files changed

Lines changed: 3798 additions & 0 deletions
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
namespace FSharp.Collections.Immutable.Tests
2+
3+
/// <summary>The helpers that every contract base shares.</summary>
4+
/// <param name="name">The name of the module under test, for the messages of the assertions.</param>
5+
/// <param name="mirror">The FSharp.Core module whose exceptions the module mirrors.</param>
6+
/// <param name="paramName">Maps the name of a parameter of the FSharp.Core oracle to the name of the parameter of the module.</param>
7+
[<AbstractClass>]
8+
type ContractBase (name : string, mirror : Mirror, paramName : string -> string) =
9+
10+
/// The result of the oracle of the FSharp.Core module that the module mirrors.
11+
member _.Mirrored (arrayOracle : unit -> 'R, listOracle : unit -> 'R) =
12+
match mirror with
13+
| ArrayMirror -> arrayOracle ()
14+
| ListMirror -> listOracle ()
15+
16+
/// Asserts that a function of the module throws what the same function of the mirrored FSharp.Core module throws.
17+
member _.ThrowsLikeFSharpCore (arrayOracle : unit -> 'A, listOracle : unit -> 'L, actual : unit -> 'R, message : string) =
18+
ExceptionAssert.mirrored
19+
mirror
20+
paramName
21+
(arrayOracle >> ignore)
22+
(listOracle >> ignore)
23+
(actual >> ignore)
24+
$"%s{name}: %s{message}"
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
namespace FSharp.Collections.Immutable.Tests
2+
3+
open System.Collections.Generic
4+
open FSharp.Collections.Immutable
5+
open Microsoft.VisualStudio.TestTools.UnitTesting
6+
7+
/// <summary>
8+
/// The contract of the index-based functions of <c>FlatList</c>, <c>IndexedList</c> and <c>IIndexedList</c>
9+
/// (ADR 0001, Appendix A).
10+
/// </summary>
11+
[<AbstractClass>]
12+
type IndexedContract<'C> (ops : IndexedOps<'C>) =
13+
inherit ContractBase (ops.Name, ops.Mirror, ops.ParamName)
14+
15+
/// An equality comparer that ignores the sign, for the functions that take a comparer.
16+
static let absoluteEquality = {
17+
new IEqualityComparer<int> with
18+
member _.Equals (left, right) = abs left = abs right
19+
member _.GetHashCode item = abs item
20+
}
21+
22+
/// The collection of the input with several elements and the elements that it enumerates.
23+
let several () =
24+
let collection = ops.OfSeq Inputs.several
25+
struct (collection, ops.ToList collection)
26+
27+
[<TestMethod; TestCategory(nameof FlatList.item)>]
28+
member _.``item returns the element at the index`` () =
29+
let struct (collection, elements) = several ()
30+
31+
for index in 0 .. elements.Length - 1 do
32+
Assert.AreEqual<int>(List.item index elements, ops.Item index collection, $"%s{ops.Name}.item %d{index}")
33+
34+
[<TestMethod; TestCategory(nameof FlatList.item)>]
35+
member this.``item throws for an index out of range as FSharp.Core does`` () =
36+
let struct (collection, elements) = several ()
37+
38+
for index in [ -1; elements.Length ] do
39+
this.ThrowsLikeFSharpCore (
40+
(fun () -> Array.item index (List.toArray elements)),
41+
(fun () -> List.item index elements),
42+
(fun () -> ops.Item index collection),
43+
$"item %d{index} must throw"
44+
)
45+
46+
[<TestMethod; TestCategory(nameof FlatList.tryItem)>]
47+
member _.``tryItem returns the element at the index or nothing`` () =
48+
let struct (collection, elements) = several ()
49+
50+
for index in -1 .. elements.Length do
51+
Assert.AreEqual<int voption>(
52+
List.tryItem index elements |> ValueOption.ofOption,
53+
ops.TryItem index collection,
54+
$"%s{ops.Name}.tryItem %d{index}"
55+
)
56+
57+
[<TestMethod; TestCategory(nameof FlatList.indexOf)>]
58+
member _.``indexOf returns the index of the first equal element or -1`` () =
59+
let struct (collection, elements) = several ()
60+
61+
for value in [ 5; -3; 7; 1000 ] do
62+
Assert.AreEqual<int>(
63+
List.tryFindIndex ((=) value) elements
64+
|> Option.defaultValue -1,
65+
ops.IndexOf value collection,
66+
$"%s{ops.Name}.indexOf %d{value}"
67+
)
68+
69+
[<TestMethod; TestCategory(nameof FlatList.indexOfWith)>]
70+
member _.``indexOfWith compares with the equality comparer`` () =
71+
let struct (collection, elements) = several ()
72+
73+
for value in [ -5; 3; -12; 1000 ] do
74+
Assert.AreEqual<int>(
75+
List.tryFindIndex (fun item -> abs item = abs value) elements
76+
|> Option.defaultValue -1,
77+
ops.IndexOfWith absoluteEquality value collection,
78+
$"%s{ops.Name}.indexOfWith %d{value}"
79+
)
80+
81+
[<TestMethod; TestCategory(nameof FlatList.lastIndexOf)>]
82+
member _.``lastIndexOf returns the index of the last equal element or -1`` () =
83+
let struct (collection, elements) = several ()
84+
85+
for value in [ 5; -3; 7; 1000 ] do
86+
Assert.AreEqual<int>(
87+
List.tryFindIndexBack ((=) value) elements
88+
|> Option.defaultValue -1,
89+
ops.LastIndexOf value collection,
90+
$"%s{ops.Name}.lastIndexOf %d{value}"
91+
)
92+
93+
Assert.AreEqual<int>(-1, ops.LastIndexOf 5 (ops.OfSeq Inputs.empty), $"%s{ops.Name}.lastIndexOf in an empty collection")
94+
95+
[<TestMethod; TestCategory(nameof FlatList.lastIndexOfWith)>]
96+
member _.``lastIndexOfWith compares with the equality comparer`` () =
97+
let struct (collection, elements) = several ()
98+
99+
for value in [ -5; 3; -12; 1000 ] do
100+
Assert.AreEqual<int>(
101+
List.tryFindIndexBack (fun item -> abs item = abs value) elements
102+
|> Option.defaultValue -1,
103+
ops.LastIndexOfWith absoluteEquality value collection,
104+
$"%s{ops.Name}.lastIndexOfWith %d{value}"
105+
)
106+
107+
Assert.AreEqual<int>(
108+
-1,
109+
ops.LastIndexOfWith absoluteEquality 5 (ops.OfSeq Inputs.empty),
110+
$"%s{ops.Name}.lastIndexOfWith in an empty collection"
111+
)
112+
113+
[<TestMethod; TestCategory(nameof FlatList.add)>]
114+
member _.``add appends the value`` () =
115+
for input in Inputs.all do
116+
let collection = ops.OfSeq input
117+
118+
Assert.AreEqual<int list>(
119+
ops.ToList collection @ [ 99 ],
120+
ops.ToList (ops.Add 99 collection),
121+
$"%s{ops.Name}.add to %A{input}"
122+
)
123+
124+
[<TestMethod; TestCategory(nameof FlatList.insertAt)>]
125+
member _.``insertAt inserts the value at the index`` () =
126+
let struct (collection, elements) = several ()
127+
128+
for index in [ 0; 3; elements.Length ] do
129+
Assert.AreEqual<int list>(
130+
List.insertAt index 99 elements,
131+
ops.ToList (ops.InsertAt index 99 collection),
132+
$"%s{ops.Name}.insertAt %d{index}"
133+
)
134+
135+
[<TestMethod; TestCategory(nameof FlatList.insertAt)>]
136+
member this.``insertAt throws for an index out of range as FSharp.Core does`` () =
137+
let struct (collection, elements) = several ()
138+
139+
for index in [ -1; elements.Length + 1 ] do
140+
this.ThrowsLikeFSharpCore (
141+
(fun () -> Array.insertAt index 99 (List.toArray elements)),
142+
(fun () -> List.insertAt index 99 elements),
143+
(fun () -> ops.InsertAt index 99 collection),
144+
$"insertAt %d{index} must throw"
145+
)
146+
147+
[<TestMethod; TestCategory(nameof FlatList.insertManyAt)>]
148+
member _.``insertManyAt inserts the values at the index`` () =
149+
let struct (collection, elements) = several ()
150+
151+
for index in [ 0; 3; elements.Length ] do
152+
for values in [ []; [ 98; 99 ] ] do
153+
Assert.AreEqual<int list>(
154+
List.insertManyAt index values elements,
155+
ops.ToList (ops.InsertManyAt index values collection),
156+
$"%s{ops.Name}.insertManyAt %d{index} %A{values}"
157+
)
158+
159+
[<TestMethod; TestCategory(nameof FlatList.insertManyAt)>]
160+
member this.``insertManyAt throws for an index out of range as FSharp.Core does`` () =
161+
let struct (collection, elements) = several ()
162+
163+
for index in [ -1; elements.Length + 1 ] do
164+
this.ThrowsLikeFSharpCore (
165+
(fun () -> Array.insertManyAt index [ 99 ] (List.toArray elements)),
166+
(fun () -> List.insertManyAt index [ 99 ] elements),
167+
(fun () -> ops.InsertManyAt index [ 99 ] collection),
168+
$"insertManyAt %d{index} must throw"
169+
)
170+
171+
[<TestMethod; TestCategory(nameof FlatList.removeAt)>]
172+
member _.``removeAt removes the element at the index`` () =
173+
let struct (collection, elements) = several ()
174+
175+
for index in [ 0; 3; elements.Length - 1 ] do
176+
Assert.AreEqual<int list>(
177+
List.removeAt index elements,
178+
ops.ToList (ops.RemoveAt index collection),
179+
$"%s{ops.Name}.removeAt %d{index}"
180+
)
181+
182+
[<TestMethod; TestCategory(nameof FlatList.removeAt)>]
183+
member this.``removeAt throws for an index out of range as FSharp.Core does`` () =
184+
let struct (collection, elements) = several ()
185+
186+
for index in [ -1; elements.Length ] do
187+
this.ThrowsLikeFSharpCore (
188+
(fun () -> Array.removeAt index (List.toArray elements)),
189+
(fun () -> List.removeAt index elements),
190+
(fun () -> ops.RemoveAt index collection),
191+
$"removeAt %d{index} must throw"
192+
)
193+
194+
[<TestMethod; TestCategory(nameof FlatList.removeManyAt)>]
195+
member _.``removeManyAt removes the elements of the range`` () =
196+
let struct (collection, elements) = several ()
197+
198+
for struct (index, count) in [ struct (0, 2); struct (3, 3); struct (elements.Length - 1, 1); struct (2, 0) ] do
199+
Assert.AreEqual<int list>(
200+
List.removeManyAt index count elements,
201+
ops.ToList (ops.RemoveManyAt index count collection),
202+
$"%s{ops.Name}.removeManyAt %d{index} %d{count}"
203+
)
204+
205+
[<TestMethod; TestCategory(nameof FlatList.removeManyAt)>]
206+
member this.``removeManyAt throws for a range out of range as FSharp.Core does`` () =
207+
let struct (collection, elements) = several ()
208+
209+
for struct (index, count) in [ struct (-1, 1); struct (elements.Length - 1, 2); struct (0, -1) ] do
210+
this.ThrowsLikeFSharpCore (
211+
(fun () -> Array.removeManyAt index count (List.toArray elements)),
212+
(fun () -> List.removeManyAt index count elements),
213+
(fun () -> ops.RemoveManyAt index count collection),
214+
$"removeManyAt %d{index} %d{count} must throw"
215+
)
216+
217+
[<TestMethod; TestCategory(nameof FlatList.updateAt)>]
218+
member _.``updateAt replaces the element at the index`` () =
219+
let struct (collection, elements) = several ()
220+
221+
for index in [ 0; elements.Length - 1 ] do
222+
Assert.AreEqual<int list>(
223+
List.updateAt index 99 elements,
224+
ops.ToList (ops.UpdateAt index 99 collection),
225+
$"%s{ops.Name}.updateAt %d{index}"
226+
)
227+
228+
[<TestMethod; TestCategory(nameof FlatList.updateAt)>]
229+
member this.``updateAt throws for an index out of range as FSharp.Core does`` () =
230+
let struct (collection, elements) = several ()
231+
232+
for index in [ -1; elements.Length ] do
233+
this.ThrowsLikeFSharpCore (
234+
(fun () -> Array.updateAt index 99 (List.toArray elements)),
235+
(fun () -> List.updateAt index 99 elements),
236+
(fun () -> ops.UpdateAt index 99 collection),
237+
$"updateAt %d{index} must throw"
238+
)

0 commit comments

Comments
 (0)