Skip to content

Commit 2e2908f

Browse files
authored
Merge pull request #1546 from fsprojects/daily-test-improver/csv-core-coverage
Daily Test Coverage Improver: Comprehensive CSV Core coverage improvements
2 parents a07523e + 80ff2eb commit 2e2908f

1 file changed

Lines changed: 245 additions & 1 deletion

File tree

tests/FSharp.Data.Core.Tests/CsvReader.fs

Lines changed: 245 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,248 @@ GHI;3
114114
[| "QUOTED"; "4" |]
115115
[| ""; "5" |] |]
116116

117-
actual |> should equal expected
117+
actual |> should equal expected
118+
119+
// --------------------------------------------------------------------------------------
120+
// Tests for CSV Core components that need better coverage
121+
// --------------------------------------------------------------------------------------
122+
123+
open FSharp.Data
124+
125+
// CsvFile tests
126+
[<Test>]
127+
let ``CsvFile.Parse can parse simple CSV`` () =
128+
let csv = CsvFile.Parse("name,age\nAlice,25\nBob,30")
129+
csv.Headers |> should equal (Some [| "name"; "age" |])
130+
csv.Rows |> Seq.length |> should equal 2
131+
let firstRow = csv.Rows |> Seq.head
132+
firstRow.["name"] |> should equal "Alice"
133+
firstRow.["age"] |> should equal "25"
134+
135+
[<Test>]
136+
let ``CsvFile.Parse with custom separators`` () =
137+
let csv = CsvFile.Parse("name;age\nAlice;25\nBob;30", separators=";")
138+
csv.Headers |> should equal (Some [| "name"; "age" |])
139+
let firstRow = csv.Rows |> Seq.head
140+
firstRow.["name"] |> should equal "Alice"
141+
142+
[<Test>]
143+
let ``CsvFile.Parse with custom quote character`` () =
144+
let csv = CsvFile.Parse("name,value\nAlice,'Hello, World'", quote=''')
145+
let firstRow = csv.Rows |> Seq.head
146+
firstRow.["value"] |> should equal "Hello, World"
147+
148+
[<Test>]
149+
let ``CsvFile.Parse without headers`` () =
150+
let csv = CsvFile.Parse("Alice,25\nBob,30", hasHeaders=false)
151+
csv.Headers |> should equal None
152+
let firstRow = csv.Rows |> Seq.head
153+
firstRow.[0] |> should equal "Alice"
154+
firstRow.[1] |> should equal "25"
155+
156+
[<Test>]
157+
let ``CsvFile.Parse with skipRows`` () =
158+
let csv = CsvFile.Parse("comment line\nname,age\nAlice,25", skipRows=1)
159+
csv.Headers |> should equal (Some [| "name"; "age" |])
160+
let firstRow = csv.Rows |> Seq.head
161+
firstRow.["name"] |> should equal "Alice"
162+
163+
[<Test>]
164+
let ``CsvFile.Parse with ignoreErrors true handles malformed rows`` () =
165+
let csv = CsvFile.Parse("name,age\nAlice,25\nBob\nCharlie,35", ignoreErrors=true)
166+
csv.Rows |> Seq.length |> should equal 2 // Malformed "Bob" row should be ignored
167+
168+
[<Test>]
169+
let ``CsvFile column indexing works correctly`` () =
170+
let csv = CsvFile.Parse("first,second,third\nA,B,C")
171+
let firstRow = csv.Rows |> Seq.head
172+
firstRow.["first"] |> should equal "A"
173+
firstRow.["second"] |> should equal "B"
174+
firstRow.["third"] |> should equal "C"
175+
176+
[<Test>]
177+
let ``CsvFile GetColumnIndex returns correct index`` () =
178+
let csv = CsvFile.Parse("first,second,third\nA,B,C")
179+
csv.GetColumnIndex("first") |> should equal 0
180+
csv.GetColumnIndex("second") |> should equal 1
181+
csv.GetColumnIndex("third") |> should equal 2
182+
183+
[<Test>]
184+
let ``CsvFile TryGetColumnIndex returns Some for valid column`` () =
185+
let csv = CsvFile.Parse("first,second\nA,B")
186+
csv.TryGetColumnIndex("first") |> should equal (Some 0)
187+
csv.TryGetColumnIndex("missing") |> should equal None
188+
189+
[<Test>]
190+
let ``CsvFile GetColumnIndex throws for invalid column`` () =
191+
let csv = CsvFile.Parse("first,second\nA,B")
192+
(fun () -> csv.GetColumnIndex("missing") |> ignore) |> should throw typeof<System.Collections.Generic.KeyNotFoundException>
193+
194+
// CsvExtensions edge case tests
195+
[<Test>]
196+
let ``AsInteger throws for invalid string`` () =
197+
(fun () -> "not_a_number".AsInteger() |> ignore) |> should throw typeof<System.Exception>
198+
199+
[<Test>]
200+
let ``AsInteger64 throws for invalid string`` () =
201+
(fun () -> "not_a_number".AsInteger64() |> ignore) |> should throw typeof<System.Exception>
202+
203+
[<Test>]
204+
let ``AsDecimal throws for invalid string`` () =
205+
(fun () -> "not_a_decimal".AsDecimal() |> ignore) |> should throw typeof<System.Exception>
206+
207+
[<Test>]
208+
let ``AsFloat throws for invalid string`` () =
209+
(fun () -> "not_a_float".AsFloat() |> ignore) |> should throw typeof<System.Exception>
210+
211+
[<Test>]
212+
let ``AsBoolean throws for invalid string`` () =
213+
(fun () -> "not_a_bool".AsBoolean() |> ignore) |> should throw typeof<System.Exception>
214+
215+
[<Test>]
216+
let ``AsDateTime throws for invalid string`` () =
217+
(fun () -> "not_a_date".AsDateTime() |> ignore) |> should throw typeof<System.Exception>
218+
219+
[<Test>]
220+
let ``AsGuid throws for invalid string`` () =
221+
(fun () -> "not_a_guid".AsGuid() |> ignore) |> should throw typeof<System.Exception>
222+
223+
[<Test>]
224+
let ``AsInteger with custom culture`` () =
225+
let result = "1234".AsInteger(System.Globalization.CultureInfo.InvariantCulture)
226+
result |> should equal 1234
227+
228+
[<Test>]
229+
let ``AsDecimal with custom culture`` () =
230+
let result = "123.45".AsDecimal(System.Globalization.CultureInfo.InvariantCulture)
231+
result |> should equal 123.45m
232+
233+
[<Test>]
234+
let ``AsFloat with custom culture`` () =
235+
let result = "123.45".AsFloat(System.Globalization.CultureInfo.InvariantCulture)
236+
result |> should equal 123.45
237+
238+
// Additional CSV parsing edge cases
239+
[<Test>]
240+
let ``readCsvFile handles empty fields correctly`` () =
241+
let sr = new StringReader("a,,c\n,b,")
242+
let actual = readCsvFile sr "," '"' |> Seq.map fst |> Array.ofSeq
243+
let expected = [|[|"a"; ""; "c"|]; [|""; "b"; ""|]|]
244+
actual |> should equal expected
245+
246+
[<Test>]
247+
let ``readCsvFile handles trailing comma`` () =
248+
let sr = new StringReader("a,b,\nc,d,")
249+
let actual = readCsvFile sr "," '"' |> Seq.map fst |> Array.ofSeq
250+
let expected = [|[|"a"; "b"; ""|]; [|"c"; "d"; ""|]|]
251+
actual |> should equal expected
252+
253+
[<Test>]
254+
let ``readCsvFile handles quoted empty string`` () =
255+
let sr = new StringReader("a,\"\",c")
256+
let actual = readCsvFile sr "," '"' |> Seq.map fst |> Array.ofSeq
257+
let expected = [|[|"a"; ""; "c"|]|]
258+
actual |> should equal expected
259+
260+
[<Test>]
261+
let ``readCsvFile handles quotes at field boundaries`` () =
262+
let sr = new StringReader("\"a\",\"b\",\"c\"")
263+
let actual = readCsvFile sr "," '"' |> Seq.map fst |> Array.ofSeq
264+
let expected = [|[|"a"; "b"; "c"|]|]
265+
actual |> should equal expected
266+
267+
[<Test>]
268+
let ``readCsvFile handles mixed quoted and unquoted fields`` () =
269+
let sr = new StringReader("a,\"b,c\",d")
270+
let actual = readCsvFile sr "," '"' |> Seq.map fst |> Array.ofSeq
271+
let expected = [|[|"a"; "b,c"; "d"|]|]
272+
actual |> should equal expected
273+
274+
[<Test>]
275+
let ``readCsvFile handles Windows line endings`` () =
276+
let sr = new StringReader("a,b\r\nc,d\r\n")
277+
let actual = readCsvFile sr "," '"' |> Seq.map fst |> Array.ofSeq
278+
let expected = [|[|"a"; "b"|]; [|"c"; "d"|]|]
279+
actual |> should equal expected
280+
281+
[<Test>]
282+
let ``readCsvFile handles Unix line endings`` () =
283+
let sr = new StringReader("a,b\nc,d\n")
284+
let actual = readCsvFile sr "," '"' |> Seq.map fst |> Array.ofSeq
285+
let expected = [|[|"a"; "b"|]; [|"c"; "d"|]|]
286+
actual |> should equal expected
287+
288+
[<Test>]
289+
let ``readCsvFile handles Mac line endings`` () =
290+
let sr = new StringReader("a,b\rc,d\r")
291+
let actual = readCsvFile sr "," '"' |> Seq.map fst |> Array.ofSeq
292+
let expected = [|[|"a"; "b"|]; [|"c"; "d"|]|]
293+
actual |> should equal expected
294+
295+
[<Test>]
296+
let ``readCsvFile handles tab separators`` () =
297+
let sr = new StringReader("a\tb\tc\nd\te\tf")
298+
let actual = readCsvFile sr "\t" '"' |> Seq.map fst |> Array.ofSeq
299+
let expected = [|[|"a"; "b"; "c"|]; [|"d"; "e"; "f"|]|]
300+
actual |> should equal expected
301+
302+
[<Test>]
303+
let ``readCsvFile handles pipe separators`` () =
304+
let sr = new StringReader("a|b|c\nd|e|f")
305+
let actual = readCsvFile sr "|" '"' |> Seq.map fst |> Array.ofSeq
306+
let expected = [|[|"a"; "b"; "c"|]; [|"d"; "e"; "f"|]|]
307+
actual |> should equal expected
308+
309+
[<Test>]
310+
let ``readCsvFile handles single field`` () =
311+
let sr = new StringReader("single")
312+
let actual = readCsvFile sr "," '"' |> Seq.map fst |> Array.ofSeq
313+
let expected = [|[|"single"|]|]
314+
actual |> should equal expected
315+
316+
[<Test>]
317+
let ``readCsvFile handles quoted field with internal quotes`` () =
318+
let sr = new StringReader("\"She said \"\"Hello\"\" to me\"")
319+
let actual = readCsvFile sr "," '"' |> Seq.map fst |> Array.ofSeq
320+
let expected = [|[|"She said \"Hello\" to me"|]|]
321+
actual |> should equal expected
322+
323+
[<Test>]
324+
let ``readCsvFile handles BOM in stream`` () =
325+
// BOM (Byte Order Mark) should be handled gracefully
326+
let bomString = "\uFEFFa,b,c"
327+
let sr = new StringReader(bomString)
328+
let actual = readCsvFile sr "," '"' |> Seq.map fst |> Array.ofSeq
329+
// Should handle BOM by treating it as part of first field
330+
actual.Length |> should be (greaterThan 0)
331+
332+
[<Test>]
333+
let ``CsvFile Parse handles various numeric formats`` () =
334+
let csv = CsvFile.Parse("int,float,decimal\n42,3.14,123.456")
335+
let row = csv.Rows |> Seq.head
336+
row.["int"] |> should equal "42"
337+
row.["float"] |> should equal "3.14"
338+
row.["decimal"] |> should equal "123.456"
339+
340+
[<Test>]
341+
let ``CsvFile with different column counts when ignoreErrors is false`` () =
342+
(fun () ->
343+
let csv = CsvFile.Parse("a,b\n1,2,3", ignoreErrors=false)
344+
csv.Rows |> Seq.toArray |> ignore
345+
) |> should throw typeof<System.Exception>
346+
347+
[<Test>]
348+
let ``CsvRow GetColumn by index works correctly`` () =
349+
let csv = CsvFile.Parse("a,b,c\n1,2,3")
350+
let row = csv.Rows |> Seq.head
351+
row.GetColumn(0) |> should equal "1"
352+
row.GetColumn(1) |> should equal "2"
353+
row.GetColumn(2) |> should equal "3"
354+
355+
[<Test>]
356+
let ``CsvRow GetColumn by name works correctly`` () =
357+
let csv = CsvFile.Parse("first,second,third\nx,y,z")
358+
let row = csv.Rows |> Seq.head
359+
row.GetColumn("first") |> should equal "x"
360+
row.GetColumn("second") |> should equal "y"
361+
row.GetColumn("third") |> should equal "z"

0 commit comments

Comments
 (0)