1
(defpackage :cl-csv-test
2
(:use :cl :cl-user :cl-csv :lisp-unit2 :iter))
4
(in-package :cl-csv-test)
5
(cl-interpol:enable-interpol-syntax)
7
(defun run-all-tests ()
11
:run-contexts #'with-summary-context ))
13
(defmacro assert-length (exp it &rest them)
14
`(assert-eql ,exp (length ,it) ,@them))
16
(defparameter +test-csv-quoted-path+
17
(asdf:system-relative-pathname :cl-csv "tests/test-csv-quoted.csv"))
18
(defparameter +test-csv-unquoted-path+
19
(asdf:system-relative-pathname :cl-csv "tests/test-csv-unquoted.csv"))
20
(defparameter +test-csv-unquoted-no-trailing-path+
21
(asdf:system-relative-pathname :cl-csv "tests/test-csv-unquoted-no-trailing.csv"))
22
(defparameter +test-multiline+
23
(asdf:system-relative-pathname :cl-csv "tests/test-multiline-data.csv"))
25
(defparameter +test-files+
27
+test-csv-quoted-path+
28
+test-csv-unquoted-path+
29
+test-csv-unquoted-no-trailing-path+) )
31
(defparameter *test-csv1-rows*
32
'(("first name" "last name" "job \"title\"" "number of hours" "id")
33
("Russ" "Tyndall" "Software Developer's, \"Position\"" "26.2" "1")
34
("Adam" "Smith" "Economist" "37.5" "2")
35
("John" "Doe" "Anonymous Human" "42.1" "3")
36
("Chuck" "Darwin" "Natural Philosipher" "17.68" "4")
37
("Bill" "Shakespear" "Bard" "12.2" "5")
38
("James" "Kirk" "Starship Captain" "13.1" "6")
39
("Bob" "Anon" "" "13.1" "6")
40
("Mr" "Iñtërnâtiônàlizætiøn" "" "1.1" "0")))
42
(defparameter *test-csv1*
43
"\"first name\",\"last name\",\"job \"\"title\"\"\",\"number of hours\",\"id\"
44
\"Russ\",\"Tyndall\",\"Software Developer's, \"\"Position\"\"\",\"26.2\",\"1\"
45
\"Adam\",\"Smith\",\"Economist\",\"37.5\",\"2\"
46
\"John\",\"Doe\",\"Anonymous Human\",\"42.1\",\"3\"
47
\"Chuck\",\"Darwin\",\"Natural Philosipher\",\"17.68\",\"4\"
48
\"Bill\",\"Shakespear\",\"Bard\",\"12.2\",\"5\"
49
\"James\",\"Kirk\",\"Starship Captain\",\"13.1\",\"6\"
50
\"Bob\",\"Anon\",\"\",\"13.1\",\"6\"
51
\"Mr\",\"Iñtërnâtiônàlizætiøn\",\"\",\"1.1\",\"0\"
54
(defparameter *test-csv1-v2*
55
"first name,last name,\"job \"\"title\"\"\",number of hours,id
56
Russ,Tyndall,\"Software Developer's, \"\"Position\"\"\",26.2,1
57
Adam,Smith,Economist,37.5,2
58
John,Doe,Anonymous Human,42.1,3
59
Chuck,Darwin,Natural Philosipher,17.68,4
60
Bill,Shakespear,Bard,12.2,5
61
James,Kirk,Starship Captain,13.1,6
63
Mr,Iñtërnâtiônàlizætiøn,,1.1,0
66
(defparameter *test-csv-no-trailing-newline*
67
"first name,last name,\"job \"\"title\"\"\",number of hours,id
68
Russ,Tyndall,\"Software Developer's, \"\"Position\"\"\",26.2,1")
70
(defparameter *test-csv-data-with-newlines*
71
"first name,last name,\"job \"\"title\"\"\",number of hours,id
72
Russ,Tyndall,\"Software Developer's,
73
\"\"Position\"\"\",26.2,1")
75
(defparameter *test-csv-data-waiting-next-error*
76
"\"Which of the following is an appropriate calming technique or statement:
77
A. \"\"I can help you.\"\"
79
C. \"\"If you don't calm down I'm not sending anyone.\"\"
80
D. \"\"Ma'am, ma'am\ ma'am!\"\"\",A")
82
(define-test parsing-1 (:tags '(parsing))
83
(assert-equal *test-csv1-rows* (read-csv *test-csv1*))
84
(assert-equal *test-csv1-rows* (read-csv *test-csv1-v2*)))
86
(define-test writing-1 (:tags '(writing))
87
(assert-equal *test-csv1* (write-csv *test-csv1-rows* :always-quote t)))
89
(define-test parsing-errors (:tags '(parsing errors))
90
(assert-error 'csv-parse-error
92
"first name, a test\" broken quote, other stuff"))
93
(assert-error 'csv-parse-error
95
"first name,\"a test broken quote\" what are these chars, other stuff"))
96
(assert-error 'csv-parse-error
98
"first name,\"a test unfinished quote, other stuff"))
99
(assert-eql 3 (length (read-csv-row "first name, \"a test broken quote\", other stuff
102
(define-test no-trailing-parse (:tags '(parsing errors))
103
(let* ((data (read-csv *test-csv-no-trailing-newline*))
104
(str (write-csv data :always-quote t))
105
(data2 (read-csv str)))
106
(assert-equal 2 (length data))
107
(assert-equal 5 (length (first data)))
108
(assert-equal 5 (length (second data)))
109
(assert-equal data data2)))
111
(define-test data-with-newlines (:tags '(whitespace parsing writing))
112
(let* ((data (read-csv *test-csv-data-with-newlines*))
113
(str (write-csv data :always-quote t))
114
(data2 (read-csv str)))
115
(assert-equal 2 (length data))
116
(assert-equal 5 (length (first data)))
117
(assert-equal 5 (length (second data)))
119
"Software Developer's,
121
(third (second data)))
122
(assert-equal data data2)))
124
(define-test data-with-whitespace-trim (:tags '(whitespace parsing trim))
126
'("first" "last" " other " "" nil nil)
127
(read-csv-row " first , last , ' other ','',, "
128
:unquoted-empty-string-is-nil t
129
:quoted-empty-string-is-nil nil
130
:trim-outer-whitespace t
133
'(" first " " last " " other " "" nil " ")
134
(read-csv-row " first , last ,' other ','',, "
135
:unquoted-empty-string-is-nil t
136
:quoted-empty-string-is-nil nil
137
:trim-outer-whitespace nil
140
(assert-error 'csv-parse-error
141
(read-csv-row " first , last , ' other ','',, "
142
:unquoted-empty-string-is-nil t
143
:quoted-empty-string-is-nil nil
144
:trim-outer-whitespace nil
146
"whitespace before quoted values is a parse error if we are
148
(assert-error 'csv-parse-error
149
(read-csv-row " first , last ,' other ' ,'',, "
150
:unquoted-empty-string-is-nil t
151
:quoted-empty-string-is-nil nil
152
:trim-outer-whitespace nil
154
"whitespace after quoted values is a parse error if we are
158
(define-test data-with-whitespace-nilling (:tags '(whitespace parsing trim))
160
'("first" "last" " other " nil nil nil)
161
(read-csv-row " first , last , ' other ' ,'',, "
162
:quoted-empty-string-is-nil t
163
:unquoted-empty-string-is-nil t
166
'("first" "last" " other " "" "" "")
167
(read-csv-row " first , last ,' other ','',, "
168
:quoted-empty-string-is-nil nil
169
:unquoted-empty-string-is-nil nil
173
'("first" "last" " other " nil "" "")
174
(read-csv-row " first , last , ' other ','',, "
175
:quoted-empty-string-is-nil T
176
:unquoted-empty-string-is-nil nil
178
"whitespace before quoted values is a parse error if we are
181
'("first" "last" " other " "" nil nil)
182
(read-csv-row " first , last ,' other ' ,'',, "
183
:quoted-empty-string-is-nil nil
184
:unquoted-empty-string-is-nil t
186
"whitespace after quoted values is a parse error if we are
191
(define-test files (:tags '(parsing files))
192
(iter (for csv in +test-files+)
193
(for data = (read-csv csv))
194
(assert-equal *test-csv1-rows* data csv)))
196
(define-test multi-line-file (:tags '(parsing files))
197
(let ((data (read-csv +test-multiline+)))
198
(assert-equal 2 (length data) data)
201
multiline" (nth 3 (first data)) ))
204
(define-test dont-always-quote-and-newline (:tags '(writing whitespace quotation))
205
(let* ((row '("Russ" "Tyndall" "Software Developer's, \"Position\"" "26.2" "1" ","))
206
(res (write-csv-row row :always-quote nil :newline #?"\n")))
207
(assert-equal #?"Russ,Tyndall,\"Software Developer's, \"\"Position\"\"\",26.2,1,\",\"\n"
210
(define-test dont-always-quote-and-newline-2 (:tags '(writing whitespace quotation))
211
(let* ((row '("," #?"a\r\nnewline\r\ntest\r\n"))
212
(res (write-csv-row row :always-quote nil :newline #?"\n")))
213
(assert-equal #?"\",\",\"a\r\nnewline\r\ntest\r\n\"\n"
216
(define-test cause-error (:tags '(parsing errors))
217
(let ((data (read-csv *test-csv-data-waiting-next-error*)))
220
(define-test chars-in-test (:tags '(utils parsing))
221
(assert-true (cl-csv::chars-in "a" "abcdef"))
222
(assert-false (cl-csv::chars-in "qu" "abcdef"))
223
(assert-true (cl-csv::chars-in "qu" "asdfqasdf"))
224
(assert-true (cl-csv::chars-in "qu" "asdfuasdf"))
225
(assert-true (cl-csv::chars-in (list "q" "u") "asdfuasdf"))
226
(assert-true (cl-csv::chars-in (list #\q #\u) "asdfuasdf"))
227
(assert-true (cl-csv::chars-in (list "q" #\u) "asdfqasdf")))
229
(define-test iterate-clauses (:tags '(utils iterate))
231
(for (a b c) in-csv "1,2,3
233
(assert-equal (if (first-time-p) "1" "4") a)
234
(assert-equal (if (first-time-p) "2" "5") b)
235
(assert-equal (if (first-time-p) "3" "6") c)
237
(finally (assert-equal 1 i)))
239
;; test SKIPPING-HEADER option
241
(for (a b c) in-csv "1,2,3
242
4,5,6" SKIPPING-HEADER T)
247
(finally (assert-equal 0 i)))
251
(for (a b c) in-csv "1|2|3
252
4|5|6" SKIPPING-HEADER T SEPARATOR #\|)
257
(finally (assert-equal 0 i))))
259
(define-test sampling-iterate (:tags '(parsing iterate))
261
9 (iter (for row in-csv *test-csv1*)
262
(cl-csv:sampling row)))
264
2 (iter (for row in-csv *test-csv1*)
265
(cl-csv:sampling row into sample size 2)
266
(finally (return sample))))
268
2 (read-csv-sample *test-csv1* 2))
270
3 (iter (for row in-csv *test-csv1* skipping-header t)
271
(cl-csv::sampling row size 3)))
273
9 (iter (for row in-csv *test-csv1*)
274
(cl-csv:sampling row into sample size 25)
275
(finally (return sample)))))
277
(define-test csv-signal-enabling (:tags '(signals))
282
(let ((*enable-signals* t))
283
(cl-csv:read-csv "1,2,3"))))
288
(let ((*enable-signals* nil))
289
(cl-csv:read-csv "1,2,3")))))
291
(define-test csv-filter (:tags '(signals))
294
(let ((*enable-signals* t))
295
(handler-bind ((csv-data-read
296
(lambda (c) (invoke-restart 'filter (parse-integer (cl-csv::data c))))))
297
(cl-csv:read-csv-row "1,2,3"))))
300
(let ((*enable-signals* t))
301
(handler-bind ((csv-row-read
302
(lambda (c) (invoke-restart 'filter (mapcar #'parse-integer (cl-csv::row c))))))
303
(cl-csv:read-csv-row "1,2,3")))))
305
(defun displaced-sub-string (s &key (start 0) (end (length s)))
306
(make-array (- end start)
307
:element-type (array-element-type s)
309
:displaced-index-offset start))
311
(define-test csv-continue-signals (:tags '(signals))
312
(handler-bind ((csv-parse-error #'continue))
316
(cl-csv:read-csv "1,2,3
318
3,4,5" :quote #\'))))
320
(define-test early-end-of-stream (:tags '(errors parsing))
321
(let ((line #?|"1","2|))
323
'cl-csv:csv-parse-error
324
(cl-csv:read-csv-row line)))
328
(cl-csv:read-csv-row line)))
330
(let ((line #?|"1","2|))
333
(handler-bind ((cl-csv:csv-parse-error
336
(invoke-restart 'cl-csv::finish-item))))
337
(cl-csv:read-csv-row line))))
340
(define-test read-into-buffer-until-test (:tags '(read-until))
342
(with-input-from-string (in #?"test this\r\n thing")
343
(let* ((s (make-string 80))
344
(l (cl-csv::read-into-buffer-until s in :nl #?"\r\n")))
346
(assert-equal #?"test this\r\n" (displaced-sub-string s :end l))))
348
(with-input-from-string (in #?"t\nest this\n thing")
349
(let* ((s (make-string 80))
350
(l (cl-csv::read-into-buffer-until s in :nl #\newline)))
352
(assert-equal #?"t\n" (displaced-sub-string s :end l))
353
(let ((l (cl-csv::read-into-buffer-until s in :nl #\newline)))
355
(assert-equal #?"est this\n" (displaced-sub-string s :end l)))))
357
(with-input-from-string (in #?"test this thing")
358
(let* ((s (make-string 80))
359
(l (cl-csv::read-into-buffer-until s in :nl #\newline)))
360
(assert-eql (length "test this thing") l)
361
(assert-equal "test this thing" (displaced-sub-string s :end l))
364
(with-input-from-string (in #?"test this thing")
365
(let* ((s (make-string 4))
366
(l (cl-csv::read-into-buffer-until s in :nl #\newline)))
368
(assert-equal "test" (displaced-sub-string s :end l))
369
(assert-eql 4 (cl-csv::read-into-buffer-until s in :nl #\newline))
370
(assert-eql 4 (cl-csv::read-into-buffer-until s in :nl #\newline))
371
(assert-eql 3 (cl-csv::read-into-buffer-until s in :nl #\newline))
372
(assert-error 'end-of-file (cl-csv::read-into-buffer-until s in :nl #\newline))
375
(define-test buffer-spanning-new-lines
376
(:tags '(read-until whitespace parsing))
377
(with-input-from-string (in "testRNtest")
378
(let* ((s (make-string 5))
381
(cl-csv::read-into-buffer-until s in :nl "RN"))
384
(cl-csv::read-into-buffer-until
389
(cl-csv::read-into-buffer-until s in :nl "RN"))
390
(assert-eql 4 len))))
392
(define-test buffer-spanning-new-lines2
393
(:tags '(read-until newlines whitespace parsing))
395
(with-input-from-string (in "test**tes**te**test")
396
(let* ((s (make-string 5))
398
(flet ((rebind ( &optional new-nl-idx)
400
(setf nl-idx new-nl-idx))
403
(cl-csv::read-into-buffer-until
407
(assert-eql 5 len :first s)
409
(assert-eql 1 len :second)
411
(assert-eql 5 len :third)
413
(assert-eql 4 len :third))
416
(define-test different-newlines (:tags '(read-until newlines whitespace parsing))
417
(with-input-from-string (in "a|b|c|d**1|2|3|4")
418
(let* ((cl-csv::*buffer-size* 8)
419
(rows (cl-csv:read-csv in :newline "**" :separator "|")))
420
(assert-equal 2 (length rows))
421
(assert-equal '("a" "b" "c" "d") (first rows))
422
(assert-equal '("1" "2" "3" "4") (second rows)))
424
(with-input-from-string (in "a|b|c|d**1|2|3|4")
425
(let ((rows (cl-csv:read-csv in :newline "**" :separator "|")))
426
(assert-equal 2 (length rows))
427
(assert-equal '("a" "b" "c" "d") (first rows))
428
(assert-equal '("1" "2" "3" "4") (second rows)))
430
(with-input-from-string (in "a|b|c|d*1|2|3|4")
431
(let ((rows (cl-csv:read-csv in :newline #\* :separator #\|)))
432
(assert-equal 2 (length rows))
433
(assert-equal '("a" "b" "c" "d") (first rows))
434
(assert-equal '("1" "2" "3" "4") (second rows)))
436
(with-input-from-string (in "a|b|c|d*1|2|3|4")
437
(let ((rows (cl-csv:read-csv in :newline "*" :separator "|")))
438
(assert-equal 2 (length rows))
439
(assert-equal '("a" "b" "c" "d") (first rows))
440
(assert-equal '("1" "2" "3" "4") (second rows)))