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  // blank != "" test.
242  c.clear();
243  BOOST_CHECK(cc["x"] != "");
244  BOOST_CHECK(cc["x"].empty());
245  BOOST_CHECK(cc["x"].blank());
246 
247  BOOST_CHECK(c["x"] != "");
248  BOOST_CHECK(c["x"].empty());
249  BOOST_CHECK(c["x"].blank());
250 
251  BOOST_CHECK_EQUAL(cc["x"], c["x"]);
252 
253  c["x"] = "";
254  BOOST_CHECK(cc["x"].empty());
255  BOOST_CHECK(cc["x"].empty());
256  BOOST_CHECK(!cc["x"].blank());
257 
258  BOOST_CHECK(c["x"].empty());
259  BOOST_CHECK(c["x"].empty());
260  BOOST_CHECK(!c["x"].blank());
261 
262  BOOST_CHECK_EQUAL(cc["x"], c["x"]);
263 }
264 
265 BOOST_AUTO_TEST_CASE(test_variable_info)
266 {
267  // Returns the integer value of the given variable in the config
268  const auto scalar_value = [](const std::string& var, const config& cfg) {
269  return variable_access_const{var, cfg}.as_scalar().to_int();
270  };
271 
272  config c;
273  {
274  variable_access_const access("", c);
275  // We dotn allow empty keys
276  BOOST_CHECK_THROW(access.as_scalar(), invalid_variablename_exception);
277  }
278  {
279  variable_access_const access("some_non_existent.", c);
280  // We dotn allow empty keys
281  BOOST_CHECK_THROW(access.as_scalar(), invalid_variablename_exception);
282  }
283  {
284  variable_access_const access("some_non_existent[0]value", c);
285  // We expect '.' after ']'
286  BOOST_CHECK_THROW(access.as_scalar(), invalid_variablename_exception);
287  }
288  {
289  variable_access_const access("some_non_existent", c);
290  // we return empty be default
291  BOOST_CHECK(!access.exists_as_container());
292  BOOST_CHECK_EQUAL(access.as_container(), config());
293  BOOST_CHECK(!access.exists_as_attribute());
294  BOOST_CHECK_EQUAL(access.as_scalar(), config::attribute_value());
295  }
296  {
297  variable_access_const access("a.b[0].c[1].d.e.f[2]", c);
298  // we return empty be default
299  BOOST_CHECK(!access.exists_as_container());
300  BOOST_CHECK_EQUAL(access.as_container(), config());
301  // Explicit indexes can never be an attribute
302  BOOST_CHECK_THROW(access.as_scalar(), invalid_variablename_exception);
303  }
304  BOOST_CHECK(c.empty());
305  {
306  config c2;
307  variable_access_create access("a.b[0].c[1].d.e.f[2].g", c2);
308  access.as_scalar() = 84;
309  BOOST_CHECK_EQUAL(scalar_value("a.length", c2), 1);
310  BOOST_CHECK_EQUAL(scalar_value("a.b.length", c2), 1);
311  BOOST_CHECK_EQUAL(scalar_value("a.b.c.length", c2), 2);
312  BOOST_CHECK_EQUAL(scalar_value("a.b.c[1].d.e.f.length", c2), 3);
313  // we set g as a scalar
314  BOOST_CHECK_EQUAL(scalar_value("a.b.c[1].d.e.f[2].g.length", c2), 0);
315  BOOST_CHECK_EQUAL(scalar_value("a.b.c[1].d.e.f[2].g", c2), 84);
316  }
317  {
318  config c2;
319  variable_access_throw access("a.b[9].c", c2);
320  BOOST_CHECK_THROW(access.as_scalar(), invalid_variablename_exception);
321  }
322  {
323  /* clang-format off */
324  const config nonempty{
325  "tag1", config(),
326  "tag1", config{
327  "tag2", config(),
328  "tag2", config(),
329  "tag2", config{
330  "atribute1", 88,
331  "atribute2", "value",
332  },
333  },
334  "tag1", config(),
335  };
336  /* clang-format on */
337  /** This is the config:
338  [tag1]
339  [/tag1]
340  [tag1]
341  [tag2]
342  [/tag2]
343  [tag2]
344  [/tag2]
345  [tag2]
346  atribute1 = 88
347  atribute2 = "value"
348  [/tag2]
349  [/tag1]
350  [tag1]
351  [/tag1]
352  */
353  BOOST_CHECK_EQUAL(scalar_value("tag1.length", nonempty), 3);
354  BOOST_CHECK_EQUAL(scalar_value("tag1.tag2.length", nonempty), 0);
355  BOOST_CHECK_EQUAL(scalar_value("tag1[1].tag2.length", nonempty), 3);
356  BOOST_CHECK_EQUAL(scalar_value("tag1[1].tag2[2].atribute1", nonempty), 88);
357  int count = 0;
358  for([[maybe_unused]] const config& child : variable_access_const("tag1", nonempty).as_array()) {
359  ++count;
360  }
361  BOOST_CHECK_EQUAL(count, 3);
362  count = 0;
363  for([[maybe_unused]] const config& child : variable_access_const("tag1.tag2", nonempty).as_array()) {
364  ++count;
365  }
366  BOOST_CHECK_EQUAL(count, 0);
367  count = 0;
368  // explicit indexes as range always return a one element range, whether they exist or not.
369  for([[maybe_unused]] const config& child : variable_access_const("tag1.tag2[5]", nonempty).as_array()) {
370  ++count;
371  }
372  BOOST_CHECK_EQUAL(count, 1);
373  }
374 }
375 
376 BOOST_AUTO_TEST_CASE(add_child_EmptyThis_newKey_AppendAndReturnNewEmptyChild)
377 {
378  config actual;
379  const config new_child = actual.add_child("A");
380  const config expected("A");
381  BOOST_CHECK_EQUAL(actual, expected);
382  BOOST_CHECK_EQUAL(new_child, config());
383 }
384 
385 namespace bdata = boost::unit_test::data;
386 BOOST_DATA_TEST_CASE(add_child_NonEmptyThis_newOrExistingKey_lOrRValue_AppendAndReturnNewChild,
387  bdata::make({"A", "B", "C"}) * bdata::make<std::string>({"lvalue_ref", "rvalue_ref"}),
388  key,
389  update_ref_kind) // 3 * 2 = 6 cases
390 {
391  // Data for testing base.add_child(key, update)
392  const config base{"A", config(), "a", 1}; // [A][/A] a = 1
393  const config update{"B", config(), "b", 2}; // [B][/B] b = 2
394  // Expected config: [A][/A] a = 1 [key] [B][/B] b = 2 [/key]
395  /* clang-format off */
396  const config expected{
397  // base
398  "A", config(), "a", 1,
399  // [key] copy of update [/key]
400  key, config(update)};
401  /* clang-format on */
402  // Make actual
403  config actual(base);
404  config new_child;
405  if(update_ref_kind == std::string("lvalue_ref"))
406  new_child = actual.add_child(key, update);
407  else
408  new_child = actual.add_child(key, config(update)); // rvalue ref.
409 
410  BOOST_CHECK_EQUAL(actual, expected);
411  BOOST_CHECK_EQUAL(new_child, update);
412  // Assert the new child is a copy of update
413  BOOST_CHECK_NE(&new_child, &update);
414 }
415 
416 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