# HG changeset patch # User Steve Losh # Date 1545877477 18000 # Node ID 5a40b5b38fe3987370ba975d437e29620ef73dac # Parent 9ee4097163962f6696a567e46c108b5c1e1cfe83 Add some tests diff -r 9ee409716396 -r 5a40b5b38fe3 conserve.asd --- a/conserve.asd Tue Dec 25 23:18:43 2018 -0500 +++ b/conserve.asd Wed Dec 26 21:24:37 2018 -0500 @@ -23,7 +23,7 @@ :license "MIT/X11" - :depends-on (:conserve :1am) + :depends-on (:conserve :1am :cl-csv :fare-csv) :serial t :components ((:file "package.test") diff -r 9ee409716396 -r 5a40b5b38fe3 package.lisp --- a/package.lisp Tue Dec 25 23:18:43 2018 -0500 +++ b/package.lisp Wed Dec 26 21:24:37 2018 -0500 @@ -1,6 +1,7 @@ (defpackage :conserve (:use :cl) (:export + :*delimiter* :read-row :read-rows :write-row diff -r 9ee409716396 -r 5a40b5b38fe3 src/main.lisp --- a/src/main.lisp Tue Dec 25 23:18:43 2018 -0500 +++ b/src/main.lisp Wed Dec 26 21:24:37 2018 -0500 @@ -100,8 +100,14 @@ ;;;; API ---------------------------------------------------------------------- -(defun read-row (&optional (stream *standard-input*) (eof-error-p t) eof-value) - "Read and return a row of fields from the CSV data in `stream`. +(defun ensure-input-stream (stream-or-string) + (etypecase stream-or-string + (stream stream-or-string) + (string (make-string-input-stream stream-or-string)))) + +(defun read-row + (&optional (stream-or-string *standard-input*) (eof-error-p t) eof-value) + "Read and return a row of fields from the CSV data in `stream-or-string`. The result will be completely fresh. @@ -109,43 +115,72 @@ signaled unless `eof-error-p` is false, in which case `eof-value` is returned. " - (check-type stream stream) - (assert (input-stream-p stream) (stream) - "Stream ~S is not an input stream." stream) (check-delimiter) - (read-row% stream *delimiter* eof-error-p eof-value)) + (let ((stream (ensure-input-stream stream-or-string))) + (assert (input-stream-p stream) (stream) + "Stream ~S is not an input stream." stream) + (read-row% stream *delimiter* eof-error-p eof-value))) -(defun read-rows (&optional (stream *standard-input*)) - "Read and return all CSV rows from the CSV data in `stream`. +(defun read-rows (&optional (stream-or-string *standard-input*)) + "Read and return all CSV rows from the CSV data in `stream-or-string`. The result will be completely fresh. " - (check-type stream stream) - (assert (input-stream-p stream) (stream) - "Stream ~S is not an input stream." stream) (check-delimiter) - (loop :with delimiter = *delimiter* - :for row = (read-row% stream delimiter nil :eof) - :until (eql row :eof) - :collect row)) + (let ((stream (ensure-input-stream stream-or-string))) + (assert (input-stream-p stream) () + "Stream ~S is not an input stream." stream) + (loop :with delimiter = *delimiter* + :for row = (read-row% stream delimiter nil :eof) + :until (eql row :eof) + :collect row))) + + +(defmacro with-output-to-stream-or-string + ((symbol &optional (stream-or-null symbol)) &body body) + "Bind `symbol` to an output stream, run `body`, and return appropriately. + + If `stream-or-null` is a stream, `symbol` will be bound to it and nothing will + be returned. + + If `stream-or-null` is `nil`, `symbol` will be bound to a string output stream + and the resulting string will be returned. + + " + (let ((want-string (gensym "WANT-STRING"))) + `(let* ((,symbol ,stream-or-null) + (,want-string (null ,symbol)) + (,symbol (or ,symbol (make-string-output-stream)))) + ,@body + (if ,want-string + (get-output-stream-string ,symbol) + (values))))) (defun write-row (row &optional (stream *standard-output*)) - "Write `row` to `stream` as CSV data." - (check-type stream stream) - (assert (output-stream-p stream) (stream) - "Stream ~S is not an output stream." stream) + "Write `row` to `stream` as CSV data. + + If `stream` is `nil`, the data will be returned as a fresh string instead. + + " (check-delimiter) - (write-row% row stream *delimiter*) - (values)) + (check-type stream (or null stream)) + (with-output-to-stream-or-string (stream) + (assert (output-stream-p stream) (stream) + "Stream ~S is not an output stream." stream) + (write-row% row stream *delimiter*))) (defun write-rows (rows &optional (stream *standard-output*)) - "Write `rows` to `stream` as CSV data." - (check-type stream stream) - (assert (output-stream-p stream) (stream) - "Stream ~S is not an output stream." stream) + "Write `rows` to `stream` as CSV data. + + If `stream` is `nil`, the data will be returned as a fresh string instead. + + " (check-delimiter) - (loop :with delimiter = *delimiter* - :for row :in rows - :do (write-row% row stream delimiter))) - + (check-type stream (or null stream)) + (with-output-to-stream-or-string (stream) + (assert (output-stream-p stream) (stream) + "Stream ~S is not an output stream." stream) + (loop :with delimiter = *delimiter* + :for row :in rows + :do (write-row% row stream delimiter)))) diff -r 9ee409716396 -r 5a40b5b38fe3 test/bench.lisp --- a/test/bench.lisp Tue Dec 25 23:18:43 2018 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,165 +0,0 @@ -(in-package :conserve) - -(defparameter *field-length* 50) -(defparameter *row-length* 50) -(defparameter *data-rows* 1000) -(defparameter *data-repetitions* 10) - -(defun random-char (string) - (aref string (random (length string)))) - -(defun random-field () - (with-output-to-string (s) - (dotimes (i (random *field-length*)) - (write-char (random-char (concatenate 'string (string #\newline) - " abcdefghijklmnop,\"")) - s)))) - -(defun random-row () - (loop :repeat (1+ (random *row-length*)) :collect (random-field))) - -(defun random-data () - (loop :repeat *data-rows* :collect (random-row))) - -(defun bench-this (data) - (let* ((str (make-string-input-stream - (with-output-to-string (s) - (time (write-rows data s))))) - (result (time (read-rows str)))) - (equal data result))) - -(defun bench-cl-csv (data) - (let* ((str (make-string-input-stream - (with-output-to-string (s) - (time (cl-csv:write-csv data :stream s))))) - (result (time (cl-csv:read-csv str)))) - (equal data result))) - -(defun bench () - (let ((data (random-data))) - (write-line "MINE") - (bench-this data) - (write-line "cl-csv") - (bench-cl-csv data)) - ) - - -(defvar *data* nil) - -(defun generate-large-data () - (setf *data* (random-data))) - -(defun write-file-this () - (with-open-file (s "test/data/large-this.csv" - :direction :output - :if-exists :supersede) - (time - (loop :repeat *data-repetitions* :do - (write-rows *data* s))))) - -(defun write-file-cl-csv () - (with-open-file (s "test/data/large-cl-csv.csv" - :direction :output - :if-exists :supersede) - (time (loop :repeat *data-repetitions* :do - (cl-csv:write-csv *data* - :stream s - :newline (string #\newline)))))) -(defun write-file-fare () - (with-open-file (s "test/data/large-fare.csv" - :direction :output - :if-exists :supersede) - (fare-csv:with-rfc4180-csv-syntax () - (time (loop :repeat *data-repetitions* :do - (fare-csv:write-csv-lines *data* s)))))) - -(defun bench-write-file () - (write-line "Generating data.") - (time (generate-large-data)) - - (write-line "Benchmarking this (writing).") - #+sbcl (sb-ext:gc :full t) - (write-file-this) - - ;; (write-line "Benchmarking cl-csv (writing).") - ;; #+sbcl (sb-ext:gc :full t) - ;; (write-file-cl-csv) - - (write-line "Benchmarking fare-csv (writing).") - #+sbcl (sb-ext:gc :full t) - (write-file-fare)) - - -(defun read-file-this () - (with-open-file (s "test/data/large-this.csv") - (time (loop - :with data = *data* - :for row = (read-row s nil :eof) - :for expected-row = (progn (when (null data) - (setf data *data*)) - (pop data)) - :until (eql :eof row) - :summing 1 - :do (assert (equal expected-row row)))))) - -(defun read-file-cl-csv () - (with-open-file (s "test/data/large-cl-csv.csv") - (let ((result 0)) - (time (handler-case - (loop - :with data = *data* - :for row = (cl-csv:read-csv-row s - :newline (string #\newline) - :trim-outer-whitespace nil) - :for expected-row = (progn (when (null data) - (setf data *data*)) - (pop data)) - :do (incf result) (assert (equal expected-row row))) - (end-of-file () result)))))) - -;; There are a couple of problems with fare-csv that make it annoying to work -;; with: -;; -;; fare-csv:read-csv-line can't tell you the difference between '("") and eof so -;; we have to check for it manually to know when we're done. This is only -;; a problem when we're reading line by line. -;; -;; fare-csv also can't roundtrip '(""). CSV itself is incapable of -;; differentiating '() and '("") unless you force quoting, but fare-csv chooses -;; '() over '(""). Why would you sell out the marginally-useful case (a 1-col -;; CSV) in favor of the utterly useless case (a 0-col CSV)? -(defun read-file-fare () - (with-open-file (s "test/data/large-fare.csv") - (fare-csv:with-rfc4180-csv-syntax () - (time (loop - :with data = *data* - :for eof = (peek-char nil s nil :eof) - :for row = (fare-csv:read-csv-line s) - :for expected-row = (progn (when (null data) - (setf data *data*)) - (pop data)) - :until (eql :eof eof) - :summing 1 - :do (if (equal expected-row '("")) - (assert (equal nil row)) - (assert (equal expected-row row)))))))) - -(defun bench-read-file () - (write-line "Benchmarking this (reading).") - #+sbcl (sb-ext:gc :full t) - (format t "Read ~D rows.~2%" (read-file-this)) - - ;; (write-line "Benchmarking cl-csv (reading).") - ;; #+sbcl (sb-ext:gc :full t) - ;; (format t "Read ~D rows.~2%" (read-file-cl-csv)) - - (write-line "Benchmarking fare-csv (reading).") - #+sbcl (sb-ext:gc :full t) - (format t "Read ~D rows.~2%" (read-file-fare)) - - (values)) - -(defun bench-file () - (bench-write-file) - (bench-read-file)) - diff -r 9ee409716396 -r 5a40b5b38fe3 test/tests.lisp --- a/test/tests.lisp Tue Dec 25 23:18:43 2018 -0500 +++ b/test/tests.lisp Wed Dec 26 21:24:37 2018 -0500 @@ -1,163 +1,338 @@ -(in-package :trivial-csv/test) +(in-package :conserve/test) ;;;; Utils -------------------------------------------------------------------- +(defun symb (&rest args) + (intern (apply #'concatenate 'string (mapcar #'princ-to-string args)))) + (defmacro define-test (name &body body) `(test ,(symb 'test- name) - (let ((*package* ,*package*) - (r #(255 0 0)) - (g #(0 255 0)) - (b #(0 0 255)) - (k #(0 0 0)) - (w #(255 255 255))) - (declare (ignorable r g b k w)) + (let ((*package* ,*package*)) ,@body))) -(defun make-image-array (initial-data) - (make-array (list (length (elt initial-data 0)) - (length initial-data)) - :initial-contents (transpose initial-data))) - -(defmacro check (form expected-data expected-format expected-bit-depth) - (with-gensyms (data format bit-depth) - `(multiple-value-bind (,data ,format ,bit-depth) ,form - (is (equalp (make-image-array ,expected-data) ,data)) - (is (eql ,expected-format ,format)) - (is (= ,expected-bit-depth ,bit-depth))))) +(defmacro define-csv-test (name csv data &optional bindings) + `(define-test ,name + (let ,bindings + (let ((data ,data) + (csv (format nil ,csv))) + (is (equalp data (conserve:read-rows csv))) + (is (equalp csv (conserve:write-rows data nil))))))) (defun run-tests () (1am:run)) -;;;; Tests -------------------------------------------------------------------- -(define-test 1x1-black-ascii-pbm - (check (trivial-ppm:read-from-file "test/data/1x1-black.ascii.pbm") - '((0)) - :pbm - 1)) +;;;; Compatibility Layer ------------------------------------------------------ +(defun cl-csv-write (data stream) + (cl-csv:write-csv data :stream stream :newline (string #\newline))) + +(defun cl-csv-write-string (data) + (cl-csv:write-csv data :stream nil :newline (string #\newline))) -(define-test 1x1-black-ascii-pgm - (check (trivial-ppm:read-from-file "test/data/1x1-black.ascii.pgm") - '((0)) - :pgm - 255)) +(defun cl-csv-read (stream) + (cl-csv:read-csv stream + :newline (string #\newline) + :trim-outer-whitespace nil)) + +(defun cl-csv-read-string (string) + (cl-csv:read-csv string + :newline (string #\newline) + :trim-outer-whitespace nil)) + -(define-test 1x1-black-ascii-ppm - (check (trivial-ppm:read-from-file "test/data/1x1-black.ascii.ppm") - `((,k)) - :ppm - 255)) +(defun fare-csv-write (data stream) + (fare-csv:with-rfc4180-csv-syntax () + (fare-csv:write-csv-lines data stream))) + +(defun fare-csv-write-string (data) + (with-output-to-string (s) + (fare-csv-write data s))) -(define-test 4x3-rgb.ascii-ppm - (check (trivial-ppm:read-from-file "test/data/4x3-rgb.ascii.ppm") - `((,r ,r ,r ,r) - (,g ,g ,g ,g) - (,b ,b ,b ,b)) - :ppm - 255)) +(defun fix-fare-csv-empty-lines (rows) + (mapcar (lambda (row) + (if (null row) + (list "") + row)) + rows)) + +(defun fare-csv-read (stream) + (fare-csv:with-rfc4180-csv-syntax () + (fare-csv:read-csv-stream stream))) + +(defun fare-csv-read-string (string) + (fare-csv-read (make-string-input-stream string))) -(define-test 1x1-black-binary-pbm - (check (trivial-ppm:read-from-file "test/data/1x1-black.binary.pbm") - '((0)) - :pbm - 1)) +;;;; Basic Tests -------------------------------------------------------------- +(define-csv-test simple-csv + "a,b,c~@ + d,e,f~%" + '(("a" "b" "c") + ("d" "e" "f"))) + +(define-csv-test empty + "" + '()) -(define-test 1x1-black-binary-pgm - (check (trivial-ppm:read-from-file "test/data/1x1-black.binary.pgm") - '((0)) - :pgm - 255)) +(define-csv-test blank-lines + "a~%~%c~%" + '(("a") + ("") + ("c"))) + +(define-csv-test empty-fields + "a,,b~@ + ,a,b~@ + a,b,~@ + ,,a,,~%" + '(("a" "" "b") + ("" "a" "b") + ("a" "b" "") + ("" "" "a" "" ""))) + +(define-csv-test spaces + "a b, c,d ~%" + '(("a b" + " c" + "d "))) -(define-test 1x1-black-binary-ppm - (check (trivial-ppm:read-from-file "test/data/1x1-black.binary.ppm") - `((,k)) - :ppm - 255)) +(define-csv-test basic-quoting + "a,b,c~@ + \"a,b\",c~%" + '(("a" "b" "c") + ("a,b" "c"))) + +(define-csv-test quote-escaping + "foo,\"x\"\"y\",baz~@ + \"\"\"start\",\"end\"\"\"~%" + '(("foo" "x\"y" "baz") + ("\"start" "end\""))) + +(define-csv-test quoted-newlines + "a,\"foo~%bar\",b~%" + `(("a" ,(format nil "foo~%bar") "b"))) -(define-test 4x3-rgb.binary-ppm - (check (trivial-ppm:read-from-file "test/data/4x3-rgb.binary.ppm") - `((,r ,r ,r ,r) - (,g ,g ,g ,g) - (,b ,b ,b ,b)) - :ppm - 255)) +(define-csv-test other-delimiter + "a_b,c_\"foo_bar\"~%" + '(("a" + "b,c" + "foo_bar")) + ((conserve:*delimiter* #\_))) + +(define-test no-trailing-newline + (is (equal '(("a" "b")) (conserve:read-rows "a,b")))) + +(define-test read-single-row + (is (equal '("a" "b") + (conserve:read-row (format nil "a,b~%c,d") nil nil)))) + +(define-test read-row-eof-value + (is (equal :eof (conserve:read-row "" nil :eof)))) + +(define-test read-row-eof-error + (signals end-of-file (conserve:read-row "" t))) -;;;; Fuzzer ------------------------------------------------------------------- -(defparameter *fuzz-test-count* 500) +;;;; Fuzzing ------------------------------------------------------------------ +(defparameter *string-characters* + (format nil "abc\", ~%")) + +(defun random-char (&optional (string *string-characters*)) + (aref string (random (length string)))) + +(defun random-field (characters) + (with-output-to-string (s) + (dotimes (i characters) + (write-char (random-char) s)))) + +(defun random-row (fields characters) + (loop :repeat fields :collect (random-field (random characters)))) + +(defun random-data (rows fields characters) + (loop :repeat rows :collect (random-row (1+ (random fields)) + characters))) -(defun random-bit () - (random 2)) +(define-test fuzz-round-trip + (dotimes (i 500) + (let* ((data (random-data 100 10 15)) + (output (conserve:write-rows data nil)) + (round-tripped (conserve:read-rows output))) + (is (equal data round-tripped))))) -(defun random-gray () - (random 256)) +(define-test fuzz-against-cl-csv + (dotimes (i 100) + (let* ((data (random-data 10 10 10)) + (conserve-out (conserve:write-rows data nil)) + (cl-csv-out (cl-csv-write-string data)) + (conserve->cl-csv (cl-csv-read-string conserve-out)) + (cl-csv->conserve (conserve:read-rows cl-csv-out))) + (is (= (length data) (length conserve->cl-csv))) + (is (= (length data) (length cl-csv->conserve))) + (is (equal data conserve->cl-csv)) + (is (equal data cl-csv->conserve))))) -(defun random-color () - (make-array 3 :initial-contents (list (random 256) - (random 256) - (random 256)))) +(define-test fuzz-against-fare-csv + (dotimes (i 100) + (let* ((data (random-data 10 10 10)) + (conserve-out (conserve:write-rows data nil)) + (fare-csv-out (fare-csv-write-string data)) + (conserve->fare-csv (fix-fare-csv-empty-lines (fare-csv-read-string conserve-out))) + (fare-csv->conserve (conserve:read-rows fare-csv-out))) + (is (= (length data) (length conserve->fare-csv))) + (is (= (length data) (length fare-csv->conserve))) + (is (equal data conserve->fare-csv)) + (is (equal data fare-csv->conserve))))) + -(defun random-format () - (ecase (random 3) - (0 :pbm) - (1 :pgm) - (2 :ppm))) +;;;; Benchmarking ------------------------------------------------------------- +(defun round-trip/conserve (data) + (conserve:read-rows (conserve:write-rows data nil))) + +(defun round-trip/cl-csv (data) + (cl-csv-read-string (cl-csv-write-string data))) + +(defun round-trip/fare-csv (data) + (fare-csv-read-string (fare-csv-write-string data))) + + +(defun benchmark-round-trip/conserve (data) + (format t "~%Timing in-memory round trip for Conserve:~%") + (let ((result (time (round-trip/conserve data)))) + (assert (equal data result)))) -(defun make-random-array () - (let* ((width (1+ (random 50))) - (height (1+ (random 50))) - (format (random-format)) - (data (make-array (list width height)))) - (dotimes (x width) - (dotimes (y height) - (setf (aref data x y) - (ecase format - (:pbm (random-bit)) - (:pgm (random-gray)) - (:ppm (random-color)))))) - (values data format))) +(defun benchmark-round-trip/fare-csv (data) + (format t "~%Timing in-memory round trip for fare-csv:~%") + (let ((result (time (round-trip/fare-csv data)))) + (assert (equal data (fix-fare-csv-empty-lines result))))) + +(defun benchmark-round-trip/cl-csv (data) + (format t "~%Timing in-memory round trip for cl-csv:~%") + (let ((result (time (round-trip/cl-csv data)))) + (assert (equal data result)))) + + +(defun benchmark-round-trip () + (let ((data (random-data 1000 100 500))) + (benchmark-round-trip/conserve data) + (benchmark-round-trip/fare-csv data) + (benchmark-round-trip/cl-csv data))) -(define-test fuzz-ascii - (dotimes (i *fuzz-test-count*) - (multiple-value-bind (original original-format) (make-random-array) - (write-to-file "test/data/fuzz.ascii" original - :if-exists :supersede - :format original-format - :encoding :ascii) - (multiple-value-bind (new new-format) - (read-from-file "test/data/fuzz.ascii") - (is (eql original-format new-format)) - (is (equalp original new)))))) +(defun write-file/conserve (data repetitions) + (with-open-file (s "test/data/large-conserve.csv" + :direction :output + :if-exists :supersede) + (loop :repeat repetitions :do (conserve:write-rows data s)))) + +(defun write-file/cl-csv (data repetitions) + (with-open-file (s "test/data/large-cl-csv.csv" + :direction :output + :if-exists :supersede) + (loop :repeat repetitions :do + (cl-csv:write-csv data + :stream s + :newline (string #\newline))))) + +(defun write-file/fare-csv (data repetitions) + (with-open-file (s "test/data/large-fare-csv.csv" + :direction :output + :if-exists :supersede) + (fare-csv:with-rfc4180-csv-syntax () + (loop :repeat repetitions :do (fare-csv:write-csv-lines data s))))) + +(defvar *data* nil) +(defparameter *verify-large-file-reads* t) +(defparameter *repetitions* 30) +(defparameter *rows* 1000) +(defparameter *fields* 200) +(defparameter *characters* 80) +(defparameter *size* (* *repetitions* *rows* *fields* *characters* 3)) + +(defun benchmark-large-file/write () + (format t "~%Generating data~%") + (let ((data (time (random-data *rows* *fields* *characters*)))) + (setf *data* data) + + (format t "~%Timing large file write for Conserve:~%") + #+sbcl (sb-ext:gc :full t) + (time (write-file/conserve data *repetitions*)) + + (format t "~%Timing large file write for fare-csv:~%") + #+sbcl (sb-ext:gc :full t) + (time (write-file/fare-csv data *repetitions*)) + + (format t "~%Timing large file write for cl-csv:~%") + #+sbcl (sb-ext:gc :full t) + (time (write-file/cl-csv data *repetitions*)) + + (values))) + -(define-test fuzz-binary - (dotimes (i *fuzz-test-count*) - (multiple-value-bind (original original-format) (make-random-array) - (write-to-file "test/data/fuzz.binary" original - :if-exists :supersede - :format original-format - :encoding :binary) - (multiple-value-bind (new new-format) - (read-from-file "test/data/fuzz.binary") - (is (eql original-format new-format)) - (is (equalp original new)))))) +(defun read-file/conserve () + (with-open-file (s "test/data/large-conserve.csv") + (if *verify-large-file-reads* + (loop + :for original :in *data* + :for row = (conserve:read-row s nil :eof) + :until (eql :eof row) + :do (assert (equal original row))) + (loop + :for row = (conserve:read-row s nil :eof) + :until (eql :eof row))))) + +(defun read-file/fare-csv () + (with-open-file (s "test/data/large-fare-csv.csv") + (fare-csv:with-rfc4180-csv-syntax () + (if *verify-large-file-reads* + (loop + :for original :in *data* + :until (eql :eof (peek-char nil s nil :eof)) + :do (assert (equal original (or (fare-csv:read-csv-line s) '(""))))) + (loop + :until (eql :eof (peek-char nil s nil :eof)) + :do (fare-csv:read-csv-line s)))))) -(define-test fuzz-convert - (dotimes (i *fuzz-test-count*) - (multiple-value-bind (original original-format) (make-random-array) - (write-to-file "test/data/fuzz.convert.in" original - :if-exists :supersede - :format original-format - :encoding :ascii) - (uiop:run-program (list "convert" "-format" (ecase original-format - (:ppm "ppm") - (:pgm "pgm") - (:pbm "pbm")) - "test/data/fuzz.convert.in" - "test/data/fuzz.convert.out")) - (multiple-value-bind (new new-format) - (read-from-file "test/data/fuzz.convert.out") - (is (eql original-format new-format)) - (is (equalp original new)))))) +(defun read-file/cl-csv () + (with-open-file (s "test/data/large-cl-csv.csv") + (handler-case + (if *verify-large-file-reads* + (loop + :for original :in *data* + :for row = (cl-csv:read-csv-row s + :newline (string #\newline) + :trim-outer-whitespace nil) + :do (assert (equal original row))) + (loop + (cl-csv:read-csv-row s + :newline (string #\newline) + :trim-outer-whitespace nil))) + (end-of-file () nil)))) + +(defun benchmark-large-file/read () + ;; circular list to make iterating easier in the readers + (setf (cdr (last *data*)) *data*) + + (format t "~%Timing large file read for Conserve:~%") + #+sbcl (sb-ext:gc :full t) + (time (read-file/conserve)) + + (format t "~%Timing large file read for fare-csv:~%") + #+sbcl (sb-ext:gc :full t) + (time (read-file/fare-csv)) + + (format t "~%Timing large file read for cl-csv:~%") + #+sbcl (sb-ext:gc :full t) + (time (read-file/cl-csv)) + + (values)) + +(defun benchmark-large-file () + (unless (y-or-n-p + "This benchmark could require over ~:Dmb of hard disk space.~@ + Do you want to proceed?" + (truncate *size* (* 1024 1024))) + (return-from benchmark-large-file)) + (benchmark-large-file/write) + (benchmark-large-file/read)) +