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 ("ˏO^O\\ˋ?ˊR,", 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 = ("noscape");
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ˎscape");
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,-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,-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,one,two", 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