Utopia  2
Framework for studying models of complex & adaptive systems.
config.hh
Go to the documentation of this file.
1 #ifndef UTOPIA_CORE_TESTTOOLS_CONFIG_HH
2 #define UTOPIA_CORE_TESTTOOLS_CONFIG_HH
3 
4 #include <string>
5 #include <functional>
6 #include <string_view>
7 #include <exception>
8 #include <type_traits>
9 
10 #include <boost/test/unit_test.hpp>
11 #include <boost/core/demangle.hpp>
12 
13 #include <yaml-cpp/yaml.h>
14 
15 #include "exceptions.hh"
16 #include "utils.hh"
17 
18 
19 namespace Utopia::TestTools {
20 
27 
108 template<typename Callable=std::function<void(const DataIO::Config&)>>
109 void test_config_callable(Callable&& func,
110  const DataIO::Config& test_cases,
111  const std::string_view context_name = "",
112  const LocationInfo loc = {})
113 {
114  // Check if callable can be invoked
115  static_assert(std::is_invocable_v<Callable, DataIO::Config&>,
116  "Callable requires DataIO::Config as its only argument!");
117 
118  // Iterate over multiple configuration cases
119  for (const auto& kv_pair : test_cases) {
120  // Extract name of the case that is to be tested and enter context
121  const auto case_name = kv_pair.first.template as<std::string>();
122  const auto case_cfg = kv_pair.second;
123 
124  BOOST_TEST_CONTEXT(loc << context_name
125  << " -- Testing case '" << case_name
126  << "' ... with the following parameters:\n\n"
127  << case_cfg << "\n")
128  {
129  // Check if it this call is expected to throw; if so, check that it
130  // throws the expected error message
131  if (case_cfg["throws"]) {
132  // Bind the config to a new callable (which accepts no arguments)
133  const auto to_test = [func, case_cfg](){
134  func(case_cfg["params"]);
135  };
136 
137  const auto exc_type = get_as<std::string>("throws", case_cfg);
138  const auto match = get_as<std::string>("match", case_cfg, "");
139 
140  // Check the exception type
141  // ... have to define this manually for all types ...
142  if (exc_type == "std::exception") {
143  check_exception<std::exception>(to_test, match, loc);
144  }
145  else if (exc_type == "std::logic_error") {
146  check_exception<std::logic_error>(to_test, match, loc);
147  }
148  else if (exc_type == "std::invalid_argument") {
149  check_exception<std::invalid_argument>(to_test, match, loc);
150  }
151  else if (exc_type == "std::domain_error") {
152  check_exception<std::domain_error>(to_test, match, loc);
153  }
154  else if (exc_type == "std::length_error") {
155  check_exception<std::length_error>(to_test, match, loc);
156  }
157  else if (exc_type == "std::out_of_range") {
158  check_exception<std::out_of_range>(to_test, match, loc);
159  }
160  else if (exc_type == "std::runtime_error") {
161  check_exception<std::runtime_error>(to_test, match, loc);
162  }
163  else if (exc_type == "std::range_error") {
164  check_exception<std::range_error>(to_test, match, loc);
165  }
166  else if (exc_type == "std::overflow_error") {
167  check_exception<std::overflow_error>(to_test, match, loc);
168  }
169  else if (exc_type == "std::underflow_error") {
170  check_exception<std::underflow_error>(to_test, match, loc);
171  }
172  else if (exc_type == "Utopia::KeyError") {
173  check_exception<Utopia::KeyError>(to_test, match, loc);
174  }
175  else if (exc_type == "Utopia::Exception") {
176  check_exception<Utopia::Exception>(to_test, match, loc);
177  }
178  else if (exc_type == "YAML::Exception") {
179  check_exception<YAML::Exception>(to_test, match, loc);
180  }
181  else {
182  BOOST_ERROR(
183  "Invalid exception type '" << exc_type << "' given in "
184  "`throws` argument! Supported exception types are: "
185  "std::exception, std::logic_error, std::invalid_argument, "
186  "std::domain_error, std::length_error, std::out_of_range, "
187  "std::runtime_error, std::range_error, "
188  "std::overflow_error, std::underflow_error, "
189  "Utopia::KeyError, Utopia::Exception, "
190  "and YAML::Exception."
191  );
192  }
193 
194  // No need to test anything else; continue in outer loop
195  continue;
196  }
197 
198  // NOT expected to throw. Invoke the test callable with its params...
199  try {
200  func(case_cfg["params"]);
201  }
202  catch (std::exception& e) {
203  BOOST_ERROR(loc << "Unexpectedly threw an error ("
204  << boost::core::demangle(typeid(e).name())
205  << ") with message: " << e.what());
206  }
207  catch (...) {
208  BOOST_ERROR(loc << "Unexpectedly threw a non-std error!");
209  }
210 
211  } // End of test context
212  }
213 }
214 
215 
216 
217 // end group TestTools
222 } // namespace Utopia::TestTools
223 
224 #endif // UTOPIA_CORE_TESTTOOLS_CONFIG_HH
YAML::Node Config
Type of a variadic dictionary-like data structure used throughout Utopia.
Definition: types.hh:71
void test_config_callable(Callable &&func, const DataIO::Config &test_cases, const std::string_view context_name="", const LocationInfo loc={})
Repeatedly invokes a unary function that expects a Config node.
Definition: config.hh:109
Definition: config.hh:19
Bundles and handles file location information: file path and line number.
Definition: utils.hh:25