OGS
TestDefinition.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Copyright (c) OpenGeoSys Community (opengeosys.org)
2// SPDX-License-Identifier: BSD-3-Clause
3
4#include "TestDefinition.h"
5
6#include <cmath>
7#include <cstdlib>
8#include <filesystem>
9#include <regex>
10#include <vector>
11
12#include "BaseLib/ConfigTree.h"
13#include "BaseLib/Error.h"
14#include "BaseLib/FileTools.h"
15#include "BaseLib/MPI.h"
16
17#ifdef USE_PETSC
18#include "MeshLib/IO/VtkIO/VtuInterface.h" // For petsc file name conversion.
19#endif
20
21namespace
22{
24bool isConvertibleToDouble(std::string const& s)
25{
26 std::size_t pos = 0;
27 double value;
28 try
29 {
30 value = std::stod(s, &pos);
31 }
32 catch (...)
33 {
34 ERR("The given string '{:s}' is not convertible to double.", s);
35 return false;
36 }
37 if (pos != s.size())
38 {
39 ERR("Only {:d} characters were used for double conversion of string "
40 "'{:s}'",
41 pos, s);
42 return false;
43 }
44
45 if (std::isnan(value))
46 {
47 ERR("The given string '{:s}' results in a NaN value.", s);
48 return false;
49 }
50 return true;
51}
52
54std::string safeString(std::string const& s)
55{
56 std::stringstream ss;
57 ss << std::quoted(s);
58 return ss.str();
59}
60
63std::string findVtkdiff()
64{
65 // Try to read the VTKDIFF_EXE environment variable.
66 if (const char* vtkdiff_exe_environment_variable =
67 std::getenv("VTKDIFF_EXE"))
68 {
69 std::string const vtkdiff_exe{vtkdiff_exe_environment_variable};
70 DBUG("VTKDIFF_EXE set to {:s}.", vtkdiff_exe);
71
72 //
73 // Sanity checks.
74 //
75 { // The base name is 'vtkdiff'
76 auto const& base_name =
78 if (base_name != "vtkdiff")
79 {
81 "The VTKDIFF_EXE environment variable does not point to "
82 "'vtkdiff'. VTKDIFF_EXE='{:s}'",
83 vtkdiff_exe);
84 }
85 }
86 { // vtkdiff must exist.
87 if (!BaseLib::IsFileExisting(vtkdiff_exe))
88 {
90 "The VTKDIFF_EXE points to a non-existing file. "
91 "VTKDIFF_EXE='{:s}'",
92 vtkdiff_exe);
93 }
94 }
95
96 //
97 // Test the actual call.
98 //
99 int const return_value =
100 // TODO (naumov) replace system call with output consuming call
101 // (fork + execl seems to be more safe), and extract the vtkdiff
102 // call to common function. Also properly escape all strings in
103 // command lines.
104 // Reference for POSIX and Windows:
105 // https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=87152177
106 // Take care when using fork, which might copy resources.
107 std::system((vtkdiff_exe + " --version").c_str());
108 if (return_value == 0)
109 {
110 return vtkdiff_exe;
111 }
112 WARN(
113 "Calling {:s} from the VTKDIFF_EXE environment variable didn't "
114 "work as expected. Return value was {:d}.",
115 vtkdiff_exe, return_value);
116 }
117
118 std::string const vtkdiff_exe{"vtkdiff"};
119 std::vector<std::string> const paths = {"", "bin"};
120 auto const path =
121 find_if(begin(paths), end(paths),
122 [&vtkdiff_exe](std::string const& path)
123 {
124 int const return_value =
125 // TODO (naumov) replace system call with output
126 // consuming call as in an above todo comment.
127 std::system((BaseLib::joinPaths(path, vtkdiff_exe) +
128 " --version")
129 .c_str());
130 return return_value == 0;
131 });
132 if (path == end(paths))
133 {
134 OGS_FATAL("vtkdiff not found.");
135 }
136 return BaseLib::joinPaths(*path, vtkdiff_exe);
137}
138
139} // namespace
140
141namespace ApplicationsLib
142{
144 std::string const& reference_path,
145 std::string const& output_directory)
146{
147 if (reference_path.empty())
148 {
149 OGS_FATAL(
150 "Reference path containing expected result files can not be "
151 "empty.");
152 }
153
154 std::string const vtkdiff = findVtkdiff();
155
156 // Construct command lines for each entry.
158 auto const& vtkdiff_configs = config_tree.getConfigSubtreeList("vtkdiff");
159 _command_lines.reserve(vtkdiff_configs.size());
160 for (auto const& vtkdiff_config : vtkdiff_configs)
161 {
162 std::string const& field_name =
164 vtkdiff_config.getConfigParameter<std::string>("field");
165 DBUG("vtkdiff will compare field '{:s}'.", field_name);
166
167 std::vector<std::string> filenames;
168 if (auto const regex_string =
170 vtkdiff_config.getConfigParameterOptional<std::string>("regex"))
171 {
172 // TODO: insert rank into regex for mpi case
173 DBUG("vtkdiff regex is '{}'.", *regex_string);
174 auto const regex = std::regex(*regex_string);
175 for (auto const& p : std::filesystem::directory_iterator(
176 std::filesystem::path(reference_path)))
177 {
178 auto const filename = p.path().filename().string();
179 if (std::regex_match(filename, regex))
180 {
181 DBUG(" -> matched '{}'", filename);
182 filenames.push_back(filename);
183 }
184 }
185 }
186 else
187 {
188 std::string filename =
190 vtkdiff_config.getConfigParameter<std::string>("file");
191#ifdef USE_PETSC
193 if (mpi.size > 1)
194 {
195 filename =
197 filename) +
198 "_" + std::to_string(mpi.rank) + ".vtu";
199 }
200#endif // OGS_USE_PETSC
201 filenames.push_back(filename);
202 }
203
204 if (empty(filenames))
205 {
206 OGS_FATAL(
207 "No files from test definitions were added for tests but {} "
208 "{:s} specified.",
209 std::size(vtkdiff_configs),
210 (std::size(vtkdiff_configs) == 1 ? "test was" : "tests were"));
211 }
212
213 auto const absolute_tolerance =
215 vtkdiff_config.getConfigParameter<std::string>("absolute_tolerance",
216 "");
217 if (!absolute_tolerance.empty() &&
218 !isConvertibleToDouble(absolute_tolerance))
219 {
220 OGS_FATAL(
221 "The absolute tolerance value '{:s}' is not convertible to "
222 "double.",
223 absolute_tolerance);
224 }
225 std::string const absolute_tolerance_parameter =
226 "--abs " + absolute_tolerance;
227 auto const relative_tolerance =
229 vtkdiff_config.getConfigParameter<std::string>("relative_tolerance",
230 "");
231 if (!relative_tolerance.empty() &&
232 !isConvertibleToDouble(relative_tolerance))
233 {
234 OGS_FATAL(
235 "The relative tolerance value '{:s}' is not convertible to "
236 "double.",
237 relative_tolerance);
238 }
239 std::string const relative_tolerance_parameter =
240 "--rel " + relative_tolerance;
241
242 for (auto const& filename : filenames)
243 {
244 std::string output_filename =
245 BaseLib::joinPaths(output_directory, filename);
246 _output_files.push_back(output_filename);
247 std::string reference_filename =
248 BaseLib::joinPaths(reference_path, filename);
249#if _WIN32
250 // vtk does not handle Windows long paths:
251 // https://gitlab.kitware.com/vtk/vtk/-/blob/master/Utilities/KWSys/vtksys/SystemTools.cxx#L1519-1521
252 // workaround is to make path absolute and prefix with a special
253 // marker and put everything in quotes, see
254 // https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=powershell
255 auto const& long_path_indicator = R"(\\?\)";
256
257 reference_filename =
258 std::filesystem::absolute(reference_filename).string();
259 reference_filename = std::format("\"{}{}\"", long_path_indicator,
260 reference_filename);
261 output_filename =
262 std::filesystem::absolute(output_filename).string();
263 output_filename =
264 std::format("\"{}{}\"", long_path_indicator, output_filename);
265
266#else
267 output_filename = safeString(output_filename);
268 reference_filename = safeString(reference_filename);
269#endif
270 //
271 // Construct command line.
272 //
273 std::string command_line =
274 vtkdiff + " -a " + safeString(field_name) + " -b " +
275 safeString(field_name) + " " + reference_filename + " " +
276 output_filename + " " + absolute_tolerance_parameter + " " +
277 relative_tolerance_parameter;
278 INFO("Will run '{:s}'", command_line);
279 _command_lines.emplace_back(std::move(command_line));
280 }
281 }
282}
283
285{
286 std::vector<int> return_values;
287 transform(begin(_command_lines), end(_command_lines),
288 back_inserter(return_values),
289 [](std::string const& command_line)
290 {
291 INFO("---------- vtkdiff begin ----------");
292 int const return_value = std::system(command_line.c_str());
293 if (return_value != 0)
294 {
295 WARN("Return value {:d} was returned by '{:s}'.",
296 return_value, command_line);
297 }
298 INFO("---------- vtkdiff end ----------\n");
299 return return_value;
300 });
301 return !return_values.empty() &&
302 all_of(begin(return_values), end(return_values),
303 [](int const& return_value) { return return_value == 0; });
304}
305
306std::vector<std::string> const& TestDefinition::getOutputFiles() const
307{
308 return _output_files;
309}
310
312{
313 return size(_command_lines);
314}
315} // namespace ApplicationsLib
#define OGS_FATAL(...)
Definition Error.h:19
void INFO(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:28
void DBUG(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:22
void ERR(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:40
void WARN(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:34
OGS_EXPORT_SYMBOL bool runTests() const
std::vector< std::string > _output_files
std::vector< std::string > _command_lines
TestDefinition(BaseLib::ConfigTree const &config_tree, std::string const &reference_path, std::string const &output_directory)
std::vector< std::string > const & getOutputFiles() const
Range< SubtreeIterator > getConfigSubtreeList(std::string const &root) const
bool IsFileExisting(const std::string &strFilename)
Returns true if given file exists.
Definition FileTools.cpp:23
std::string extractBaseNameWithoutExtension(std::string const &pathname)
std::string joinPaths(std::string const &pathA, std::string const &pathB)
std::string getVtuFileNameForPetscOutputWithoutExtension(std::string const &file_name)
bool isConvertibleToDouble(std::string const &s)
Test if the given string is convertible to a valid double value, not a NaN.
std::string safeString(std::string const &s)
Wraps a string into double ticks.