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