VectorImplTestHelper.hpp
1 // Distributed under the MIT License.
2 // See LICENSE.txt for details.
3 
4 #pragma once
5 
6 #include <algorithm>
7 #include <array>
8 #include <cstddef>
9 #include <functional>
10 #include <map>
11 #include <memory> // IWYU pragma: keep
12 #include <random>
13 #include <tuple>
14 #include <utility>
15 #include <vector>
16 
17 #include "DataStructures/VectorImpl.hpp"
18 #include "Utilities/Algorithm.hpp"
19 #include "Utilities/DereferenceWrapper.hpp" // IWYU pragma: keep
20 #include "Utilities/Gsl.hpp"
21 #include "Utilities/Math.hpp" // IWYU pragma: keep
22 #include "Utilities/Requires.hpp"
23 #include "Utilities/TMPL.hpp"
24 #include "Utilities/Tuple.hpp"
25 #include "Utilities/TypeTraits.hpp"
29 
30 namespace TestHelpers {
31 namespace VectorImpl {
32 
33 /// \ingroup TestingFrameworkGroup
34 /// \brief test construction and assignment of a `VectorType` with a `ValueType`
35 template <typename VectorType, typename ValueType>
37  tt::get_fundamental_type_t<ValueType> low =
38  tt::get_fundamental_type_t<ValueType>{-100.0},
39  tt::get_fundamental_type_t<ValueType> high =
40  tt::get_fundamental_type_t<ValueType>{100.0}) noexcept {
41  MAKE_GENERATOR(gen);
43  high};
45 
46  const size_t size = sdist(gen);
47 
48  const VectorType size_constructed{size};
49  CHECK(size_constructed.size() == size);
50  const auto generated_value1 = make_with_random_values<ValueType>(
51  make_not_null(&gen), make_not_null(&dist));
52 
53  const VectorType value_size_constructed{size, generated_value1};
54  CHECK(value_size_constructed.size() == size);
55  alg::for_each(value_size_constructed, [
56  generated_value1, &value_size_constructed
57  ](typename VectorType::value_type element) noexcept {
58  CAPTURE_PRECISE(value_size_constructed);
59  CHECK(element == generated_value1);
60  });
61 
62  // random generation must use `make_with_random_values`, because stored value
63  // in vector type might be a non-fundamental type.
64  const auto generated_value2 = make_with_random_values<ValueType>(
65  make_not_null(&gen), make_not_null(&dist));
66  const auto generated_value3 = make_with_random_values<ValueType>(
67  make_not_null(&gen), make_not_null(&dist));
68 
69  VectorType initializer_list_constructed{
70  {static_cast<typename VectorType::value_type>(generated_value2),
71  static_cast<typename VectorType::value_type>(generated_value3)}};
72  CHECK(initializer_list_constructed.size() == 2);
73  CHECK(initializer_list_constructed.is_owning());
74  CHECK(gsl::at(initializer_list_constructed, 0) == generated_value2);
75  CHECK(gsl::at(initializer_list_constructed, 1) == generated_value3);
76 
77  typename VectorType::value_type raw_ptr[2] = {generated_value2,
78  generated_value3};
79  const VectorType pointer_size_constructed{
80  static_cast<typename VectorType::value_type*>(raw_ptr), 2};
81  CHECK(initializer_list_constructed == pointer_size_constructed);
82  CHECK_FALSE(initializer_list_constructed != pointer_size_constructed);
83 
84  test_copy_semantics(initializer_list_constructed);
85  auto initializer_list_constructed_copy = initializer_list_constructed;
86  CHECK(initializer_list_constructed_copy.is_owning());
87  CHECK(initializer_list_constructed_copy == pointer_size_constructed);
88  test_move_semantics(std::move(initializer_list_constructed),
89  initializer_list_constructed_copy);
90 
91  VectorType move_assignment_initialized;
92  move_assignment_initialized = std::move(initializer_list_constructed_copy);
93  CHECK(move_assignment_initialized.is_owning());
94 
95  const VectorType move_constructed{std::move(move_assignment_initialized)};
96  CHECK(move_constructed.is_owning());
97  CHECK(move_constructed == pointer_size_constructed);
98 
99  // clang-tidy has performance complaints, and we're checking functionality
100  const VectorType copy_constructed{move_constructed}; // NOLINT
101  CHECK(copy_constructed.is_owning());
102  CHECK(copy_constructed == pointer_size_constructed);
103 }
104 
105 /// \ingroup TestingFrameworkGroup
106 /// \brief test the serialization of a `VectorType` constructed with a
107 /// `ValueType`
108 template <typename VectorType, typename ValueType>
109 void vector_test_serialize(tt::get_fundamental_type_t<ValueType> low =
110  tt::get_fundamental_type_t<ValueType>{-100.0},
111  tt::get_fundamental_type_t<ValueType> high =
112  tt::get_fundamental_type_t<ValueType>{
113  100.0}) noexcept {
114  MAKE_GENERATOR(gen);
116  high};
118 
119  const size_t size = sdist(gen);
120  VectorType vector_test{size}, vector_control{size};
121  VectorType vector_ref;
122  const auto start_value = make_with_random_values<ValueType>(
123  make_not_null(&gen), make_not_null(&dist));
124  const auto value_difference = make_with_random_values<ValueType>(
125  make_not_null(&gen), make_not_null(&dist));
126  // generate_series is used to generate a pair of equivalent, but independently
127  // constructed, data sets to fill the vectors with.
128  ValueType current_value = start_value;
129  const auto generate_series = [&current_value, value_difference ]() noexcept {
130  return current_value += value_difference;
131  };
132  std::generate(vector_test.begin(), vector_test.end(), generate_series);
133  current_value = start_value;
134  std::generate(vector_control.begin(), vector_control.end(), generate_series);
135  // checks the vectors have been constructed as expected
136  CHECK(vector_control == vector_test);
137  CHECK(vector_test.is_owning());
138  CHECK(vector_control.is_owning());
139  const VectorType serialized_vector_test =
140  serialize_and_deserialize(vector_test);
141  // check that the vector is unaltered by serialize_and_deserialize
142  CHECK(vector_control == vector_test);
143  CHECK(serialized_vector_test == vector_control);
144  CHECK(serialized_vector_test.is_owning());
145  CHECK(serialized_vector_test.data() != vector_test.data());
146  CHECK(vector_test.is_owning());
147  // checks serialization for reference
148  vector_ref.set_data_ref(&vector_test);
149  CHECK(vector_test.is_owning());
150  CHECK_FALSE(vector_ref.is_owning());
151  CHECK(vector_ref == vector_test);
152  const VectorType serialized_vector_ref =
153  serialize_and_deserialize(vector_ref);
154  CHECK(vector_test.is_owning());
155  CHECK(vector_test == vector_control);
156  CHECK(vector_ref == vector_test);
157  CHECK(serialized_vector_ref == vector_test);
158  CHECK(serialized_vector_ref.is_owning());
159  CHECK(serialized_vector_ref.data() != vector_ref.data());
160  CHECK_FALSE(vector_ref.is_owning());
161 }
162 
163 /// \ingroup TestingFrameworkGroup
164 /// \brief test the construction and move of a reference `VectorType`
165 /// constructed with a `ValueType`
166 template <typename VectorType, typename ValueType>
167 void vector_test_ref(tt::get_fundamental_type_t<ValueType> low =
168  tt::get_fundamental_type_t<ValueType>{-100.0},
169  tt::get_fundamental_type_t<ValueType> high =
170  tt::get_fundamental_type_t<ValueType>{
171  100.0}) noexcept {
172  MAKE_GENERATOR(gen);
174  high};
176 
177  const size_t size = sdist(gen);
178  VectorType original_vector = make_with_random_values<VectorType>(
179  make_not_null(&gen), make_not_null(&dist), VectorType{size});
180 
181  {
182  INFO("Check construction, copy, move, and ownership of reference vectors")
183  VectorType ref_vector;
184  ref_vector.set_data_ref(&original_vector);
185  CHECK_FALSE(ref_vector.is_owning());
186  CHECK(original_vector.is_owning());
187  CHECK(ref_vector.data() == original_vector.data());
188 
189  const VectorType data_check{original_vector};
190  CHECK(ref_vector.size() == size);
191  CHECK(ref_vector == data_check);
192  test_copy_semantics(ref_vector);
193 
194  VectorType ref_vector_copy;
195  ref_vector_copy.set_data_ref(&ref_vector);
196  test_move_semantics(std::move(ref_vector), ref_vector_copy);
197  VectorType move_assignment_initialized;
198  move_assignment_initialized = std::move(ref_vector_copy);
199  CHECK(not move_assignment_initialized.is_owning());
200  const VectorType move_constructed{std::move(move_assignment_initialized)};
201  CHECK(not move_constructed.is_owning());
202  }
203  {
204  INFO("Check move acts appropriately on both source and target refs")
205  VectorType ref_original_vector;
206  ref_original_vector.set_data_ref(&original_vector);
207  VectorType generated_vector = make_with_random_values<VectorType>(
208  make_not_null(&gen), make_not_null(&dist), VectorType{size});
209  const VectorType generated_vector_copy = generated_vector;
210  ref_original_vector = std::move(generated_vector);
211  // clang-tidy : Intentionally testing use after move
212  CHECK(original_vector != generated_vector); // NOLINT
213  CHECK(original_vector == generated_vector_copy);
214 // Intentionally testing self-move
215 #ifdef __clang__
216 #pragma GCC diagnostic push
217 #pragma GCC diagnostic ignored "-Wself-move"
218 #endif // defined(__clang__)
219  ref_original_vector = std::move(ref_original_vector);
220 #ifdef __clang__
221 #pragma GCC diagnostic pop
222 #endif // defined(__clang__)
223  CHECK(original_vector == generated_vector_copy);
224  // clang-tidy: false positive, used after it was moved
225  const VectorType data_check_vector = ref_original_vector; // NOLINT
226  CHECK(data_check_vector == generated_vector_copy);
227  }
228  {
229  INFO("Check math affects both data vectors which share a ref")
230  const auto generated_value1 = make_with_random_values<ValueType>(
231  make_not_null(&gen), make_not_null(&dist));
232  const auto generated_value2 = make_with_random_values<ValueType>(
233  make_not_null(&gen), make_not_null(&dist));
234  const auto sum_generated_values = generated_value1 + generated_value2;
235  VectorType sharing_vector{size, generated_value1};
236  VectorType owning_vector{size, generated_value2};
237  sharing_vector.set_data_ref(&owning_vector);
238  sharing_vector = sharing_vector + generated_value1;
239  CHECK_ITERABLE_APPROX(owning_vector,
240  (VectorType{size, sum_generated_values}));
241  CHECK_ITERABLE_APPROX(sharing_vector,
242  (VectorType{size, sum_generated_values}));
243  }
244 }
245 
246 enum RefSizeErrorTestKind { Copy, ExpressionAssign, Move };
247 
248 /// \ingroup TestingFrameworkGroup
249 /// \brief Test that assigning to a non-owning `VectorType` of the wrong size
250 /// appropriately generates an error.
251 ///
252 /// \details a calling function should be an `ASSERTION_TEST()` and check for
253 /// the string "Must copy into same size".
254 /// Three types of tests are provided and one must be provided as the first
255 /// function argument:
256 /// - `RefSizeErrorTestKind::Copy`: Checks that copy-assigning to a non-owning
257 /// `VectorType` from a `VectorType` with the wrong size generates an error.
258 /// - `RefSizeErrorTestKind::ExpressionAssign`: Checks that assigning to a
259 /// non-owning `VectorType` from an expression with alias `ResultType` of
260 /// `VectorType` with the wrong size generates an error
261 /// - `RefSizeErrorTestKind::Move`: Checks that move-assigning to a non-owning
262 /// `VectorType` from a `VectorType` with the wrong size generates an error.
263 template <typename VectorType,
264  typename ValueType = typename VectorType::ElementType>
266  RefSizeErrorTestKind test_kind,
267  tt::get_fundamental_type_t<ValueType> low =
268  tt::get_fundamental_type_t<ValueType>{-100.0},
269  tt::get_fundamental_type_t<ValueType> high =
270  tt::get_fundamental_type_t<ValueType>{100.0}) noexcept {
271  MAKE_GENERATOR(gen);
273  high};
275 
276  const size_t size = sdist(gen);
277  VectorType generated_vector = make_with_random_values<VectorType>(
278  make_not_null(&gen), make_not_null(&dist), VectorType{size});
279  VectorType ref_generated_vector;
280  ref_generated_vector.set_data_ref(&generated_vector);
281  const VectorType larger_generated_vector =
282  make_with_random_values<VectorType>(
283  make_not_null(&gen), make_not_null(&dist), VectorType{size + 1});
284  // each of the following options should error, the reference should have
285  // received the wrong size
286  if (test_kind == RefSizeErrorTestKind::Copy) {
287  ref_generated_vector = larger_generated_vector;
288  }
289  if (test_kind == RefSizeErrorTestKind::ExpressionAssign) {
290  ref_generated_vector = (larger_generated_vector + larger_generated_vector);
291  }
292  if (test_kind == RefSizeErrorTestKind::Move) {
293  ref_generated_vector = std::move(larger_generated_vector);
294  }
295 }
296 
297 /// \ingroup TestingFrameworkGroup
298 /// \brief tests a small sample of math functions after a move of a
299 /// `VectorType` initialized with `ValueType`
300 template <typename VectorType, typename ValueType>
302  tt::get_fundamental_type_t<ValueType> low =
303  tt::get_fundamental_type_t<ValueType>{-100.0},
304  tt::get_fundamental_type_t<ValueType> high =
305  tt::get_fundamental_type_t<ValueType>{100.0}) noexcept {
306  MAKE_GENERATOR(gen);
308  high};
310 
311  const size_t size = sdist(gen);
312  const auto generated_value1 = make_with_random_values<ValueType>(
313  make_not_null(&gen), make_not_null(&dist));
314  const auto generated_value2 = make_with_random_values<ValueType>(
315  make_not_null(&gen), make_not_null(&dist));
316  const auto sum_generated_values = generated_value1 + generated_value2;
317  const auto difference_generated_values = generated_value1 - generated_value2;
318 
319  const VectorType vector_math_lhs{size, generated_value1},
320  vector_math_rhs{size, generated_value2};
321  {
322  INFO("Check move assignment and use after move");
323  VectorType from_vector = make_with_random_values<VectorType>(
324  make_not_null(&gen), make_not_null(&dist), VectorType{size});
325  VectorType to_vector{};
326  to_vector = std::move(from_vector);
327  to_vector = vector_math_lhs + vector_math_rhs;
328  CHECK_ITERABLE_APPROX(to_vector, (VectorType{size, sum_generated_values}));
329  // clang-tidy: use after move (intentional here)
330  CHECK(from_vector.size() == 0); // NOLINT
331  CHECK(from_vector.is_owning());
332  from_vector = vector_math_lhs - vector_math_rhs;
333  CHECK_ITERABLE_APPROX(from_vector,
334  (VectorType{size, difference_generated_values}));
335  CHECK_ITERABLE_APPROX(to_vector, (VectorType{size, sum_generated_values}));
336  }
337  {
338  INFO("Check move assignment and value of target")
339  auto from_value = make_with_random_values<ValueType>(make_not_null(&gen),
340  make_not_null(&dist));
341  VectorType from_vector{size, from_value};
342  VectorType to_vector{};
343  to_vector = std::move(from_vector);
344  from_vector = vector_math_lhs + vector_math_rhs;
345  CHECK_ITERABLE_APPROX(to_vector, (VectorType{size, from_value}));
346  CHECK_ITERABLE_APPROX(from_vector,
347  (VectorType{size, sum_generated_values}));
348  }
349  {
350  INFO("Check move constructor and use after move")
351  VectorType from_vector = make_with_random_values<VectorType>(
352  make_not_null(&gen), make_not_null(&dist), VectorType{size});
353  VectorType to_vector{std::move(from_vector)};
354  to_vector = vector_math_lhs + vector_math_rhs;
355  CHECK(to_vector.size() == size);
356  CHECK_ITERABLE_APPROX(to_vector, (VectorType{size, sum_generated_values}));
357  // clang-tidy: use after move (intentional here)
358  CHECK(from_vector.size() == 0); // NOLINT
359  CHECK(from_vector.is_owning());
360  from_vector = vector_math_lhs - vector_math_rhs;
361  CHECK_ITERABLE_APPROX(from_vector,
362  (VectorType{size, difference_generated_values}));
363  CHECK_ITERABLE_APPROX(to_vector, (VectorType{size, sum_generated_values}));
364  }
365 
366  {
367  INFO("Check move constructor and value of target")
368  auto from_value = make_with_random_values<ValueType>(make_not_null(&gen),
369  make_not_null(&dist));
370  VectorType from_vector{size, from_value};
371  const VectorType to_vector{std::move(from_vector)};
372  from_vector = vector_math_lhs + vector_math_rhs;
373  CHECK_ITERABLE_APPROX(to_vector, (VectorType{size, from_value}));
374  CHECK_ITERABLE_APPROX(from_vector,
375  (VectorType{size, sum_generated_values}));
376  }
377 }
378 
379 /// \ingroup TestingFrameworkGroup
380 /// \brief Type alias to be more expressive with distribution bounds in vector
381 /// tests which call the generic math test below
383 
384 /// \ingroup TestingFrameworkGroup
385 /// \brief the set of test types that may be used for the math operations
386 ///
387 /// \details Three types of test are provided:
388 /// - `Normal` is used to indicate those tests which should be performed over
389 /// all combinations of the supplied vector type(s) and their value
390 /// types. This is useful for e.g. `+`.
391 ///
392 /// - `Strict` is used to indicate those tests which should be performed over
393 /// only sets of the vector type and compared to the same operation of the set
394 /// of its value type. This is useful for e.g. `atan2`, which cannot take a
395 /// `DataVector` and a double as arguments.
396 ///
397 /// - `Inplace` is used to indicate those tests which should be performed
398 /// maintaining the type of the left-hand side of the operator and not
399 /// including it in the combinations. Inplace operators such as `+=` have a
400 /// more restrictive condition on the type of the left hand side than do
401 /// simply `+`. (e.g. `double + complex<double>` compiles, but
402 /// `double += complex<double>` does not)
403 ///
404 /// - `GivenOrderOfArgumentsOnly` is used to indicate that the arguments given
405 /// should not be taken in any combination apart from the given combination.
406 /// This should be used for highly restrictive operations which are only
407 /// supported for certain type combinations.
408 enum TestKind { Normal, Strict, Inplace, GivenOrderOfArgumentsOnly };
409 
410 namespace detail {
411 
412 // to choose between possible reference wrappers for the math tests.
413 namespace UseRefWrap {
414 struct Cref {};
415 struct None {};
416 struct Ref {};
417 } // namespace UseRefWrap
418 
419 // Wrap is used to wrap values in a std::reference_wrapper using std::cref and
420 // std::ref, or to not wrap at all. This is done to verify that all math
421 // operations work transparently with a `std::reference_wrapper` too.
422 template <class T>
423 decltype(auto) wrap(UseRefWrap::Cref /*meta*/, T& t) noexcept {
424  return std::cref(t);
425 }
426 
427 template <class T>
428 decltype(auto) wrap(UseRefWrap::None /*meta*/, T& t) noexcept {
429  return t;
430 }
431 
432 template <class T>
433 decltype(auto) wrap(UseRefWrap::Ref /*meta*/, T& t) noexcept {
434  return std::ref(t);
435 }
436 
437 using NonConstWrapperList = tmpl::list<UseRefWrap::None, UseRefWrap::Ref>;
438 
439 using WrapperList = tmpl::push_front<NonConstWrapperList, UseRefWrap::Cref>;
440 
441 // struct used for determining the full number of elements in a vector, array of
442 // vectors. Designed for use with `test_element_wise_function`
443 struct VectorOrArraySize {
444  // array of vectors version, assumes all vectors in the array are the same
445  // size, as will be the case for the relevant test helpers in this detail
446  // namespace
447  template <typename T, size_t S>
448  size_t operator()(const std::array<T, S>& container) const noexcept {
449  return S * VectorOrArraySize{}(container[0]);
450  }
451  // vector version
452  template <typename T,
454  tt::is_complex_of_fundamental_v<typename T::ElementType>> =
455  nullptr>
456  size_t operator()(const T& container) const noexcept {
457  return container.size();
458  }
459 };
460 
461 // struct used for obtaining an indexed value in a vector, array of vectors.
462 // Designed for use with `test_element_wise_function`
463 struct VectorOrArrayAt {
464  // array of vectors version
465  template <typename T, size_t S>
466  decltype(auto) operator()(std::array<T, S>& container,
467  size_t index) noexcept {
468  return VectorOrArrayAt{}(gsl::at(container, index % S), index / S);
469  }
470  template <typename T, size_t S>
471  decltype(auto) operator()(const std::array<T, S>& container,
472  size_t index) noexcept {
473  return VectorOrArrayAt{}(gsl::at(container, index % S), index / S);
474  }
475  // vector version
476  template <typename T,
478  tt::is_complex_of_fundamental_v<typename T::ElementType>> =
479  nullptr>
480  decltype(auto) operator()(T& container, size_t index) noexcept {
481  return container.at(index);
482  }
483 };
484 
485 // given an explicit template parameter pack `Wraps`, wrap the elements of
486 // `operand`, passed by pointer, element-by-element, and return the
487 // resulting tuple of wrapped elements.
488 template <typename... Wraps, typename... Operands, size_t... Is>
489 auto wrap_tuple(std::tuple<Operands...>& operand_values,
490  std::index_sequence<Is...> /*meta*/) noexcept {
491  return std::make_tuple(wrap(Wraps{}, get<Is>(operand_values))...);
492 }
493 
494 // given the set of types of operands to test (`Operands`), and a set of
495 // reference wrappers (`Wraps`), make each operand with random values according
496 // to the bound from `Bounds`. Then, call
497 // `CHECK_CUSTOM_ELEMENT_WISE_FUNCTION_APPROX` to test the element wise `func`.
498 template <typename Function, typename... Bounds, typename... Wraps,
499  typename... Operands, size_t... Is>
500 void test_function_on_vector_operands(
501  const Function& function, const std::tuple<Bounds...>& bounds,
502  const std::tuple<Wraps...>& /*wraps*/,
503  const std::tuple<Operands...>& /*operands*/,
504  std::index_sequence<Is...> /*meta*/) noexcept {
505  MAKE_GENERATOR(generator);
506  UniformCustomDistribution<size_t> size_distribution{2, 5};
507  const size_t size = size_distribution(generator);
508  // using each distribution, generate a value for the appropriate operand type
509  // and put it in a tuple.
510  auto operand_values = std::make_tuple(make_with_random_values<Operands>(
511  make_not_null(&generator),
513  tt::get_fundamental_type_t<get_vector_element_type_t<Operands>>>{
514  std::get<Is>(bounds)},
515  size)...);
516  // wrap the tuple of random values according to the passed in `Wraps`
517  auto wrapped_operands = wrap_tuple<Wraps...>(
518  operand_values, std::make_index_sequence<sizeof...(Bounds)>{});
520  function, make_not_null(&wrapped_operands), VectorOrArrayAt{},
521  VectorOrArraySize{});
522 }
523 
524 // Set of structs for choosing between execution paths when assembling the
525 // argument list for the math testing utility
526 namespace TestFunctionsWithVectorArgumentsChoices {
527 struct FirstInplace {};
528 struct Continuing {};
529 struct Done {};
530 struct GivenOrderOfArgumentsOnly {};
531 } // namespace TestFunctionsWithVectorArgumentsChoices
532 
533 // helper struct implementation for choosing the next branch for assembling the
534 // argument list for the math testing utility. This is done to use pattern
535 // matching instead of SFINAE.
536 template <bool IsDone, bool IsFirstAssignment, bool GivenOrderOfArgumentsOnly>
537 struct next_choice_for_test_functions_recursion_impl;
538 
539 // if the size of the vector list is the same as the size of the distribution
540 // bound list, then assembly is done
541 template <bool IsFirstAssignment, bool GivenOrderOfArgumentsOnly>
542 struct next_choice_for_test_functions_recursion_impl<
543  true, IsFirstAssignment, GivenOrderOfArgumentsOnly> {
544  using type = TestFunctionsWithVectorArgumentsChoices::Done;
545 };
546 
547 // if we are assembling the first argument and the test type is Inplace, then
548 // treat it separately
549 template <>
550 struct next_choice_for_test_functions_recursion_impl<false, true, false> {
551  using type = TestFunctionsWithVectorArgumentsChoices::FirstInplace;
552 };
553 
554 // otherwise, continue assembling as ordinary
555 template <>
556 struct next_choice_for_test_functions_recursion_impl<false, false, false> {
557  using type = TestFunctionsWithVectorArgumentsChoices::Continuing;
558 };
559 
560 // if using mode GivenOrderOfArgumentsOnly, follow a distinct exection path with
561 // only the wraps assembled in combinations
562 template <bool IsFirstAssignment>
563 struct next_choice_for_test_functions_recursion_impl<false, IsFirstAssignment,
564  true> {
565  using type =
566  TestFunctionsWithVectorArgumentsChoices::GivenOrderOfArgumentsOnly;
567 };
568 
569 // helper function for easily determining the next branch of the call operator
570 // for TestFunctionsWithVectorArgumentsImpl
571 template <typename VectorList, typename BoundList, TestKind Test>
572 using next_choice_for_test_functions_recursion =
573  typename next_choice_for_test_functions_recursion_impl<
574  tmpl::size<VectorList>::value == tmpl::size<BoundList>::value,
575  tmpl::size<VectorList>::value == 0 and Test == TestKind::Inplace,
576  Test == TestKind::GivenOrderOfArgumentsOnly>::type;
577 
578 // functions to recursively assemble the arguments and wrappers for
579 // calling the operator tester `test_function_on_vector_operands`.
580 //
581 // `UniqueTypeList` is a tmpl::list which stores the set of unique types to
582 // test the functions with.
583 //
584 // `FirstOperand` is the first operand type, used when the Test is
585 // `TestKind::Inplace`, as inplace operators have specific demands on the
586 // first operand.
587 
588 // base case: the correct number of operand types has been obtained in
589 // `Operands`, so this calls the test function with the function, bounds,
590 // reference wrap, and operand type information.
591 template <TestKind Test, typename UniqueTypeList, typename FirstOperand,
592  typename... Operands, typename Function, typename... DistBounds,
593  typename... Wraps>
594 void assemble_test_function_arguments_and_execute_tests(
595  const Function& function, const std::tuple<DistBounds...>& bounds,
596  const std::tuple<Wraps...>& wraps, const std::tuple<Operands...> operands,
597  TestFunctionsWithVectorArgumentsChoices::Done /*meta*/) noexcept {
598  test_function_on_vector_operands(
599  function, bounds, wraps, operands,
600  std::make_index_sequence<sizeof...(DistBounds)>{});
601 }
602 
603 // general case: add an additional reference wrapper identification type from
604 // `WrapperList` to `wraps`, and an additional type from `UniqueTypeList`
605 // to `operands`, and recurse on each option.
606 template <TestKind Test, typename UniqueTypeList, typename FirstOperand,
607  typename... Operands, typename Function, typename... DistBounds,
608  typename... Wraps>
609 void assemble_test_function_arguments_and_execute_tests(
610  const Function& function, const std::tuple<DistBounds...>& bounds,
611  const std::tuple<Wraps...>& wraps, const std::tuple<Operands...>&& operands,
612  TestFunctionsWithVectorArgumentsChoices::Continuing
613  /*meta*/) noexcept {
614  tmpl::for_each<WrapperList>([&function, &bounds, &wraps,
615  &operands ](const auto x) noexcept {
616  tmpl::for_each<UniqueTypeList>([&function, &bounds, &wraps,
617  &operands ](const auto y) noexcept {
618  using next_vector = typename decltype(y)::type;
619  using next_wrap = typename decltype(x)::type;
620  assemble_test_function_arguments_and_execute_tests<Test, UniqueTypeList,
621  FirstOperand>(
622  function, bounds, std::tuple_cat(wraps, std::tuple<next_wrap>{}),
623  std::tuple_cat(operands, std::tuple<next_vector>{}),
624  detail::next_choice_for_test_functions_recursion<
625  tmpl::list<Wraps..., next_wrap>, tmpl::list<DistBounds...>,
626  Test>{});
627  });
628  });
629 }
630 
631 // case of first operand and inplace test: the left hand operand for inplace
632 // tests cannot be const, so the reference wrapper must be chosen
633 // accordingly. Also, the left hand size type is fixed to be FirstOperand.
634 template <TestKind Test, typename UniqueTypeList, typename FirstOperand,
635  typename... Operands, typename Function, typename... DistBounds,
636  typename... Wraps>
637 void assemble_test_function_arguments_and_execute_tests(
638  const Function& function, const std::tuple<DistBounds...>& bounds,
639  const std::tuple<Wraps...>& /*wraps*/,
640  const std::tuple<Operands...>&& /*operands*/,
641  TestFunctionsWithVectorArgumentsChoices::FirstInplace /*meta*/) noexcept {
642  tmpl::for_each<NonConstWrapperList>([&function,
643  &bounds ](const auto x) noexcept {
644  using next_wrap = typename decltype(x)::type;
645  assemble_test_function_arguments_and_execute_tests<Test, UniqueTypeList,
646  FirstOperand>(
647  function, bounds, std::tuple<next_wrap>{}, std::tuple<FirstOperand>{},
648  detail::next_choice_for_test_functions_recursion<
649  tmpl::list<FirstOperand>, tmpl::list<DistBounds...>, Test>{});
650  });
651 }
652 
653 // case of TestKind of NoArgumentsCombinations: The Operands are already
654 // assembled, we just need to loop through the wrap possibilities.
655 template <TestKind Test, typename UniqueTypeList, typename FirstOperand,
656  typename... Operands, typename Function, typename... DistBounds,
657  typename... Wraps>
658 void assemble_test_function_arguments_and_execute_tests(
659  const Function& function, const std::tuple<DistBounds...>& bounds,
660  const std::tuple<Wraps...>& wraps, const std::tuple<Operands...>& operands,
661  TestFunctionsWithVectorArgumentsChoices::
662  GivenOrderOfArgumentsOnly /*meta*/) noexcept {
663  tmpl::for_each<NonConstWrapperList>(
664  [&function, &bounds, &wraps, &operands ](const auto x) noexcept {
665  using next_wrap = typename decltype(x)::type;
666  assemble_test_function_arguments_and_execute_tests<Test, UniqueTypeList,
667  FirstOperand>(
668  function, bounds, std::tuple_cat(wraps, std::tuple<next_wrap>{}),
669  operands,
670  detail::next_choice_for_test_functions_recursion<
671  tmpl::list<Wraps..., next_wrap>, tmpl::list<DistBounds...>,
672  Test>{});
673  });
674 }
675 
676 // dispatch function for the individual tuples of functions and bounds for their
677 // respective distributions. This processes the requested set of vector types to
678 // test and passes the resulting request to the recursive argument assembly
679 // function assemble_test_function_arguments_and_execute_tests above
680 template <TestKind Test, typename VectorType0, typename... VectorTypes,
681  typename Function, typename... DistBounds>
682 void test_function_with_vector_arguments_impl(
683  const std::tuple<Function, std::tuple<DistBounds...>>&
684  function_and_argument_bounds) noexcept {
685  // The unique set of possible operands
686  using operand_type_list = tmpl::conditional_t<
687  Test == TestKind::Strict,
688  // if strict, the operand choices are just the vector types passed in
689  tmpl::remove_duplicates<tmpl::list<VectorType0, VectorTypes...>>,
690  // else, the operand choices include both the vectors and their element
691  // types
692  tmpl::remove_duplicates<tmpl::list<
693  VectorType0, VectorTypes..., get_vector_element_type_t<VectorType0>,
694  get_vector_element_type_t<VectorTypes>...>>>;
695 
696  using starting_operands =
697  tmpl::conditional_t<Test == TestKind::GivenOrderOfArgumentsOnly,
698  std::tuple<VectorType0, VectorTypes...>,
699  std::tuple<>>;
700  assemble_test_function_arguments_and_execute_tests<Test, operand_type_list,
701  VectorType0>(
702  get<0>(function_and_argument_bounds) /*function*/,
703  get<1>(function_and_argument_bounds) /*tuple of bounds*/, std::tuple<>{},
704  starting_operands{},
705  next_choice_for_test_functions_recursion<
706  tmpl::list<>, tmpl::list<DistBounds...>, Test>{});
707 }
708 } // namespace detail
709 
710 /*!
711  * \ingroup TestingFrameworkGroup
712  * \brief General entry function for testing arbitrary math functions
713  * on vector types
714  *
715  * \details This utility tests all combinations of the operator on the type
716  * arguments, and all combinations of reference or constant reference wrappers
717  * on all arguments. In certain test cases (see below), it also tests using the
718  * vector type's `value_type`s in the operators as well (e.g. `DataVector +
719  * double`). This is very useful for quickly generating a lot of tests, but the
720  * number of tests scales exponentially in the number of arguments. Therefore,
721  * functions with many arguments can be time-consuming to
722  * run. 4-or-more-argument functions should be used only if completely necessary
723  * and with caution. Any number of vector types may be specified, and tests are
724  * run on all unique combinations of the provided. For instance, if only one
725  * type is provided, the tests will be run only on combinations of that single
726  * type and its `value_type`.
727  *
728  * \param tuple_of_functions_and_argument_bounds A tuple of tuples, in which
729  * the inner tuple contains first a function object followed by a tuple of
730  * 2-element arrays equal to the number of arguments, which represent the
731  * bounds for the random generation of the respective arguments. This system
732  * is provided for robust testing of operators like `/`, where the left-hand
733  * side has a different valid set of values than the right-hand-side.
734  *
735  * \tparam Test from the `TestKind` enum, determines whether the tests will
736  * be:
737  * - `TestKind::Normal`: executed on all combinations of arguments and value
738  * types
739  * - `TestKind::Strict`: executed on all combinations of arguments, for only
740  * the vector types
741  * - `TestKind::Inplace`: executed on all combinations of arguments after the
742  * first, so first is always the 'left hand side' of the operator. In this
743  * case, at least two `VectorTypes` must be specified, where the first is
744  * used only for the left-hand side.
745  * - `TestKind::GivenOrderOfArgumentsOnly`: executed on only the combination of
746  * arguments provided, in the order provided. In this case, the number of
747  * provided types in `typename VectorType0, typename... VectorTypes` must
748  * precisely match the number of arguments taken by the function.
749  *
750  * \tparam VectorType0 The first vector type for which combinations are
751  * tested. The first is accepted as a separate template argument for
752  * appropriately handling `Inplace` tests.
753  * \tparam VectorTypes The remaining types for which combinations are tested.
754  * Any number of types may be passed in, and the test will check the
755  * appropriate combinations of the vector types and (depending on the `Test`)
756  * the respective `value_type`s.
757  */
758 template <TestKind Test, typename VectorType0, typename... VectorTypes,
759  typename... FunctionsAndArgumentBounds>
762  tuple_of_functions_and_argument_bounds) noexcept {
763  tuple_fold(
764  tuple_of_functions_and_argument_bounds,
765  [](const auto& function_and_argument_bounds) noexcept {
766  detail::test_function_with_vector_arguments_impl<Test, VectorType0,
767  VectorTypes...>(
768  function_and_argument_bounds);
769  });
770 }
771 } // namespace VectorImpl
772 } // namespace TestHelpers
Defines function dereference_wrapper.
Definition: VectorImplTestHelper.hpp:30
ReturnType make_with_random_values(const gsl::not_null< UniformRandomBitGenerator *> generator, const gsl::not_null< RandomNumberDistribution *> distribution, const T &used_for_size) noexcept
Make a data structure and fill it with random values.
Definition: MakeWithRandomValues.hpp:160
void vector_test_construct_and_assign(tt::get_fundamental_type_t< ValueType > low=tt::get_fundamental_type_t< ValueType >{-100.0}, tt::get_fundamental_type_t< ValueType > high=tt::get_fundamental_type_t< ValueType >{100.0}) noexcept
test construction and assignment of a VectorType with a ValueType
Definition: VectorImplTestHelper.hpp:36
Helper functions for data structures used in unit tests.
Base class template for various DataVector and related types.
Definition: VectorImpl.hpp:109
#define CAPTURE_PRECISE(variable)
Alternative to Catch&#39;s CAPTURE that prints more digits.
Definition: TestingFramework.hpp:46
constexpr void tuple_fold(const std::tuple< Elements... > &tuple, N_aryOp &&op, Args &&... args) noexcept(noexcept(tuple_impl_detail::tuple_fold_impl< ReverseIteration >(tuple, std::forward< N_aryOp >(op), std::make_index_sequence< sizeof...(Elements)>{}, args...)))
Perform a fold over a std::tuple.
Definition: Tuple.hpp:112
Defines the type alias Requires.
TestKind
the set of test types that may be used for the math operations
Definition: VectorImplTestHelper.hpp:408
void vector_test_serialize(tt::get_fundamental_type_t< ValueType > low=tt::get_fundamental_type_t< ValueType >{-100.0}, tt::get_fundamental_type_t< ValueType > high=tt::get_fundamental_type_t< ValueType >{ 100.0}) noexcept
test the serialization of a VectorType constructed with a ValueType
Definition: VectorImplTestHelper.hpp:109
#define MAKE_GENERATOR(...)
MAKE_GENERATOR(NAME [, SEED]) declares a variable of name NAME containing a generator of type std::mt...
Definition: TestHelpers.hpp:387
Definition: Determinant.hpp:11
void test_functions_with_vector_arguments(const std::tuple< FunctionsAndArgumentBounds... > &tuple_of_functions_and_argument_bounds) noexcept
General entry function for testing arbitrary math functions on vector types.
Definition: VectorImplTestHelper.hpp:760
void test_copy_semantics(const T &a)
Test for copy semantics assuming operator== is implement correctly.
Definition: TestHelpers.hpp:83
void test_move_semantics(T &&a, const T &comparison, Args &&... args)
Test for move semantics assuming operator== is implemented correctly.
Definition: TestHelpers.hpp:116
Defines functions for manipulating tuples.
void vector_test_math_after_move(tt::get_fundamental_type_t< ValueType > low=tt::get_fundamental_type_t< ValueType >{-100.0}, tt::get_fundamental_type_t< ValueType > high=tt::get_fundamental_type_t< ValueType >{100.0}) noexcept
tests a small sample of math functions after a move of a VectorType initialized with ValueType ...
Definition: VectorImplTestHelper.hpp:301
#define CHECK_ITERABLE_APPROX(a, b)
A wrapper around Catch&#39;s CHECK macro that checks approximate equality of entries in iterable containe...
Definition: TestingFramework.hpp:110
void vector_ref_test_size_error(RefSizeErrorTestKind test_kind, tt::get_fundamental_type_t< ValueType > low=tt::get_fundamental_type_t< ValueType >{-100.0}, tt::get_fundamental_type_t< ValueType > high=tt::get_fundamental_type_t< ValueType >{100.0}) noexcept
Test that assigning to a non-owning VectorType of the wrong size appropriately generates an error...
Definition: VectorImplTestHelper.hpp:265
Wraps the template metaprogramming library used (brigand)
Commonly used routines, functions and definitions shared amongst unit tests.
decltype(auto) for_each(const Container &c, UnaryFunction &&f)
Convenience wrapper around std::for_each, returns the result of std::for_each(begin(c), end(c), f).
Definition: Algorithm.hpp:240
Defines functions and classes from the GSL.
gsl::not_null< T * > make_not_null(T *ptr) noexcept
Construct a not_null from a pointer. Often this will be done as an implicit conversion, but it may be necessary to perform the conversion explicitly when type deduction is desired.
Definition: Gsl.hpp:863
Code to wrap or improve the Catch testing framework used for unit tests.
T serialize_and_deserialize(const T &t)
Serializes and deserializes an object t of type T
Definition: TestHelpers.hpp:42
typename Requires_detail::requires_impl< B >::template_error_type_failed_to_meet_requirements_on_template_parameters Requires
Express requirements on the template parameters of a function or class, replaces std::enable_if_t ...
Definition: Requires.hpp:67
void vector_test_ref(tt::get_fundamental_type_t< ValueType > low=tt::get_fundamental_type_t< ValueType >{-100.0}, tt::get_fundamental_type_t< ValueType > high=tt::get_fundamental_type_t< ValueType >{ 100.0}) noexcept
test the construction and move of a reference VectorType constructed with a ValueType ...
Definition: VectorImplTestHelper.hpp:167
Defines type traits, some of which are future STL type_traits header.
A uniform distribution function object which redirects appropriately to either the std::uniform_int_d...
Definition: MakeWithRandomValues.hpp:113
#define CHECK_CUSTOM_ELEMENT_WISE_FUNCTION_APPROX( function, arguments, at_operator, size_of_operator)
Same as CHECK_ELEMENT_WISE_FUNCTION_APPROX, but with a user-defined function at_operator and size_of_...
Definition: TestHelpers.hpp:461
constexpr T & at(std::array< T, N > &arr, Size index)
Retrieve a entry from a container, with checks in Debug mode that the index being retrieved is valid...
Definition: Gsl.hpp:124