The Battle for Wesnoth  1.19.15+dev
test_config.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2025
3  by Chris Beck <render787@gmail.com>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program 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  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #define GETTEXT_DOMAIN "wesnoth-test"
17 
18 #include <boost/test/unit_test.hpp>
19 // Work around a Boost<1.67 bug fixed in 1.67: Include test_case.hpp after
20 // unit_tests.hpp. https://svn.boost.org/trac10/ticket/13387
21 #include <boost/test/data/test_case.hpp> // for parametrized test
22 #include <cmath>
23 
24 #include "config.hpp"
25 #include "variable_info.hpp"
26 
27 BOOST_AUTO_TEST_SUITE(test_config)
28 
29 BOOST_AUTO_TEST_CASE(test_config_attribute_value)
30 {
31  config c;
32  config c1;
33  config c2;
34  const config& cc = c;
35  int x_int;
36  std::string x_str;
37  long long x_sll;
38  double x_dbl;
39 
40 // compare identical assigned int vs string
41  c1["x"] = 6;
42  c2["x"] = "6";
43  BOOST_CHECK_EQUAL(c1["x"], c2["x"]);
44 
45 // compare identical assigned int vs floating point
46  c1["x"] = 6;
47  c2["x"] = 6.0;
48  BOOST_CHECK_EQUAL(c1["x"], c2["x"]);
49 
50 // compare identical assigned int-string vs floating point
51  c1["x"] = "6";
52  c2["x"] = 6.0;
53  BOOST_CHECK_EQUAL(c1["x"], c2["x"]);
54 
55 // compare identical assigned floating point-string vs int
56  c1["x"] = 6;
57  c2["x"] = "6.0";
58  BOOST_CHECK_NE(c1["x"], c2["x"]);
59 
60 // compare identical assigned floating point vs string
61  c1["x"] = 6.0;
62  c2["x"] = "6.0";
63  BOOST_CHECK_NE(c1["x"], c2["x"]);
64 
65 // check what happens when trying to get a numeric result from a non-numeric value
66  c["x"] = "1aaaa";
67  x_str = c["x"].str();
68  BOOST_CHECK_EQUAL(x_str, "1aaaa");
69  x_int = c["x"].to_int();
70  BOOST_CHECK_EQUAL(x_int, 1);
71  x_sll = c["x"].to_long_long();
72  BOOST_CHECK_EQUAL(x_sll, 1ll);
73  x_dbl = c["x"].to_double();
74  BOOST_CHECK_EQUAL(x_dbl, 1.0);
75 
76  c["x"] = "1.7aaaa";
77  x_str = c["x"].str();
78  BOOST_CHECK_EQUAL(x_str, "1.7aaaa");
79  x_int = c["x"].to_int();
80  BOOST_CHECK_EQUAL(x_int, 1);
81  x_sll = c["x"].to_long_long();
82  BOOST_CHECK_EQUAL(x_sll, 1ll);
83  x_dbl = c["x"].to_double();
84  BOOST_CHECK_EQUAL(x_dbl, 1.7);
85 
86  c["x"] = "aaaa1";
87  x_str = c["x"].str();
88  BOOST_CHECK_EQUAL(x_str, "aaaa1");
89  x_int = c["x"].to_int();
90  BOOST_CHECK_EQUAL(x_int, 0);
91  x_sll = c["x"].to_long_long();
92  BOOST_CHECK_EQUAL(x_sll, 0ll);
93  x_dbl = c["x"].to_double();
94  BOOST_CHECK_EQUAL(x_dbl, 0.0);
95 
96 // check type conversion when assigned as int
97  c["x"] = 1;
98  x_str = c["x"].str();
99  BOOST_CHECK_EQUAL(x_str, "1");
100  x_int = c["x"].to_int();
101  BOOST_CHECK_EQUAL(x_int, 1);
102  x_sll = c["x"].to_long_long();
103  BOOST_CHECK_EQUAL(x_sll, 1ll);
104  x_dbl = c["x"].to_double();
105  BOOST_CHECK_EQUAL(x_dbl, 1.0);
106 
107 // check type conversion when assigned as int (again)
108  c["x"] = 10000000;
109  x_int = c["x"].to_int();
110  BOOST_CHECK_EQUAL(x_int, 10000000);
111  x_str = c["x"].str();
112  BOOST_CHECK_EQUAL(x_str, "10000000");
113  x_sll = c["x"].to_long_long();
114  BOOST_CHECK_EQUAL(x_sll, 10000000ll);
115  x_dbl = c["x"].to_double();
116  BOOST_CHECK_EQUAL(x_dbl, 1e7);
117 
118 // check type conversion when assigned aan empty string
119  c["x"] = "";
120  x_sll = c["x"].to_long_long();
121  BOOST_CHECK_EQUAL(x_sll, 0ll);
122  x_str = c["x"].str();
123  BOOST_CHECK_EQUAL(x_str, "");
124  x_int = c["x"].to_int();
125  BOOST_CHECK_EQUAL(x_int, 0);
126  x_dbl = c["x"].to_double();
127  BOOST_CHECK_EQUAL(x_dbl, 0.0);
128 
129 // check type conversion when assigned as a hex string
130  c["x"] = "0x11";
131  x_int = c["x"].to_int();
132  BOOST_CHECK_EQUAL(x_int, 0);
133  x_str = c["x"].str();
134  BOOST_CHECK_EQUAL(x_str, "0x11");
135  x_sll = c["x"].to_long_long();
136  BOOST_CHECK_EQUAL(x_sll, 0ll);
137  x_dbl = c["x"].to_double();
138  BOOST_CHECK_EQUAL(x_dbl, 0.0);
139 
140 // check type conversion when assigned as a hex string (again)
141  c["x"] = "0xab";
142  x_int = c["x"].to_int();
143  BOOST_CHECK_EQUAL(x_int, 0);
144  x_str = c["x"].str();
145  BOOST_CHECK_EQUAL(x_str, "0xab");
146  x_sll = c["x"].to_long_long();
147  BOOST_CHECK_EQUAL(x_sll, 0ll);
148  x_dbl = c["x"].to_double();
149  BOOST_CHECK_EQUAL(x_dbl, 0.0);
150 
151 // check type conversion when assigned as a string with leading zeroes
152  c["x"] = "00001111";
153  x_int = c["x"].to_int();
154  BOOST_CHECK_EQUAL(x_int, 1111);
155  x_str = c["x"].str();
156  BOOST_CHECK_EQUAL(x_str, "00001111");
157  x_sll = c["x"].to_long_long();
158  BOOST_CHECK_EQUAL(x_sll, 1111ll);
159  x_dbl = c["x"].to_double();
160  BOOST_CHECK_EQUAL(x_dbl, 1.111e3);
161 
162 // check type conversion when assigned as a string with only zeroes
163  c["x"] = "000000";
164  x_int = c["x"].to_int();
165  BOOST_CHECK_EQUAL(x_int, 0);
166  x_str = c["x"].str();
167  BOOST_CHECK_EQUAL(x_str, "000000");
168  x_sll = c["x"].to_long_long();
169  BOOST_CHECK_EQUAL(x_sll, 0ll);
170  x_dbl = c["x"].to_double();
171  BOOST_CHECK_EQUAL(x_dbl, 0.0);
172 
173 // check type conversion when assigned as a string with leading zeroes and is too large to fit in an int
174  c["x"] = "01234567890123456789";
175  x_sll = c["x"].to_long_long();
176  BOOST_CHECK_EQUAL(x_sll, 1234567890123456789ll);
177  x_str = c["x"].str();
178  BOOST_CHECK_EQUAL(x_str, "01234567890123456789");
179  x_int = c["x"].to_int();
180  BOOST_CHECK_EQUAL(x_int, 0);
181  x_dbl = c["x"].to_double();
182  BOOST_CHECK_EQUAL(x_dbl, 1.23456789012345678e18);
183 
184 // check type conversion when assigned as a string with no leading zeroes and is too large to fit in an int
185  c["x"] = "99999999999999999999";
186  x_sll = c["x"].to_long_long();
187  BOOST_CHECK_EQUAL(x_sll, 0ll);
188  x_str = c["x"].str();
189  BOOST_CHECK_EQUAL(x_str, "99999999999999999999");
190  x_int = c["x"].to_int();
191  BOOST_CHECK_EQUAL(x_int, 0);
192  x_dbl = c["x"].to_double();
193  BOOST_CHECK_EQUAL(x_dbl, 1e20);
194 
195 // check type conversion when assigned as a floating point
196  c["x"] = 1.499;
197  x_sll = c["x"].to_long_long();
198  BOOST_CHECK_EQUAL(x_sll, 1ll);
199  x_str = c["x"].str();
200  BOOST_CHECK_EQUAL(x_str, "1.499");
201  x_int = c["x"].to_int();
202  BOOST_CHECK_EQUAL(x_int, 1);
203  x_dbl = c["x"].to_double();
204  BOOST_CHECK(std::abs(x_dbl - 1.499) < 1e-6);
205 
206 // check type conversion when assigned as a long long (int overflows)
207  c["x"] = 123456789123ll;
208  x_int = c["x"].to_int();
209  BOOST_CHECK_EQUAL(x_int, -1097262461);
210  x_dbl = c["x"].to_double();
211  BOOST_CHECK_EQUAL(x_dbl, 1.23456789123e11);
212  x_sll = c["x"].to_long_long();
213  BOOST_CHECK_EQUAL(x_sll, 123456789123ll);
214  x_str = c["x"].str();
215  BOOST_CHECK_EQUAL(x_str, "123456789123");
216 
217 // check heterogeneous comparison
218  using namespace std::literals;
219 
220  c["x"] = "1";
221  BOOST_CHECK_EQUAL(c["x"], "1");
222  c["x"] = 222;
223  BOOST_CHECK_EQUAL(c["x"], "222");
224  BOOST_CHECK_EQUAL(c["x"], "222"s);
225  c["x"] = "test";
226  BOOST_CHECK_EQUAL(c["x"], "test"sv);
227  c["x"] = false;
228  BOOST_CHECK_EQUAL(c["x"], "no");
229  BOOST_CHECK_EQUAL(c["x"], "false");
230  c["x"] = 9.87654321;
231  BOOST_CHECK_EQUAL(c["x"], "9.87654321");
232  BOOST_CHECK_EQUAL(c["x"], t_string{"9.87654321"});
233  BOOST_CHECK_EQUAL(c["x"], config_attribute_value::create("9.87654321"));
234  c["x"] = "sfvsdgdsfg";
235  BOOST_CHECK_EQUAL(c["x"], "sfvsdgdsfg");
236  BOOST_CHECK_EQUAL(c["x"], "sfvsdgdsfg"s);
237  BOOST_CHECK_EQUAL(c["x"], "sfvsdgdsfg"sv);
238  BOOST_CHECK_EQUAL(c["x"], t_string{"sfvsdgdsfg"});
239  BOOST_CHECK_NE(c["x"], "a random string");
240 
241  // reversed order heterogenous comparison
242  c["x"] = 9.87654321;
243  BOOST_CHECK_EQUAL("9.87654321", c["x"]);
244  BOOST_CHECK_EQUAL(t_string{"9.87654321"}, c["x"]);
245  BOOST_CHECK_EQUAL(config_attribute_value::create("9.87654321"), c["x"]);
246  BOOST_CHECK_NE("1.23456789", c["x"]);
247  BOOST_CHECK_NE("1.23456789"s, c["x"]);
248  BOOST_CHECK_NE("1.23456789"sv, c["x"]);
249 
250  // blank != "" test.
251  c.clear();
252  BOOST_CHECK(cc["x"] != "");
253  BOOST_CHECK(cc["x"].empty());
254  BOOST_CHECK(cc["x"].blank());
255 
256  BOOST_CHECK(c["x"] != "");
257  BOOST_CHECK(c["x"].empty());
258  BOOST_CHECK(c["x"].blank());
259 
260  BOOST_CHECK_EQUAL(cc["x"], c["x"]);
261 
262  c["x"] = "";
263  BOOST_CHECK(cc["x"].empty());
264  BOOST_CHECK(cc["x"].empty());
265  BOOST_CHECK(!cc["x"].blank());
266 
267  BOOST_CHECK(c["x"].empty());
268  BOOST_CHECK(c["x"].empty());
269  BOOST_CHECK(!c["x"].blank());
270 
271  BOOST_CHECK_EQUAL(cc["x"], c["x"]);
272 }
273 
274 BOOST_AUTO_TEST_CASE(test_variable_info)
275 {
276  // Returns the integer value of the given variable in the config
277  const auto scalar_value = [](const std::string& var, const config& cfg) {
278  return variable_access_const{var, cfg}.as_scalar().to_int();
279  };
280 
281  config c;
282  {
283  variable_access_const access("", c);
284  // We dotn allow empty keys
285  BOOST_CHECK_THROW(access.as_scalar(), invalid_variablename_exception);
286  }
287  {
288  variable_access_const access("some_non_existent.", c);
289  // We dotn allow empty keys
290  BOOST_CHECK_THROW(access.as_scalar(), invalid_variablename_exception);
291  }
292  {
293  variable_access_const access("some_non_existent[0]value", c);
294  // We expect '.' after ']'
295  BOOST_CHECK_THROW(access.as_scalar(), invalid_variablename_exception);
296  }
297  {
298  variable_access_const access("some_non_existent", c);
299  // we return empty be default
300  BOOST_CHECK(!access.exists_as_container());
301  BOOST_CHECK_EQUAL(access.as_container(), config());
302  BOOST_CHECK(!access.exists_as_attribute());
303  BOOST_CHECK_EQUAL(access.as_scalar(), config::attribute_value());
304  }
305  {
306  variable_access_const access("a.b[0].c[1].d.e.f[2]", c);
307  // we return empty be default
308  BOOST_CHECK(!access.exists_as_container());
309  BOOST_CHECK_EQUAL(access.as_container(), config());
310  // Explicit indexes can never be an attribute
311  BOOST_CHECK_THROW(access.as_scalar(), invalid_variablename_exception);
312  }
313  BOOST_CHECK(c.empty());
314  {
315  config c2;
316  variable_access_create access("a.b[0].c[1].d.e.f[2].g", c2);
317  access.as_scalar() = 84;
318  BOOST_CHECK_EQUAL(scalar_value("a.length", c2), 1);
319  BOOST_CHECK_EQUAL(scalar_value("a.b.length", c2), 1);
320  BOOST_CHECK_EQUAL(scalar_value("a.b.c.length", c2), 2);
321  BOOST_CHECK_EQUAL(scalar_value("a.b.c[1].d.e.f.length", c2), 3);
322  // we set g as a scalar
323  BOOST_CHECK_EQUAL(scalar_value("a.b.c[1].d.e.f[2].g.length", c2), 0);
324  BOOST_CHECK_EQUAL(scalar_value("a.b.c[1].d.e.f[2].g", c2), 84);
325  }
326  {
327  config c2;
328  variable_access_throw access("a.b[9].c", c2);
329  BOOST_CHECK_THROW(access.as_scalar(), invalid_variablename_exception);
330  }
331  {
332  /* clang-format off */
333  const config nonempty{
334  "tag1", config(),
335  "tag1", config{
336  "tag2", config(),
337  "tag2", config(),
338  "tag2", config{
339  "atribute1", 88,
340  "atribute2", "value",
341  },
342  },
343  "tag1", config(),
344  };
345  /* clang-format on */
346  /** This is the config:
347  [tag1]
348  [/tag1]
349  [tag1]
350  [tag2]
351  [/tag2]
352  [tag2]
353  [/tag2]
354  [tag2]
355  atribute1 = 88
356  atribute2 = "value"
357  [/tag2]
358  [/tag1]
359  [tag1]
360  [/tag1]
361  */
362  BOOST_CHECK_EQUAL(scalar_value("tag1.length", nonempty), 3);
363  BOOST_CHECK_EQUAL(scalar_value("tag1.tag2.length", nonempty), 0);
364  BOOST_CHECK_EQUAL(scalar_value("tag1[1].tag2.length", nonempty), 3);
365  BOOST_CHECK_EQUAL(scalar_value("tag1[1].tag2[2].atribute1", nonempty), 88);
366  int count = 0;
367  for([[maybe_unused]] const config& child : variable_access_const("tag1", nonempty).as_array()) {
368  ++count;
369  }
370  BOOST_CHECK_EQUAL(count, 3);
371  count = 0;
372  for([[maybe_unused]] const config& child : variable_access_const("tag1.tag2", nonempty).as_array()) {
373  ++count;
374  }
375  BOOST_CHECK_EQUAL(count, 0);
376  count = 0;
377  // explicit indexes as range always return a one element range, whether they exist or not.
378  for([[maybe_unused]] const config& child : variable_access_const("tag1.tag2[5]", nonempty).as_array()) {
379  ++count;
380  }
381  BOOST_CHECK_EQUAL(count, 1);
382  }
383 }
384 
385 BOOST_AUTO_TEST_CASE(add_child_EmptyThis_newKey_AppendAndReturnNewEmptyChild)
386 {
387  config actual;
388  const config new_child = actual.add_child("A");
389  const config expected("A");
390  BOOST_CHECK_EQUAL(actual, expected);
391  BOOST_CHECK_EQUAL(new_child, config());
392 }
393 
394 namespace bdata = boost::unit_test::data;
395 BOOST_DATA_TEST_CASE(add_child_NonEmptyThis_newOrExistingKey_lOrRValue_AppendAndReturnNewChild,
396  bdata::make({"A", "B", "C"}) * bdata::make<std::string>({"lvalue_ref", "rvalue_ref"}),
397  key,
398  update_ref_kind) // 3 * 2 = 6 cases
399 {
400  // Data for testing base.add_child(key, update)
401  const config base{"A", config(), "a", 1}; // [A][/A] a = 1
402  const config update{"B", config(), "b", 2}; // [B][/B] b = 2
403  // Expected config: [A][/A] a = 1 [key] [B][/B] b = 2 [/key]
404  /* clang-format off */
405  const config expected{
406  // base
407  "A", config(), "a", 1,
408  // [key] copy of update [/key]
409  key, config(update)};
410  /* clang-format on */
411  // Make actual
412  config actual(base);
413  config new_child;
414  if(update_ref_kind == std::string("lvalue_ref"))
415  new_child = actual.add_child(key, update);
416  else
417  new_child = actual.add_child(key, config(update)); // rvalue ref.
418 
419  BOOST_CHECK_EQUAL(actual, expected);
420  BOOST_CHECK_EQUAL(new_child, update);
421  // Assert the new child is a copy of update
422  BOOST_CHECK_NE(&new_child, &update);
423 }
424 
425 BOOST_AUTO_TEST_SUITE_END()
Variant for storing WML attributes.
static config_attribute_value create(const T val)
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
config & add_child(config_key_type key)
Definition: config.cpp:436
Additional functionality for a non-const variable_info.
Information on a WML variable.
maybe_const_t< config::attribute_value, V > & as_scalar() const
If instantiated with vi_policy_const, the lifetime of the returned const attribute_value reference mi...
bool exists_as_container() const
maybe_const_t< config, V > & as_container() const
If instantiated with vi_policy_const, the lifetime of the returned const attribute_value reference mi...
bool exists_as_attribute() const
Definitions for the interface to Wesnoth Markup Language (WML).
const config * cfg
static void update()
std::string_view data
Definition: picture.cpp:188
BOOST_AUTO_TEST_CASE(test_config_attribute_value)
Definition: test_config.cpp:29
BOOST_DATA_TEST_CASE(add_child_NonEmptyThis_newOrExistingKey_lOrRValue_AppendAndReturnNewChild, bdata::make({"A", "B", "C"}) *bdata::make< std::string >({"lvalue_ref", "rvalue_ref"}), key, update_ref_kind)
BOOST_AUTO_TEST_SUITE(filesystem)
mock_char c
static map_location::direction s
variable_info< const variable_info_implementation::vi_policy_const > variable_access_const
Read-only access.
#define e