mxlib
c++ tools for analyzing astronomical data and other tasks by Jared R. Males. [git repo]
Loading...
Searching...
No Matches
appConfigurator_test.cpp
Go to the documentation of this file.
1/** \file appConfigurator_test.cpp
2 * \brief Tests application configuration parsing.
3 */
4
5#include "../../catch2/catch.hpp"
6
7#include <fstream>
8
10
11using namespace mx::app;
12
13/// \cond appConfiguratorTestHelpers
14namespace
15{
16
17std::string logName;
18int logCode;
19std::string logValue;
20std::string logSource;
21
22void captureConfigLog( const std::string &name, const int &code, const std::string &value, const std::string &source )
23{
24 logName = name;
25 logCode = code;
26 logValue = value;
27 logSource = source;
28}
29
30void resetConfigLog()
31{
32 logName.clear();
33 logCode = 0;
34 logValue.clear();
35 logSource.clear();
36}
37
38/// Return a deterministic parser allocation failure without changing parser state.
39int allocationFailureParser( iniFile &parser /**< [in,out] parser ignored by the injected failure. */,
40 const std::string &filename /**< [in] filename ignored by the injected failure. */ )
41{
42 static_cast<void>( parser );
43 static_cast<void>( filename );
44 return -2;
45}
46
47/// Restore the production parser after a readConfig failure-injection test.
48struct parserReset
49{
50 /// Reset the parser hook before the test body runs.
51 parserReset()
52 {
54 }
55
56 /// Restore the production parser when the test scope exits.
57 ~parserReset()
58 {
60 }
61};
62
63} // namespace
64/// \endcond
65
66/// Verify that appConfigurator parses global, sectioned, and unused configuration entries.
67/**
68 * \ingroup appConfigurator_unit_tests
69 */
70TEST_CASE( "config file parsing", "[appConfigurator]" )
71{
72 GIVEN( "a basic config file" )
73 {
74 WHEN( "no sections" )
75 {
76 writeConfigFile( "/tmp/test.conf",
77 { "", "", "", "", "", "" },
78 { "key0", "key1", "key2", "key3", "key4", "key5" },
79 { "val0", "val1", "val2", "val3", "val4", "val5" } );
80
82 config.add( "key0", "", "", 0, "", "key0", false, "", "" );
83 config.add( "key1", "", "", 0, "", "key1", false, "", "" );
84 config.add( "key2", "", "", 0, "", "key2", false, "", "" );
85 config.add( "key3", "", "", 0, "", "key3", false, "", "" );
86 config.add( "key4", "", "", 0, "", "key4", false, "", "" );
87 config.add( "key5", "", "", 0, "", "key5", false, "", "" );
88
89 config.readConfig( "/tmp/test.conf" );
90
91 std::string val;
92
93 config( val, "key0" );
94 REQUIRE( val == "val0" );
95
96 config( val, "key1" );
97 REQUIRE( val == "val1" );
98
99 config( val, "key2" );
100 REQUIRE( val == "val2" );
101
102 config( val, "key3" );
103 REQUIRE( val == "val3" );
104
105 config( val, "key4" );
106 REQUIRE( val == "val4" );
107
108 config( val, "key5" );
109 REQUIRE( val == "val5" );
110 }
111
112 WHEN( "sections, unique keys" )
113 {
114 writeConfigFile( "/tmp/test.conf",
115 { "", "", "sect1", "sect1", "sect2", "sect2" },
116 { "key0", "key1", "key2", "key3", "key4", "key5" },
117 { "val0", "val1", "val2", "val3", "val4", "val5" } );
118
120 config.add( "key0", "", "", 0, "", "key0", false, "", "" );
121 config.add( "key1", "", "", 0, "", "key1", false, "", "" );
122 config.add( "sect1.key2", "", "", 0, "sect1", "key2", false, "", "" );
123 config.add( "sect1.key3", "", "", 0, "sect1", "key3", false, "", "" );
124 config.add( "sect2.key4", "", "", 0, "sect2", "key4", false, "", "" );
125 config.add( "sect2.key5", "", "", 0, "sect2", "key5", false, "", "" );
126
127 config.readConfig( "/tmp/test.conf" );
128
129 std::string val;
130
131 config( val, "key0" );
132 REQUIRE( val == "val0" );
133
134 config( val, "key1" );
135 REQUIRE( val == "val1" );
136
137 config( val, "sect1.key2" );
138 REQUIRE( val == "val2" );
139
140 config( val, "sect1.key3" );
141 REQUIRE( val == "val3" );
142
143 config( val, "sect2.key4" );
144 REQUIRE( val == "val4" );
145
146 config( val, "sect2.key5" );
147 REQUIRE( val == "val5" );
148 }
149
150 WHEN( "sections, repeated keys still unique within sections" )
151 {
152 writeConfigFile( "/tmp/test.conf",
153 { "", "", "sect1", "sect1", "sect2", "sect2" },
154 { "key0", "key1", "key2", "key3", "key2", "key3" },
155 { "val0", "val1", "val2", "val3", "val4", "val5" } );
156
158 config.add( "key0", "", "", 0, "", "key0", false, "", "" );
159 config.add( "key1", "", "", 0, "", "key1", false, "", "" );
160 config.add( "sect1.key2", "", "", 0, "sect1", "key2", false, "", "" );
161 config.add( "sect1.key3", "", "", 0, "sect1", "key3", false, "", "" );
162 config.add( "sect2.key2", "", "", 0, "sect2", "key2", false, "", "" );
163 config.add( "sect2.key3", "", "", 0, "sect2", "key3", false, "", "" );
164
165 config.readConfig( "/tmp/test.conf" );
166
167 std::string val;
168
169 config( val, "key0" );
170 REQUIRE( val == "val0" );
171
172 config( val, "key1" );
173 REQUIRE( val == "val1" );
174
175 config( val, "sect1.key2" );
176 REQUIRE( val == "val2" );
177
178 config( val, "sect1.key3" );
179 REQUIRE( val == "val3" );
180
181 config( val, "sect2.key2" );
182 REQUIRE( val == "val4" );
183
184 config( val, "sect2.key3" );
185 REQUIRE( val == "val5" );
186 }
187 }
188
189 GIVEN( "a config file with unused entries" )
190 {
191
192 WHEN( "no sections, unused entry in middle" )
193 {
194 writeConfigFile( "/tmp/test.conf",
195 { "", "", "", "", "", "" },
196 { "key0", "key1", "key2", "key3", "key4", "key5" },
197 { "val0", "val1", "val2", "val3", "val4", "val5" } );
198
200 config.add( "key0", "", "", 0, "", "key0", false, "", "" );
201 config.add( "key1", "", "", 0, "", "key1", false, "", "" );
202 config.add( "key2", "", "", 0, "", "key2", false, "", "" );
203 config.add( "key4", "", "", 0, "", "key4", false, "", "" );
204 config.add( "key5", "", "", 0, "", "key5", false, "", "" );
205
206 config.readConfig( "/tmp/test.conf" );
207
208 std::string val;
209
210 // Check normal parsing
211 config( val, "key0" );
212 REQUIRE( val == "val0" );
213
214 config( val, "key1" );
215 REQUIRE( val == "val1" );
216
217 config( val, "key2" );
218 REQUIRE( val == "val2" );
219
220 config( val, "key4" );
221 REQUIRE( val == "val4" );
222
223 config( val, "key5" );
224 REQUIRE( val == "val5" );
225
226 // Check taht the unused on is unused
227 REQUIRE( config.m_unusedConfigs[iniFile::makeKey( "", "key3" )].used == false );
228
229 // Check that the unused one is available.
230 config.configUnused( val, "", "key3" );
231 REQUIRE( val == "val3" );
232
233 // Check taht the unused on is now used
234 REQUIRE( config.m_unusedConfigs[iniFile::makeKey( "", "key3" )].used == true );
235 }
236
237 WHEN( "sections, repeated keys, unused sections" )
238 {
239 writeConfigFile( "/tmp/test.conf",
240 { "", "", "sect1", "sect1", "sect2", "sect2", "sect3" },
241 { "key0", "key1", "key2", "key3", "key2", "key3", "key4" },
242 { "val0", "val1", "val2", "val3", "val4", "val5", "val6" } );
243
245 config.add( "key0", "", "", 0, "", "key0", false, "", "" );
246 config.add( "key1", "", "", 0, "", "key1", false, "", "" );
247 config.add( "sect2.key2", "", "", 0, "sect2", "key2", false, "", "" );
248 config.add( "sect2.key3", "", "", 0, "sect2", "key3", false, "", "" );
249
250 config.readConfig( "/tmp/test.conf" );
251
252 std::string val;
253
254 config( val, "key0" );
255 REQUIRE( val == "val0" );
256
257 config( val, "key1" );
258 REQUIRE( val == "val1" );
259
260 config( val, "sect2.key2" );
261 REQUIRE( val == "val4" );
262
263 config( val, "sect2.key3" );
264 REQUIRE( val == "val5" );
265
266 config.configUnused( val, "sect1", "key2" );
267 REQUIRE( val == "val2" );
268
269 config.configUnused( val, "sect1", "key3" );
270 REQUIRE( val == "val3" );
271
272 config.configUnused( val, "sect3", "key4" );
273 REQUIRE( val == "val6" );
274
275 std::vector<std::string> sections;
276 config.unusedSections( sections );
277 REQUIRE( sections.size() == 2 );
278 REQUIRE( ( sections[0] == "sect1" || sections[1] == "sect1" ) );
279 REQUIRE( ( sections[0] == "sect3" || sections[1] == "sect3" ) );
280 }
281 }
282
283#if 1
284 GIVEN( "a config file with repeated keys within the same section" )
285 {
286 WHEN( "no sections" )
287 {
288 writeConfigFile( "/tmp/test.conf",
289 { "", "", "", "", "", "" },
290 { "key0", "key0", "key2", "key2", "key4", "key4" },
291 { "val0", "val1", "val2", "val3", "val4", "val5" } );
292
294 config.add( "key0", "", "", 0, "", "key0", false, "", "" );
295 config.add( "key2", "", "", 0, "", "key2", false, "", "" );
296 config.add( "key4", "", "", 0, "", "key4", false, "", "" );
297
298 config.readConfig( "/tmp/test.conf" );
299
300 std::string val;
301
302 config( val, "key0" );
303 REQUIRE( val == "val0val1" );
304
305 config( val, "key2" );
306 REQUIRE( val == "val2val3" );
307
308 config( val, "key4" );
309 REQUIRE( val == "val4val5" );
310 }
311
312 WHEN( "repeated sections and keys" )
313 {
314 writeConfigFile( "/tmp/test7.conf",
315 { "", "sect1", "sect2", "sect1", "sect2", "sect2", "sect3" },
316 { "key0", "key1", "key2", "key1", "key2", "key2", "key3" },
317 { "val0", "val1", "val2", "val3", "val4", "val4.1", "val5" } );
318
320 config.add( "key0", "", "", 0, "", "key0", false, "", "" );
321 config.add( "key1", "", "", 0, "sect1", "key1", false, "", "" );
322 config.add( "key2", "", "", 0, "sect2", "key2", false, "", "" );
323 config.add( "key3", "", "", 0, "sect3", "key3", false, "", "" );
324
325 config.readConfig( "/tmp/test7.conf" );
326
327 std::string val;
328
329 config( val, "key0" );
330 REQUIRE( val == "val0" );
331
332 config( val, "key1" );
333 REQUIRE( val == "val1val3" );
334
335 config( val, "key2" );
336 REQUIRE( val == "val2val4val4.1" );
337
338 config( val, "key3" );
339 REQUIRE( val == "val5" );
340 }
341
342 WHEN( "multi-line keys" )
343 {
344 writeConfigFile( "/tmp/test.conf",
345 { "", "sect1", "sect2", "sect1", "sect2", "sect3" },
346 { "key0", "key1", "key2", "key1", "key2", "key3" },
347 { "val0\n val0.1", "val1", "val2", "val3\n val3.1", "val4", "val5" } );
348
350 config.add( "key0", "", "", 0, "", "key0", false, "", "" );
351 config.add( "key1", "", "", 0, "sect1", "key1", false, "", "" );
352 config.add( "key2", "", "", 0, "sect2", "key2", false, "", "" );
353 config.add( "key3", "", "", 0, "sect3", "key3", false, "", "" );
354
355 config.readConfig( "/tmp/test.conf" );
356
357 std::string val;
358
359 config( val, "key0" );
360 REQUIRE( val == "val0val0.1" );
361
362 config( val, "key1" );
363 REQUIRE( val == "val1val3val3.1" );
364
365 config( val, "key2" );
366 REQUIRE( val == "val2val4" );
367
368 config( val, "key3" );
369 REQUIRE( val == "val5" );
370 }
371 }
372
373 GIVEN( "a config file with vectors" )
374 {
375 WHEN( "no sections" )
376 {
377 writeConfigFile( "/tmp/test.conf",
378 { "", "", "" },
379 { "key0", "key1", "key2" },
380 { "val0,val1,val2", " val3, val4, ", "val5" } );
381
383 config.add( "key0", "", "", 0, "", "key0", false, "", "" );
384 config.add( "key1", "", "", 0, "", "key1", false, "", "" );
385 config.add( "key2", "", "", 0, "", "key2", false, "", "" );
386
387 config.readConfig( "/tmp/test.conf" );
388
389 std::vector<std::string> vals;
390
391 config( vals, "key0" );
392 REQUIRE( vals[0] == "val0" );
393 REQUIRE( vals[1] == "val1" );
394 REQUIRE( vals[2] == "val2" );
395
396 config( vals, "key1" );
397 REQUIRE( vals[0] == "val3" );
398 REQUIRE( vals[1] == "val4" );
399 REQUIRE( vals[2] == "" );
400
401 config( vals, "key2" );
402 REQUIRE( vals[0] == "val5" );
403 }
404 }
405#endif
406}
407
408/** \brief Verifies mx::app::appConfigurator::readConfig handles parser errors and records config sources.
409 *
410 * \ingroup appConfigurator_unit_tests
411 */
412TEST_CASE( "readConfig handles failures and tracks recognized sources", "[appConfigurator]" )
413{
415 config.m_sources = true;
416 config.add( "known", "", "", 0, "section", "known", false, "", "" );
417
418 REQUIRE( config.readConfig( "" ) == 0 );
419 REQUIRE( config.readConfig( "/tmp/mxlib-missing-config.conf", false ) == -1 );
420 REQUIRE( config.readConfig( "/tmp/mxlib-missing-config.conf" ) == -1 );
421
422 {
423 std::ofstream badConfig( "/tmp/mxlib-invalid-config.conf" );
424 badConfig << "[unterminated\n";
425 }
426 REQUIRE( config.readConfig( "/tmp/mxlib-invalid-config.conf" ) == -1 );
427
428 {
429 std::ofstream goodConfig( "/tmp/mxlib-read-config.conf" );
430 goodConfig << "[section]\nknown = value\n[extra]\nunused = retained\n";
431 }
432 REQUIRE( config.readConfig( "/tmp/mxlib-read-config.conf" ) == 0 );
433 REQUIRE( config.m_targets.at( "known" ).values.at( 0 ) == "value" );
434 REQUIRE( config.m_targets.at( "known" ).sources.at( 0 ) == "/tmp/mxlib-read-config.conf" );
435 REQUIRE( config.m_unusedConfigs.at( "extra=unused" ).values.at( 0 ) == "retained" );
436 REQUIRE( config.m_unusedConfigs.at( "extra=unused" ).sources.at( 0 ) == "/tmp/mxlib-read-config.conf" );
437
438 parserReset reset;
439 mx::app::detail::appConfiguratorParser = allocationFailureParser;
440 REQUIRE( config.readConfig( "/tmp/mxlib-allocation-failure.conf" ) == -1 );
441}
442
443/** \brief Verifies command-line target metadata and typed retrieval used by hciReduce configuration loading.
444 *
445 * \ingroup appConfigurator_unit_tests
446 */
447TEST_CASE( "command-line configuration targets preserve metadata and typed values", "[appConfigurator]" )
448{
450 config.add( "images", "i", "images", mx::app::argType::Required, "files", "images", true, "string", "" );
451 config.add( "threshold", "t", "threshold", mx::app::argType::Required, "quality", "threshold", false, "float", "" );
452 config.add( "enabled", "", "enabled", mx::app::argType::True, "preProcess", "enabled", false, "bool", "" );
453
454 char invokedName[] = "configTest";
455 char imagesOption[] = "--images=target.fits";
456 char thresholdOption[] = "--threshold=0.75";
457 char enabledOption[] = "--enabled";
458 char *argv[] = { invokedName, imagesOption, thresholdOption, enabledOption };
459 config.parseCommandLine( 4, argv );
460
461 REQUIRE( config.nAdded == 3 );
462 REQUIRE( config.m_targets.at( "images" ).section == "files" );
463 REQUIRE( config.m_targets.at( "images" ).keyword == "images" );
464 REQUIRE( config.m_targets.at( "images" ).isRequired );
465 REQUIRE( config.isSet( "images" ) );
466 REQUIRE( config.isSet( "threshold" ) );
467 REQUIRE( config.isSet( "enabled" ) );
468
469 std::string images;
470 float threshold{ 0 };
471 bool enabled{ false };
472 REQUIRE( config( images, "images" ) == 0 );
473 REQUIRE( config( threshold, "threshold" ) == 0 );
474 REQUIRE( config( enabled, "enabled" ) == 0 );
475 REQUIRE( images == "target.fits" );
476 REQUIRE( threshold == 0.75f );
477 REQUIRE( enabled );
478
479 int unset{ 19 };
480 REQUIRE( config( unset, "not-added" ) == 0 );
481 REQUIRE( unset == 19 );
482}
483
484/** \brief Verifies command-line filtering, indexed retrieval, unknown options, and reusable configuration state.
485 *
486 * \ingroup appConfigurator_unit_tests
487 */
488TEST_CASE( "command-line configuration tracks selected and unknown options", "[appConfigurator]" )
489{
491 config.m_sources = true;
492 config.add( "first", "", "first", mx::app::argType::Required, "", "first", false, "int", "" );
493 config.add( "second", "", "second", mx::app::argType::Required, "", "second", false, "int", "" );
494 char invokedName[] = "configTest";
495 char firstOption[] = "--first=5";
496 char secondOption[] = "--second=7";
497 char unknownOption[] = "--unknown=value";
498 char *argv[] = { invokedName, firstOption, secondOption, unknownOption };
499
500 config.parseCommandLine( 4, argv, "first" );
501 REQUIRE( config.isSet( "first" ) );
502 REQUIRE_FALSE( config.isSet( "second" ) );
503 REQUIRE( config.m_targets.at( "first" ).sources.at( 0 ) == "command line" );
504 REQUIRE( config.m_unusedConfigs.size() == 1 );
505 REQUIRE( config.m_unusedConfigs.begin()->second.set );
506 REQUIRE( config.m_unusedConfigs.begin()->second.sources.at( 0 ) == "command line" );
507
508 int first{ 0 };
509 REQUIRE( config.get( first, "first", 0 ) == 0 );
510 REQUIRE( first == 5 );
511 REQUIRE( config.get( first, "first", 1 ) == -1 );
512
513 config.parseCommandLine( 4, argv );
514 int second{ 0 };
515 REQUIRE( config( second, "second" ) == 0 );
516 REQUIRE( second == 7 );
517 REQUIRE( config.verbosity( "first" ) == 1 );
518 REQUIRE( config.count( "first" ) == 2 );
519
520 config.clear();
521 REQUIRE( config.m_targets.empty() );
522 REQUIRE( config.clOnlyTargets.empty() );
523 REQUIRE( config.nonOptions.empty() );
524 REQUIRE( config.m_unusedConfigs.empty() );
525}
526
527/** \brief Verifies that repeated command-line-only names retain their separate target registrations.
528 *
529 * \ingroup appConfigurator_unit_tests
530 */
531TEST_CASE( "command-line-only targets retain duplicate names", "[appConfigurator]" )
532{
534 config.add( "verbosity", "v", "verbose", mx::app::argType::True, "", "", false, "", "" );
535 config.add( "verbosity", "V", "very-verbose", mx::app::argType::True, "", "", false, "", "" );
536
537 REQUIRE( config.m_targets.size() == 1 );
538 REQUIRE( config.clOnlyTargets.size() == 1 );
539 REQUIRE( config.m_targets.at( "verbosity" ).orderAdded == 0 );
540 REQUIRE( config.clOnlyTargets.begin()->orderAdded == 1 );
541 REQUIRE( config.clOnlyTargets.begin()->longOpt == "very-verbose" );
542}
543
544/** \brief Verifies scalar configuration defaults, typed conversions, indexed access, and source logging.
545 *
546 * \ingroup appConfigurator_unit_tests
547 */
548TEST_CASE( "scalar configuration access covers HCI value types", "[appConfigurator]" )
549{
551 config.configLog = captureConfigLog;
552
553 resetConfigLog();
554 int defaultValue{ 42 };
555 REQUIRE( config( defaultValue, "unset" ) == 0 );
556 REQUIRE( defaultValue == 42 );
557 REQUIRE( logName == "unset" );
558 REQUIRE( logCode == mx::meta::typeDescription<int>::code() );
559 REQUIRE( logValue == "42" );
560 REQUIRE( logSource == "default" );
561
562 resetConfigLog();
563 REQUIRE( config.get( defaultValue, "unset-indexed", 0 ) == 0 );
564 REQUIRE( defaultValue == 42 );
565 REQUIRE( logName == "unset-indexed" );
566 REQUIRE( logValue == "42" );
567 REQUIRE( logSource == "default" );
568
569 config.add( "integer", "", "", 0, "", "integer" );
570 config.m_targets.at( "integer" ).set = true;
571 config.m_targets.at( "integer" ).values = { "11", "23" };
572 config.m_targets.at( "integer" ).sources = { "first.conf", "second.conf" };
573
574 int integerValue{ 0 };
575 resetConfigLog();
576 REQUIRE( config.get( integerValue, "integer", 0 ) == 0 );
577 REQUIRE( integerValue == 11 );
578 REQUIRE( logValue == "11" );
579 REQUIRE( logSource.empty() );
580 REQUIRE( config.get( integerValue, "integer", 2 ) == -1 );
581
582 config.m_sources = true;
583 resetConfigLog();
584 REQUIRE( config.get( integerValue, "integer" ) == 0 );
585 REQUIRE( integerValue == 23 );
586 REQUIRE( logValue == "23" );
587 REQUIRE( logSource == "second.conf" );
588
589 config.m_sources = false;
590
591 config.add( "empty", "", "", 0, "", "empty" );
592 config.m_targets.at( "empty" ).set = true;
593 REQUIRE( config.get( integerValue, "empty" ) == -1 );
594
595 config.add( "string", "", "", 0, "", "string" );
596 config.add( "boolean", "", "", 0, "", "boolean" );
597 config.add( "float", "", "", 0, "", "float" );
598 config.add( "double", "", "", 0, "", "double" );
599 config.m_targets.at( "string" ).set = true;
600 config.m_targets.at( "boolean" ).set = true;
601 config.m_targets.at( "float" ).set = true;
602 config.m_targets.at( "double" ).set = true;
603 config.m_targets.at( "string" ).values = { "target.fits" };
604 config.m_targets.at( "boolean" ).values = { "true" };
605 config.m_targets.at( "float" ).values = { "1.25" };
606 config.m_targets.at( "double" ).values = { "2.5" };
607
608 std::string stringValue;
609 bool boolValue{ false };
610 float floatValue{ 0 };
611 double doubleValue{ 0 };
612 REQUIRE( config( stringValue, "string" ) == 0 );
613 REQUIRE( config( boolValue, "boolean" ) == 0 );
614 REQUIRE( config( floatValue, "float" ) == 0 );
615 REQUIRE( config( doubleValue, "double" ) == 0 );
616 REQUIRE( stringValue == "target.fits" );
617 REQUIRE( boolValue );
618 REQUIRE( floatValue == 1.25f );
619 REQUIRE( doubleValue == 2.5 );
620}
621
622/** \brief Verifies vector configuration defaults, empty values, delimiters, typed conversions, and source logging.
623 *
624 * \ingroup appConfigurator_unit_tests
625 */
626TEST_CASE( "vector configuration access covers HCI value types", "[appConfigurator]" )
627{
629 config.configLog = captureConfigLog;
630
631 std::vector<int> defaultValues{ 7, 8 };
632 resetConfigLog();
633 REQUIRE( config( defaultValues, "unset-vector" ) == 0 );
634 REQUIRE( defaultValues == ( std::vector<int>{ 7, 8 } ) );
635 REQUIRE( logName == "unset-vector" );
636 REQUIRE( logValue == "[need a vector to string]" );
637 REQUIRE( logSource == "default" );
638
639 resetConfigLog();
640 REQUIRE( config.get( defaultValues, "unset-vector-indexed", 0 ) == 0 );
641 REQUIRE( defaultValues == ( std::vector<int>{ 7, 8 } ) );
642 REQUIRE( logName == "unset-vector-indexed" );
643 REQUIRE( logValue == "[need a vector to string]" );
644 REQUIRE( logSource == "default" );
645
646 config.add( "empty-vector", "", "", 0, "", "empty-vector" );
647 config.m_targets.at( "empty-vector" ).set = true;
648 config.m_targets.at( "empty-vector" ).values = { "" };
649 config.m_targets.at( "empty-vector" ).sources = { "empty.conf" };
650 config.m_sources = true;
651 resetConfigLog();
652 REQUIRE( config.get( defaultValues, "empty-vector", 0 ) == 0 );
653 REQUIRE( defaultValues.empty() );
654 REQUIRE( logValue.empty() );
655 REQUIRE( logSource == "empty.conf" );
656
657 config.m_sources = false;
658 defaultValues = { 9 };
659 resetConfigLog();
660 REQUIRE( config.get( defaultValues, "empty-vector", 0 ) == 0 );
661 REQUIRE( defaultValues.empty() );
662 REQUIRE( logSource.empty() );
663 REQUIRE( config.get( defaultValues, "empty-vector", 1 ) == -1 );
664
665 config.add( "integer-vector", "", "", 0, "", "integer-vector" );
666 config.m_targets.at( "integer-vector" ).set = true;
667 config.m_targets.at( "integer-vector" ).values = { " 1, 2,3", "4,5,6" };
668 config.m_targets.at( "integer-vector" ).sources = { "first.conf", "second.conf" };
669
670 std::vector<int> integerValues;
671 resetConfigLog();
672 REQUIRE( config.get( integerValues, "integer-vector", 0 ) == 0 );
673 REQUIRE( integerValues == ( std::vector<int>{ 1, 2, 3 } ) );
674 REQUIRE( logValue == " 1, 2,3" );
675 REQUIRE( logSource.empty() );
676
677 config.m_sources = true;
678 resetConfigLog();
679 REQUIRE( config.get( integerValues, "integer-vector" ) == 0 );
680 REQUIRE( integerValues == ( std::vector<int>{ 4, 5, 6 } ) );
681 REQUIRE( logValue == "4,5,6" );
682 REQUIRE( logSource == "second.conf" );
683
684 config.m_sources = false;
685
686 config.add( "float-vector", "", "", 0, "", "float-vector" );
687 config.m_targets.at( "float-vector" ).set = true;
688 config.m_targets.at( "float-vector" ).values = { "1.5,2.5" };
689 std::vector<float> floatValues;
690 REQUIRE( config( floatValues, "float-vector" ) == 0 );
691 REQUIRE( floatValues == ( std::vector<float>{ 1.5f, 2.5f } ) );
692
693 config.add( "string-vector", "", "", 0, "", "string-vector" );
694 config.m_targets.at( "string-vector" ).set = true;
695 config.m_targets.at( "string-vector" ).values = { "alpha, beta," };
696 std::vector<std::string> stringValues;
697 REQUIRE( config( stringValues, "string-vector" ) == 0 );
698 REQUIRE( stringValues == ( std::vector<std::string>{ "alpha", "beta", "" } ) );
699
700 config.add( "empty-values", "", "", 0, "", "empty-values" );
701 config.m_targets.at( "empty-values" ).set = true;
702 REQUIRE( config.get( integerValues, "empty-values" ) == -1 );
703}
704
705/*
706} // namespace appConfiguratorTest
707} // namespace appTest
708} // namespace testUnit
709 */
An application configuration manager.
void writeConfigFile(const std::string &fname, const std::vector< std::string > &sections, const std::vector< std::string > &keywords, const std::vector< std::string > &values)
A simple config file writing function, useful for testing.
int(* appConfiguratorParser)(iniFile &parser, const std::string &filename)
Optional parser override used to deterministically test appConfigurator parser failures.
TEST_CASE("config file parsing", "[appConfigurator]")
Verify that appConfigurator parses global, sectioned, and unused configuration entries.
Class to manage a set of configurable values, and read their values from config/ini files and the com...
std::unordered_map< std::string, configTarget > m_targets
The targets are stored in an unordered_map for fast access by key.
int readConfig(const std::string &fname, bool reportFileNotFound=true)
Read and parse a config/ini file, updating the targets.
void add(const configTarget &tgt)
Add a configTarget.
bool m_sources
Flag controlling whether or not to record config sources.
int get(typeT &v, const std::string &name, size_t i, std::unordered_map< std::string, configTarget > &targets)
Get the i-th value of the target, converted to the specified type.
std::vector< std::string > nonOptions
Non-option arguments from the command line.
void(* configLog)(const std::string &name, const int &code, const std::string &valueStr, const std::string &source)
Call an external logging function whenever a config value is accessed by get or operator().
int verbosity(const std::string &name, std::unordered_map< std::string, configTarget > &targets)
Get the command line verbosity count for this option.
bool isSet(const std::string &name, std::unordered_map< std::string, configTarget > &targets)
Check if a target has been set by the configuration.
void clear()
Clear the containers and free up the associated memory.
std::list< configTarget > clOnlyTargets
Targets which are only for the command line are stored separately in a list.
int count(const std::string &name, std::unordered_map< std::string, configTarget > &targets)
Get the number of different values set for the specified config target.
void parseCommandLine(int argc, char **argv, const std::string &oneTarget="")
Parse the command line, updating the targets.
int nAdded
Running count of options added, used to track order.
std::unordered_map< std::string, configTarget > m_unusedConfigs
A wrapper for the ini functions.
Definition iniFile.hpp:47
static std::string makeKey(const std::string &section, const std::string &name)
Return a key generated from the section and name.
Definition iniFile.hpp:57
static constexpr int code()
Returns a unique numeric code for this type.