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