csv_file_unittest.cc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. // Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // Permission to use, copy, modify, and/or distribute this software for any
  4. // purpose with or without fee is hereby granted, provided that the above
  5. // copyright notice and this permission notice appear in all copies.
  6. //
  7. // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
  8. // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  9. // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
  10. // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  11. // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
  12. // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  13. // PERFORMANCE OF THIS SOFTWARE.
  14. #include <config.h>
  15. #include <util/csv_file.h>
  16. #include <boost/scoped_ptr.hpp>
  17. #include <gtest/gtest.h>
  18. #include <fstream>
  19. #include <sstream>
  20. #include <string>
  21. namespace {
  22. using namespace isc::util;
  23. // This test checks that the single data row is parsed.
  24. TEST(CSVRow, parse) {
  25. CSVRow row0("foo,bar,foo-bar");
  26. ASSERT_EQ(3, row0.getValuesCount());
  27. EXPECT_EQ("foo", row0.readAt(0));
  28. EXPECT_EQ("bar", row0.readAt(1));
  29. EXPECT_EQ("foo-bar", row0.readAt(2));
  30. row0.parse("bar,,foo-bar");
  31. ASSERT_EQ(3, row0.getValuesCount());
  32. EXPECT_EQ("bar", row0.readAt(0));
  33. EXPECT_TRUE(row0.readAt(1).empty());
  34. EXPECT_EQ("foo-bar", row0.readAt(2));
  35. CSVRow row1("foo-bar|foo|bar|", '|');
  36. ASSERT_EQ(4, row1.getValuesCount());
  37. EXPECT_EQ("foo-bar", row1.readAt(0));
  38. EXPECT_EQ("foo", row1.readAt(1));
  39. EXPECT_EQ("bar", row1.readAt(2));
  40. EXPECT_TRUE(row1.readAt(3).empty());
  41. row1.parse("");
  42. ASSERT_EQ(1, row1.getValuesCount());
  43. EXPECT_TRUE(row1.readAt(0).empty());
  44. }
  45. // This test checks that the text representation of the CSV row
  46. // is created correctly.
  47. TEST(CSVRow, render) {
  48. CSVRow row0(3);
  49. row0.writeAt(0, "foo");
  50. row0.writeAt(1, "foo-bar");
  51. row0.writeAt(2, "bar");
  52. std::string text;
  53. ASSERT_NO_THROW(text = row0.render());
  54. EXPECT_EQ(text, "foo,foo-bar,bar");
  55. CSVRow row1(4, ';');
  56. row1.writeAt(0, "foo");
  57. row1.writeAt(2, "bar");
  58. row1.writeAt(3, 10);
  59. ASSERT_NO_THROW(text = row1.render());
  60. EXPECT_EQ(text, "foo;;bar;10");
  61. CSVRow row2(0);
  62. ASSERT_NO_THROW(text = row2.render());
  63. EXPECT_TRUE(text.empty());
  64. }
  65. // This test checks that the data values can be set for the CSV row.
  66. TEST(CSVRow, writeAt) {
  67. CSVRow row(3);
  68. row.writeAt(0, 10);
  69. row.writeAt(1, "foo");
  70. row.writeAt(2, "bar");
  71. EXPECT_EQ("10", row.readAt(0));
  72. EXPECT_EQ("foo", row.readAt(1));
  73. EXPECT_EQ("bar", row.readAt(2));
  74. EXPECT_THROW(row.writeAt(3, 20), CSVFileError);
  75. EXPECT_THROW(row.writeAt(3, "foo"), CSVFileError);
  76. }
  77. // Checks whether writeAt() and append() can be mixed together.
  78. TEST(CSVRow, append) {
  79. CSVRow row(3);
  80. EXPECT_EQ(3, row.getValuesCount());
  81. row.writeAt(0, "alpha");
  82. ASSERT_NO_THROW(row.append("delta"));
  83. EXPECT_EQ(4, row.getValuesCount());
  84. row.writeAt(1, "beta");
  85. row.writeAt(2, "gamma");
  86. ASSERT_NO_THROW(row.append("epsilon"));
  87. EXPECT_EQ(5, row.getValuesCount());
  88. std::string text;
  89. ASSERT_NO_THROW(text = row.render());
  90. EXPECT_EQ("alpha,beta,gamma,delta,epsilon", text);
  91. }
  92. /// @brief Test fixture class for testing operations on CSV file.
  93. ///
  94. /// It implements basic operations on files, such as reading writing
  95. /// file removal and checking presence of the file. This is used by
  96. /// unit tests to verify correctness of the file created by the
  97. /// CSVFile class.
  98. class CSVFileTest : public ::testing::Test {
  99. public:
  100. /// @brief Constructor.
  101. ///
  102. /// Sets the path to the CSV file used throughout the tests.
  103. /// The name of the file is test.csv and it is located in the
  104. /// current build folder.
  105. ///
  106. /// It also deletes any dangling files after previous tests.
  107. CSVFileTest();
  108. /// @brief Destructor.
  109. ///
  110. /// Deletes the test CSV file if any.
  111. virtual ~CSVFileTest();
  112. /// @brief Prepends the absolute path to the file specified
  113. /// as an argument.
  114. ///
  115. /// @param filename Name of the file.
  116. /// @return Absolute path to the test file.
  117. static std::string absolutePath(const std::string& filename);
  118. /// @brief Check if test file exists on disk.
  119. bool exists() const;
  120. /// @brief Reads whole CSV file.
  121. ///
  122. /// @return Contents of the file.
  123. std::string readFile() const;
  124. /// @brief Removes existing file (if any).
  125. int removeFile() const;
  126. /// @brief Creates file with contents.
  127. ///
  128. /// @param contents Contents of the file.
  129. void writeFile(const std::string& contents) const;
  130. /// @brief Absolute path to the file used in the tests.
  131. std::string testfile_;
  132. };
  133. CSVFileTest::CSVFileTest()
  134. : testfile_(absolutePath("test.csv")) {
  135. static_cast<void>(removeFile());
  136. }
  137. CSVFileTest::~CSVFileTest() {
  138. static_cast<void>(removeFile());
  139. }
  140. std::string
  141. CSVFileTest::absolutePath(const std::string& filename) {
  142. std::ostringstream s;
  143. s << TEST_DATA_BUILDDIR << "/" << filename;
  144. return (s.str());
  145. }
  146. bool
  147. CSVFileTest::exists() const {
  148. std::ifstream fs(testfile_.c_str());
  149. bool ok = fs.good();
  150. fs.close();
  151. return (ok);
  152. }
  153. std::string
  154. CSVFileTest::readFile() const {
  155. std::ifstream fs(testfile_.c_str());
  156. if (!fs.is_open()) {
  157. return ("");
  158. }
  159. std::string contents((std::istreambuf_iterator<char>(fs)),
  160. std::istreambuf_iterator<char>());
  161. fs.close();
  162. return (contents);
  163. }
  164. int
  165. CSVFileTest::removeFile() const {
  166. return (remove(testfile_.c_str()));
  167. }
  168. void
  169. CSVFileTest::writeFile(const std::string& contents) const {
  170. std::ofstream fs(testfile_.c_str(), std::ofstream::out);
  171. if (fs.is_open()) {
  172. fs << contents;
  173. fs.close();
  174. }
  175. }
  176. // This test checks that the function which is used to add columns of the
  177. // CSV file works as expected.
  178. TEST_F(CSVFileTest, addColumn) {
  179. boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
  180. // Add two columns.
  181. ASSERT_NO_THROW(csv->addColumn("animal"));
  182. ASSERT_NO_THROW(csv->addColumn("color"));
  183. // Make sure we can't add duplicates.
  184. EXPECT_THROW(csv->addColumn("animal"), CSVFileError);
  185. EXPECT_THROW(csv->addColumn("color"), CSVFileError);
  186. // But we should still be able to add unique columns.
  187. EXPECT_NO_THROW(csv->addColumn("age"));
  188. EXPECT_NO_THROW(csv->addColumn("comments"));
  189. // Assert that the file is opened, because the rest of the test relies
  190. // on this.
  191. ASSERT_NO_THROW(csv->recreate());
  192. ASSERT_TRUE(exists());
  193. // Make sure we can't add columns (even unique) when the file is open.
  194. ASSERT_THROW(csv->addColumn("zoo"), CSVFileError);
  195. // Close the file.
  196. ASSERT_NO_THROW(csv->close());
  197. // And check that now it is possible to add the column.
  198. EXPECT_NO_THROW(csv->addColumn("zoo"));
  199. }
  200. // This test checks that the appropriate file name is initialized.
  201. TEST_F(CSVFileTest, getFilename) {
  202. CSVFile csv(testfile_);
  203. EXPECT_EQ(testfile_, csv.getFilename());
  204. }
  205. // This test checks that the file can be opened, its whole content is
  206. // parsed correctly and data may be appended. It also checks that empty
  207. // row is returned when EOF is reached.
  208. TEST_F(CSVFileTest, openReadAllWrite) {
  209. // Create a new CSV file that contains a header and two data rows.
  210. writeFile("animal,age,color\n"
  211. "cat,10,white\n"
  212. "lion,15,yellow\n");
  213. // Open this file and check that the header is parsed.
  214. boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
  215. ASSERT_NO_THROW(csv->open());
  216. ASSERT_EQ(3, csv->getColumnCount());
  217. EXPECT_EQ("animal", csv->getColumnName(0));
  218. EXPECT_EQ("age", csv->getColumnName(1));
  219. EXPECT_EQ("color", csv->getColumnName(2));
  220. // Read first row.
  221. CSVRow row;
  222. ASSERT_TRUE(csv->next(row));
  223. ASSERT_EQ(3, row.getValuesCount());
  224. EXPECT_EQ("cat", row.readAt(0));
  225. EXPECT_EQ("10", row.readAt(1));
  226. EXPECT_EQ("white", row.readAt(2));
  227. // Read second row.
  228. ASSERT_TRUE(csv->next(row));
  229. ASSERT_EQ(3, row.getValuesCount());
  230. EXPECT_EQ("lion", row.readAt(0));
  231. EXPECT_EQ("15", row.readAt(1));
  232. EXPECT_EQ("yellow", row.readAt(2));
  233. // There is no 3rd row, so the empty one should be returned.
  234. ASSERT_TRUE(csv->next(row));
  235. EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
  236. // It should be fine to read again, but again empty row should be returned.
  237. ASSERT_TRUE(csv->next(row));
  238. EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
  239. // Now, let's try to append something to this file.
  240. CSVRow row_write(3);
  241. row_write.writeAt(0, "dog");
  242. row_write.writeAt(1, 2);
  243. row_write.writeAt(2, "blue");
  244. ASSERT_NO_THROW(csv->append(row_write));
  245. // Close the file.
  246. ASSERT_NO_THROW(csv->flush());
  247. csv->close();
  248. // Check the the file contents are correct.
  249. EXPECT_EQ("animal,age,color\n"
  250. "cat,10,white\n"
  251. "lion,15,yellow\n"
  252. "dog,2,blue\n",
  253. readFile());
  254. // Any attempt to read from the file or write to it should now fail.
  255. EXPECT_FALSE(csv->next(row));
  256. EXPECT_THROW(csv->append(row_write), CSVFileError);
  257. CSVRow row_write2(3);
  258. row_write2.writeAt(0, "bird");
  259. row_write2.writeAt(1, 3);
  260. row_write2.writeAt(2, "purple");
  261. // Reopen the file, seek to the end of file so as we can append
  262. // some more data.
  263. ASSERT_NO_THROW(csv->open(true));
  264. // The file pointer should be at the end of file, so an attempt
  265. // to read should result in an empty row.
  266. ASSERT_TRUE(csv->next(row));
  267. EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
  268. // We should be able to append new data.
  269. ASSERT_NO_THROW(csv->append(row_write2));
  270. ASSERT_NO_THROW(csv->flush());
  271. csv->close();
  272. // Check that new data has been appended.
  273. EXPECT_EQ("animal,age,color\n"
  274. "cat,10,white\n"
  275. "lion,15,yellow\n"
  276. "dog,2,blue\n"
  277. "bird,3,purple\n",
  278. readFile());
  279. }
  280. // This test checks that contents may be appended to a file which hasn't
  281. // been fully parsed/read.
  282. TEST_F(CSVFileTest, openReadPartialWrite) {
  283. // Create a CSV file with two rows in it.
  284. writeFile("animal,age,color\n"
  285. "cat,10,white\n"
  286. "lion,15,yellow\n");
  287. // Open this file.
  288. boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
  289. ASSERT_NO_THROW(csv->open());
  290. // Read the first row.
  291. CSVRow row0(0);
  292. ASSERT_NO_THROW(csv->next(row0));
  293. ASSERT_EQ(3, row0.getValuesCount());
  294. EXPECT_EQ("cat", row0.readAt(0));
  295. EXPECT_EQ("10", row0.readAt(1));
  296. EXPECT_EQ("white", row0.readAt(2));
  297. // There is still second row to be read. But, it should be possible to
  298. // skip reading it and append new row to the end of file.
  299. CSVRow row_write(3);
  300. row_write.writeAt(0, "dog");
  301. row_write.writeAt(1, 2);
  302. row_write.writeAt(2, "blue");
  303. ASSERT_NO_THROW(csv->append(row_write));
  304. // At this point, the file pointer is at the end of file, so reading
  305. // should return empty row.
  306. CSVRow row1(0);
  307. ASSERT_NO_THROW(csv->next(row1));
  308. EXPECT_EQ(CSVFile::EMPTY_ROW(), row1);
  309. // Close the file.
  310. ASSERT_NO_THROW(csv->flush());
  311. csv->close();
  312. // Check that there are two initial lines and one new there.
  313. EXPECT_EQ("animal,age,color\n"
  314. "cat,10,white\n"
  315. "lion,15,yellow\n"
  316. "dog,2,blue\n",
  317. readFile());
  318. }
  319. // This test checks that the new CSV file is created and header
  320. // is written to it. It also checks that data rows can be
  321. // appended to it.
  322. TEST_F(CSVFileTest, recreate) {
  323. boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
  324. csv->addColumn("animal");
  325. csv->addColumn("color");
  326. csv->addColumn("age");
  327. csv->addColumn("comments");
  328. ASSERT_NO_THROW(csv->recreate());
  329. ASSERT_TRUE(exists());
  330. CSVRow row0(4);
  331. row0.writeAt(0, "dog");
  332. row0.writeAt(1, "grey");
  333. row0.writeAt(2, 3);
  334. row0.writeAt(3, "nice one");
  335. ASSERT_NO_THROW(csv->append(row0));
  336. CSVRow row1(4);
  337. row1.writeAt(0, "cat");
  338. row1.writeAt(1, "black");
  339. row1.writeAt(2, 2);
  340. ASSERT_NO_THROW(csv->append(row1));
  341. ASSERT_NO_THROW(csv->flush());
  342. csv->close();
  343. EXPECT_EQ("animal,color,age,comments\n"
  344. "dog,grey,3,nice one\n"
  345. "cat,black,2,\n",
  346. readFile());
  347. }
  348. // This test checks that the error is reported when the size of the row being
  349. // read doesn't match the number of columns of the CSV file.
  350. TEST_F(CSVFileTest, validate) {
  351. // Create CSV file with 2 invalid rows in it: one too long, one too short.
  352. // Apart from that, there are two valid columns that should be read
  353. // successfully.
  354. writeFile("animal,age,color\n"
  355. "cat,10,white\n"
  356. "lion,15,yellow,black\n"
  357. "dog,3,green\n"
  358. "elephant,11\n");
  359. boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
  360. ASSERT_NO_THROW(csv->open());
  361. // First row is correct.
  362. CSVRow row0;
  363. ASSERT_TRUE(csv->next(row0));
  364. EXPECT_EQ("cat", row0.readAt(0));
  365. EXPECT_EQ("10", row0.readAt(1));
  366. EXPECT_EQ("white", row0.readAt(2));
  367. EXPECT_EQ("success", csv->getReadMsg());
  368. // This row is too long.
  369. CSVRow row1;
  370. EXPECT_FALSE(csv->next(row1));
  371. EXPECT_NE("success", csv->getReadMsg());
  372. // This row is correct.
  373. CSVRow row2;
  374. ASSERT_TRUE(csv->next(row2));
  375. EXPECT_EQ("dog", row2.readAt(0));
  376. EXPECT_EQ("3", row2.readAt(1));
  377. EXPECT_EQ("green", row2.readAt(2));
  378. EXPECT_EQ("success", csv->getReadMsg());
  379. // This row is too short.
  380. CSVRow row3;
  381. EXPECT_FALSE(csv->next(row3));
  382. EXPECT_NE("success", csv->getReadMsg());
  383. }
  384. // Test test checks that exception is thrown when the header of the CSV file
  385. // parsed, doesn't match the columns specified.
  386. TEST_F(CSVFileTest, validateHeader) {
  387. // Create CSV file with 3 columns.
  388. writeFile("animal,age,color\n"
  389. "cat,10,white\n"
  390. "lion,15,yellow,black\n");
  391. // Invalid order of columns.
  392. boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
  393. csv->addColumn("color");
  394. csv->addColumn("animal");
  395. csv->addColumn("age");
  396. EXPECT_THROW(csv->open(), CSVFileError);
  397. // Too many columns.
  398. csv.reset(new CSVFile(testfile_));
  399. csv->addColumn("animal");
  400. csv->addColumn("age");
  401. csv->addColumn("color");
  402. csv->addColumn("notes");
  403. EXPECT_THROW(csv->open(), CSVFileError);
  404. // Too few columns.
  405. csv.reset(new CSVFile(testfile_));
  406. csv->addColumn("animal");
  407. csv->addColumn("age");
  408. EXPECT_THROW(csv->open(), CSVFileError);
  409. }
  410. // This test checks that the exists method of the CSVFile class properly
  411. // checks that the file exists.
  412. TEST_F(CSVFileTest, exists) {
  413. // Create a new CSV file that contains a header and two data rows.
  414. writeFile("animal,age,color\n"
  415. "cat,10,white\n"
  416. "lion,15,yellow\n");
  417. boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
  418. // The CSVFile class should return true even if the file hasn't been
  419. // opened.
  420. EXPECT_TRUE(csv->exists());
  421. // Now open the file and make sure it still returns true.
  422. ASSERT_NO_THROW(csv->open());
  423. EXPECT_TRUE(csv->exists());
  424. // Close the file and remove it.
  425. csv->close();
  426. EXPECT_EQ(0, removeFile());
  427. // The file should not exist.
  428. EXPECT_FALSE(csv->exists());
  429. }
  430. } // end of anonymous namespace