1 // Copyright (C) 2014-2021 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 #include <config.h>
8 #include <util/csv_file.h>
9 #include <boost/scoped_ptr.hpp>
10 #include <gtest/gtest.h>
11 #include <fstream>
12 #include <sstream>
13 #include <string>
14 
15 namespace {
16 
17 using namespace isc::util;
18 
19 // This test exercises escaping and unescaping of characters.
TEST(CSVRowTest,escapeUnescape)20 TEST(CSVRowTest, escapeUnescape) {
21     std::string orig(",FO^O\\,B?,AR,");
22 
23     // We'll escape commas, question marks, and carets.
24     std::string escaped = CSVRow::escapeCharacters(orig, ",?^");
25     EXPECT_EQ ("&#x2cFO&#x5eO\\&#x2cB&#x3f&#x2cAR&#x2c", escaped);
26 
27     // Now make sure we can unescape it correctly.
28     std::string unescaped = CSVRow::unescapeCharacters(escaped);
29     EXPECT_EQ (orig, unescaped);
30 
31     // Make sure that an incident occurrence of just the escape tag
32     // is left intact.
33     orig = ("no&#xescape");
34     escaped = CSVRow::escapeCharacters(orig, ",");
35     unescaped = CSVRow::unescapeCharacters(orig);
36     EXPECT_EQ (orig, unescaped);
37 
38     // Make sure that an incidental occurrence of a valid
39     // escape tag sequence left intact.
40     orig = ("no&#x2cescape");
41     escaped = CSVRow::escapeCharacters(orig, ",");
42     unescaped = CSVRow::unescapeCharacters(escaped);
43     EXPECT_EQ (orig, unescaped);
44 }
45 
46 // This test checks that the single data row is parsed.
TEST(CSVRow,parse)47 TEST(CSVRow, parse) {
48     CSVRow row0("foo,bar,foo-bar");
49     ASSERT_EQ(3, row0.getValuesCount());
50     EXPECT_EQ("foo", row0.readAt(0));
51     EXPECT_EQ("bar", row0.readAt(1));
52     EXPECT_EQ("foo-bar", row0.readAt(2));
53 
54     row0.parse("bar,,foo-bar");
55     ASSERT_EQ(3, row0.getValuesCount());
56     EXPECT_EQ("bar", row0.readAt(0));
57     EXPECT_TRUE(row0.readAt(1).empty());
58     EXPECT_EQ("foo-bar", row0.readAt(2));
59 
60     row0.parse("bar,foo&#x2c-bar");
61     ASSERT_EQ(2, row0.getValuesCount());
62     EXPECT_EQ("bar", row0.readAt(0));
63     // Read the second column as-is and escaped
64     EXPECT_EQ("foo&#x2c-bar", row0.readAt(1));
65     EXPECT_EQ("foo,-bar", row0.readAtEscaped(1));
66 
67     CSVRow row1("foo-bar|foo|bar|", '|');
68     ASSERT_EQ(4, row1.getValuesCount());
69     EXPECT_EQ("foo-bar", row1.readAt(0));
70     EXPECT_EQ("foo", row1.readAt(1));
71     EXPECT_EQ("bar", row1.readAt(2));
72     EXPECT_TRUE(row1.readAt(3).empty());
73 
74     row1.parse("");
75     ASSERT_EQ(1, row1.getValuesCount());
76     EXPECT_TRUE(row1.readAt(0).empty());
77 }
78 
79 // Verifies that empty columns are handled correctly.
TEST(CSVRow,emptyColumns)80 TEST(CSVRow, emptyColumns) {
81     // Should get four columns, all blank except column the second one.
82     CSVRow row(",one,,");
83     ASSERT_EQ(4, row.getValuesCount());
84     EXPECT_EQ("", row.readAt(0));
85     EXPECT_EQ("one", row.readAt(1));
86     EXPECT_EQ("", row.readAt(2));
87     EXPECT_EQ("", row.readAt(3));
88 }
89 
90 // Verifies that empty columns are handled correctly.
TEST(CSVRow,oneColumn)91 TEST(CSVRow, oneColumn) {
92     // Should get one column
93     CSVRow row("zero");
94     ASSERT_EQ(1, row.getValuesCount());
95     EXPECT_EQ("zero", row.readAt(0));
96 }
97 
98 // This test checks that the text representation of the CSV row
99 // is created correctly.
TEST(CSVRow,render)100 TEST(CSVRow, render) {
101     CSVRow row0(3);
102     row0.writeAt(0, "foo");
103     row0.writeAt(1, "foo-bar");
104     row0.writeAt(2, "bar");
105 
106     std::string text;
107     ASSERT_NO_THROW(text = row0.render());
108     EXPECT_EQ(text, "foo,foo-bar,bar");
109 
110     CSVRow row1(4, ';');
111     row1.writeAt(0, "foo");
112     row1.writeAt(2, "bar");
113     row1.writeAt(3, 10);
114 
115     ASSERT_NO_THROW(text = row1.render());
116     EXPECT_EQ(text, "foo;;bar;10");
117 
118     CSVRow row2(0);
119     ASSERT_NO_THROW(text = row2.render());
120     EXPECT_TRUE(text.empty());
121 }
122 
123 // This test checks that the data values can be set for the CSV row.
TEST(CSVRow,writeAt)124 TEST(CSVRow, writeAt) {
125     CSVRow row(4);
126     row.writeAt(0, 10);
127     row.writeAt(1, "foo");
128     row.writeAt(2, "bar");
129     row.writeAtEscaped(3, "bar,one,two");
130 
131     EXPECT_EQ("10", row.readAt(0));
132     EXPECT_EQ("foo", row.readAt(1));
133     EXPECT_EQ("bar", row.readAt(2));
134     // Read third column as-is and unescaped
135     EXPECT_EQ("bar&#x2cone&#x2ctwo", row.readAt(3));
136     EXPECT_EQ("bar,one,two", row.readAtEscaped(3));
137 
138     EXPECT_THROW(row.writeAt(4, 20), CSVFileError);
139     EXPECT_THROW(row.writeAt(4, "foo"), CSVFileError);
140 }
141 
142 // Checks whether writeAt() and append() can be mixed together.
TEST(CSVRow,append)143 TEST(CSVRow, append) {
144     CSVRow row(3);
145 
146     EXPECT_EQ(3, row.getValuesCount());
147 
148     row.writeAt(0, "alpha");
149     ASSERT_NO_THROW(row.append("delta"));
150     EXPECT_EQ(4, row.getValuesCount());
151     row.writeAt(1, "beta");
152     row.writeAt(2, "gamma");
153     ASSERT_NO_THROW(row.append("epsilon"));
154     EXPECT_EQ(5, row.getValuesCount());
155 
156     std::string text;
157     ASSERT_NO_THROW(text = row.render());
158     EXPECT_EQ("alpha,beta,gamma,delta,epsilon", text);
159 }
160 
161 // This test checks that a row can be trimmed of
162 // a given number of elements
TEST(CSVRow,trim)163 TEST(CSVRow, trim) {
164     CSVRow row("zero,one,two,three,four");
165     ASSERT_EQ(5, row.getValuesCount());
166     EXPECT_EQ("zero", row.readAt(0));
167     EXPECT_EQ("one", row.readAt(1));
168     EXPECT_EQ("two", row.readAt(2));
169     EXPECT_EQ("three", row.readAt(3));
170     EXPECT_EQ("four", row.readAt(4));
171 
172     ASSERT_THROW(row.trim(10), CSVFileError);
173 
174     // Verify that we can erase just one
175     ASSERT_NO_THROW(row.trim(1));
176     ASSERT_EQ(4, row.getValuesCount());
177     EXPECT_EQ("zero", row.readAt(0));
178     EXPECT_EQ("one", row.readAt(1));
179     EXPECT_EQ("two", row.readAt(2));
180     EXPECT_EQ("three", row.readAt(3));
181 
182     // Verify we can trim more than one
183     ASSERT_NO_THROW(row.trim(2));
184     ASSERT_EQ(2, row.getValuesCount());
185     EXPECT_EQ("zero", row.readAt(0));
186     EXPECT_EQ("one", row.readAt(1));
187 }
188 
189 /// @brief Test fixture class for testing operations on CSV file.
190 ///
191 /// It implements basic operations on files, such as reading writing
192 /// file removal and checking presence of the file. This is used by
193 /// unit tests to verify correctness of the file created by the
194 /// CSVFile class.
195 class CSVFileTest : public ::testing::Test {
196 public:
197 
198     /// @brief Constructor.
199     ///
200     /// Sets the path to the CSV file used throughout the tests.
201     /// The name of the file is test.csv and it is located in the
202     /// current build folder.
203     ///
204     /// It also deletes any dangling files after previous tests.
205     CSVFileTest();
206 
207     /// @brief Destructor.
208     ///
209     /// Deletes the test CSV file if any.
210     virtual ~CSVFileTest();
211 
212     /// @brief Prepends the absolute path to the file specified
213     /// as an argument.
214     ///
215     /// @param filename Name of the file.
216     /// @return Absolute path to the test file.
217     static std::string absolutePath(const std::string& filename);
218 
219     /// @brief Check if test file exists on disk.
220     bool exists() const;
221 
222     /// @brief Reads whole CSV file.
223     ///
224     /// @return Contents of the file.
225     std::string readFile() const;
226 
227     /// @brief Removes existing file (if any).
228     int removeFile() const;
229 
230     /// @brief Creates file with contents.
231     ///
232     /// @param contents Contents of the file.
233     void writeFile(const std::string& contents) const;
234 
235     /// @brief Absolute path to the file used in the tests.
236     std::string testfile_;
237 
238 };
239 
CSVFileTest()240 CSVFileTest::CSVFileTest()
241     : testfile_(absolutePath("test.csv")) {
242     static_cast<void>(removeFile());
243 }
244 
~CSVFileTest()245 CSVFileTest::~CSVFileTest() {
246     static_cast<void>(removeFile());
247 }
248 
249 std::string
absolutePath(const std::string & filename)250 CSVFileTest::absolutePath(const std::string& filename) {
251     std::ostringstream s;
252     s << TEST_DATA_BUILDDIR << "/" << filename;
253     return (s.str());
254 }
255 
256 bool
exists() const257 CSVFileTest::exists() const {
258     std::ifstream fs(testfile_.c_str());
259     bool ok = fs.good();
260     fs.close();
261     return (ok);
262 }
263 
264 std::string
readFile() const265 CSVFileTest::readFile() const {
266     std::ifstream fs(testfile_.c_str());
267     if (!fs.is_open()) {
268         return ("");
269     }
270     std::string contents((std::istreambuf_iterator<char>(fs)),
271                          std::istreambuf_iterator<char>());
272     fs.close();
273     return (contents);
274 }
275 
276 int
removeFile() const277 CSVFileTest::removeFile() const {
278     return (remove(testfile_.c_str()));
279 }
280 
281 void
writeFile(const std::string & contents) const282 CSVFileTest::writeFile(const std::string& contents) const {
283     std::ofstream fs(testfile_.c_str(), std::ofstream::out);
284     if (fs.is_open()) {
285         fs << contents;
286         fs.close();
287     }
288 }
289 
290 // This test checks that the function which is used to add columns of the
291 // CSV file works as expected.
TEST_F(CSVFileTest,addColumn)292 TEST_F(CSVFileTest, addColumn) {
293     boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
294     // Add two columns.
295     ASSERT_NO_THROW(csv->addColumn("animal"));
296     ASSERT_NO_THROW(csv->addColumn("color"));
297     // Make sure we can't add duplicates.
298     EXPECT_THROW(csv->addColumn("animal"), CSVFileError);
299     EXPECT_THROW(csv->addColumn("color"), CSVFileError);
300     // But we should still be able to add unique columns.
301     EXPECT_NO_THROW(csv->addColumn("age"));
302     EXPECT_NO_THROW(csv->addColumn("comments"));
303     // Assert that the file is opened, because the rest of the test relies
304     // on this.
305     ASSERT_NO_THROW(csv->recreate());
306     ASSERT_TRUE(exists());
307 
308     // Make sure we can't add columns (even unique) when the file is open.
309     ASSERT_THROW(csv->addColumn("zoo"), CSVFileError);
310     // Close the file.
311     ASSERT_NO_THROW(csv->close());
312     // And check that now it is possible to add the column.
313     EXPECT_NO_THROW(csv->addColumn("zoo"));
314 }
315 
316 // This test checks that the appropriate file name is initialized.
TEST_F(CSVFileTest,getFilename)317 TEST_F(CSVFileTest, getFilename) {
318     CSVFile csv(testfile_);
319     EXPECT_EQ(testfile_, csv.getFilename());
320 }
321 
322 // This test checks that the file can be opened,  its whole content is
323 // parsed correctly and data may be appended. It also checks that empty
324 // row is returned when EOF is reached.
TEST_F(CSVFileTest,openReadAllWrite)325 TEST_F(CSVFileTest, openReadAllWrite) {
326     // Create a new CSV file that contains a header and two data rows.
327     writeFile("animal,age,color\n"
328               "cat,10,white\n"
329               "lion,15,yellow\n");
330 
331     // Open this file and check that the header is parsed.
332     boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
333     ASSERT_NO_THROW(csv->open());
334     ASSERT_EQ(3, csv->getColumnCount());
335     EXPECT_EQ("animal", csv->getColumnName(0));
336     EXPECT_EQ("age", csv->getColumnName(1));
337     EXPECT_EQ("color", csv->getColumnName(2));
338 
339     // Read first row.
340     CSVRow row;
341     ASSERT_TRUE(csv->next(row));
342     ASSERT_EQ(3, row.getValuesCount());
343     EXPECT_EQ("cat", row.readAt(0));
344     EXPECT_EQ("10", row.readAt(1));
345     EXPECT_EQ("white", row.readAt(2));
346 
347     // Read second row.
348     ASSERT_TRUE(csv->next(row));
349     ASSERT_EQ(3, row.getValuesCount());
350     EXPECT_EQ("lion", row.readAt(0));
351     EXPECT_EQ("15", row.readAt(1));
352     EXPECT_EQ("yellow", row.readAt(2));
353 
354     // There is no 3rd row, so the empty one should be returned.
355     ASSERT_TRUE(csv->next(row));
356     EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
357 
358     // It should be fine to read again, but again empty row should be returned.
359     ASSERT_TRUE(csv->next(row));
360     EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
361 
362     // Now, let's try to append something to this file.
363     CSVRow row_write(3);
364     row_write.writeAt(0, "dog");
365     row_write.writeAt(1, 2);
366     row_write.writeAt(2, "blue");
367     ASSERT_NO_THROW(csv->append(row_write));
368 
369     // Close the file.
370     ASSERT_NO_THROW(csv->flush());
371     csv->close();
372 
373     // Check the file contents are correct.
374     EXPECT_EQ("animal,age,color\n"
375               "cat,10,white\n"
376               "lion,15,yellow\n"
377               "dog,2,blue\n",
378               readFile());
379 
380     // Any attempt to read from the file or write to it should now fail.
381     EXPECT_FALSE(csv->next(row));
382     EXPECT_THROW(csv->append(row_write), CSVFileError);
383 
384     CSVRow row_write2(3);
385     row_write2.writeAt(0, "bird");
386     row_write2.writeAt(1, 3);
387     row_write2.writeAt(2, "purple");
388 
389     // Reopen the file, seek to the end of file so as we can append
390     // some more data.
391     ASSERT_NO_THROW(csv->open(true));
392     // The file pointer should be at the end of file, so an attempt
393     //  to read should result in an empty row.
394     ASSERT_TRUE(csv->next(row));
395     EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
396     // We should be able to append new data.
397     ASSERT_NO_THROW(csv->append(row_write2));
398     ASSERT_NO_THROW(csv->flush());
399     csv->close();
400     // Check that new data has been appended.
401     EXPECT_EQ("animal,age,color\n"
402               "cat,10,white\n"
403               "lion,15,yellow\n"
404               "dog,2,blue\n"
405               "bird,3,purple\n",
406               readFile());
407 }
408 
409 // This test checks that contents may be appended to a file which hasn't
410 // been fully parsed/read.
TEST_F(CSVFileTest,openReadPartialWrite)411 TEST_F(CSVFileTest, openReadPartialWrite) {
412     // Create a CSV file with two rows in it.
413     writeFile("animal,age,color\n"
414               "cat,10,white\n"
415               "lion,15,yellow\n");
416 
417     // Open this file.
418     boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
419     ASSERT_NO_THROW(csv->open());
420 
421     // Read the first row.
422     CSVRow row0(0);
423     ASSERT_NO_THROW(csv->next(row0));
424     ASSERT_EQ(3, row0.getValuesCount());
425     EXPECT_EQ("cat", row0.readAt(0));
426     EXPECT_EQ("10", row0.readAt(1));
427     EXPECT_EQ("white", row0.readAt(2));
428 
429     // There is still second row to be read. But, it should be possible to
430     // skip reading it and append new row to the end of file.
431     CSVRow row_write(3);
432     row_write.writeAt(0, "dog");
433     row_write.writeAt(1, 2);
434     row_write.writeAt(2, "blue");
435     ASSERT_NO_THROW(csv->append(row_write));
436 
437     // At this point, the file pointer is at the end of file, so reading
438     // should return empty row.
439     CSVRow row1(0);
440     ASSERT_NO_THROW(csv->next(row1));
441     EXPECT_EQ(CSVFile::EMPTY_ROW(), row1);
442 
443     // Close the file.
444     ASSERT_NO_THROW(csv->flush());
445     csv->close();
446 
447     // Check that there are two initial lines and one new there.
448     EXPECT_EQ("animal,age,color\n"
449               "cat,10,white\n"
450               "lion,15,yellow\n"
451               "dog,2,blue\n",
452               readFile());
453 
454 }
455 
456 // This test checks that the new CSV file is created and header
457 // is written to it. It also checks that data rows can be
458 // appended to it.
TEST_F(CSVFileTest,recreate)459 TEST_F(CSVFileTest, recreate) {
460     boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
461     csv->addColumn("animal");
462     csv->addColumn("color");
463     csv->addColumn("age");
464     csv->addColumn("comments");
465     ASSERT_NO_THROW(csv->recreate());
466     ASSERT_TRUE(exists());
467 
468     CSVRow row0(4);
469     row0.writeAt(0, "dog");
470     row0.writeAt(1, "grey");
471     row0.writeAt(2, 3);
472     row0.writeAt(3, "nice one");
473     ASSERT_NO_THROW(csv->append(row0));
474 
475     CSVRow row1(4);
476     row1.writeAt(0, "cat");
477     row1.writeAt(1, "black");
478     row1.writeAt(2, 2);
479     ASSERT_NO_THROW(csv->append(row1));
480 
481     ASSERT_NO_THROW(csv->flush());
482     csv->close();
483 
484     EXPECT_EQ("animal,color,age,comments\n"
485               "dog,grey,3,nice one\n"
486               "cat,black,2,\n",
487               readFile());
488 }
489 
490 // This test checks that the error is reported when the size of the row being
491 // read doesn't match the number of columns of the CSV file.
TEST_F(CSVFileTest,validate)492 TEST_F(CSVFileTest, validate) {
493     // Create CSV file with 2 invalid rows in it: one too long, one too short.
494     // Apart from that, there are two valid columns that should be read
495     // successfully.
496     writeFile("animal,age,color\n"
497               "cat,10,white\n"
498               "lion,15,yellow,black\n"
499               "dog,3,green\n"
500               "elephant,11\n");
501 
502     boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
503     ASSERT_NO_THROW(csv->open());
504     // First row is correct.
505     CSVRow row0;
506     ASSERT_TRUE(csv->next(row0));
507     EXPECT_EQ("cat", row0.readAt(0));
508     EXPECT_EQ("10", row0.readAt(1));
509     EXPECT_EQ("white", row0.readAt(2));
510     EXPECT_EQ("success", csv->getReadMsg());
511     // This row is too long.
512     CSVRow row1;
513     EXPECT_FALSE(csv->next(row1));
514     EXPECT_NE("success", csv->getReadMsg());
515     // This row is correct.
516     CSVRow row2;
517     ASSERT_TRUE(csv->next(row2));
518     EXPECT_EQ("dog", row2.readAt(0));
519     EXPECT_EQ("3", row2.readAt(1));
520     EXPECT_EQ("green", row2.readAt(2));
521     EXPECT_EQ("success", csv->getReadMsg());
522     // This row is too short.
523     CSVRow row3;
524     EXPECT_FALSE(csv->next(row3));
525     EXPECT_NE("success", csv->getReadMsg());
526 }
527 
528 // Test test checks that exception is thrown when the header of the CSV file
529 // parsed, doesn't match the columns specified.
TEST_F(CSVFileTest,validateHeader)530 TEST_F(CSVFileTest, validateHeader) {
531     // Create CSV file with 3 columns.
532     writeFile("animal,age,color\n"
533               "cat,10,white\n"
534               "lion,15,yellow,black\n");
535 
536     // Invalid order of columns.
537     boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
538     csv->addColumn("color");
539     csv->addColumn("animal");
540     csv->addColumn("age");
541     EXPECT_THROW(csv->open(), CSVFileError);
542 
543     // Too many columns.
544     csv.reset(new CSVFile(testfile_));
545     csv->addColumn("animal");
546     csv->addColumn("age");
547     csv->addColumn("color");
548     csv->addColumn("notes");
549     EXPECT_THROW(csv->open(), CSVFileError);
550 
551     // Too few columns.
552     csv.reset(new CSVFile(testfile_));
553     csv->addColumn("animal");
554     csv->addColumn("age");
555     EXPECT_THROW(csv->open(), CSVFileError);
556 }
557 
558 // This test checks that the exists method of the CSVFile class properly
559 // checks that the file exists.
TEST_F(CSVFileTest,exists)560 TEST_F(CSVFileTest, exists) {
561     // Create a new CSV file that contains a header and two data rows.
562     writeFile("animal,age,color\n"
563               "cat,10,white\n"
564               "lion,15,yellow\n");
565 
566     boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
567     // The CSVFile class should return true even if the file hasn't been
568     // opened.
569     EXPECT_TRUE(csv->exists());
570     // Now open the file and make sure it still returns true.
571     ASSERT_NO_THROW(csv->open());
572     EXPECT_TRUE(csv->exists());
573 
574     // Close the file and remove it.
575     csv->close();
576     EXPECT_EQ(0, removeFile());
577 
578     // The file should not exist.
579     EXPECT_FALSE(csv->exists());
580 }
581 
582 // Check that a single header without a trailing blank line can be parsed.
TEST_F(CSVFileTest,parseHeaderWithoutTrailingBlankLine)583 TEST_F(CSVFileTest, parseHeaderWithoutTrailingBlankLine) {
584     // Create a new CSV file that only contains a header without a new line.
585     writeFile("animal,age,color");
586 
587     // Open this file and check that the header is parsed.
588     CSVFile csv(testfile_);
589     ASSERT_NO_THROW(csv.open());
590     ASSERT_EQ(3, csv.getColumnCount());
591     EXPECT_EQ("animal", csv.getColumnName(0));
592     EXPECT_EQ("age", csv.getColumnName(1));
593     EXPECT_EQ("color", csv.getColumnName(2));
594 
595     // Attempt to read the next row which doesn't exist.
596     CSVRow row;
597     ASSERT_TRUE(csv.next(row));
598     EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
599 
600     // Close the file.
601     csv.close();
602 }
603 
604 // Check that content without a trailing blank line can be parsed.
TEST_F(CSVFileTest,parseContentWithoutTrailingBlankLine)605 TEST_F(CSVFileTest, parseContentWithoutTrailingBlankLine) {
606     // Now create a new CSV file that contains header plus data, but the last
607     // line is missing a new line.
608     writeFile("animal,age,color\n"
609               "cat,4,white\n"
610               "lion,8,yellow");
611 
612     // Open this file and check that the header is parsed.
613     CSVFile csv(testfile_);
614     ASSERT_NO_THROW(csv.open());
615     ASSERT_EQ(3, csv.getColumnCount());
616     EXPECT_EQ("animal", csv.getColumnName(0));
617     EXPECT_EQ("age", csv.getColumnName(1));
618     EXPECT_EQ("color", csv.getColumnName(2));
619 
620     // Check the first data row.
621     CSVRow row;
622     ASSERT_TRUE(csv.next(row));
623     EXPECT_EQ("cat", row.readAt(0));
624     EXPECT_EQ("4", row.readAt(1));
625     EXPECT_EQ("white", row.readAt(2));
626     EXPECT_EQ("success", csv.getReadMsg());
627 
628     // Check the second data row.
629     ASSERT_TRUE(csv.next(row));
630     EXPECT_EQ("lion", row.readAt(0));
631     EXPECT_EQ("8", row.readAt(1));
632     EXPECT_EQ("yellow", row.readAt(2));
633     EXPECT_EQ("success", csv.getReadMsg());
634 
635     // Attempt to read the next row which doesn't exist.
636     ASSERT_TRUE(csv.next(row));
637     EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
638 
639     // Close the file.
640     csv.close();
641 }
642 
643 // Check that blank lines are skipped when reading from a file.
TEST_F(CSVFileTest,parseContentWithBlankLines)644 TEST_F(CSVFileTest, parseContentWithBlankLines) {
645     for (char const* const& content : {
646         // Single intermediary blank line
647         "animal,age,color\n"
648         "cat,4,white\n"
649         "\n"
650         "lion,8,yellow\n",
651 
652         // Blank lines all over
653         "\n"
654         "\n"
655         "animal,age,color\n"
656         "\n"
657         "\n"
658         "cat,4,white\n"
659         "\n"
660         "\n"
661         "lion,8,yellow\n"
662         "\n"
663         "\n",
664     }) {
665         // Create a new CSV file.
666         writeFile(content);
667 
668         // Open this file and check that the header is parsed.
669         CSVFile csv(testfile_);
670         ASSERT_NO_THROW(csv.open());
671         ASSERT_EQ(3, csv.getColumnCount());
672         EXPECT_EQ("animal", csv.getColumnName(0));
673         EXPECT_EQ("age", csv.getColumnName(1));
674         EXPECT_EQ("color", csv.getColumnName(2));
675 
676         // Check the first data row.
677         CSVRow row;
678         ASSERT_TRUE(csv.next(row));
679         EXPECT_EQ("cat", row.readAt(0));
680         EXPECT_EQ("4", row.readAt(1));
681         EXPECT_EQ("white", row.readAt(2));
682         EXPECT_EQ("success", csv.getReadMsg());
683 
684         // Check the second non-blank data row.
685         ASSERT_TRUE(csv.next(row));
686         EXPECT_EQ("lion", row.readAt(0));
687         EXPECT_EQ("8", row.readAt(1));
688         EXPECT_EQ("yellow", row.readAt(2));
689         EXPECT_EQ("success", csv.getReadMsg());
690 
691         // Attempt to read the next row which doesn't exist.
692         ASSERT_TRUE(csv.next(row));
693         EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
694 
695         // Close the file.
696         csv.close();
697     }
698 }
699 
700 } // end of anonymous namespace
701