Lumiera  0.pre.03
»edit your freedom«
data-csv-test.cpp
Go to the documentation of this file.
1 /*
2  DataCSV(Test) - verify data table with CSV storage support
3 
4  Copyright (C) Lumiera.org
5  2009, Hermann Vosseler <Ichthyostega@web.de>
6 
7  This program is free software; you can redistribute it and/or
8  modify it under the terms of the GNU General Public License as
9  published by the Free Software Foundation; either version 2 of
10  the License, or (at your option) any later version.
11 
12  This program is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  GNU General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with this program; if not, write to the Free Software
19  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 
21 * *****************************************************/
22 
28 #include "lib/test/run.hpp"
29 #include "lib/test/test-helper.hpp"
30 #include "lib/test/temp-dir.hpp"
31 #include "lib/stat/data.hpp"
32 #include "lib/time/timevalue.hpp"
33 #include "lib/format-cout.hpp"
34 #include "lib/util.hpp"
35 
36 #include <sstream>
37 #include <string>
38 #include <vector>
39 
40 using util::isnil;
41 using lib::time::Time;
42 using lib::test::TempDir;
43 using std::make_tuple;
44 using std::string;
45 using std::vector;
46 
47 
48 
49 namespace lib {
50 namespace stat{
51 namespace test{
52 
53  namespace {//Setup for test
54 
55 
57  struct TableForm
58  {
59  Column<string> id{"ID"}; // ◁────── names given here must match first storage line
60  Column<double> val{"Value"};
61  Column<int> off{"Offset"};
62 
63  auto allColumns() // ◁────────── mandatory function; defines actual sequence of columns
64  { return std::tie(id
65  ,val
66  ,off
67  );
68  }
69  };
70 
72 
73  }//(End)Test setup
74 
75  using error::LUMIERA_ERROR_STATE;
76 
77 
78 
79  /***********************************************************/
86  class DataCSV_test : public Test
87  {
88  void
89  run (Arg)
90  {
91  simpleUsage();
92  verify_rowHandling();
93  verify_CSV_Format();
94  verify_persistentDataFile();
95  demonnstrate_CSV_Notation();
96  }
97 
98 
100  void
102  {
103  TestTab tab;
104  CHECK (isnil (tab));
105  tab.newRow();
106  CHECK (not isnil (tab));
107  CHECK (1 == tab.size());
108  CHECK ( "" == string{tab.id});
109  CHECK (0.0 == tab.val);
110  CHECK ( 0 == tab.off);
111  tab.id = "one";
112  tab.val = 1.0;
113 
114  tab.dupRow();
115  CHECK (2 == tab.size());
116  CHECK ("one" == string{tab.id});
117  CHECK ( 1.0 == tab.val);
118  CHECK ( 0 == tab.off);
119 
120  tab.id = "two";
121  tab.val = 5.0;
122  tab.off = -23;
123  CHECK ("two" == string{tab.id});
124  CHECK ( 5.0 == tab.val);
125  CHECK ( -23 == tab.off);
126 
127  CHECK (tab.off.header == "Offset");
128  CHECK (tab.off.data == vector({0,-23}));
129  }
130 
131 
132 
133 
134  void
135  verify_rowHandling()
136  {
137  TestTab tab;
138  CHECK (3 == tab.columnCnt);
139 
140  CHECK (isnil (tab));
141  CHECK (0 == tab.size());
142  CHECK (0 == tab.id.data.size());
143  CHECK (0 == tab.val.data.size());
144  CHECK (0 == tab.off.data.size());
145  CHECK ("ID" == tab.id.header);
146  CHECK ("Value" == tab.val.header);
147  CHECK ("Offset" == tab.off.header);
148 
149  VERIFY_ERROR (STATE, tab.id.get() );
150  VERIFY_ERROR (STATE, tab.val.get());
151  VERIFY_ERROR (STATE, tab.off.get());
152  VERIFY_ERROR (STATE, tab.off = 5 );
153  VERIFY_ERROR (STATE,(tab.off == 5));
154 
155  // direct access to the data is possible and tolerated
156  tab.val.data.push_back (5.5);
157  CHECK (tab.val == 5.5);
158  VERIFY_ERROR (STATE, (tab.off == 5));
159  CHECK (1 == tab.val.data.size());
160  CHECK (0 == tab.off.data.size());
161  CHECK (0 == tab.id.data.size());
162  CHECK (0 == tab.size());
163  CHECK (isnil (tab));
164 
165  tab.newRow();
166  CHECK ( "" == string{tab.id});
167  CHECK (5.5 == tab.val);
168  CHECK ( 0 == tab.off);
169  CHECK (1 == tab.val.data.size());
170  CHECK (1 == tab.off.data.size());
171  CHECK (1 == tab.id.data.size());
172  CHECK (1 == tab.size());
173  CHECK (not isnil (tab));
174  CHECK (tab.off.data == vector({0}));
175  CHECK (tab.val.data == vector({5.5}));
176 
177  tab.allColumns() = make_tuple("●", 2.3, -11);
178  CHECK ("●" == string{tab.id});
179  CHECK (2.3 == tab.val);
180  CHECK (-11 == tab.off);
181 
182  tab.dupRow();
183  tab.val = 42;
184  tab.id = "◆";
185  CHECK (tab.off.data == vector({-11,-11}));
186  CHECK (tab.val.data == vector({2.3,42.0}));
187  CHECK (tab.id.data == vector<string>({"●","◆"}));
188 
189  tab.reserve(100);
190  CHECK (tab.id.data.capacity() >= 100);
191  CHECK (tab.val.data.capacity() >= 100);
192  CHECK (tab.off.data.capacity() >= 100);
193  CHECK (tab.id.data.size() == 2);
194  CHECK (tab.val.data.size() == 2);
195  CHECK (tab.off.data.size() == 2);
196  CHECK (2 == tab.size());
197  CHECK ("◆" == string{tab.id});
198  CHECK ( 42 == tab.val);
199  CHECK (-11 == tab.off);
200 
201  meta::forEach (tab.allColumns()
202  ,[](auto& col){ col.data.resize(2); }
203  );
204  CHECK (2 == tab.size());
205  CHECK ("◆" == string{tab.id});
206  CHECK ( 42 == tab.val);
207  CHECK (-11 == tab.off);
208 
209  tab.dropLastRow();
210  CHECK (1 == tab.size());
211  CHECK ("●" == string{tab.id});
212  CHECK (2.3 == tab.val);
213  CHECK (-11 == tab.off);
214  CHECK (tab.val.data.size() == 1);
215  CHECK (tab.val.data.capacity() >= 100);
216 
217  tab.clear();
218  CHECK (isnil (tab));
219  CHECK (tab.val.data.size() == 0);
220  CHECK (tab.val.data.capacity() >= 100);
221  }
222 
223 
225  void
227  {
228  double val = 1.0 / 3;
229  CHECK (util::toString(val) == "0.33333333"_expect );
230  CHECK (util::showDecimal(val) == "0.333333333333333"_expect );
231  CHECK (util::showComplete(val) == "0.33333333333333331"_expect);
232  CHECK (boost::lexical_cast<string>(val) == "0.33333333333333331"_expect);
233 
234  CHECK (format4Csv(double(1) / 3) == "0.333333333333333"_expect );
235  CHECK (format4Csv(float(1) / 3) == "0.333333"_expect );
236  CHECK (format4Csv(f128(1) / 3) == "0.333333333333333333"_expect);
237  CHECK (format4Csv(bool(1)) == "true"_expect );
238  CHECK (format4Csv(bool(0)) == "false"_expect);
239  CHECK (format4Csv("Starship-3") == "\"Starship-3\""_expect ); // 3rd test today ;-)
240  CHECK (format4Csv(Time(1,2,25,13)) == "\"13:25:02.001\""_expect);
241 
242 
243  string line;
244  int64_t ii = -100000;
245  bool boo = true;
246 
247  appendCsvField (line, ii);
248  CHECK (line == "-100000"_expect);
249  appendCsvField (line, val);
250  CHECK (line == "-100000,0.333333333333333"_expect);
251  appendCsvField (line, boo);
252  CHECK (line == "-100000,0.333333333333333,true"_expect);
253  appendCsvField (line, "Raptor");
254  CHECK (line == "-100000,0.333333333333333,true,\"Raptor\""_expect);
255 
256 
257  CsvParser parse{line};
258  CHECK (parse.isValid());
259  CHECK (*parse == "-100000"_expect);
260  CHECK (-100000 == parseAs<int>(*parse));
261  ++parse;
262  CHECK (parse.isValid());
263  CHECK (*parse == "0.333333333333333"_expect);
264  CHECK (0.333333343f == parseAs<float>(*parse));
265  ++parse;
266  CHECK (parse.isValid());
267 
268  CHECK (*parse == "true"_expect);
269  CHECK (true == parseAs<bool>(*parse));
270  ++parse;
271  CHECK (parse.isValid());
272  CHECK (*parse == "Raptor"_expect);
273  CHECK ("Raptor" == parseAs<string>(*parse));
274  ++parse;
275  CHECK (not parse.isValid());
276 
277  line = " ◐0◑. ; \t \"' \" \n ,oh my ;";
278  CsvParser horror{line};
279  CHECK ("◐0◑." == *horror); // as far as our CSV format is concerned, this is valid
280  CHECK (0 == horror.getParsedFieldCnt());
281  ++horror;
282  CHECK (1 == horror.getParsedFieldCnt());
283  CHECK ("' " == *horror);
284  ++horror;
285  CHECK ("oh" == *horror);
286  CHECK (2 == horror.getParsedFieldCnt());
287 
288  // next field is not quoted, but contains space
289  VERIFY_FAIL (",oh |↯|my ;", ++horror );
290 
291  CHECK (not horror.isValid());
292  CHECK (horror.isParseFail());
293 
294  // CsvParser is a »Lumiera Forward Iterator«
296  }
297 
298 
300  void
302  {
303  TempDir temp;
304  // prepare a data file to load into the table...
305  fs::path f = temp.makeFile("dataz.csv");
306  std::ofstream content{f};
307  content << R"("ID", "Value", "Offset")"<<endl
308  << R"( "one" , 5.5 ; +1 )"<<endl
309  << R"(;" 0 ";0)" <<endl; // ◁────── demonstrating some leeway in storage format
310  content.close();
311 
312  TestTab dat{f};
313  CHECK (2 == dat.size());
314  CHECK ("ID" == dat.id.header);
315  CHECK ("Value" == dat.val.header);
316  CHECK ("Offset" == dat.off.header);
317  //Note: data is reversed in storage — last/newest line first
318  CHECK ("one" == string{dat.id});
319  CHECK ( 5.5 == dat.val);
320  CHECK ( 1 == dat.off);
321  CHECK (dat.id.data == vector<string>({"","one"}));
322  CHECK (dat.val.data == vector<double>({0 ,5.5 }));
323  CHECK (dat.off.data == vector<int> ({0 ,1 }));
324 
325  // can modify some values....
326  dat.id = "mid";
327  dat.dupRow();
328  dat.id = "last";
329  dat.off *= -1;
330  // can render the contents as CSV
331  CHECK (dat.renderCSV() ==
332 R"("ID","Value","Offset"
333 "",0,0
334 "mid",5.5,1
335 "last",5.5,-1
336 )"_expect);
337 
338  // save complete table in current state, overwriting on disk
339  dat.save();
340 
341  // read back data rewritten on disk...
342  std::ifstream readback{f};
343  std::ostringstream inBuff;
344  inBuff << readback.rdbuf();
345  CHECK (inBuff.str() ==
346 R"("ID","Value","Offset"
347 "last",5.5,-1
348 "mid",5.5,1
349 "",0,0
350 )"_expect);
351  // note again the reversed order in storage: last line at top
352  }
353 
354 
355 
357  void
359  {
360  CHECK (CSVLine(1,"2",3.4,5555/55) == "1,\"2\",3.4,101"_expect);
361  CHECK (CSVLine(string{"himself"}) == "\"himself\""_expect);
362  CHECK (CSVLine{CSVLine{1e9}} == "1000000000"_expect);
363  CHECK (CSVLine{} == ""_expect);
364 
365  auto appended = (CSVLine{} += 5.5) += Symbol();
366  CHECK (appended == "5.5,\"⟂\""_expect);
367 
368  CHECK (CSVData({"eeny","meeny","miny","moe"}) == "\"eeny\",\"meeny\",\"miny\",\"moe\"\n"_expect);
369  CHECK (CSVData({"eeny , meeny","miny","moe"}) == "\"eeny , meeny\"\n\"miny\"\n\"moe\"\n"_expect); // you dirty dirty dishrag you
370 
371  auto csv = CSVData{{"la","la","schland"}
372  ,{{3.2,1l,88}
373  ,{"mit", string{"mia"}, Literal("ned")}
374  ,CSVLine(string(";"))
375  ,{false}
376  ,{}
377  }};
378  CHECK (csv.size() == 6);
379  CHECK (string(csv) ==
380 R"("la","la","schland"
381 3.2,1,88
382 "mit","mia","ned"
383 ";"
384 false
385 
386 )"_expect);
387 
388  VERIFY_FAIL ("Header mismatch in CSV file", TestTab{csv} );
389 
390  csv = CSVData{{"ID","Value","Offset"}
391  ,{{"Baby","toe"}
392  }};
393  VERIFY_FAIL ("unable to parse \"toe\"", TestTab{csv} );
394 
395  csv = CSVData{{"ID","Value","Offset"}
396  ,{{"Baby",1.6180,23}
397  ,{"Tiger",10101,-5}
398  }};
399  TestTab dat{csv};
400  CHECK (dat.val == 1.0101e4);
401  CHECK (dat.renderCSV() == string(csv));
402  }
403  };
404 
405  LAUNCHER (DataCSV_test, "unit calculation");
406 
407 
408 }}} // namespace lib::stat::test
Automatically use custom string conversion in C++ stream output.
Wrapper to simplify notation in tests.
Definition: csv.hpp:149
Definition: run.hpp:49
void appendCsvField(string &csv, VAL const &val)
Format and append a data value to a CSV string representation.
Definition: csv.hpp:103
inline string literal This is a marker type to indicate that
Definition: symbol.hpp:85
A string with the ability to construct or append the CSV-rendering of data fields.
Definition: csv.hpp:114
#define VERIFY_ERROR(ERROR_ID, ERRONEOUS_STATEMENT)
Macro to verify that a statement indeed raises an exception.
Manage a temporary directory for storage, with automated clean-up.
Implementation namespace for support and library code.
Lumiera&#39;s internal time value datatype.
Definition: timevalue.hpp:308
Token or Atom with distinct identity.
Definition: symbol.hpp:126
Manage a table with data records, stored persistently as CSV.
Simple test class runner.
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...
Trait template to detect a type usable immediately as "Lumiera Forward Iterator" in a specialised for...
Definition: trait.hpp:505
A collection of frequently used helper functions to support unit testing.
A RAII style temporary directory.
Definition: temp-dir.hpp:63
Table with data values, stored persistently as CSV file.
Definition: data.hpp:194
a family of time value like entities and their relationships.
Parser to split one line of CSV data into fields.
Definition: csv.hpp:250
#define VERIFY_FAIL(FAILURE_MSG, ERRONEOUS_STATEMENT)
Macro to verify that a statement indeed raises a std::exception, which additionally contains some FAI...