1 //============================================================================
2 // Copyright (c) Kitware, Inc.
3 // All rights reserved.
4 // See LICENSE.txt for details.
5 //
6 // This software is distributed WITHOUT ANY WARRANTY; without even
7 // the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
8 // PURPOSE. See the above copyright notice for more information.
9 //============================================================================
10 #ifndef vtk_m_cont_testing_Testing_h
11 #define vtk_m_cont_testing_Testing_h
12
13 #include <vtkm/cont/EnvironmentTracker.h>
14 #include <vtkm/cont/Error.h>
15 #include <vtkm/cont/Initialize.h>
16 #include <vtkm/cont/internal/OptionParser.h>
17 #include <vtkm/testing/Testing.h>
18 #include <vtkm/thirdparty/diy/Configure.h>
19
20 #include <vtkm/cont/ArrayHandle.h>
21 #include <vtkm/cont/CellSetExplicit.h>
22 #include <vtkm/cont/CellSetStructured.h>
23 #include <vtkm/cont/DataSet.h>
24 #include <vtkm/cont/DynamicCellSet.h>
25 #include <vtkm/cont/UnknownArrayHandle.h>
26
27 #include <vtkm/cont/testing/vtkm_cont_testing_export.h>
28
29 #include <sstream>
30 #include <vtkm/thirdparty/diy/diy.h>
31
32 // We could, conceivably, use CUDA or Kokkos specific print statements here.
33 // But we cannot use std::stringstream on device, so for now, we'll just accept
34 // that on CUDA and Kokkos we print less actionalble information.
35 #if defined(VTKM_ENABLE_CUDA) || defined(VTKM_ENABLE_KOKKOS)
36 #define VTKM_MATH_ASSERT(condition, message) \
37 { \
38 if (!(condition)) \
39 { \
40 this->RaiseError(message); \
41 } \
42 }
43 #else
44 #define VTKM_MATH_ASSERT(condition, message) \
45 { \
46 if (!(condition)) \
47 { \
48 std::stringstream ss; \
49 ss << "\n\tError at " << __FILE__ << ":" << __LINE__ << ":" << __func__ << "\n\t" << message \
50 << "\n"; \
51 this->RaiseError(ss.str().c_str()); \
52 } \
53 }
54 #endif
55
56 namespace opt = vtkm::cont::internal::option;
57
58 namespace vtkm
59 {
60 namespace cont
61 {
62 namespace testing
63 {
64
65 enum TestOptionsIndex
66 {
67 TEST_UNKNOWN,
68 DATADIR, // base dir containing test data files
69 BASELINEDIR, // base dir for regression test images
70 WRITEDIR // base dir for generated regression test images
71 };
72
73 struct TestVtkmArg : public opt::Arg
74 {
RequiredTestVtkmArg75 static opt::ArgStatus Required(const opt::Option& option, bool msg)
76 {
77 if (option.arg == nullptr)
78 {
79 if (msg)
80 {
81 VTKM_LOG_ALWAYS_S(vtkm::cont::LogLevel::Error,
82 "Missing argument after option '"
83 << std::string(option.name, static_cast<size_t>(option.namelen))
84 << "'.\n");
85 }
86 return opt::ARG_ILLEGAL;
87 }
88 else
89 {
90 return opt::ARG_OK;
91 }
92 }
93
94 // Method used for guessing whether an option that do not support (perhaps that calling
95 // program knows about it) has an option attached to it (which should also be ignored).
UnknownTestVtkmArg96 static opt::ArgStatus Unknown(const opt::Option& option, bool msg)
97 {
98 // If we don't have an arg, obviously we don't have an arg.
99 if (option.arg == nullptr)
100 {
101 return opt::ARG_NONE;
102 }
103
104 // The opt::Arg::Optional method will return that the ARG is OK if and only if
105 // the argument is attached to the option (e.g. --foo=bar). If that is the case,
106 // then we definitely want to report that the argument is OK.
107 if (opt::Arg::Optional(option, msg) == opt::ARG_OK)
108 {
109 return opt::ARG_OK;
110 }
111
112 // Now things get tricky. Maybe the next argument is an option or maybe it is an
113 // argument for this option. We will guess that if the next argument does not
114 // look like an option, we will treat it as such.
115 if (option.arg[0] == '-')
116 {
117 return opt::ARG_NONE;
118 }
119 else
120 {
121 return opt::ARG_OK;
122 }
123 }
124 };
125
126 struct Testing
127 {
128 public:
GetTestDataBasePathTesting129 static VTKM_CONT const std::string GetTestDataBasePath() { return SetAndGetTestDataBasePath(); }
130
DataPathTesting131 static VTKM_CONT const std::string DataPath(const std::string& filename)
132 {
133 return GetTestDataBasePath() + filename;
134 }
135
GetRegressionTestImageBasePathTesting136 static VTKM_CONT const std::string GetRegressionTestImageBasePath()
137 {
138 return SetAndGetRegressionImageBasePath();
139 }
140
RegressionImagePathTesting141 static VTKM_CONT const std::string RegressionImagePath(const std::string& filename)
142 {
143 return GetRegressionTestImageBasePath() + filename;
144 }
145
GetWriteDirBasePathTesting146 static VTKM_CONT const std::string GetWriteDirBasePath() { return SetAndGetWriteDirBasePath(); }
147
WriteDirPathTesting148 static VTKM_CONT const std::string WriteDirPath(const std::string& filename)
149 {
150 return GetWriteDirBasePath() + filename;
151 }
152
153 template <class Func>
RunTesting154 static VTKM_CONT int Run(Func function, int& argc, char* argv[])
155 {
156 std::unique_ptr<vtkmdiy::mpi::environment> env_diy = nullptr;
157 if (!vtkmdiy::mpi::environment::initialized())
158 {
159 env_diy.reset(new vtkmdiy::mpi::environment(argc, argv));
160 }
161
162 vtkm::cont::Initialize(argc, argv);
163 ParseAdditionalTestArgs(argc, argv);
164
165 try
166 {
167 function();
168 }
169 catch (vtkm::testing::Testing::TestFailure const& error)
170 {
171 std::cerr << "Error at " << error.GetFile() << ":" << error.GetLine() << ":"
172 << error.GetFunction() << "\n\t" << error.GetMessage() << "\n";
173 return 1;
174 }
175 catch (vtkm::cont::Error const& error)
176 {
177 std::cerr << "Uncaught VTKm exception thrown.\n" << error.GetMessage() << "\n";
178 std::cerr << "Stacktrace:\n" << error.GetStackTrace() << "\n";
179 return 1;
180 }
181 catch (std::exception const& error)
182 {
183 std::cerr << "STL exception throw.\n" << error.what() << "\n";
184 return 1;
185 }
186 catch (...)
187 {
188 std::cerr << "Unidentified exception thrown.\n";
189 return 1;
190 }
191 return 0;
192 }
193
194 template <class Func>
RunOnDeviceTesting195 static VTKM_CONT int RunOnDevice(Func function, int argc, char* argv[])
196 {
197 auto opts = vtkm::cont::InitializeOptions::RequireDevice;
198 auto config = vtkm::cont::Initialize(argc, argv, opts);
199 ParseAdditionalTestArgs(argc, argv);
200
201 try
202 {
203 function(config.Device);
204 }
205 catch (vtkm::testing::Testing::TestFailure& error)
206 {
207 std::cerr << "Error at " << error.GetFile() << ":" << error.GetLine() << ":"
208 << error.GetFunction() << "\n\t" << error.GetMessage() << "\n";
209 return 1;
210 }
211 catch (vtkm::cont::Error& error)
212 {
213 std::cerr << "Uncaught VTKm exception thrown.\n" << error.GetMessage() << "\n";
214 std::cerr << "Stacktrace:\n" << error.GetStackTrace() << "\n";
215 return 1;
216 }
217 catch (std::exception& error)
218 {
219 std::cerr << "STL exception throw.\n\t" << error.what() << "\n";
220 return 1;
221 }
222 catch (...)
223 {
224 std::cerr << "Unidentified exception thrown.\n";
225 return 1;
226 }
227 return 0;
228 }
229
230 private:
231 static std::string& SetAndGetTestDataBasePath(std::string path = "")
232 {
233 static std::string TestDataBasePath;
234
235 if (!path.empty())
236 {
237 TestDataBasePath = path;
238 if ((TestDataBasePath.back() != '/') && (TestDataBasePath.back() != '\\'))
239 {
240 TestDataBasePath = TestDataBasePath + "/";
241 }
242 }
243
244 if (TestDataBasePath.empty())
245 {
246 VTKM_LOG_S(
247 vtkm::cont::LogLevel::Error,
248 "TestDataBasePath was never set, was --data-dir set correctly? (hint: ../data/data)");
249 }
250
251 return TestDataBasePath;
252 }
253
254 static std::string& SetAndGetRegressionImageBasePath(std::string path = "")
255 {
256 static std::string RegressionTestImageBasePath;
257
258 if (!path.empty())
259 {
260 RegressionTestImageBasePath = path;
261 if ((RegressionTestImageBasePath.back() != '/') &&
262 (RegressionTestImageBasePath.back() != '\\'))
263 {
264 RegressionTestImageBasePath = RegressionTestImageBasePath + '/';
265 }
266 }
267
268 if (RegressionTestImageBasePath.empty())
269 {
270 VTKM_LOG_S(vtkm::cont::LogLevel::Error,
271 "RegressionTestImageBasePath was never set, was --baseline-dir set correctly? "
272 "(hint: ../data/baseline)");
273 }
274
275 return RegressionTestImageBasePath;
276 }
277
278 static std::string& SetAndGetWriteDirBasePath(std::string path = "")
279 {
280 static std::string WriteDirBasePath;
281
282 if (!path.empty())
283 {
284 WriteDirBasePath = path;
285 if ((WriteDirBasePath.back() != '/') && (WriteDirBasePath.back() != '\\'))
286 {
287 WriteDirBasePath = WriteDirBasePath + '/';
288 }
289 }
290
291 return WriteDirBasePath;
292 }
293
294 // Method to parse the extra arguments given to unit tests
ParseAdditionalTestArgsTesting295 static VTKM_CONT void ParseAdditionalTestArgs(int& argc, char* argv[])
296 {
297 { // Parse test arguments
298 std::vector<opt::Descriptor> usage;
299
300 usage.push_back({ DATADIR,
301 0,
302 "D",
303 "data-dir",
304 TestVtkmArg::Required,
305 " --data-dir, -D "
306 "<data-dir-path> \tPath to the "
307 "base data directory in the VTK-m "
308 "src dir." });
309 usage.push_back({ BASELINEDIR,
310 0,
311 "B",
312 "baseline-dir",
313 TestVtkmArg::Required,
314 " --baseline-dir, -B "
315 "<baseline-dir-path> "
316 "\tPath to the base dir "
317 "for regression test "
318 "images" });
319 usage.push_back({ WRITEDIR,
320 0,
321 "",
322 "write-dir",
323 TestVtkmArg::Required,
324 " --write-dir "
325 "<write-dir-path> "
326 "\tPath to the write dir "
327 "to store generated "
328 "regression test images" });
329 // Required to collect unknown arguments when help is off.
330 usage.push_back({ TEST_UNKNOWN, 0, "", "", TestVtkmArg::Unknown, "" });
331 usage.push_back({ 0, 0, 0, 0, 0, 0 });
332
333
334 // Remove argv[0] (executable name) if present:
335 int vtkmArgc = argc > 0 ? argc - 1 : 0;
336 char** vtkmArgv = argc > 0 ? argv + 1 : argv;
337
338 opt::Stats stats(usage.data(), vtkmArgc, vtkmArgv);
339 std::unique_ptr<opt::Option[]> options{ new opt::Option[stats.options_max] };
340 std::unique_ptr<opt::Option[]> buffer{ new opt::Option[stats.buffer_max] };
341 opt::Parser parse(usage.data(), vtkmArgc, vtkmArgv, options.get(), buffer.get());
342
343 if (parse.error())
344 {
345 std::cerr << "Internal Initialize parser error" << std::endl;
346 exit(1);
347 }
348
349 if (options[DATADIR])
350 {
351 SetAndGetTestDataBasePath(options[DATADIR].arg);
352 }
353
354 if (options[BASELINEDIR])
355 {
356 SetAndGetRegressionImageBasePath(options[BASELINEDIR].arg);
357 }
358
359 if (options[WRITEDIR])
360 {
361 SetAndGetWriteDirBasePath(options[WRITEDIR].arg);
362 }
363
364 for (const opt::Option* opt = options[TEST_UNKNOWN]; opt != nullptr; opt = opt->next())
365 {
366 VTKM_LOG_S(vtkm::cont::LogLevel::Info,
367 "Unknown option to internal Initialize: " << opt->name << "\n");
368 }
369
370 for (int nonOpt = 0; nonOpt < parse.nonOptionsCount(); ++nonOpt)
371 {
372 VTKM_LOG_S(vtkm::cont::LogLevel::Info,
373 "Unknown argument to internal Initialize: " << parse.nonOption(nonOpt) << "\n");
374 }
375 }
376 }
377 };
378
379 }
380 }
381 } // namespace vtkm::cont::testing
382
383 //============================================================================
384 template <typename T1, typename T2, typename StorageTag1, typename StorageTag2>
385 VTKM_CONT TestEqualResult
test_equal_ArrayHandles(const vtkm::cont::ArrayHandle<T1,StorageTag1> & array1,const vtkm::cont::ArrayHandle<T2,StorageTag2> & array2)386 test_equal_ArrayHandles(const vtkm::cont::ArrayHandle<T1, StorageTag1>& array1,
387 const vtkm::cont::ArrayHandle<T2, StorageTag2>& array2)
388 {
389 TestEqualResult result;
390
391 if (array1.GetNumberOfValues() != array2.GetNumberOfValues())
392 {
393 result.PushMessage("Arrays have different sizes.");
394 return result;
395 }
396
397 auto portal1 = array1.ReadPortal();
398 auto portal2 = array2.ReadPortal();
399 for (vtkm::Id i = 0; i < portal1.GetNumberOfValues(); ++i)
400 {
401 if (!test_equal(portal1.Get(i), portal2.Get(i)))
402 {
403 result.PushMessage("Values don't match at index " + std::to_string(i));
404 break;
405 }
406 }
407
408 return result;
409 }
410
411 VTKM_CONT_TESTING_EXPORT TestEqualResult
412 test_equal_ArrayHandles(const vtkm::cont::UnknownArrayHandle& array1,
413 const vtkm::cont::UnknownArrayHandle& array2);
414
415 namespace detail
416 {
417
418 struct TestEqualCellSet
419 {
420 template <typename ShapeST, typename ConnectivityST, typename OffsetST>
operatorTestEqualCellSet421 void operator()(const vtkm::cont::CellSetExplicit<ShapeST, ConnectivityST, OffsetST>& cs1,
422 const vtkm::cont::CellSetExplicit<ShapeST, ConnectivityST, OffsetST>& cs2,
423 TestEqualResult& result) const
424 {
425 vtkm::TopologyElementTagCell visitTopo{};
426 vtkm::TopologyElementTagPoint incidentTopo{};
427
428 if (cs1.GetNumberOfPoints() != cs2.GetNumberOfPoints())
429 {
430 result.PushMessage("number of points don't match");
431 return;
432 }
433
434 result = test_equal_ArrayHandles(cs1.GetShapesArray(visitTopo, incidentTopo),
435 cs2.GetShapesArray(visitTopo, incidentTopo));
436 if (!result)
437 {
438 result.PushMessage("shapes arrays don't match");
439 return;
440 }
441
442 result = test_equal_ArrayHandles(cs1.GetNumIndicesArray(visitTopo, incidentTopo),
443 cs2.GetNumIndicesArray(visitTopo, incidentTopo));
444 if (!result)
445 {
446 result.PushMessage("counts arrays don't match");
447 return;
448 }
449 result = test_equal_ArrayHandles(cs1.GetConnectivityArray(visitTopo, incidentTopo),
450 cs2.GetConnectivityArray(visitTopo, incidentTopo));
451 if (!result)
452 {
453 result.PushMessage("connectivity arrays don't match");
454 return;
455 }
456 result = test_equal_ArrayHandles(cs1.GetOffsetsArray(visitTopo, incidentTopo),
457 cs2.GetOffsetsArray(visitTopo, incidentTopo));
458 if (!result)
459 {
460 result.PushMessage("offsets arrays don't match");
461 return;
462 }
463 }
464
465 template <vtkm::IdComponent DIMENSION>
operatorTestEqualCellSet466 void operator()(const vtkm::cont::CellSetStructured<DIMENSION>& cs1,
467 const vtkm::cont::CellSetStructured<DIMENSION>& cs2,
468 TestEqualResult& result) const
469 {
470 if (cs1.GetPointDimensions() != cs2.GetPointDimensions())
471 {
472 result.PushMessage("point dimensions don't match");
473 return;
474 }
475 }
476
477 template <typename CellSetTypes1, typename CellSetTypes2>
operatorTestEqualCellSet478 void operator()(const vtkm::cont::DynamicCellSetBase<CellSetTypes1>& cs1,
479 const vtkm::cont::DynamicCellSetBase<CellSetTypes2>& cs2,
480 TestEqualResult& result) const
481 {
482 cs1.CastAndCall(*this, cs2, result);
483 }
484
485 template <typename CellSet, typename CellSetTypes>
operatorTestEqualCellSet486 void operator()(const CellSet& cs,
487 const vtkm::cont::DynamicCellSetBase<CellSetTypes>& dcs,
488 TestEqualResult& result) const
489 {
490 if (!dcs.IsSameType(cs))
491 {
492 result.PushMessage("types don't match");
493 return;
494 }
495 this->operator()(cs, dcs.template Cast<CellSet>(), result);
496 }
497 };
498 } // detail
499
500 template <typename CellSet1, typename CellSet2>
test_equal_CellSets(const CellSet1 & cellset1,const CellSet2 & cellset2)501 inline VTKM_CONT TestEqualResult test_equal_CellSets(const CellSet1& cellset1,
502 const CellSet2& cellset2)
503 {
504 TestEqualResult result;
505 detail::TestEqualCellSet{}(cellset1, cellset2, result);
506 return result;
507 }
508
test_equal_Fields(const vtkm::cont::Field & f1,const vtkm::cont::Field & f2)509 inline VTKM_CONT TestEqualResult test_equal_Fields(const vtkm::cont::Field& f1,
510 const vtkm::cont::Field& f2)
511 {
512 TestEqualResult result;
513
514 if (f1.GetName() != f2.GetName())
515 {
516 result.PushMessage("names don't match");
517 return result;
518 }
519
520 if (f1.GetAssociation() != f2.GetAssociation())
521 {
522 result.PushMessage("associations don't match");
523 return result;
524 }
525
526 result = test_equal_ArrayHandles(f1.GetData(), f2.GetData());
527 if (!result)
528 {
529 result.PushMessage("data doesn't match");
530 }
531
532 return result;
533 }
534
535 template <typename CellSetTypes = VTKM_DEFAULT_CELL_SET_LIST>
536 inline VTKM_CONT TestEqualResult test_equal_DataSets(const vtkm::cont::DataSet& ds1,
537 const vtkm::cont::DataSet& ds2,
538 CellSetTypes ctypes = CellSetTypes())
539 {
540 TestEqualResult result;
541 if (ds1.GetNumberOfCoordinateSystems() != ds2.GetNumberOfCoordinateSystems())
542 {
543 result.PushMessage("number of coordinate systems don't match");
544 return result;
545 }
546 for (vtkm::IdComponent i = 0; i < ds1.GetNumberOfCoordinateSystems(); ++i)
547 {
548 result = test_equal_ArrayHandles(ds1.GetCoordinateSystem(i).GetData(),
549 ds2.GetCoordinateSystem(i).GetData());
550 if (!result)
551 {
552 result.PushMessage(std::string("coordinate systems don't match at index ") +
553 std::to_string(i));
554 return result;
555 }
556 }
557
558 result = test_equal_CellSets(ds1.GetCellSet().ResetCellSetList(ctypes),
559 ds2.GetCellSet().ResetCellSetList(ctypes));
560 if (!result)
561 {
562 result.PushMessage(std::string("cellsets don't match"));
563 return result;
564 }
565
566 if (ds1.GetNumberOfFields() != ds2.GetNumberOfFields())
567 {
568 result.PushMessage("number of fields don't match");
569 return result;
570 }
571 for (vtkm::IdComponent i = 0; i < ds1.GetNumberOfFields(); ++i)
572 {
573 result = test_equal_Fields(ds1.GetField(i), ds2.GetField(i));
574 if (!result)
575 {
576 result.PushMessage(std::string("fields don't match at index ") + std::to_string(i));
577 return result;
578 }
579 }
580
581 return result;
582 }
583
584 #endif //vtk_m_cont_internal_Testing_h
585