1 /*
2  * ebusd - daemon for communication with eBUS heating systems.
3  * Copyright (C) 2014-2021 John Baier <ebusd@ebusd.eu>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <iostream>
20 #include <iomanip>
21 #include <string>
22 #include <vector>
23 #include "lib/ebus/filereader.h"
24 
25 using namespace std;
26 using namespace ebusd;
27 
28 static bool error = false;
29 
verify(bool expectFailMatch,string type,string input,bool match,string expectStr,string gotStr)30 void verify(bool expectFailMatch, string type, string input,
31     bool match, string expectStr, string gotStr) {
32   match = match && expectStr == gotStr;
33   if (expectFailMatch) {
34     if (match) {
35       cout << "  failed " << type << " match >" << input
36               << "< error: unexpectedly succeeded" << endl;
37       error = true;
38     } else {
39       cout << "  failed " << type << " match >" << input << "< OK" << endl;
40     }
41   } else if (match) {
42     cout << "  " << type << " match >" << input << "< OK" << endl;
43   } else {
44     cout << "  " << type << " match >" << input << "< error: got >"
45             << gotStr << "<, expected >" << expectStr << "<" << endl;
46       error = true;
47   }
48 }
49 
50 string resultlines[][3] = {
51   {"col 1", "col 2", "col 3"},
52   {"line 2 col 1 de", "line 2 col 2", "line 2 \"col 3\";default of col 3"},
53   {"", "", ""},
54   {"line 4 col 1 de", "line 4 col 2 part 1;line 4 col 2 part 2", "line 4 col 3;default of col 3"},
55   {"", "", ""},
56   {"line 6 col 1 de", "", "line 6 col 3;default of col 3"},
57   {"", "", ""},
58   {"line 8 col 1 de", "line 8 col 2 part 1;line 8 col 2 part 2", "line 8 col 3;default of col 3"},
59 };
60 
61 string resultsublines[][2][4] = {
62   {},
63   {{"subcol 1", "line 2 subcol 1", "subcol 2", "line 2 subcol 2;default of sub 0 subcol 2"},{"subcol 2", "line 2 subcol 2", "subcol 3", "line 2 subcol 3"}},
64   {},
65   {{"subcol 1", "line 4 subcol 1", "subcol 2", "line 4 subcol 2;default of sub 0 subcol 2"},{"subcol 2", "line 4 subcol 2", "subcol 3", "line 4 subcol 3"}},
66   {},
67   {{"subcol 1", "line 6 subcol 1", "subcol 2", "line 6 subcol 2;default of sub 0 subcol 2"},{"subcol 2", "line 6 subcol 2", "subcol 3", "line 6 subcol 3"}},
68   {},
69   {{"subcol 1", "line 8 subcol 1", "subcol 2", "line 8 subcol 2;default of sub 0 subcol 2"},{"subcol 2", "line 8 subcol 2", "subcol 3", "line 8 subcol 3"}},
70 };
71 
72 static unsigned int baseLine = 0;
73 
74 class NoopReader : public FileReader {
75  public:
addFromFile(const string & filename,unsigned int lineNo,vector<string> * row,string * errorDescription,bool replace)76   result_t addFromFile(const string& filename, unsigned int lineNo, vector<string>* row, string* errorDescription,
77     bool replace) override {
78     return RESULT_OK;
79   }
80 };
81 
82 class TestReader : public MappedFileReader {
83  public:
TestReader(size_t expectedCols,size_t langCols)84   TestReader(size_t expectedCols, size_t langCols)
85   : MappedFileReader::MappedFileReader(false, ""), m_expectedCols(expectedCols), m_langCols(langCols) {}
getFieldMap(const string & preferLanguage,vector<string> * row,string * errorDescription) const86   result_t getFieldMap(const string& preferLanguage, vector<string>* row, string* errorDescription) const override {
87     if (row->size() == m_expectedCols+m_langCols) {
88       cout << "get field map: split OK" << endl;
89       if (m_langCols == 1) {
90         (*row)[0] = SKIP_COLUMN;
91         size_t pos = (*row)[1].find_last_of('.');
92         (*row)[1] = (*row)[1].substr(0, pos);
93       }
94       return RESULT_OK;
95     }
96     cout << "get field map: error got " << static_cast<unsigned>(row->size()) << " columns, expected " <<
97         static_cast<unsigned>(m_expectedCols+m_langCols) << endl;
98     return RESULT_ERR_EOF;
99   }
addFromFile(const string & filename,unsigned int lineNo,map<string,string> * row,vector<map<string,string>> * subRows,string * errorDescription,bool replace)100   result_t addFromFile(const string& filename, unsigned int lineNo, map<string, string>* row,
101       vector< map<string, string> >* subRows, string* errorDescription, bool replace) override {
102     if (row->empty() || (m_expectedCols == 3) != subRows->empty()) {
103       cout << "read line " << static_cast<unsigned>(baseLine + lineNo) << ": read error: got "
104           << static_cast<unsigned>(row->size()) << "/3 main, " << static_cast<unsigned>(subRows->size())
105           << (m_expectedCols == 3 ? "/0 sub" : "/>0 sub") << endl;
106       return RESULT_ERR_EOF;
107     }
108     if (lineNo < 2 || lineNo >= 1+sizeof(resultlines)/sizeof(string[3])) {
109       cout << "read line " << static_cast<unsigned>(baseLine + lineNo) << ": error invalid line" << endl;
110       return RESULT_ERR_INVALID_ARG;
111     }
112     cout << "read line " << static_cast<unsigned>(baseLine + lineNo) << ": split OK" << endl;
113     string* resultline = resultlines[lineNo - 1];
114     if (row->empty()) {
115       cout << "  result empty";
116       if (resultline[0] == "") {
117         cout << ": OK" << endl;
118       } else {
119         cout << ": error" << endl;
120         return RESULT_ERR_INVALID_ARG;
121       }
122       return RESULT_EMPTY;
123     }
124 
125     bool error = false;
126     string* colnames = resultlines[0];
127     map<string, string>& defaults = getDefaults()[""];
128     for (size_t colIdx = 0; colIdx < 3; colIdx++) {
129       string col = colnames[colIdx];
130       string got = (*row)[col] + defaults[col];
131       string expect = resultline[colIdx];
132       ostringstream type;
133       type << "line " << static_cast<unsigned>(baseLine + lineNo) << " column \"" << col << "\"";
134       bool match = got == expect;
135       verify(false, type.str(), expect, match, expect, got);
136       if (!match) {
137         error = true;
138       }
139     }
140     if (row->size() > 3) {
141       ostringstream type;
142       type << "line " << static_cast<unsigned>(baseLine + lineNo);
143       verify(false, type.str(), "", false, "", "extra column");
144       error = true;
145     }
146 
147     for (size_t subIdx = 0; subIdx < subRows->size(); subIdx++) {
148       string* resultsubline = resultsublines[lineNo - 1][subIdx];
149       *row = (*subRows)[subIdx];
150       if (row->empty()) {
151         cout << "  sub " << subIdx << " result empty";
152         if (resultline[0] == "") {
153           cout << ": OK" << endl;
154         } else {
155           cout << ": error" << endl;
156           return RESULT_ERR_INVALID_ARG;
157         }
158         return RESULT_EMPTY;
159       }
160 
161       vector< map<string, string> >& subDefaults = getSubDefaults()[""];
162       for (size_t colIdx = 0; colIdx < 2; colIdx++) {
163         string col = resultsubline[colIdx*2];
164         string got = (*row)[col];
165         if (subIdx < subDefaults.size()) {
166           got += subDefaults[subIdx][col];
167         }
168         string expect = resultsubline[colIdx*2+1];
169         ostringstream type;
170         type << "line " << static_cast<unsigned>(baseLine + lineNo) << " sub " << subIdx << " column \"" << col << "\"";
171         bool match = got == expect;
172         verify(false, type.str(), expect, match, expect, got);
173         if (!match) {
174           error = true;
175         }
176       }
177       if (row->size() > 2) {
178         ostringstream type;
179         type << "line " << static_cast<unsigned>(baseLine + lineNo) << " sub " << subIdx;
180         verify(false, type.str(), "", false, "", "extra sub column");
181         error = true;
182       }
183     }
184     return error ? RESULT_ERR_INVALID_ARG : RESULT_OK;
185   }
186  private:
187   size_t m_expectedCols;
188   size_t m_langCols;
189 };
190 
191 
main(int argc,char ** argv)192 int main(int argc, char** argv) {
193   if (argc > 1) {
194     NoopReader reader;
195     for (int argpos = 1; argpos < argc; argpos++) {
196       size_t hash = 0, size = 0;
197       time_t time = 0;
198       string errorDescription;
199       istream* stream = FileReader::openFile(argv[argpos], &errorDescription, &time);
200       result_t result;
201       if (!stream) {
202         result = RESULT_ERR_NOTFOUND;
203       } else {
204         result = reader.readFromStream(stream, argv[argpos], time, false, nullptr, &errorDescription, false, &hash, &size);
205       }
206       cout << argv[argpos] << " ";
207       if (result != RESULT_OK) {
208         cout << getResultCode(result) << ", " << errorDescription << endl;
209         error = true;
210         continue;
211       }
212       FileReader::formatHash(hash, &cout);
213       cout << " " << size << " " << time << endl;
214     }
215     return error ? 1 : 0;
216   }
217   baseLine = __LINE__+1;
218   istringstream ifs(
219     "col 1.en,col 1.de,col 2,col 3\n"
220     "line 2 col 1 en,line 2 col 1 de,\"line 2 col 2\",\"line 2 \"\"col 3\"\";default of col 3\"\n"
221     "line 4 col 1 en,line 4 col 1 de,\"line 4 col 2 part 1\n"
222     "line 4 col 2 part 2\",line 4 col 3;default of col 3\n"
223     ",,,\n"
224     "line 6 col 1 en,line 6 col 1 de,,line 6 col 3;default of col 3\n"
225     "line 8 col 1 en,line 8 col 1 de,\"line 8 col 2 part 1;\n"
226     "line 8 col 2 part 2\",line 8 col 3;default of col 3\n"
227   );
228   size_t hash = 0, size = 0, expectHash = 0xb958f1cb, expectSize = 389;
229   TestReader reader{3, 1};
230   unsigned int lineNo = 0;
231   vector<string> row;
232   string errorDescription;
233   while (ifs.peek() != EOF) {
234     result_t result = reader.readLineFromStream(&ifs, "", true, &lineNo, &row, &errorDescription, false, &hash, &size);
235     if (result != RESULT_OK) {
236       cout << "  error " << getResultCode(result) << endl;
237       error = true;
238     }
239   }
240   if (hash == expectHash) {
241     cout << "hash OK" << endl;
242   } else {
243     cout << "hash error: got 0x" << hex << hash << ", expected 0x" << expectHash << dec << endl;
244     error = true;
245   }
246   if (size == expectSize) {
247     cout << "size OK" << endl;
248   } else {
249     cout << "size error: got " << size << ", expected " << expectSize << endl;
250     error = true;
251   }
252 
253   ifs.clear();
254   baseLine = __LINE__+1;
255   ifs.str(
256     "col 1,col 2,col 3,*subcol 1,subcol 2,*subcol 2,subcol 3\n"
257     "line 2 col 1 de,\"line 2 col 2\",\"line 2 \"\"col 3\"\"\",line 2 subcol 1,line 2 subcol 2,line 2 subcol 2,line 2 subcol 3\n"
258     "line 4 col 1 de,\"line 4 col 2 part 1\n"
259     "line 4 col 2 part 2\",line 4 col 3,line 4 subcol 1,line 4 subcol 2,line 4 subcol 2,line 4 subcol 3\n"
260     ",,,\n"
261     "line 6 col 1 de,,line 6 col 3,line 6 subcol 1,line 6 subcol 2,line 6 subcol 2,line 6 subcol 3\n"
262     "line 8 col 1 de,\"line 8 col 2 part 1;\n"
263     "line 8 col 2 part 2\",line 8 col 3,line 8 subcol 1,line 8 subcol 2,line 8 subcol 2,line 8 subcol 3\n"
264   );
265   hash = 0, size = 0, expectHash = 0x2584e0f2, expectSize = 539;
266   TestReader reader2{7, 0};
267   lineNo = 0;
268   map<string, string> defaults;
269   reader2.getDefaults()[""]["col 3"] = ";default of col 3";
270   vector< map<string, string> >& subDefaults = reader2.getSubDefaults()[""];
271   subDefaults.resize(1);
272   subDefaults[0]["subcol 2"] = ";default of sub 0 subcol 2";
273   while (ifs.peek() != EOF) {
274     result_t result = reader2.readLineFromStream(&ifs, "", true, &lineNo, &row, &errorDescription, false, &hash, &size);
275     if (result != RESULT_OK) {
276       cout << "  error " << getResultCode(result) << endl;
277       error = true;
278     }
279   }
280   if (hash == expectHash) {
281     cout << "hash OK" << endl;
282   } else {
283     cout << "hash error: got 0x" << hex << hash << ", expected 0x" << expectHash << dec << endl;
284     error = true;
285   }
286   if (size == expectSize) {
287     cout << "size OK" << endl;
288   } else {
289     cout << "size error: got " << size << ", expected " << expectSize << endl;
290     error = true;
291   }
292 
293   return error ? 1 : 0;
294 }
295