Skip to content

Commit 864c43d

Browse files
Test class for array.h classes (#190)
* Skeleton base class * CollectionBase tests reworked * ComplexAmplitudeSample and DetectorSensitivityArrays * DispersiveMultilayer * DTilde * Avoid trailing bools * FieldSample * FrequencyVectors.cpp * IncidentField, tidy up create_empty_struct() * Material * Matrix * Tensor3D * Vector * XYZ Classes * MacOS pedantisism * Sam's suggestion * Samcunliffe array test class (#192) * Update array_test_class.h * Use clang suggestion * Use `const static` Co-authored-by: Sam Cunliffe <[email protected]> * Apply suggestions from code review Co-authored-by: Sam Cunliffe <[email protected]> Co-authored-by: Sam Cunliffe <[email protected]>
1 parent 27ea4ae commit 864c43d

16 files changed

+1210
-734
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
/**
2+
* @file array_test_class.h
3+
* @brief Defines the abstract class for testing objects defined in arrays.h
4+
*/
5+
#pragma once
6+
7+
#include <catch2/catch_test_macros.hpp>
8+
#include <spdlog/spdlog.h>
9+
#include <string>
10+
11+
#include "arrays.h"
12+
13+
/**
14+
* @brief Abstract container for the tests that classes in arrays.h will have to pass.
15+
*
16+
* Private methods are empty, and designed to be overridden in the derived class. However this also means that definitions for these tests can be left out of those subclasses, whilst not changing the run_all_tests() function.
17+
*
18+
*/
19+
class AbstractArrayTest {
20+
protected:
21+
int empty_dimensions[2] = {0, 1}; //< For initialising empty arrays
22+
int I_tot = 4, J_tot = 8, K_tot = 4;//< For mimicing simulation grid dimensions
23+
int n_numeric_elements =
24+
8;//< For standardising the number of elements we initialise MATLAB vectors with
25+
int dimensions_2d[2] = {1, 1}; //< For defining 2D MATLAB arrays
26+
int dimensions_3d[3] = {I_tot, J_tot, K_tot};//< For defining 3D MATLAB arrays
27+
28+
// To point to the (top-level) matlab inputs the classes require. Top-level structure ensures mxDestroyArray clears all sub-arrays too
29+
mxArray *matlab_input = nullptr;
30+
// Flags if we have created an mxArray manually, so we are forced to destroy to free memory when done
31+
bool matlab_input_assigned = false;
32+
33+
/**
34+
* @brief Destroys the matlab_input array if it is assigned.
35+
*
36+
* @returns true If mxDestroyArray was called successfully
37+
* @returns false matlab_input was not assigned, so no deletion was necessary
38+
*/
39+
bool destroy_matlab_input() {
40+
if (matlab_input_assigned) {
41+
mxDestroyArray(matlab_input);
42+
matlab_input_assigned = false;
43+
return true;
44+
} else {
45+
return false;
46+
}
47+
}
48+
/**
49+
* @brief Creates a MATLAB structure array
50+
*
51+
* @param n_rows,n_cols Dimensions of the array. This is the number of identical structs in the array, not the size of a particular field
52+
* @param n_fields Number of fields
53+
* @param fields Names of the fields
54+
*/
55+
void create_struct_array(int n_rows, int n_cols, int n_fields, const char *fields[]) {
56+
matlab_input = mxCreateStructMatrix(n_rows, n_cols, n_fields, fields);
57+
matlab_input_assigned = true;
58+
}
59+
/**
60+
* @brief Creates a MATLAB structure array
61+
*
62+
* @param n_dimensions Number of dimensions of the array.
63+
* @param dimensions Array or vector, containing the number of elements in each axis of the array
64+
* @param n_fields Number of fields
65+
* @param fields Names of the fields
66+
*/
67+
void create_struct_array(int n_dimensions, int *dimensions, int n_fields, const char *fields[]) {
68+
matlab_input = mxCreateStructArray(n_dimensions, (const mwSize *) dimensions, n_fields, fields);
69+
matlab_input_assigned = true;
70+
}
71+
/**
72+
* @brief Creates a 1-by-1 MATLAB structure array
73+
*
74+
* @param n_fields Number of fields
75+
* @param fields Names of the fields
76+
*/
77+
void create_1by1_struct(int n_fields, const char *fields[]) {
78+
create_struct_array(1, 1, n_fields, fields);
79+
}
80+
/**
81+
* @brief Creates the MATLAB empty struct (no fields, 0 size)
82+
*/
83+
void create_empty_struct() {
84+
create_struct_array(0, 1, 0, {});
85+
}
86+
/**
87+
* @brief Creates a MATLAB numeric array
88+
*
89+
* @param n_dims Number of dimensions in the array
90+
* @param dimensions Array/vector containing the number of elements in each dimension, sequentially
91+
* @param data_type The MATLAB data type to populate the array with
92+
* @param complex_flag Whether the data is real or complex
93+
*/
94+
void create_numeric_array(int n_dims, int *dimensions, mxClassID data_type = mxDOUBLE_CLASS,
95+
mxComplexity complex_flag = mxREAL) {
96+
matlab_input =
97+
mxCreateNumericArray(n_dims, (const mwSize *) dimensions, data_type, complex_flag);
98+
matlab_input_assigned = true;
99+
}
100+
101+
/* Test functions to be overriden by each class.
102+
These methods are designed to be overriden by the subclasses specific to each arrays.h class.
103+
104+
The default behaviour of each test_ method is to set ran_a_test to false - this means that subclasses that skip over certain functionality (EG do not need to test the result of providing an empty MATLAB input) are skipped in when run_all_class_tests() is run, and do not bloat the log.
105+
If overriden, a test_ method should not alter ran_a_test
106+
*/
107+
108+
bool ran_a_test = true;
109+
/**
110+
* @brief Tests the behaviour of construction when passed an empty MATLAB array
111+
*/
112+
virtual void test_empty_construction() { ran_a_test = false; }
113+
/**
114+
* @brief Tests the behaviour of construction when passed an array of the incorrect dimensions
115+
*/
116+
virtual void test_wrong_input_dimensions() { ran_a_test = false; }
117+
/**
118+
* @brief Tests the behaviour of construction when passed an array of the incorrect type
119+
*/
120+
virtual void test_wrong_input_type() { ran_a_test = false; }
121+
/**
122+
* @brief Tests the behaviour of construction when passed a struct with the wrong number of fields
123+
*/
124+
virtual void test_incorrect_number_of_fields() { ran_a_test = false; }
125+
/**
126+
* @brief Tests the behaviour of construction methods when passed a struct with an incorrect fieldname
127+
*/
128+
virtual void test_incorrect_fieldname() { ran_a_test = false; }
129+
/**
130+
* @brief Tests the behaviour of construction methods when passing the expected inputs
131+
*/
132+
virtual void test_correct_construction() { ran_a_test = false; }
133+
/**
134+
* @brief Tests the behaviour of the initialise method
135+
*/
136+
virtual void test_initialise_method() { ran_a_test = false; }
137+
/**
138+
* @brief Tests any other methods that the class may have
139+
*/
140+
virtual void test_other_methods() { ran_a_test = false; }
141+
142+
public:
143+
AbstractArrayTest() = default;
144+
145+
/**
146+
* @brief Returns the name of the class, for logging purposes
147+
*/
148+
virtual std::string get_class_name() = 0;
149+
150+
/**
151+
* @brief Runs all the unit tests associated to this class
152+
*/
153+
void run_all_class_tests() {
154+
// String to print to the log
155+
std::string logging_string = get_class_name() + ": ";
156+
157+
// run all tests
158+
SECTION("Empty construction") {
159+
logging_string += "Empty construction";
160+
test_empty_construction();
161+
}
162+
SECTION("Incorrect dimension of input") {
163+
logging_string += "Incorrect dimension of input";
164+
test_wrong_input_dimensions();
165+
}
166+
SECTION("Incorrect type of input") {
167+
logging_string += "Incorrect type of input";
168+
test_wrong_input_type();
169+
}
170+
SECTION("Incorrect number of fields") {
171+
logging_string += "Incorrect number of fields";
172+
test_incorrect_number_of_fields();
173+
}
174+
SECTION("Incorrect fieldname") {
175+
logging_string += "Incorrect fieldname";
176+
test_incorrect_fieldname();
177+
}
178+
SECTION("Correct construction") {
179+
logging_string += "Correct construction";
180+
test_correct_construction();
181+
}
182+
SECTION("initialise() method") {
183+
logging_string += "initialise() method";
184+
test_initialise_method();
185+
}
186+
SECTION("Testing class-specific methods") {
187+
logging_string += "class specific method";
188+
test_other_methods();
189+
}
190+
191+
// suppress output if no test was run
192+
if (ran_a_test) {
193+
// tear down is necessary if a test was run
194+
bool needed_to_destroy = destroy_matlab_input();
195+
if (needed_to_destroy) {
196+
logging_string += " (matlab_input destroyed)";
197+
} else {
198+
logging_string += " (nothing to tear down)";
199+
}
200+
SPDLOG_INFO(logging_string);
201+
}
202+
}
203+
};

0 commit comments

Comments
 (0)