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