Visual Servoing Platform  version 3.6.1 under development (2024-11-15)
catchJsonArrayConversion.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 vpArray2D and children JSON parse / save.
32  */
33 
40 #include <visp3/core/vpConfig.h>
41 
42 #if defined(VISP_HAVE_NLOHMANN_JSON) && defined(VISP_HAVE_CATCH2)
43 
44 #include <random>
45 #include <visp3/core/vpArray2D.h>
46 #include <visp3/core/vpIoTools.h>
47 
48 #include VISP_NLOHMANN_JSON(json.hpp)
49 using json = nlohmann::json;
50 
51 #include <catch_amalgamated.hpp>
52 
53 #ifdef ENABLE_VISP_NAMESPACE
54 using namespace VISP_NAMESPACE_NAME;
55 #endif
56 
57 namespace
58 {
59 using StringMatcherBase = Catch::Matchers::StringMatcherBase;
60 class vpExceptionMatcher : public Catch::Matchers::MatcherBase<vpException>
61 {
63  const StringMatcherBase &m_messageMatcher;
64 
65 public:
66  vpExceptionMatcher(vpException::generalExceptionEnum type, const StringMatcherBase &messageMatcher)
67  : m_type(type), m_messageMatcher(messageMatcher)
68  { }
69 
70  bool match(vpException const &in) const VP_OVERRIDE
71  {
72  return m_type == in.getCode() && m_messageMatcher.match(in.getStringMessage());
73  }
74 
75  std::string describe() const VP_OVERRIDE
76  {
77  std::ostringstream ss;
78  ss << "vpException has type " << m_type << " and message " << m_messageMatcher.describe();
79  return ss.str();
80  }
81 };
82 
83 class vpRandomArray2DGenerator : public Catch::Generators::IGenerator<vpArray2D<double> >
84 {
85 private:
86  std::minstd_rand m_rand;
87  std::uniform_real_distribution<> m_val_dist;
88  std::uniform_int_distribution<> m_dim_dist;
89 
90  vpArray2D<double> current;
91 
92 public:
93  vpRandomArray2DGenerator(double valueRange, int minSize, int maxSize)
94  : m_rand(std::random_device {}()), m_val_dist(-valueRange, valueRange), m_dim_dist(minSize, maxSize)
95 
96  {
97  static_cast<void>(next());
98  }
99 
100  vpArray2D<double> const &get() const VP_OVERRIDE { return current; }
101  bool next() VP_OVERRIDE
102  {
103  const unsigned nCols = m_dim_dist(m_rand);
104  const unsigned nRows = m_dim_dist(m_rand);
105  current.resize(nRows, nCols);
106  for (unsigned i = 0; i < nRows; ++i) {
107  for (unsigned j = 0; j < nCols; ++j) {
108  current[i][j] = m_val_dist(m_rand);
109  }
110  }
111  return true;
112  }
113 };
114 class vpRandomColVectorGenerator : public Catch::Generators::IGenerator<vpColVector>
115 {
116 private:
117  std::minstd_rand m_rand;
118  std::uniform_real_distribution<> m_val_dist;
119  std::uniform_int_distribution<> m_dim_dist;
120 
121  vpColVector current;
122 
123 public:
124  vpRandomColVectorGenerator(double valueRange, int minSize, int maxSize)
125  : m_rand(std::random_device {}()), m_val_dist(-valueRange, valueRange), m_dim_dist(minSize, maxSize)
126 
127  {
128  static_cast<void>(next());
129  }
130 
131  const vpColVector &get() const VP_OVERRIDE { return current; }
132  bool next() VP_OVERRIDE
133  {
134  const unsigned nRows = m_dim_dist(m_rand);
135  current.resize(nRows);
136  for (unsigned i = 0; i < nRows; ++i) {
137  current[i] = m_val_dist(m_rand);
138  }
139  return true;
140  }
141 };
142 Catch::Generators::GeneratorWrapper<vpArray2D<double> > randomArray(double v, int minSize, int maxSize)
143 {
144  return Catch::Generators::GeneratorWrapper<vpArray2D<double> >(Catch::Detail::make_unique<vpRandomArray2DGenerator>(v, minSize, maxSize));
145 }
146 Catch::Generators::GeneratorWrapper<vpColVector> randomColVector(double v, int minSize, int maxSize)
147 {
148  return Catch::Generators::GeneratorWrapper<vpColVector>(Catch::Detail::make_unique<vpRandomColVectorGenerator>(v, minSize, maxSize));
149 }
150 vpExceptionMatcher matchVpException(vpException::generalExceptionEnum type, const StringMatcherBase &matcher)
151 {
152  return vpExceptionMatcher(type, matcher);
153 }
154 } // namespace
155 SCENARIO("Serializing a vpArray2D", "[json]")
156 {
157  GIVEN("A random vpArray2D<double>")
158  {
159  const vpArray2D<double> array = GENERATE(take(10, randomArray(50.0, 1, 10)));
160  WHEN("Serializing to a JSON object")
161  {
162  const json j = array;
163  THEN("JSON object is a dictionary") { REQUIRE(j.is_object()); }
164  THEN("JSON object contains correct number of columns")
165  {
166  REQUIRE(j.at("cols").get<unsigned int>() == array.getCols());
167  }
168  THEN("JSON object contains correct number of rows")
169  {
170  REQUIRE(j.at("rows").get<unsigned int>() == array.getRows());
171  }
172  THEN("JSON object contains the array values")
173  {
174  const json jData = j.at("data");
175  THEN("The data field is an array") { REQUIRE(jData.is_array()); }
176  THEN("The data field contains the correct number of values") { REQUIRE(jData.size() == array.size()); }
177  THEN("The data field contains the correct number of values")
178  {
179  const double *const start = array[0];
180  const std::vector<double> vec(start, start + array.size());
181  REQUIRE(vec == jData.get<std::vector<double> >());
182  }
183  }
184  }
185  }
186 }
187 
188 SCENARIO("Trying to instantiate a vpArray with a wrong type of object", "[json]")
189 {
190  GIVEN("A random scalar converted to a JSON representation")
191  {
192  vpArray2D<double> array;
193  std::uniform_real_distribution<> dist;
194  const json j = GENERATE(take(50, random(-200.0, 200.0)));
195  THEN("An exception is thrown")
196  {
197  const auto matcher = Catch::Matchers::ContainsSubstring("is not an array or object");
198  REQUIRE_THROWS_MATCHES(from_json(j, array), vpException, matchVpException(vpException::badValue, matcher));
199  }
200  }
201 }
202 
203 SCENARIO("Recovering a vpArray2D from a JSON array", "[json]")
204 {
205  GIVEN("An empty array")
206  {
207  const json j = json::array_t();
208  WHEN("Converting to a vpArray2D")
209  {
210  vpArray2D<double> array = j;
211  THEN("The resulting array is empty") { REQUIRE(array.size() == 0); }
212  }
213  }
214  GIVEN("A 1D array")
215  {
216  const json j = { 10.0, 20.0, 30.0 };
217  WHEN("Converting to a vpArray2D")
218  {
219  THEN("An exception is thrown, since this is an ambiguous array")
220  {
221  vpArray2D<double> array;
222  const auto matcher = Catch::Matchers::ContainsSubstring("is not an array of array");
223  REQUIRE_THROWS_MATCHES(from_json(j, array), vpException, matchVpException(vpException::badValue, matcher));
224  }
225  }
226  }
227  GIVEN("A vpArray2D converted to a json 2D array")
228  {
229  vpArray2D<double> array = GENERATE(take(10, randomArray(50.0, 2, 10)));
230  json j;
231  for (unsigned i = 0; i < array.getRows(); ++i) {
232  json jRow;
233  for (unsigned j = 0; j < array.getCols(); ++j) {
234  jRow.push_back(array[i][j]);
235  }
236  j.push_back(jRow);
237  }
238  WHEN("Converting back to a vpArray2D")
239  {
240  vpArray2D<double> array2 = j;
241  THEN("The values are correct") { REQUIRE(array == array2); }
242  }
243  WHEN("Removing elements from rows so that they do not have the same size")
244  {
245  j[0].erase(0); // Generating at least a 2x2 array
246  THEN("An exception is thrown")
247  {
248  const auto matcher = Catch::Matchers::ContainsSubstring("row arrays that are not of the same size");
249  REQUIRE_THROWS_MATCHES(from_json(j, array), vpException, matchVpException(vpException::badValue, matcher));
250  }
251  }
252  }
253 }
254 
255 SCENARIO("Recovering a vpArray2D from a JSON object as serialized by ViSP", "[json]")
256 {
257  GIVEN("A vpArray2D converted to JSON format")
258  {
259  const vpArray2D<double> array = GENERATE(take(10, randomArray(50.0, 1, 10)));
260  json j = array;
261  WHEN("Converting back to a vpArray2D")
262  {
263  const vpArray2D<double> array2 = j;
264  THEN("The 2 arrays are equal") { REQUIRE(array == array2); }
265  }
266  WHEN("Removing or adding some values from the data field so that its size does not match the expected array size")
267  {
268  j.at("data").erase(0);
269  THEN("An exception is thrown")
270  {
271  vpArray2D<double> array2;
272  const auto matcher = Catch::Matchers::ContainsSubstring("must be an array of size");
273  REQUIRE_THROWS_MATCHES(from_json(j, array2), vpException, matchVpException(vpException::badValue, matcher));
274  }
275  }
276  }
277 }
278 
279 SCENARIO("Serializing and deserializing a vpColVector", "[json]")
280 {
281  GIVEN("A random vpColVector")
282  {
283  const vpColVector v = GENERATE(take(100, randomColVector(100.0, 1, 50)));
284  WHEN("Serializing to JSON")
285  {
286  const json j = v;
287  THEN("There is only one column") { REQUIRE(j.at("cols") == 1); }
288  THEN("The type is vpColVector") { REQUIRE(j.at("type") == "vpColVector"); }
289  WHEN("Deserializing back to a vpColVector")
290  {
291  const vpColVector v2 = j;
292  THEN("The 2 vectors are the same") { REQUIRE(v == v2); }
293  }
294  }
295  }
296  GIVEN("A random 2D array with number of cols > 1")
297  {
298  const vpArray2D<double> array = GENERATE(take(10, randomArray(50.0, 2, 5)));
299  WHEN("Serializing this array to JSON")
300  {
301  const json j = array;
302  THEN("Serializing back to a vpColVector throws an exception")
303  {
304  vpColVector v;
305  const auto matcher = Catch::Matchers::ContainsSubstring("tried to read a 2D array into a vpColVector");
306  REQUIRE_THROWS_MATCHES(from_json(j, v), vpException, matchVpException(vpException::badValue, matcher));
307  }
308  }
309  }
310  GIVEN("A random 2D array with number of cols = 1")
311  {
312  const vpColVector v = GENERATE(take(10, randomColVector(100.0, 1, 50)));
313  const vpArray2D<double> array = (vpArray2D<double>)v;
314  WHEN("Serializing this array to JSON")
315  {
316  const json j = array;
317  THEN("Serializing back to a vpColVector is ok and gives the same vector")
318  {
319  const vpColVector v2 = j;
320  REQUIRE(v == v2);
321  }
322  }
323  }
324 }
325 
326 int main(int argc, char *argv[])
327 {
328  Catch::Session session;
329  session.applyCommandLine(argc, argv);
330  int numFailed = session.run();
331  return numFailed;
332 }
333 
334 #else
335 
336 int main() { return EXIT_SUCCESS; }
337 
338 #endif
unsigned int getCols() const
Definition: vpArray2D.h:337
void resize(unsigned int nrows, unsigned int ncols, bool flagNullify=true, bool recopy_=true)
Definition: vpArray2D.h:362
unsigned int size() const
Return the number of elements of the 2D array.
Definition: vpArray2D.h:349
unsigned int getRows() const
Definition: vpArray2D.h:347
Implementation of column vector and the associated operations.
Definition: vpColVector.h:191
void resize(unsigned int i, bool flagNullify=true)
Definition: vpColVector.h:1143
error that can be emitted by ViSP classes.
Definition: vpException.h:60
@ badValue
Used to indicate that a value is not in the allowed range.
Definition: vpException.h:73