Visual Servoing Platform version 3.7.0
Loading...
Searching...
No Matches
catchJsonArgumentParser.cpp
1/*
2 * ViSP, open source Visual Servoing Platform software.
3 * Copyright (C) 2005 - 2024 by Inria. All rights reserved.
4 *
5 * This software 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 2 of the License, or
8 * (at your option) any later version.
9 * See the file LICENSE.txt at the root directory of this source
10 * distribution for additional information about the GNU GPL.
11 *
12 * For using ViSP with software that can not be combined with the GNU
13 * GPL, please contact Inria about acquiring a ViSP Professional
14 * Edition License.
15 *
16 * See https://visp.inria.fr for more information.
17 *
18 * This software was developed at:
19 * Inria Rennes - Bretagne Atlantique
20 * Campus Universitaire de Beaulieu
21 * 35042 Rennes Cedex
22 * France
23 *
24 * If you have questions regarding the use of this file, please contact
25 * Inria at visp@inria.fr
26 *
27 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
28 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
29 *
30 * Description:
31 * Test vpJsonArgumentParser
32 */
33
39
40#include <visp3/core/vpIoTools.h>
41#include <visp3/io/vpJsonArgumentParser.h>
42
43#if defined(VISP_HAVE_NLOHMANN_JSON) && defined(VISP_HAVE_CATCH2)
44#include VISP_NLOHMANN_JSON(json.hpp)
45using json = nlohmann::json;
46
47#include <catch_amalgamated.hpp>
48
49#ifdef ENABLE_VISP_NAMESPACE
50using namespace VISP_NAMESPACE_NAME;
51#endif
52
53std::pair<int, std::vector<char *>> convertToArgcAndArgv(const std::vector<std::string> &args)
54{
55 std::vector<char *> argvs;
56 argvs.reserve(args.size());
57 int argc = static_cast<int>(args.size());
58 for (unsigned i = 0; i < args.size(); ++i) {
59 argvs.push_back(const_cast<char *>(args[i].c_str()));
60 }
61 return std::make_pair(argc, argvs);
62}
63
64json loadJson(const std::string &path)
65{
66 std::ifstream json_file(path);
67 if (!json_file.good()) {
68 throw vpException(vpException::ioError, "Could not open JSON settings file");
69 }
70 json j = json::parse(json_file);
71 json_file.close();
72 return j;
73}
74
75void saveJson(const json &j, const std::string &path)
76{
77 std::ofstream json_file(path);
78 if (!json_file.good()) {
79 throw vpException(vpException::ioError, "Could not open JSON settings file to write modifications");
80 }
81 json_file << j.dump();
82 json_file.close();
83}
84
85SCENARIO("Parsing arguments from JSON file", "[json]")
86{
87 // setup test dir
88 // Get the user login name
89
90 std::string tmp_dir = vpIoTools::makeTempDirectory(vpIoTools::getTempPath() + vpIoTools::path("/") + "visp_test_json_argument_parsing");
91 const std::string jsonPath = tmp_dir + "/" + "arguments.json";
92
93 const auto modifyJson = [&jsonPath](std::function<void(json &)> modify) -> void {
94 json j = loadJson(jsonPath);
95 modify(j);
96 saveJson(j, jsonPath);
97 };
98
99 GIVEN("Some specific arguments")
100 {
101 const std::string s = "hello";
102 WHEN("Converting a string to a json rep")
103 {
104 const json js = convertCommandLineArgument<std::string>(s);
105 const json truejs = s;
106 THEN("Conversion is correct")
107 {
108 REQUIRE(js == truejs);
109 }
110 }
111 }
112
113 GIVEN("Some JSON parameters saved in a file, and some C++ variables")
114 {
115 json j = json {
116 {"a", 2},
117 {"b", 2.0},
118 {"c", "a string"},
119 {"d", true},
120 {"flag", true},
121 {"flagDefaultTrue", true},
122 {"e", {{"a", 5} }
123 }
124 };
125 saveJson(j, jsonPath);
126
127 int a = 1;
128 double b = 1.0;
129 std::string c = "";
130 bool d = false;
131 int ea = 4;
132 bool flag = false;
133 bool flagInitialValue = flag;
134
135 bool invertedFlag = true;
136 WHEN("Declaring a parser with all parameters required")
137 {
138 vpJsonArgumentParser parser("A program", "--config", "/");
139 parser.addArgument("a", a, true)
140 .addArgument("b", b, true)
141 .addArgument("c", c, true)
142 .addArgument("d", d, true)
143 .addArgument("e/a", ea, true)
144 .addFlag("flag", flag)
145 .addFlag("flagDefaultTrue", invertedFlag);
146
147 THEN("Calling the parser without any argument fails")
148 {
149 const int argc = 1;
150 const char *argv[] = {
151 "program"
152 };
153
154 REQUIRE_THROWS(parser.parse(argc, argv));
155 }
156
157 THEN("Calling the parser with only the JSON file works")
158 {
159 const int argc = 3;
160 const char *argv[] = {
161 "program",
162 "--config",
163 jsonPath.c_str()
164 };
165 REQUIRE_NOTHROW(parser.parse(argc, argv));
166 REQUIRE(a == j["a"]);
167 REQUIRE(b == j["b"]);
168 REQUIRE(c == j["c"]);
169 REQUIRE(d == j["d"]);
170 REQUIRE(ea == j["e"]["a"]);
171 REQUIRE(flag != flagInitialValue);
172 REQUIRE(invertedFlag != true);
173 }
174 THEN("Calling the parser by specifying the json argument but leaving the file path empty throws an error")
175 {
176 const int argc = 2;
177 const char *argv[] = {
178 "program",
179 "--config",
180 };
181 REQUIRE_THROWS(parser.parse(argc, argv));
182 }
183 THEN("Calling the parser with only the json file but deleting a random field throws an error")
184 {
185 const int argc = 3;
186 for (const auto &jsonElem : j.items()) {
187 if (jsonElem.key().rfind("flag", 0) != 0) {
188 modifyJson([&jsonElem](json &j) { j.erase(jsonElem.key()); });
189 const char *argv[] = {
190 "program",
191 "--config",
192 jsonPath.c_str()
193 };
194 REQUIRE_THROWS(parser.parse(argc, argv));
195 }
196 }
197 }
198 THEN("Calling the parser with only the json file but setting a random field to null throws an error")
199 {
200 const int argc = 3;
201 for (const auto &jsonElem : j.items()) {
202 if (jsonElem.key().rfind("flag", 0) != 0) {
203 modifyJson([&jsonElem](json &j) { j[jsonElem.key()] = nullptr; });
204 const char *argv[] = {
205 "program",
206 "--config",
207 jsonPath.c_str()
208 };
209 REQUIRE_THROWS(parser.parse(argc, argv));
210 }
211 }
212 }
213 THEN("Calling the parser with an invalid json file path throws an error")
214 {
215 const int argc = 3;
216 const char *argv[] = {
217 "program",
218 "--config",
219 "some_invalid_json/file/path.json"
220 };
221 REQUIRE_THROWS(parser.parse(argc, argv));
222 }
223 THEN("Calling the parser with only the command line arguments works")
224 {
225 const int newa = a + 1, newea = ea + 6;
226 const double newb = b + 2.0;
227 const std::string newc = c + "hello";
228 const bool newd = !d;
229
230 const std::string newdstr(newd ? "true" : "false");
231 std::vector<std::string> args = {
232 "program",
233 "a", std::to_string(newa),
234 "b", std::to_string(newb),
235 "c", newc,
236 "d", newdstr,
237 "e/a", std::to_string(newea),
238 "flag"
239 };
240 int argc;
241 std::vector<char *> argv;
242 std::tie(argc, argv) = convertToArgcAndArgv(args);
243 REQUIRE_NOTHROW(parser.parse(argc, (const char **)(&argv[0])));
244 REQUIRE(a == newa);
245 REQUIRE(b == newb);
246 REQUIRE(c == newc);
247 REQUIRE(d == newd);
248 REQUIRE(ea == newea);
249 REQUIRE(flag != flagInitialValue);
250
251 }
252 THEN("Calling the parser with JSON and command line argument works")
253 {
254 const int newa = a + 1;
255 const double newb = b + 2.0;
256 std::vector<std::string> args = {
257 "program",
258 "--config", jsonPath,
259 "a", std::to_string(newa),
260 "b", std::to_string(newb),
261 "flagDefaultTrue"
262 };
263 int argc;
264 std::vector<char *> argv;
265 std::tie(argc, argv) = convertToArgcAndArgv(args);
266 REQUIRE_NOTHROW(parser.parse(argc, (const char **)(&argv[0])));
267 REQUIRE(a == newa);
268 REQUIRE(b == newb);
269 REQUIRE(c == j["c"]);
270 REQUIRE(d == j["d"]);
271 REQUIRE(ea == j["e"]["a"]);
272 REQUIRE(invertedFlag == false);
273 }
274 THEN("Calling the parser with a missing argument value throws an error")
275 {
276
277 std::vector<std::string> args = {
278 "program",
279 "--config", jsonPath,
280 "a"
281 };
282 int argc;
283 std::vector<char *> argv;
284 std::tie(argc, argv) = convertToArgcAndArgv(args);
285 REQUIRE_THROWS(parser.parse(argc, (const char **)(&argv[0])));
286 }
287 }
288 }
289 THEN("Declaring a parser with an undefined nesting delimiter fails")
290 {
291 REQUIRE_THROWS(vpJsonArgumentParser("A program", "--config", ""));
292 }
293 THEN("Declaring a parser with an invalid JSON file argument fails")
294 {
295 REQUIRE_THROWS(vpJsonArgumentParser("A program", "", "/"));
296 }
297
298 WHEN("Instantiating a parser with some optional fields")
299 {
300 vpJsonArgumentParser parser("A program", "--config", "/");
301 float b = 0.0;
302 parser.addArgument("b", b, false);
303
304 THEN("Calling the parser without any argument works and does not modify the default value")
305 {
306 float bcopy = b;
307 const int argc = 1;
308 const char *argv[] = {
309 "program"
310 };
311
312 REQUIRE_NOTHROW(parser.parse(argc, argv));
313 REQUIRE(b == bcopy);
314
315 }
316 }
317 WHEN("Instantiating a parser with nested parameters")
318 {
319 vpJsonArgumentParser parser("A program", "--config", "/");
320 float b = 0.0;
321 parser.addArgument("b", b, false);
322
323 THEN("Calling the parser without any argument works and does not modify the default value")
324 {
325 float bcopy = b;
326 const int argc = 1;
327 const char *argv[] = {
328 "program"
329 };
330
331 REQUIRE_NOTHROW(parser.parse(argc, argv));
332 REQUIRE(b == bcopy);
333
334 }
335 }
336
337
338 WHEN("Instantiating a parser with some documentation")
339 {
340 const std::string programString = "ProgramString";
341 const std::string firstArg = "FirstArgName", firstArgDescription = "FirstArgDescription";
342 const std::string secondArg = "secondArgName", secondArgDescription = "secondArgDescription";
343 vpJsonArgumentParser parser(programString, "--config", "/");
344 std::string a = "DefaultFirstArg", b = "DefaultSecondArg";
345 parser.addArgument(firstArg, a, true, firstArgDescription);
346 parser.addArgument(secondArg, b, false, secondArgDescription);
347 WHEN("Getting the help string")
348 {
349 const std::string help = parser.help();
350 THEN("Output should contain the basic program description")
351 {
352 REQUIRE(help.find(programString) < help.size());
353 }
354 THEN("Output should contain the json argument")
355 {
356 REQUIRE(help.find("--config") < help.size());
357 }
358 THEN("Output should contain the arguments, their description and their default value")
359 {
360 const std::vector<std::string> requireds = { firstArg, secondArg, firstArgDescription, secondArgDescription, a, b };
361 for (const auto &required : requireds) {
362 REQUIRE(help.find(required) < help.size());
363 }
364 }
365 }
366
367
368 }
369}
370
371int main(int argc, char *argv[])
372{
373 Catch::Session session; // There must be exactly one instance
374 session.applyCommandLine(argc, argv);
375
376 int numFailed = session.run();
377 return numFailed;
378}
379
380#else
381
382int main()
383{
384 return EXIT_SUCCESS;
385}
386
387#endif
error that can be emitted by ViSP classes.
Definition vpException.h:60
@ ioError
I/O error.
Definition vpException.h:67
static std::string path(const std::string &pathname)
static std::string getTempPath()
static std::string makeTempDirectory(const std::string &dirname)
Command line argument parsing with support for JSON files. If a JSON file is supplied,...