diff --git a/tdms/tests/include/unit_test_utils.h b/tdms/tests/include/unit_test_utils.h index 22a85873d..628377648 100644 --- a/tdms/tests/include/unit_test_utils.h +++ b/tdms/tests/include/unit_test_utils.h @@ -5,12 +5,15 @@ #pragma once #include +#include +#include +#include namespace tdms_tests { - inline double TOLERANCE = 1e-16; //< Floating-point comparison tolerance +inline double TOLERANCE = 1e-16;//< Floating-point comparison tolerance - /** +/** * @brief Determines if two numerical values are close by relative comparison. * * Checks the truth value of the condition @@ -21,54 +24,82 @@ namespace tdms_tests { * @param tol Relative comparison tolerance * @param close_to_zero_tol Cutoff value for the "close to zero" criterion */ - template - inline bool is_close(T a, T b, double tol = 1E-10, double close_to_zero_tol = 1E-30) { - - auto max_norm = std::max(std::abs(a), std::abs(b)); +template +inline bool is_close(T a, T b, double tol = 1E-10, double close_to_zero_tol = 1E-30) { - if (max_norm < close_to_zero_tol) {// Prevent dividing by zero - return true; - } + auto max_norm = std::max(std::abs(a), std::abs(b)); - return std::abs(a - b) / std::max(std::abs(a), std::abs(b)) < tol; + if (max_norm < close_to_zero_tol) {// Prevent dividing by zero + return true; } - /** - * @brief Determines whether an error value is better than a benchmark, or sufficiently close to be insignificant. - * - * If the error to check is superior (IE, closer to zero in absolute value) than the benchmark, return true. - * Otherwise, use relative comparison to determine if the errors are sufficiently similar. - * - * @param to_check Numerical error to evaluate suitability of - * @param to_beat Benchmark error - * @param tol Relative comparison tolerance - * @param close_to_zero_tol Cutoff value for the "close to zero" criterion - * @return true to_check is a superior or equivalent error to to_beat - * @return false to_check is an inferior error - */ - template - inline bool is_close_or_better(T to_check, T to_beat, double tol = 1E-10, double close_to_zero_tol = 1E-30) { - if (std::abs(to_check) < std::abs(to_beat)) { - // return true if the value to_check is better (closer to 0) than to_beat - return true; - } else { - // determine if the numerical values are close by relative comparison - return is_close(to_check, to_beat, tol, close_to_zero_tol); - } - } + return std::abs(a - b) / std::max(std::abs(a), std::abs(b)) < tol; +} - /** - * @brief Computes the Euclidean norm of the vector provided +/** + * @brief Determines whether an error value is better than a benchmark, or sufficiently close to be insignificant. + * + * If the error to check is superior (IE, closer to zero in absolute value) than the benchmark, return true. + * Otherwise, use relative comparison to determine if the errors are sufficiently similar. * - * @param v Vector or array - * @param end (Inclusive) end of buffer to read vector from - * @param start (Inclusive) start of buffer to read vector from - * @return double Euclidean norm + * @param to_check Numerical error to evaluate suitability of + * @param to_beat Benchmark error + * @param tol Relative comparison tolerance + * @param close_to_zero_tol Cutoff value for the "close to zero" criterion + * @return true to_check is a superior or equivalent error to to_beat + * @return false to_check is an inferior error */ - template - inline double euclidean(T *v, int end, int start = 0) { - double norm_val = 0.; - for (int i = start; i < end; i++) { norm_val += std::norm(v[i]); } - return std::sqrt(norm_val); +template +inline bool is_close_or_better(T to_check, T to_beat, double tol = 1E-10, + double close_to_zero_tol = 1E-30) { + if (std::abs(to_check) < std::abs(to_beat)) { + // return true if the value to_check is better (closer to 0) than to_beat + return true; + } else { + // determine if the numerical values are close by relative comparison + return is_close(to_check, to_beat, tol, close_to_zero_tol); } } + +/** +* @brief Computes the Euclidean norm of the vector provided +* +* @param v Vector or array +* @param end (Inclusive) end of buffer to read vector from +* @param start (Inclusive) start of buffer to read vector from +* @return double Euclidean norm +*/ +template +inline double euclidean(T *v, int end, int start = 0) { + double norm_val = 0.; + for (int i = start; i < end; i++) { norm_val += std::norm(v[i]); } + return std::sqrt(norm_val); +} + +/** + * @brief Create a temporary directory for writing files. + * + * Creates a subdirectory (with randomised name) in the system tmp. This can be + * used for writing files when testing file I/O and field exporters and similar. + * + * @warning You should probably clean up after yourself: call + * `std::filesystem::remove_all` when you're done. + * + * @return std::filesystem::path Path to the temporary directory. + */ +inline std::filesystem::path create_tmp_dir() { + // random number setup + std::random_device device_seed; + std::mt19937 random_number_generator(device_seed()); + + // get system tmp directory (OS-dependent), add a uniquely named subdirectory + auto tmp = std::filesystem::temp_directory_path(); + std::string subdir = "tdms_unit_tests_" + std::to_string(random_number_generator()); + auto path = tmp / subdir; + + // mkdir and return the path to the directory we've just created + std::filesystem::create_directory(path); + return path; +} + +}// namespace tdms_tests diff --git a/tdms/tests/unit/field_tests/test_TDFieldExporter2D.cpp b/tdms/tests/unit/field_tests/test_TDFieldExporter2D.cpp index c3661afdd..f3a32b51a 100644 --- a/tdms/tests/unit/field_tests/test_TDFieldExporter2D.cpp +++ b/tdms/tests/unit/field_tests/test_TDFieldExporter2D.cpp @@ -6,28 +6,39 @@ #include +#include "unit_test_utils.h" + TEST_CASE("TDFieldExporter2D") { + // create a directory to write to + auto temporary_directory = tdms_tests::create_tmp_dir(); - // create a 0-field for us to export - const int I_tot = 8, J_tot = 1, K_tot = 8; - ElectricSplitField Es(I_tot, J_tot, K_tot); - Es.allocate_and_zero(); - - // create our field exporter - TDFieldExporter2D tdfe2d; - tdfe2d.folder_name = "."; - int stride; - - SECTION("Stride too small") { - int nI = 4, nK = 4; - stride = 1; - REQUIRE_NOTHROW(tdfe2d.allocate(nI, nK)); - REQUIRE_THROWS_AS(tdfe2d.export_field(Es, stride, 0), std::runtime_error); - } - SECTION("Stride able to write out") { - int nI = 8, nK = 8; - stride = 2; - REQUIRE_NOTHROW(tdfe2d.allocate(nI, nK)); - REQUIRE_NOTHROW(tdfe2d.export_field(Es, stride, 0)); + // block to ensure everything is out of scope before we remove the directory + { + // create a 0-field for us to export + const int I_tot = 8, J_tot = 1, K_tot = 8; + ElectricSplitField Es(I_tot, J_tot, K_tot); + Es.allocate_and_zero(); + + + // create our field exporter + TDFieldExporter2D tdfe2d; + tdfe2d.folder_name = temporary_directory.c_str(); + int stride; + + SECTION("Stride too small") { + int nI = 4, nK = 4; + stride = 1; + REQUIRE_NOTHROW(tdfe2d.allocate(nI, nK)); + REQUIRE_THROWS_AS(tdfe2d.export_field(Es, stride, 0), std::runtime_error); + } + SECTION("Stride able to write out") { + int nI = 8, nK = 8; + stride = 2; + REQUIRE_NOTHROW(tdfe2d.allocate(nI, nK)); + REQUIRE_NOTHROW(tdfe2d.export_field(Es, stride, 0)); + } } + + // tear down - remove the exported files + std::filesystem::remove_all(temporary_directory); }