summaryrefslogtreecommitdiffstats
path: root/src/lib/util/tests/csv_file_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/util/tests/csv_file_unittest.cc')
-rw-r--r--src/lib/util/tests/csv_file_unittest.cc700
1 files changed, 700 insertions, 0 deletions
diff --git a/src/lib/util/tests/csv_file_unittest.cc b/src/lib/util/tests/csv_file_unittest.cc
new file mode 100644
index 0000000..f55ec55
--- /dev/null
+++ b/src/lib/util/tests/csv_file_unittest.cc
@@ -0,0 +1,700 @@
+// Copyright (C) 2014-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <util/csv_file.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+#include <fstream>
+#include <sstream>
+#include <string>
+
+namespace {
+
+using namespace isc::util;
+
+// This test exercises escaping and unescaping of characters.
+TEST(CSVRowTest, escapeUnescape) {
+ std::string orig(",FO^O\\,B?,AR,");
+
+ // We'll escape commas, question marks, and carets.
+ std::string escaped = CSVRow::escapeCharacters(orig, ",?^");
+ EXPECT_EQ ("&#x2cFO&#x5eO\\&#x2cB&#x3f&#x2cAR&#x2c", escaped);
+
+ // Now make sure we can unescape it correctly.
+ std::string unescaped = CSVRow::unescapeCharacters(escaped);
+ EXPECT_EQ (orig, unescaped);
+
+ // Make sure that an incident occurrence of just the escape tag
+ // is left intact.
+ orig = ("no&#xescape");
+ escaped = CSVRow::escapeCharacters(orig, ",");
+ unescaped = CSVRow::unescapeCharacters(orig);
+ EXPECT_EQ (orig, unescaped);
+
+ // Make sure that an incidental occurrence of a valid
+ // escape tag sequence left intact.
+ orig = ("no&#x2cescape");
+ escaped = CSVRow::escapeCharacters(orig, ",");
+ unescaped = CSVRow::unescapeCharacters(escaped);
+ EXPECT_EQ (orig, unescaped);
+}
+
+// This test checks that the single data row is parsed.
+TEST(CSVRow, parse) {
+ CSVRow row0("foo,bar,foo-bar");
+ ASSERT_EQ(3, row0.getValuesCount());
+ EXPECT_EQ("foo", row0.readAt(0));
+ EXPECT_EQ("bar", row0.readAt(1));
+ EXPECT_EQ("foo-bar", row0.readAt(2));
+
+ row0.parse("bar,,foo-bar");
+ ASSERT_EQ(3, row0.getValuesCount());
+ EXPECT_EQ("bar", row0.readAt(0));
+ EXPECT_TRUE(row0.readAt(1).empty());
+ EXPECT_EQ("foo-bar", row0.readAt(2));
+
+ row0.parse("bar,foo&#x2c-bar");
+ ASSERT_EQ(2, row0.getValuesCount());
+ EXPECT_EQ("bar", row0.readAt(0));
+ // Read the second column as-is and escaped
+ EXPECT_EQ("foo&#x2c-bar", row0.readAt(1));
+ EXPECT_EQ("foo,-bar", row0.readAtEscaped(1));
+
+ CSVRow row1("foo-bar|foo|bar|", '|');
+ ASSERT_EQ(4, row1.getValuesCount());
+ EXPECT_EQ("foo-bar", row1.readAt(0));
+ EXPECT_EQ("foo", row1.readAt(1));
+ EXPECT_EQ("bar", row1.readAt(2));
+ EXPECT_TRUE(row1.readAt(3).empty());
+
+ row1.parse("");
+ ASSERT_EQ(1, row1.getValuesCount());
+ EXPECT_TRUE(row1.readAt(0).empty());
+}
+
+// Verifies that empty columns are handled correctly.
+TEST(CSVRow, emptyColumns) {
+ // Should get four columns, all blank except column the second one.
+ CSVRow row(",one,,");
+ ASSERT_EQ(4, row.getValuesCount());
+ EXPECT_EQ("", row.readAt(0));
+ EXPECT_EQ("one", row.readAt(1));
+ EXPECT_EQ("", row.readAt(2));
+ EXPECT_EQ("", row.readAt(3));
+}
+
+// Verifies that empty columns are handled correctly.
+TEST(CSVRow, oneColumn) {
+ // Should get one column
+ CSVRow row("zero");
+ ASSERT_EQ(1, row.getValuesCount());
+ EXPECT_EQ("zero", row.readAt(0));
+}
+
+// This test checks that the text representation of the CSV row
+// is created correctly.
+TEST(CSVRow, render) {
+ CSVRow row0(3);
+ row0.writeAt(0, "foo");
+ row0.writeAt(1, "foo-bar");
+ row0.writeAt(2, "bar");
+
+ std::string text;
+ ASSERT_NO_THROW(text = row0.render());
+ EXPECT_EQ(text, "foo,foo-bar,bar");
+
+ CSVRow row1(4, ';');
+ row1.writeAt(0, "foo");
+ row1.writeAt(2, "bar");
+ row1.writeAt(3, 10);
+
+ ASSERT_NO_THROW(text = row1.render());
+ EXPECT_EQ(text, "foo;;bar;10");
+
+ CSVRow row2(0);
+ ASSERT_NO_THROW(text = row2.render());
+ EXPECT_TRUE(text.empty());
+}
+
+// This test checks that the data values can be set for the CSV row.
+TEST(CSVRow, writeAt) {
+ CSVRow row(4);
+ row.writeAt(0, 10);
+ row.writeAt(1, "foo");
+ row.writeAt(2, "bar");
+ row.writeAtEscaped(3, "bar,one,two");
+
+ EXPECT_EQ("10", row.readAt(0));
+ EXPECT_EQ("foo", row.readAt(1));
+ EXPECT_EQ("bar", row.readAt(2));
+ // Read third column as-is and unescaped
+ EXPECT_EQ("bar&#x2cone&#x2ctwo", row.readAt(3));
+ EXPECT_EQ("bar,one,two", row.readAtEscaped(3));
+
+ EXPECT_THROW(row.writeAt(4, 20), CSVFileError);
+ EXPECT_THROW(row.writeAt(4, "foo"), CSVFileError);
+}
+
+// Checks whether writeAt() and append() can be mixed together.
+TEST(CSVRow, append) {
+ CSVRow row(3);
+
+ EXPECT_EQ(3, row.getValuesCount());
+
+ row.writeAt(0, "alpha");
+ ASSERT_NO_THROW(row.append("delta"));
+ EXPECT_EQ(4, row.getValuesCount());
+ row.writeAt(1, "beta");
+ row.writeAt(2, "gamma");
+ ASSERT_NO_THROW(row.append("epsilon"));
+ EXPECT_EQ(5, row.getValuesCount());
+
+ std::string text;
+ ASSERT_NO_THROW(text = row.render());
+ EXPECT_EQ("alpha,beta,gamma,delta,epsilon", text);
+}
+
+// This test checks that a row can be trimmed of
+// a given number of elements
+TEST(CSVRow, trim) {
+ CSVRow row("zero,one,two,three,four");
+ ASSERT_EQ(5, row.getValuesCount());
+ EXPECT_EQ("zero", row.readAt(0));
+ EXPECT_EQ("one", row.readAt(1));
+ EXPECT_EQ("two", row.readAt(2));
+ EXPECT_EQ("three", row.readAt(3));
+ EXPECT_EQ("four", row.readAt(4));
+
+ ASSERT_THROW(row.trim(10), CSVFileError);
+
+ // Verify that we can erase just one
+ ASSERT_NO_THROW(row.trim(1));
+ ASSERT_EQ(4, row.getValuesCount());
+ EXPECT_EQ("zero", row.readAt(0));
+ EXPECT_EQ("one", row.readAt(1));
+ EXPECT_EQ("two", row.readAt(2));
+ EXPECT_EQ("three", row.readAt(3));
+
+ // Verify we can trim more than one
+ ASSERT_NO_THROW(row.trim(2));
+ ASSERT_EQ(2, row.getValuesCount());
+ EXPECT_EQ("zero", row.readAt(0));
+ EXPECT_EQ("one", row.readAt(1));
+}
+
+/// @brief Test fixture class for testing operations on CSV file.
+///
+/// It implements basic operations on files, such as reading writing
+/// file removal and checking presence of the file. This is used by
+/// unit tests to verify correctness of the file created by the
+/// CSVFile class.
+class CSVFileTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Sets the path to the CSV file used throughout the tests.
+ /// The name of the file is test.csv and it is located in the
+ /// current build folder.
+ ///
+ /// It also deletes any dangling files after previous tests.
+ CSVFileTest();
+
+ /// @brief Destructor.
+ ///
+ /// Deletes the test CSV file if any.
+ virtual ~CSVFileTest();
+
+ /// @brief Prepends the absolute path to the file specified
+ /// as an argument.
+ ///
+ /// @param filename Name of the file.
+ /// @return Absolute path to the test file.
+ static std::string absolutePath(const std::string& filename);
+
+ /// @brief Check if test file exists on disk.
+ bool exists() const;
+
+ /// @brief Reads whole CSV file.
+ ///
+ /// @return Contents of the file.
+ std::string readFile() const;
+
+ /// @brief Removes existing file (if any).
+ int removeFile() const;
+
+ /// @brief Creates file with contents.
+ ///
+ /// @param contents Contents of the file.
+ void writeFile(const std::string& contents) const;
+
+ /// @brief Absolute path to the file used in the tests.
+ std::string testfile_;
+
+};
+
+CSVFileTest::CSVFileTest()
+ : testfile_(absolutePath("test.csv")) {
+ static_cast<void>(removeFile());
+}
+
+CSVFileTest::~CSVFileTest() {
+ static_cast<void>(removeFile());
+}
+
+std::string
+CSVFileTest::absolutePath(const std::string& filename) {
+ std::ostringstream s;
+ s << TEST_DATA_BUILDDIR << "/" << filename;
+ return (s.str());
+}
+
+bool
+CSVFileTest::exists() const {
+ std::ifstream fs(testfile_.c_str());
+ bool ok = fs.good();
+ fs.close();
+ return (ok);
+}
+
+std::string
+CSVFileTest::readFile() const {
+ std::ifstream fs(testfile_.c_str());
+ if (!fs.is_open()) {
+ return ("");
+ }
+ std::string contents((std::istreambuf_iterator<char>(fs)),
+ std::istreambuf_iterator<char>());
+ fs.close();
+ return (contents);
+}
+
+int
+CSVFileTest::removeFile() const {
+ return (remove(testfile_.c_str()));
+}
+
+void
+CSVFileTest::writeFile(const std::string& contents) const {
+ std::ofstream fs(testfile_.c_str(), std::ofstream::out);
+ if (fs.is_open()) {
+ fs << contents;
+ fs.close();
+ }
+}
+
+// This test checks that the function which is used to add columns of the
+// CSV file works as expected.
+TEST_F(CSVFileTest, addColumn) {
+ boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+ // Add two columns.
+ ASSERT_NO_THROW(csv->addColumn("animal"));
+ ASSERT_NO_THROW(csv->addColumn("color"));
+ // Make sure we can't add duplicates.
+ EXPECT_THROW(csv->addColumn("animal"), CSVFileError);
+ EXPECT_THROW(csv->addColumn("color"), CSVFileError);
+ // But we should still be able to add unique columns.
+ EXPECT_NO_THROW(csv->addColumn("age"));
+ EXPECT_NO_THROW(csv->addColumn("comments"));
+ // Assert that the file is opened, because the rest of the test relies
+ // on this.
+ ASSERT_NO_THROW(csv->recreate());
+ ASSERT_TRUE(exists());
+
+ // Make sure we can't add columns (even unique) when the file is open.
+ ASSERT_THROW(csv->addColumn("zoo"), CSVFileError);
+ // Close the file.
+ ASSERT_NO_THROW(csv->close());
+ // And check that now it is possible to add the column.
+ EXPECT_NO_THROW(csv->addColumn("zoo"));
+}
+
+// This test checks that the appropriate file name is initialized.
+TEST_F(CSVFileTest, getFilename) {
+ CSVFile csv(testfile_);
+ EXPECT_EQ(testfile_, csv.getFilename());
+}
+
+// This test checks that the file can be opened, its whole content is
+// parsed correctly and data may be appended. It also checks that empty
+// row is returned when EOF is reached.
+TEST_F(CSVFileTest, openReadAllWrite) {
+ // Create a new CSV file that contains a header and two data rows.
+ writeFile("animal,age,color\n"
+ "cat,10,white\n"
+ "lion,15,yellow\n");
+
+ // Open this file and check that the header is parsed.
+ boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+ ASSERT_NO_THROW(csv->open());
+ ASSERT_EQ(3, csv->getColumnCount());
+ EXPECT_EQ("animal", csv->getColumnName(0));
+ EXPECT_EQ("age", csv->getColumnName(1));
+ EXPECT_EQ("color", csv->getColumnName(2));
+
+ // Read first row.
+ CSVRow row;
+ ASSERT_TRUE(csv->next(row));
+ ASSERT_EQ(3, row.getValuesCount());
+ EXPECT_EQ("cat", row.readAt(0));
+ EXPECT_EQ("10", row.readAt(1));
+ EXPECT_EQ("white", row.readAt(2));
+
+ // Read second row.
+ ASSERT_TRUE(csv->next(row));
+ ASSERT_EQ(3, row.getValuesCount());
+ EXPECT_EQ("lion", row.readAt(0));
+ EXPECT_EQ("15", row.readAt(1));
+ EXPECT_EQ("yellow", row.readAt(2));
+
+ // There is no 3rd row, so the empty one should be returned.
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
+
+ // It should be fine to read again, but again empty row should be returned.
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
+
+ // Now, let's try to append something to this file.
+ CSVRow row_write(3);
+ row_write.writeAt(0, "dog");
+ row_write.writeAt(1, 2);
+ row_write.writeAt(2, "blue");
+ ASSERT_NO_THROW(csv->append(row_write));
+
+ // Close the file.
+ ASSERT_NO_THROW(csv->flush());
+ csv->close();
+
+ // Check the file contents are correct.
+ EXPECT_EQ("animal,age,color\n"
+ "cat,10,white\n"
+ "lion,15,yellow\n"
+ "dog,2,blue\n",
+ readFile());
+
+ // Any attempt to read from the file or write to it should now fail.
+ EXPECT_FALSE(csv->next(row));
+ EXPECT_THROW(csv->append(row_write), CSVFileError);
+
+ CSVRow row_write2(3);
+ row_write2.writeAt(0, "bird");
+ row_write2.writeAt(1, 3);
+ row_write2.writeAt(2, "purple");
+
+ // Reopen the file, seek to the end of file so as we can append
+ // some more data.
+ ASSERT_NO_THROW(csv->open(true));
+ // The file pointer should be at the end of file, so an attempt
+ // to read should result in an empty row.
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
+ // We should be able to append new data.
+ ASSERT_NO_THROW(csv->append(row_write2));
+ ASSERT_NO_THROW(csv->flush());
+ csv->close();
+ // Check that new data has been appended.
+ EXPECT_EQ("animal,age,color\n"
+ "cat,10,white\n"
+ "lion,15,yellow\n"
+ "dog,2,blue\n"
+ "bird,3,purple\n",
+ readFile());
+}
+
+// This test checks that contents may be appended to a file which hasn't
+// been fully parsed/read.
+TEST_F(CSVFileTest, openReadPartialWrite) {
+ // Create a CSV file with two rows in it.
+ writeFile("animal,age,color\n"
+ "cat,10,white\n"
+ "lion,15,yellow\n");
+
+ // Open this file.
+ boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+ ASSERT_NO_THROW(csv->open());
+
+ // Read the first row.
+ CSVRow row0(0);
+ ASSERT_NO_THROW(csv->next(row0));
+ ASSERT_EQ(3, row0.getValuesCount());
+ EXPECT_EQ("cat", row0.readAt(0));
+ EXPECT_EQ("10", row0.readAt(1));
+ EXPECT_EQ("white", row0.readAt(2));
+
+ // There is still second row to be read. But, it should be possible to
+ // skip reading it and append new row to the end of file.
+ CSVRow row_write(3);
+ row_write.writeAt(0, "dog");
+ row_write.writeAt(1, 2);
+ row_write.writeAt(2, "blue");
+ ASSERT_NO_THROW(csv->append(row_write));
+
+ // At this point, the file pointer is at the end of file, so reading
+ // should return empty row.
+ CSVRow row1(0);
+ ASSERT_NO_THROW(csv->next(row1));
+ EXPECT_EQ(CSVFile::EMPTY_ROW(), row1);
+
+ // Close the file.
+ ASSERT_NO_THROW(csv->flush());
+ csv->close();
+
+ // Check that there are two initial lines and one new there.
+ EXPECT_EQ("animal,age,color\n"
+ "cat,10,white\n"
+ "lion,15,yellow\n"
+ "dog,2,blue\n",
+ readFile());
+
+}
+
+// This test checks that the new CSV file is created and header
+// is written to it. It also checks that data rows can be
+// appended to it.
+TEST_F(CSVFileTest, recreate) {
+ boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+ csv->addColumn("animal");
+ csv->addColumn("color");
+ csv->addColumn("age");
+ csv->addColumn("comments");
+ ASSERT_NO_THROW(csv->recreate());
+ ASSERT_TRUE(exists());
+
+ CSVRow row0(4);
+ row0.writeAt(0, "dog");
+ row0.writeAt(1, "grey");
+ row0.writeAt(2, 3);
+ row0.writeAt(3, "nice one");
+ ASSERT_NO_THROW(csv->append(row0));
+
+ CSVRow row1(4);
+ row1.writeAt(0, "cat");
+ row1.writeAt(1, "black");
+ row1.writeAt(2, 2);
+ ASSERT_NO_THROW(csv->append(row1));
+
+ ASSERT_NO_THROW(csv->flush());
+ csv->close();
+
+ EXPECT_EQ("animal,color,age,comments\n"
+ "dog,grey,3,nice one\n"
+ "cat,black,2,\n",
+ readFile());
+}
+
+// This test checks that the error is reported when the size of the row being
+// read doesn't match the number of columns of the CSV file.
+TEST_F(CSVFileTest, validate) {
+ // Create CSV file with 2 invalid rows in it: one too long, one too short.
+ // Apart from that, there are two valid columns that should be read
+ // successfully.
+ writeFile("animal,age,color\n"
+ "cat,10,white\n"
+ "lion,15,yellow,black\n"
+ "dog,3,green\n"
+ "elephant,11\n");
+
+ boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+ ASSERT_NO_THROW(csv->open());
+ // First row is correct.
+ CSVRow row0;
+ ASSERT_TRUE(csv->next(row0));
+ EXPECT_EQ("cat", row0.readAt(0));
+ EXPECT_EQ("10", row0.readAt(1));
+ EXPECT_EQ("white", row0.readAt(2));
+ EXPECT_EQ("success", csv->getReadMsg());
+ // This row is too long.
+ CSVRow row1;
+ EXPECT_FALSE(csv->next(row1));
+ EXPECT_NE("success", csv->getReadMsg());
+ // This row is correct.
+ CSVRow row2;
+ ASSERT_TRUE(csv->next(row2));
+ EXPECT_EQ("dog", row2.readAt(0));
+ EXPECT_EQ("3", row2.readAt(1));
+ EXPECT_EQ("green", row2.readAt(2));
+ EXPECT_EQ("success", csv->getReadMsg());
+ // This row is too short.
+ CSVRow row3;
+ EXPECT_FALSE(csv->next(row3));
+ EXPECT_NE("success", csv->getReadMsg());
+}
+
+// Test test checks that exception is thrown when the header of the CSV file
+// parsed, doesn't match the columns specified.
+TEST_F(CSVFileTest, validateHeader) {
+ // Create CSV file with 3 columns.
+ writeFile("animal,age,color\n"
+ "cat,10,white\n"
+ "lion,15,yellow,black\n");
+
+ // Invalid order of columns.
+ boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+ csv->addColumn("color");
+ csv->addColumn("animal");
+ csv->addColumn("age");
+ EXPECT_THROW(csv->open(), CSVFileError);
+
+ // Too many columns.
+ csv.reset(new CSVFile(testfile_));
+ csv->addColumn("animal");
+ csv->addColumn("age");
+ csv->addColumn("color");
+ csv->addColumn("notes");
+ EXPECT_THROW(csv->open(), CSVFileError);
+
+ // Too few columns.
+ csv.reset(new CSVFile(testfile_));
+ csv->addColumn("animal");
+ csv->addColumn("age");
+ EXPECT_THROW(csv->open(), CSVFileError);
+}
+
+// This test checks that the exists method of the CSVFile class properly
+// checks that the file exists.
+TEST_F(CSVFileTest, exists) {
+ // Create a new CSV file that contains a header and two data rows.
+ writeFile("animal,age,color\n"
+ "cat,10,white\n"
+ "lion,15,yellow\n");
+
+ boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+ // The CSVFile class should return true even if the file hasn't been
+ // opened.
+ EXPECT_TRUE(csv->exists());
+ // Now open the file and make sure it still returns true.
+ ASSERT_NO_THROW(csv->open());
+ EXPECT_TRUE(csv->exists());
+
+ // Close the file and remove it.
+ csv->close();
+ EXPECT_EQ(0, removeFile());
+
+ // The file should not exist.
+ EXPECT_FALSE(csv->exists());
+}
+
+// Check that a single header without a trailing blank line can be parsed.
+TEST_F(CSVFileTest, parseHeaderWithoutTrailingBlankLine) {
+ // Create a new CSV file that only contains a header without a new line.
+ writeFile("animal,age,color");
+
+ // Open this file and check that the header is parsed.
+ CSVFile csv(testfile_);
+ ASSERT_NO_THROW(csv.open());
+ ASSERT_EQ(3, csv.getColumnCount());
+ EXPECT_EQ("animal", csv.getColumnName(0));
+ EXPECT_EQ("age", csv.getColumnName(1));
+ EXPECT_EQ("color", csv.getColumnName(2));
+
+ // Attempt to read the next row which doesn't exist.
+ CSVRow row;
+ ASSERT_TRUE(csv.next(row));
+ EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
+
+ // Close the file.
+ csv.close();
+}
+
+// Check that content without a trailing blank line can be parsed.
+TEST_F(CSVFileTest, parseContentWithoutTrailingBlankLine) {
+ // Now create a new CSV file that contains header plus data, but the last
+ // line is missing a new line.
+ writeFile("animal,age,color\n"
+ "cat,4,white\n"
+ "lion,8,yellow");
+
+ // Open this file and check that the header is parsed.
+ CSVFile csv(testfile_);
+ ASSERT_NO_THROW(csv.open());
+ ASSERT_EQ(3, csv.getColumnCount());
+ EXPECT_EQ("animal", csv.getColumnName(0));
+ EXPECT_EQ("age", csv.getColumnName(1));
+ EXPECT_EQ("color", csv.getColumnName(2));
+
+ // Check the first data row.
+ CSVRow row;
+ ASSERT_TRUE(csv.next(row));
+ EXPECT_EQ("cat", row.readAt(0));
+ EXPECT_EQ("4", row.readAt(1));
+ EXPECT_EQ("white", row.readAt(2));
+ EXPECT_EQ("success", csv.getReadMsg());
+
+ // Check the second data row.
+ ASSERT_TRUE(csv.next(row));
+ EXPECT_EQ("lion", row.readAt(0));
+ EXPECT_EQ("8", row.readAt(1));
+ EXPECT_EQ("yellow", row.readAt(2));
+ EXPECT_EQ("success", csv.getReadMsg());
+
+ // Attempt to read the next row which doesn't exist.
+ ASSERT_TRUE(csv.next(row));
+ EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
+
+ // Close the file.
+ csv.close();
+}
+
+// Check that blank lines are skipped when reading from a file.
+TEST_F(CSVFileTest, parseContentWithBlankLines) {
+ for (char const* const& content : {
+ // Single intermediary blank line
+ "animal,age,color\n"
+ "cat,4,white\n"
+ "\n"
+ "lion,8,yellow\n",
+
+ // Blank lines all over
+ "\n"
+ "\n"
+ "animal,age,color\n"
+ "\n"
+ "\n"
+ "cat,4,white\n"
+ "\n"
+ "\n"
+ "lion,8,yellow\n"
+ "\n"
+ "\n",
+ }) {
+ // Create a new CSV file.
+ writeFile(content);
+
+ // Open this file and check that the header is parsed.
+ CSVFile csv(testfile_);
+ ASSERT_NO_THROW(csv.open());
+ ASSERT_EQ(3, csv.getColumnCount());
+ EXPECT_EQ("animal", csv.getColumnName(0));
+ EXPECT_EQ("age", csv.getColumnName(1));
+ EXPECT_EQ("color", csv.getColumnName(2));
+
+ // Check the first data row.
+ CSVRow row;
+ ASSERT_TRUE(csv.next(row));
+ EXPECT_EQ("cat", row.readAt(0));
+ EXPECT_EQ("4", row.readAt(1));
+ EXPECT_EQ("white", row.readAt(2));
+ EXPECT_EQ("success", csv.getReadMsg());
+
+ // Check the second non-blank data row.
+ ASSERT_TRUE(csv.next(row));
+ EXPECT_EQ("lion", row.readAt(0));
+ EXPECT_EQ("8", row.readAt(1));
+ EXPECT_EQ("yellow", row.readAt(2));
+ EXPECT_EQ("success", csv.getReadMsg());
+
+ // Attempt to read the next row which doesn't exist.
+ ASSERT_TRUE(csv.next(row));
+ EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
+
+ // Close the file.
+ csv.close();
+ }
+}
+
+} // end of anonymous namespace