92{
93 int status = 0;
94 string command;
95
96
97 po::options_description desc("Available options");
98 desc.add_options()("help,h", "Produce this help message.")(
99 "verbose,v", "Turn on verbosity.")("generate-metric,g",
100 po::value<vector<int>>(),
101 "Generate a single metric.")(
102 "generate-all-metrics,a", "Generate all metrics.")(
103 "executable,e", po::value<string>(), "Use specified executable.");
104
105 po::options_description hidden("Hidden options");
106 hidden.add_options()("input-file", po::value<string>(), "Input filename");
107
108 po::options_description cmdline_options("Command-line options");
109 cmdline_options.add(hidden).add(desc);
110
111 po::options_description visible("Allowed options");
112 visible.add(desc);
113
114 po::positional_options_description
p;
115 p.add(
"input-file", -1);
116
117 po::variables_map vm;
118
119 try
120 {
121 po::store(po::command_line_parser(argc, argv)
122 .options(cmdline_options)
125 vm);
126 po::notify(vm);
127 }
128 catch (const exception &e)
129 {
130 cerr << e.what() << endl;
131 cerr << desc;
132 return 1;
133 }
134
135 if (vm.count("help") || vm.count("input-file") != 1)
136 {
137 cerr << "Usage: Tester [options] input-file.tst" << endl;
138 cout << desc;
139 return 1;
140 }
141
142 bool verbose = vm.count("verbose");
143
144
145 vector<int> metricGenVec;
146 if (vm.count("generate-metric"))
147 {
148 metricGenVec = vm["generate-metric"].as<vector<int>>();
149 }
150 set<int> metricGen(metricGenVec.begin(), metricGenVec.end());
151
152
153 const fs::path specFile(vm["input-file"].as<string>());
154
155
156 fs::path specPath = specFile.parent_path();
157
158 if (specPath.empty())
159 {
160 specPath = fs::current_path();
161 }
162
163 string specFileStem = specFile.stem().string();
164
165
166
167 const fs::path masterDir =
169
170
171 const fs::path startDir = fs::current_path();
172
173 try
174 {
175 if (verbose)
176 {
177 cerr << "Reading test file definition: " << specFile << endl;
178 }
179
180
182
183 if (verbose && file.GetNumMetrics() > 0)
184 {
185 cerr << "Creating metrics:" << endl;
186 }
187
188
189 vector<MetricSharedPtr> metrics;
190 for (unsigned int i = 0; i < file.GetNumMetrics(); ++i)
191 {
192 set<int>::iterator it = metricGen.find(file.GetMetricId(i));
193 bool genMetric =
194 it != metricGen.end() || (vm.count("generate-all-metrics") > 0);
195
197 file.GetMetricType(i), file.GetMetric(i), genMetric));
198
199 if (verbose)
200 {
201 cerr << " - ID " << metrics.back()->GetID() << ": "
202 << metrics.back()->GetType() << endl;
203 }
204
205 if (it != metricGen.end())
206 {
207 metricGen.erase(it);
208 }
209 }
210
211 if (metricGen.size() != 0)
212 {
213 string s = metricGen.size() == 1 ? "s" : "";
214 set<int>::iterator it;
215 cerr << "Unable to find metric" + s + " with ID" + s + " ";
216 for (it = metricGen.begin(); it != metricGen.end(); ++it)
217 {
218 cerr << *it << " ";
219 }
220 cerr << endl;
221 return 1;
222 }
223
224
225 if (fs::exists(masterDir))
226 {
227 fs::remove_all(masterDir);
228 }
229
230 if (verbose)
231 {
232 cerr << "Creating master directory: " << masterDir << endl;
233 }
234
235
236 fs::create_directory(masterDir);
237
238
239 fs::current_path(masterDir);
240
241
242
243 fstream masterOut("master.out", ios::out | ios::in | ios::trunc);
244 fstream masterErr("master.err", ios::out | ios::in | ios::trunc);
245
246 if (masterOut.bad() || masterErr.bad())
247 {
248 cerr << "One or more master output files are unreadable." << endl;
249 throw 1;
250 }
251
252
253 vector<fs::path> tmpWorkingDirs;
254 string line;
255
256 for (unsigned int i = 0; i < file.GetNumRuns(); ++i)
257 {
258 command = "";
259
260 if (verbose)
261 {
262 cerr << "Starting run " << i << "." << endl;
263 }
264
265
266 const fs::path tmpDir =
267 masterDir / fs::path("run" + std::to_string(i));
268 tmpWorkingDirs.push_back(tmpDir);
269
270 if (verbose)
271 {
272 cerr << "Creating working directory: " << tmpDir << endl;
273 }
274
275
276 fs::create_directory(tmpDir);
277
278
279 fs::current_path(tmpDir);
280
281 if (verbose && file.GetNumDependentFiles())
282 {
283 cerr << "Copying required files: " << endl;
284 }
285
286
287
288 for (unsigned int j = 0; j < file.GetNumDependentFiles(); ++j)
289 {
290 fs::path source_file(file.GetDependentFile(j).m_filename);
291
292 fs::path source = specPath / source_file;
293 fs::path dest = tmpDir / source_file.filename();
294 if (verbose)
295 {
296 cerr << " - " << source << " -> " << dest << endl;
297 }
298
299 if (fs::is_directory(source))
300 {
301 fs::create_directory(dest);
302
303
304 for (const auto &dirEnt :
305 fs::recursive_directory_iterator{source})
306 {
307 fs::path newdest = dest / dirEnt.path().filename();
308 fs::copy_file(dirEnt.path(), newdest);
309 }
310 }
311 else
312 {
313 fs::copy_file(source, dest);
314 }
315 }
316
317
318 fs::path source_file("test.opt");
319 fs::path source = specPath / source_file;
320 bool HaveOptFile = false;
321 if (fs::exists(source))
322 {
323 fs::path dest = tmpDir / source_file.filename();
324 if (verbose)
325 {
326 cerr << " - " << source << " -> " << dest << endl;
327 }
328
329 if (fs::is_directory(source))
330 {
331 fs::create_directory(dest);
332
333
334 for (const auto &dirEnt :
335 fs::recursive_directory_iterator{source})
336 {
337 fs::path newdest = dest / dirEnt.path().filename();
338 fs::copy_file(dirEnt.path(), newdest);
339 }
340 }
341 else
342 {
343 fs::copy_file(source, dest);
344 }
345
346 HaveOptFile = true;
347 }
348
349
350
351
352
353 setenv("PYTHONPATH", "@NEKPY_BASE_DIR@", true);
354
355
356
357
358 bool mpiAdded = false;
359 for (unsigned int j = 0; j < file.GetNumCommands(); ++j)
360 {
361 Command cmd = file.GetCommand(j);
362
363#ifdef NEKTAR_TEST_FORCEMPIEXEC
364#else
365 if (cmd.
m_processes > 1 || (file.GetNumCommands() > 1 &&
367#endif
368 {
369 if (mpiAdded)
370 {
371 continue;
372 }
373
374 command += "\"@MPIEXEC@\" ";
375 if (std::string("@NEKTAR_TEST_USE_HOSTFILE@") == "ON")
376 {
377 command += "-hostfile hostfile ";
378#if (NEKTAR_MPI_TYPE == 1)
379 if (system("echo 'localhost:12' > hostfile"))
380#else
381 if (system("echo 'localhost slots=12' > hostfile"))
382#endif
383 {
384 cerr << "Unable to write 'hostfile' in path '"
385 << fs::current_path() << endl;
386 status = 1;
387 }
388 }
389
390 if (file.GetNumCommands() > 1)
391 {
392#if (NEKTAR_MPI_TYPE == 1)
393
394
395
396
397
398
399#else
400 command += "--tag-output ";
401#endif
402 }
403
404 mpiAdded = true;
405 }
406 }
407
408
409 for (unsigned int j = 0; j < file.GetNumCommands(); ++j)
410 {
411 Command cmd = file.GetCommand(j);
412
413
414
416 {
417 command += " : ";
418 }
420 {
421 command += " && ";
423 {
424 command += "\"@MPIEXEC@\" ";
425 if (std::string("@NEKTAR_TEST_USE_HOSTFILE@") == "ON")
426 {
427 command += "-hostfile hostfile ";
428 }
429 }
430 }
431
432
433 if (cmd.
m_processes > 1 || (file.GetNumCommands() > 1 &&
435 {
436 command += "@MPIEXEC_NUMPROC_FLAG@ ";
438 }
439
440
442 if (!fs::exists(execPath))
443 {
446 }
447
448
449
451 {
452 command += "@Python3_EXECUTABLE@ ";
453 }
454
456 command += pathString;
457 if (HaveOptFile)
458 {
459 command += " --use-opt-file test.opt ";
460 }
461
462 command += " ";
464 command += " 1>output.out 2>output.err";
465 }
466
467 status = 0;
468
469 if (verbose)
470 {
471 cerr << "Running command: " << command << endl;
472 }
473
474
475 if (system(command.c_str()))
476 {
477 cerr << "Error occurred running test:" << endl;
478 cerr << "Command: " << command << endl;
479 status = 1;
480 }
481
482
483 if (!(fs::exists("output.out") && fs::exists("output.err")))
484 {
485 cerr << "One or more test output files are missing." << endl;
486 throw 1;
487 }
488
489
490 ifstream vStdout("output.out");
491 ifstream vStderr("output.err");
492 if (vStdout.bad() || vStderr.bad())
493 {
494 cerr << "One or more test output files are unreadable." << endl;
495 throw 1;
496 }
497
498
499 if (verbose)
500 {
501 cerr << "Appending run " << i << " output and error to master."
502 << endl;
503 }
504
505 while (getline(vStdout, line))
506 {
507 masterOut << line << endl;
508 }
509
510 while (getline(vStderr, line))
511 {
512 masterErr << line << endl;
513 }
514
515 vStdout.close();
516 vStderr.close();
517 }
518
519
520 for (int i = 0; i < metrics.size(); ++i)
521 {
522 if (!metrics[i]->SupportsAverage() && file.GetNumRuns() > 1)
523 {
524 cerr << "WARNING: Metric " << metrics[i]->GetType()
525 << " does not support multiple runs. Test may yield "
526 "unexpected results."
527 << endl;
528 }
529 }
530
531
532 if (status == 0)
533 {
534 if (verbose && metrics.size())
535 {
536 cerr << "Checking metrics:" << endl;
537 }
538
539 for (int i = 0; i < metrics.size(); ++i)
540 {
541 bool gen =
542 metricGen.find(metrics[i]->GetID()) != metricGen.end() ||
543 (vm.count("generate-all-metrics") > 0);
544
545 masterOut.clear();
546 masterErr.clear();
547 masterOut.seekg(0, ios::beg);
548 masterErr.seekg(0, ios::beg);
549
550 if (verbose)
551 {
552 cerr << " - " << (gen ? "generating" : "checking")
553 << " metric " << metrics[i]->GetID() << " ("
554 << metrics[i]->GetType() << ")... ";
555 }
556
557 if (!metrics[i]->Test(masterOut, masterErr))
558 {
559 status = 1;
560 if (verbose)
561 {
562 cerr << "failed!" << endl;
563 }
564 }
565 else if (verbose)
566 {
567 cerr << "passed" << endl;
568 }
569 }
570 }
571
572 if (verbose)
573 {
574 cerr << endl << endl;
575 }
576
577
578 if (status == 1 || verbose)
579 {
580 masterOut.clear();
581 masterErr.clear();
582 masterOut.seekg(0, ios::beg);
583 masterErr.seekg(0, ios::beg);
584
585 cout << "=== Output ===" << endl;
586 while (masterOut.good())
587 {
588 getline(masterOut, line);
589 cout << line << endl;
590 }
591 cout << "=== Errors ===" << endl;
592 while (masterErr.good())
593 {
594 getline(masterErr, line);
595 cout << line << endl;
596 }
597 }
598
599
600 masterOut.close();
601 masterErr.close();
602
603
604 fs::current_path(startDir);
605
606 if (verbose)
607 {
608 cerr << "Removing working directory" << endl;
609 }
610
611
612
613
614
615 int i = 1000;
616 while (i > 0)
617 {
618 try
619 {
620
621 fs::remove_all(masterDir);
622 break;
623 }
624 catch (const fs::filesystem_error &e)
625 {
626 using namespace std::chrono_literals;
627 std::this_thread::sleep_for(1ms);
628 i--;
629 if (i > 0)
630 {
631 cout << "Locked files encountered. "
632 << "Retrying after 1ms..." << endl;
633 }
634 else
635 {
636
637
638 throw e;
639 }
640 }
641 }
642
643
644 if (vm.count("generate-metric") > 0 ||
645 vm.count("generate-all-metrics") > 0)
646 {
647 file.SaveFile();
648 }
649
650
651 return status;
652 }
653 catch (const fs::filesystem_error &e)
654 {
655 cerr << "Filesystem operation error occurred:" << endl;
656 cerr << " " << e.what() << endl;
657 cerr << " Files left in " << masterDir.string() << endl;
658 }
660 {
661 cerr << "Error occurred during test:" << endl;
662 cerr << " " << e.what() << endl;
663 cerr << " Files left in " << masterDir.string() << endl;
664 }
665 catch (const std::exception &e)
666 {
667 cerr << "Unhandled exception during test:" << endl;
668 cerr << " " << e.what() << endl;
669 cerr << " Files left in " << masterDir.string() << endl;
670 }
671 catch (...)
672 {
673 cerr << "Unknown error during test" << endl;
674 cerr << " Files left in " << masterDir.string() << endl;
675 }
676
677
678 return 2;
679}
#define ASSERTL0(condition, msg)
MetricSharedPtr CreateInstance(std::string key, TiXmlElement *elmt, bool generate)
The TestData class is responsible for parsing a test XML file and storing the data.
static std::string PortablePath(const fs::path &path)
create portable path on different platforms for std::filesystem path.
static fs::path UniquePath(std::string specFileStem)
Create a unique (random) path, based on an input stem string. The returned string is a filename or di...
MetricFactory & GetMetricFactory()
CommandType m_commandType
Subclass of std::runtime_error to handle exceptions raised by Tester.