Skip to content

Commit f0248c3

Browse files
committed
include/nutconf.hpp, common/nutwriter.cpp, tests/nutconf_ut.cpp: introduce and unit-test a BoolInt helper class [networkupstools#2402]
Signed-off-by: Jim Klimov <[email protected]>
1 parent 865a88d commit f0248c3

File tree

2 files changed

+318
-0
lines changed

2 files changed

+318
-0
lines changed

include/nutconf.hpp

+196
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,202 @@ class Serialisable
143143
}; // end of class Serialisable
144144

145145

146+
/**
147+
* \brief Mix a boolean (yes/no) or wider integer range
148+
*/
149+
class BoolInt
150+
{
151+
private:
152+
Settable<bool> b;
153+
Settable<int> i;
154+
155+
public:
156+
inline void clear()
157+
{
158+
i.clear();
159+
b.clear();
160+
}
161+
162+
inline BoolInt& operator=(const BoolInt& other)
163+
{
164+
clear();
165+
166+
if (other.b.set()) b = other.b;
167+
if (other.i.set()) i = other.i;
168+
return *this;
169+
}
170+
171+
inline BoolInt& operator=(BoolInt&& other)
172+
{
173+
clear();
174+
175+
if (other.b.set()) b = other.b;
176+
if (other.i.set()) i = other.i;
177+
return *this;
178+
}
179+
180+
inline BoolInt& operator=(int val)
181+
{
182+
clear();
183+
i = val;
184+
return *this;
185+
}
186+
187+
inline BoolInt& operator=(bool val)
188+
{
189+
clear();
190+
b = val;
191+
return *this;
192+
}
193+
194+
inline BoolInt& operator=(const char* s)
195+
{
196+
if (!s)
197+
throw std::invalid_argument(
198+
"BoolInt value from <null> string is is not supported");
199+
200+
std::string src(s);
201+
return (*this = src);
202+
}
203+
204+
inline BoolInt& operator=(std::string src)
205+
{
206+
static const Settable<bool> b0(false);
207+
static const Settable<bool> b1(true);
208+
209+
// NOTE: Not a pointer, is not null at least
210+
if (src.empty())
211+
throw std::invalid_argument(
212+
"BoolInt value from <empty> string is is not supported");
213+
214+
clear();
215+
216+
if ("false" == src) { b = b0; return *this; }
217+
if ("off" == src) { b = b0; return *this; }
218+
if ("0" == src) { b = b0; return *this; }
219+
if ("no" == src) { b = b0; return *this; }
220+
221+
if ("true" == src) { b = b1; return *this; }
222+
if ("on" == src) { b = b1; return *this; }
223+
if ("1" == src) { b = b1; return *this; }
224+
if ("yes" == src) { b = b1; return *this; }
225+
if ("ok" == src) { b = b1; return *this; }
226+
227+
std::stringstream ss(src);
228+
int result;
229+
if (ss >> result && ss.rdbuf()->in_avail() == 0) {
230+
// Conversion succeeded and all chars were read
231+
// (e.g. not a decimal number)
232+
i = result;
233+
#ifdef DEBUG
234+
std::cerr << "BoolInt assigned from '" << src
235+
<< "': got int '" << result << "'"
236+
<< " stream empty? " << ss.rdbuf()->in_avail()
237+
<< std::endl;
238+
#endif
239+
return *this;
240+
}
241+
242+
throw std::invalid_argument("BoolInt value from '" + src +
243+
"' string not understood as bool nor int");
244+
}
245+
246+
inline bool operator==(const BoolInt& other)const
247+
{
248+
// Either direct values are set and then equal;
249+
// else numeric values of int and bool are cross-equal.
250+
if (b.set() && other.b.set()) return (b == other.b);
251+
if (i.set() && other.i.set()) return (i == other.i);
252+
253+
// false if at least one object has neither i nor b set(), or
254+
// if their numeric values do not match up as 0 or 1 exactly.
255+
if (i.set() && other.b.set())
256+
return ( (other.b && i == 1) || (!other.b && i == 0) );
257+
if (b.set() && other.i.set())
258+
return ( (b && other.i == 1) || (!b && other.i == 0) );
259+
260+
return false;
261+
}
262+
263+
inline bool operator==(const bool other)const
264+
{
265+
if (b.set()) return (b == other);
266+
if (i.set()) return ((other && i == 1) || (!other && i == 0));
267+
return false;
268+
}
269+
270+
inline bool operator==(const int other)const
271+
{
272+
if (i.set()) return (i == other);
273+
if (b.set()) return ((b && other == 1) || (!b && other == 0));
274+
return false;
275+
}
276+
277+
inline bool operator==(const std::string other)const
278+
{
279+
BoolInt tmp;
280+
tmp = other;
281+
return (*this == tmp);
282+
}
283+
284+
inline bool operator==(const char* s)const
285+
{
286+
if (!s)
287+
return false;
288+
289+
std::string src(s);
290+
return (*this == src);
291+
}
292+
293+
inline bool set()const
294+
{
295+
if (i.set() && b.set())
296+
throw std::invalid_argument(
297+
"BoolInt value somehow got both bool and int values set");
298+
299+
return (i.set() || b.set());
300+
}
301+
302+
operator int() {
303+
if (i.set()) return i;
304+
if (b.set()) {
305+
if (b) return 1;
306+
return 0;
307+
}
308+
309+
throw std::invalid_argument(
310+
"BoolInt value not set, neither to bool nor to int");
311+
}
312+
313+
operator bool() {
314+
if (b.set()) return b;
315+
if (i.set()) {
316+
if (i == 0) return false;
317+
if (i == 1) return true;
318+
}
319+
320+
throw std::invalid_argument(
321+
"BoolInt value not set, neither to bool nor to int");
322+
}
323+
324+
operator std::string() {
325+
if (b.set()) {
326+
if (b) return "yes";
327+
return "no";
328+
}
329+
330+
if (i.set()) {
331+
std::ostringstream ss;
332+
ss << i;
333+
return ss.str();
334+
}
335+
336+
throw std::invalid_argument(
337+
"BoolInt value not set, neither to bool nor to int");
338+
}
339+
};
340+
341+
146342
/**
147343
* \brief Certificate Identification structure for NUT
148344
*

tests/nutconf_parser_ut.cpp

+122
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class NutConfTest : public CppUnit::TestFixture
4040
CPPUNIT_TEST( testOptions );
4141
CPPUNIT_TEST( testParseCHARS );
4242
CPPUNIT_TEST( testParseSTRCHARS );
43+
CPPUNIT_TEST( testParseBoolInt );
4344
CPPUNIT_TEST( testParseToken );
4445
CPPUNIT_TEST( testParseTokenWithoutColon );
4546
CPPUNIT_TEST( testGenericConfigParser );
@@ -55,6 +56,7 @@ class NutConfTest : public CppUnit::TestFixture
5556
void testOptions();
5657
void testParseCHARS();
5758
void testParseSTRCHARS();
59+
void testParseBoolInt();
5860
void testParseToken();
5961
void testParseTokenWithoutColon();
6062

@@ -140,6 +142,126 @@ void NutConfTest::testParseSTRCHARS()
140142
}
141143
}
142144

145+
void NutConfTest::testParseBoolInt()
146+
{
147+
// NOTE: Can not use CPPUNIT_ASSERT_EQUAL() below, requires an assertEqual() method
148+
BoolInt bi;
149+
CPPUNIT_ASSERT_MESSAGE("BoolInt should be not 'set()' initially", !(bi.set()));
150+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("Unassigned BoolInt should not be equal to an int",
151+
CPPUNIT_ASSERT(bi == 0));
152+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("Unassigned BoolInt should not be equal to an int",
153+
CPPUNIT_ASSERT(bi == 1));
154+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("Unassigned BoolInt should not be equal to a bool",
155+
CPPUNIT_ASSERT(bi == true));
156+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("Unassigned BoolInt should not be equal to a bool",
157+
CPPUNIT_ASSERT(bi == false));
158+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("Unassigned BoolInt should not be equal to a string",
159+
CPPUNIT_ASSERT(bi == "2"));
160+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("Unassigned BoolInt should not be equal to a string",
161+
CPPUNIT_ASSERT(bi == "on"));
162+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("Unassigned BoolInt should not be equal to a string",
163+
CPPUNIT_ASSERT(bi == "no"));
164+
/*
165+
// Actually not "must throw", just returns false for any comparisons - see above
166+
CPPUNIT_ASSERT_THROW_MESSAGE("Unassigned BoolInt comparisons must throw exceptions (string)",
167+
if (bi == "1") {}, std::invalid_argument);
168+
CPPUNIT_ASSERT_THROW_MESSAGE("Unassigned BoolInt comparisons must throw exceptions (int)",
169+
if (bi == 1) {}, std::invalid_argument);
170+
CPPUNIT_ASSERT_THROW_MESSAGE("Unassigned BoolInt comparisons must throw exceptions (bool)",
171+
if (bi == true) {}, std::invalid_argument);
172+
*/
173+
174+
bi = 42;
175+
CPPUNIT_ASSERT_MESSAGE("BoolInt should be 'set()' after assignment from int", bi.set());
176+
CPPUNIT_ASSERT_MESSAGE("BoolInt should be equal to the int", (bi == 42));
177+
CPPUNIT_ASSERT_MESSAGE("BoolInt should be equal to the string value of int", (bi == "42"));
178+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("BoolInt should not be equal to another int",
179+
CPPUNIT_ASSERT(bi == 2));
180+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("BoolInt should not be equal to a bool",
181+
CPPUNIT_ASSERT(bi == true));
182+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("BoolInt should not be equal to a bool",
183+
CPPUNIT_ASSERT(bi == false));
184+
185+
bi = true;
186+
CPPUNIT_ASSERT_MESSAGE("BoolInt should be 'set()' after assignment from bool", bi.set());
187+
CPPUNIT_ASSERT_MESSAGE("BoolInt should be equal to the bool", (bi == true));
188+
CPPUNIT_ASSERT_MESSAGE("BoolInt should be equal to the int value of bool", (bi == 1));
189+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("BoolInt should not be equal to another bool",
190+
CPPUNIT_ASSERT(bi == false));
191+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("BoolInt should not be equal to int value of another bool",
192+
CPPUNIT_ASSERT(bi == 0));
193+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("BoolInt should not be equal to another int",
194+
CPPUNIT_ASSERT(bi == 2));
195+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("BoolInt should not be equal to old int",
196+
CPPUNIT_ASSERT(bi == 42));
197+
198+
bi = false;
199+
CPPUNIT_ASSERT_MESSAGE("BoolInt should be 'set()' after assignment from bool", bi.set());
200+
CPPUNIT_ASSERT_MESSAGE("BoolInt should be equal to the bool", (bi == false));
201+
CPPUNIT_ASSERT_MESSAGE("BoolInt should be equal to the int value of bool", (bi == 0));
202+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("BoolInt should not be equal to another bool",
203+
CPPUNIT_ASSERT(bi == true));
204+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("BoolInt should not be equal to int value of another bool",
205+
CPPUNIT_ASSERT(bi == 1));
206+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("BoolInt should not be equal to another int",
207+
CPPUNIT_ASSERT(bi == 2));
208+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("BoolInt should not be equal to old int",
209+
CPPUNIT_ASSERT(bi == 42));
210+
211+
bi = "-1";
212+
CPPUNIT_ASSERT_MESSAGE("BoolInt should be 'set()' after assignment from string", bi.set());
213+
CPPUNIT_ASSERT_MESSAGE("BoolInt should be equal to the int", (bi == -1));
214+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("BoolInt should not be equal to another int",
215+
CPPUNIT_ASSERT(bi == 2));
216+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("BoolInt should not be equal to a bool",
217+
CPPUNIT_ASSERT(bi == true));
218+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("BoolInt should not be equal to a bool",
219+
CPPUNIT_ASSERT(bi == false));
220+
221+
bi = "off";
222+
CPPUNIT_ASSERT_MESSAGE("BoolInt should be 'set()' after assignment from string", bi.set());
223+
CPPUNIT_ASSERT_MESSAGE("BoolInt should be equal to the bool", (bi == false));
224+
CPPUNIT_ASSERT_MESSAGE("BoolInt should be equal to the int value of bool", (bi == 0));
225+
CPPUNIT_ASSERT_MESSAGE("BoolInt should be equal to the string value of bool", (bi == "off"));
226+
CPPUNIT_ASSERT_MESSAGE("BoolInt should be equal to the string value of bool", (bi == "false"));
227+
CPPUNIT_ASSERT_MESSAGE("BoolInt should be equal to the string value of bool", (bi == "0"));
228+
CPPUNIT_ASSERT_MESSAGE("BoolInt should be equal to the string value of bool", (bi == "no"));
229+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("BoolInt should not be equal to the string value of another bool",
230+
CPPUNIT_ASSERT(bi == "yes"));
231+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("BoolInt should not be equal to the string value of another bool",
232+
CPPUNIT_ASSERT(bi == "true"));
233+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("BoolInt should not be equal to the string value of another bool",
234+
CPPUNIT_ASSERT(bi == "1"));
235+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("BoolInt should not be equal to the string value of another bool",
236+
CPPUNIT_ASSERT(bi == "on"));
237+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("BoolInt should not be equal to the string value of another bool",
238+
CPPUNIT_ASSERT(bi == "ok"));
239+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("BoolInt should not be equal to another bool",
240+
CPPUNIT_ASSERT(bi == true));
241+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("BoolInt should not be equal to int value of another bool",
242+
CPPUNIT_ASSERT(bi == 1));
243+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("BoolInt should not be equal to another int",
244+
CPPUNIT_ASSERT(bi == 2));
245+
CPPUNIT_ASSERT_ASSERTION_FAIL_MESSAGE("BoolInt should not be equal to old int",
246+
CPPUNIT_ASSERT(bi == 42));
247+
248+
CPPUNIT_ASSERT_THROW_MESSAGE("BoolInt assignment from invalid strings must throw exceptions",
249+
(bi = "AbraCadabra"), std::invalid_argument);
250+
251+
CPPUNIT_ASSERT_THROW_MESSAGE("BoolInt assignment from invalid strings must throw exceptions",
252+
(bi = "1.5"), std::invalid_argument);
253+
254+
CPPUNIT_ASSERT_THROW_MESSAGE("BoolInt assignment from invalid strings must throw exceptions",
255+
(bi = "-3.8"), std::invalid_argument);
256+
257+
// Standard casing only
258+
CPPUNIT_ASSERT_THROW_MESSAGE("BoolInt assignment from invalid strings must throw exceptions",
259+
(bi = "OFF"), std::invalid_argument);
260+
261+
bi.clear();
262+
CPPUNIT_ASSERT_MESSAGE("BoolInt should be not 'set()' after 'clear()", !(bi.set()));
263+
}
264+
143265
void NutConfTest::testParseToken()
144266
{
145267
static const char* src =

0 commit comments

Comments
 (0)