@@ -16,10 +16,54 @@ final class ReaderTests: XCTestCase {
1616 override func setUp( ) {
1717 self . continueAfterFailure = false
1818 }
19+ }
20+
21+ // MARK: -
22+
23+ extension ReaderTests {
24+ /// The test data used for this file.
25+ private enum TestData {
26+ /// A CSV row representing a header row (4 fields).
27+ static let headers = [ " seq " , " Name " , " Country " , " Number Pair " ]
28+ /// Small amount of regular CSV rows (4 fields per row).
29+ static let content = [ [ " 1 " , " Marcos " , " Spain " , " 99 " ] ,
30+ [ " 2 " , " Marine-Anaïs " , " France " , " 88 " ] ,
31+ [ " 3 " , " Alex " , " Germany " , " 77 " ] ,
32+ [ " 4 " , " Pei " , " China " , " 66 " ] ]
33+ /// A bunch of rows each one containing an edge case.
34+ static let edgeCases = [ [ " " , " Marcos " , " Spaiñ " , " 99 " ] ,
35+ [ " 2 " , " Marine-Anaïs " , #""Fra""nce""# , " " ] ,
36+ [ " " , " " , " " , " " ] ,
37+ [ " 4 " , " Pei " , " China " , #""\#n""# ] ,
38+ [ " " , " " , " " , #""\#r\#n""# ] ,
39+ [ " 5 " , #""A\#rh,me\#nd""# , " Egypt " , #""\#r""# ] ,
40+ [ " 6 " , #""Man""olo""# , " México " , " 100_000 " ] ]
41+ /// Exactly the same data as `contentEdgeCases`, but the quotes delimiting the beginning and end of a field have been removed.
42+ ///
43+ /// It is tipically used to check the result of parsing `contentEdgeCases`.
44+ static let unescapedEdgeCases = [
45+ [ " " , " Marcos " , " Spaiñ " , " 99 " ] ,
46+ [ " 2 " , " Marine-Anaïs " , #"Fra"nce"# , " " ] ,
47+ [ " " , " " , " " , " " ] ,
48+ [ " 4 " , " Pei " , " China " , " \n " ] ,
49+ [ " " , " " , " " , " \r \n " ] ,
50+ [ " 5 " , " A \r h,me \n d " , " Egypt " , " \r " ] ,
51+ [ " 6 " , #"Man"olo"# , " México " , " 100_000 " ] ]
52+ /// Encodes the test data into a Swift `String`.
53+ /// - parameter sample:
54+ /// - parameter delimiters: Unicode scalars to use to mark fields and rows.
55+ /// - returns: Swift String representing the CSV file.
56+ static func toCSV( _ sample: [ [ String ] ] , delimiters: Delimiter . Pair ) -> String {
57+ let ( f, r) = ( String ( delimiters. field. rawValue) , String ( delimiters. row. rawValue) )
58+ return sample. map { $0. joined ( separator: f) } . joined ( separator: r) . appending ( r)
59+ }
60+ }
1961
2062 private typealias Encoded = ( string: String , data: Data )
2163}
2264
65+ // MARK: -
66+
2367extension ReaderTests {
2468 /// Tests the correct parsing of an empty CSV.
2569 func testEmpty( ) throws {
@@ -30,8 +74,14 @@ extension ReaderTests {
3074
3175 /// Tests the correct parsing of a single value CSV.
3276 func testSingleValue( ) throws {
77+ let delimiters : Delimiter . Pair = ( " , " , " \n " )
3378 let input = [ [ " Marine-Anaïs " ] ]
34- let parsed = try CSVReader . parse ( input: input. toCSV ( ) as String ) { $0. headerStrategy = . none }
79+
80+ let parsed = try CSVReader . parse ( input: TestData . toCSV ( input, delimiters: delimiters) ) {
81+ $0. delimiters = delimiters
82+ $0. headerStrategy = . none
83+ }
84+
3585 XCTAssertTrue ( parsed. headers. isEmpty)
3686 XCTAssertEqual ( parsed. rows, input)
3787 }
@@ -45,8 +95,7 @@ extension ReaderTests {
4595 let trimStrategy : [ CharacterSet ] = [ . init( ) , . whitespaces]
4696 let presamples : [ Bool ] = [ true , false ]
4797 // The data used for testing.
48- let headers = TestData . headers
49- let content = TestData . content
98+ let ( headers, content) = ( TestData . headers, TestData . content)
5099
51100 // The actual testing implementation.
52101 let work : ( _ configuration: CSVReader . Configuration , _ encoded: Encoded ) throws -> Void = {
@@ -79,7 +128,9 @@ extension ReaderTests {
79128 case . firstLine: input = [ headers] + content
80129// case .unknown: return XCTFail("Testing header inference is not yet supported")
81130 }
82- let encoded : Encoded = ( input. toCSV ( delimiters: pair) , input. toCSV ( delimiters: pair) )
131+
132+ let string = TestData . toCSV ( input, delimiters: pair)
133+ let encoded : Encoded = ( string, string. data ( using: . utf8) !)
83134
84135 for t in trimStrategy {
85136 var toTrim = t
@@ -112,9 +163,8 @@ extension ReaderTests {
112163 let trimStrategy : [ CharacterSet ] = [ . init( ) , /*.whitespaces*/] // The whitespaces remove the row or field delimiters.
113164 let presamples : [ Bool ] = [ true , false ]
114165 // The data used for testing.
115- let headers = TestData . headers
116- let content = TestData . contentEdgeCases
117- let unescapedContent = TestData . contentUnescapedEdgeCases
166+ let ( headers, content) = ( TestData . headers, TestData . edgeCases)
167+ let unescapedContent = TestData . unescapedEdgeCases
118168
119169 // The actual testing implementation.
120170 let work : ( _ configuration: CSVReader . Configuration , _ encoded: Encoded ) throws -> Void = {
@@ -146,7 +196,9 @@ extension ReaderTests {
146196 case . firstLine: input = [ headers] + content
147197// case .unknown: return XCTFail("Testing header inference is not yet supported")
148198 }
149- let encoded : Encoded = ( input. toCSV ( delimiters: pair) , input. toCSV ( delimiters: pair) )
199+
200+ let string = TestData . toCSV ( input, delimiters: pair)
201+ let encoded : Encoded = ( string, string. data ( using: . utf8) !)
150202
151203 for t in trimStrategy {
152204 var toTrim = t
@@ -177,8 +229,7 @@ extension ReaderTests {
177229 let trimStrategy : [ CharacterSet ] = [ . init( ) , . whitespaces]
178230 let presamples : [ Bool ] = [ true , false ]
179231 // The data used for testing.
180- let headers = TestData . headers
181- let content = TestData . content
232+ let ( headers, content) = ( TestData . headers, TestData . content)
182233 let input = ( [ headers] + content) . mappingRandomFields ( count: 5 ) { [ quote = Character ( " \" " ) ] in
183234 guard !$0. hasPrefix ( String ( quote) ) else { return $0 }
184235
@@ -203,7 +254,9 @@ extension ReaderTests {
203254 for r in rowDelimiters {
204255 for f in fieldDelimiters {
205256 let pair : Delimiter . Pair = ( f, r)
206- let encoded : Encoded = ( input. toCSV ( delimiters: pair) , input. toCSV ( delimiters: pair) )
257+
258+ let string = TestData . toCSV ( input, delimiters: pair)
259+ let encoded : Encoded = ( string, string. data ( using: . utf8) !)
207260
208261 for t in trimStrategy {
209262 var toTrim = t
@@ -232,14 +285,15 @@ extension ReaderTests {
232285 let fieldDelimiters : [ Delimiter . Field ] = [ " , " , " ; " , " \t " ]
233286 let presamples : [ Bool ] = [ true , false ]
234287 // The data used for testing.
235- let headers = TestData . headers
236- let content = TestData . content
288+ let ( headers, content) = ( TestData . headers, TestData . content)
237289 let input = ( [ headers] + content) . removingRandomFields ( count: 2 )
238290 // Iterate through all configuration values.
239291 for r in rowDelimiters {
240292 for f in fieldDelimiters {
241293 let pair : Delimiter . Pair = ( f, r)
242- let encoded : Encoded = ( input. toCSV ( delimiters: pair) , input. toCSV ( delimiters: pair) )
294+
295+ let string = TestData . toCSV ( input, delimiters: pair)
296+ let encoded : Encoded = ( string, string. data ( using: . utf8) !)
243297
244298 for p in presamples {
245299 var c = CSVReader . Configuration ( )
@@ -253,3 +307,56 @@ extension ReaderTests {
253307 }
254308 }
255309}
310+
311+ // MARK: -
312+
313+ fileprivate extension Array where Element == [ String ] {
314+ /// Removes a random field from a random row.
315+ /// - parameter num: The number of random fields to remove.
316+ mutating func removeRandomFields( count: Int = 1 ) {
317+ guard !self . isEmpty && !self . first!. isEmpty else {
318+ fatalError ( " The receiving rows cannot be empty. " )
319+ }
320+
321+ for _ in 0 ..< count {
322+ let selectedRow = Int . random ( in: 0 ..< self . count)
323+ let selectedField = Int . random ( in: 0 ..< self [ selectedRow] . count)
324+
325+ let _ = self [ selectedRow] . remove ( at: selectedField)
326+ }
327+ }
328+
329+ /// Copies the receiving array and removes from it a random field from a random row.
330+ /// - parameter num: The number of random fields to remove.
331+ /// - returns: A copy of the receiving array lacking `count` number of fields.
332+ func removingRandomFields( count: Int = 1 ) -> [ [ String ] ] {
333+ var result = self
334+ result. removeRandomFields ( count: count)
335+ return result
336+ }
337+
338+ /// Transform a random field into the value returned in the argument closure.
339+ /// - parameter num: The number of random fields to modify.
340+ mutating func mapRandomFields( count: Int = 1 , _ transform: ( String ) -> String ) {
341+ guard !self . isEmpty && !self . first!. isEmpty else {
342+ fatalError ( " The receiving rows cannot be empty. " )
343+ }
344+
345+ for _ in 0 ..< count {
346+ let selectedRow = Int . random ( in: 0 ..< self . count)
347+ let selectedField = Int . random ( in: 0 ..< self [ selectedRow] . count)
348+
349+ self [ selectedRow] [ selectedField] = transform ( self [ selectedRow] [ selectedField] )
350+ }
351+ }
352+
353+ /// Copies the receiving array and transforms a random field from it into another value.
354+ /// - parameter num: The number of random fields to modify.
355+ /// - returns: A copy of the receiving array with the `count` number of fields modified.
356+ func mappingRandomFields( count: Int = 1 , _ transform: ( String ) -> String ) -> [ [ String ] ] {
357+ var result = self
358+ result. mapRandomFields ( count: count, transform)
359+ return result
360+ }
361+ }
362+
0 commit comments