testdriver.hxx
Go to the documentation of this file.
1 // ===========================================================================
2 // Copyright (c) 2011-2012 University of Pennsylvania
3 // Copyright (c) 2013-2016 Andreas Schuh
4 // All rights reserved.
5 //
6 // See COPYING file for license information or visit
7 // https://cmake-basis.github.io/download.html#license
8 // ===========================================================================
9 
10 /**
11  * @file testdriver.hxx
12  * @brief Default test driver implementation.
13  */
14 
15 #pragma once
16 #ifndef _BASIS_TESTDRIVER_HXX
17 #define _BASIS_TESTDRIVER_HXX
18 
19 
20 #include <iterator>
21 
22 #if WINDOWS
23 # include <Winsock2.h> // gethostname()
24 # ifdef max
25 # undef max
26 # endif
27 # pragma comment(lib, "Ws2_32.lib")
28 #else
29 # include <unistd.h> // gethostname()
30 #endif
31 
32 #ifdef ITK_VERSION
33 # include "testdriver-itk.hxx"
34 #endif
35 
36 
37 // ===========================================================================
38 // initialization
39 // ===========================================================================
40 
41 // ---------------------------------------------------------------------------
42 void testdriversetup(int* argc, char** argv[])
43 {
44  try {
45  string name;
46  #ifdef TESTDRIVER_NAME
47  name = TESTDRIVER_NAME;
48  #else
49  name = "testdriver";
50  #endif
51  #ifdef ITK_VERSION
52  name += " build with ITK ";
53  name += ITK_VERSION;
54  #endif
55 
56  // -------------------------------------------------------------------
57  // construct command-line
58  CmdLine cmd(
59  // program identification
60  name, PROJECT,
61  // description
62  "This program alters the environment, runs a test and "
63  "compares the output image to one or more baseline images.",
64  // example usage
65  "EXENAME GaussFilter --compare output.nii baseline.nii"
66  "\n"
67  "Runs the test GaussFilter which presumably writes the"
68  " gaussian smoothed image to the image file output.nii."
69  " Compares the image produced by the test to the reference"
70  " image named baseline.nii with default intensity tolerance.",
71  // version information
72  RELEASE, "2011, 2012 University of Pennsylvania");
73 
74  cmd.add(add_before_libpath);
75  cmd.add(add_before_env);
76  cmd.add(clean_cwd_before_test);
77  cmd.add(clean_cwd_after_test);
78  cmd.add(diff);
79  cmd.add(diff_lines);
80  cmd.add(compare);
82  cmd.add(intensity_tolerance);
83  cmd.add(tolerance_radius);
84  cmd.add(orientation_insensitive);
85  cmd.add(redirect_output);
86  cmd.add(max_number_of_threads);
87  cmd.add(full_output);
88  cmd.add(verbose);
89 
90  #ifdef BASIS_STANDALONE_TESTDRIVER
91  cmd.xorAdd(noprocess, testcmd);
92  #else
93  cmd.add(testcmd);
94  #endif
95 
96  // -------------------------------------------------------------------
97  // parse command-line
98  cmd.parse(*argc, *argv);
99 
100  // -------------------------------------------------------------------
101  // rearrange argc and argv of main()
102  if (testcmd.isSet()) {
103  for (unsigned int i = 0; i < testcmd.getValue().size(); i++) {
104  for (int j = 1; j < (*argc); j++) {
105  if (testcmd.getValue()[i] == (*argv)[j]) {
106  (*argv)[i + 1] = (*argv)[j];
107  break;
108  }
109  }
110  }
111  *argc = static_cast<int>(testcmd.getValue().size()) + 1;
112  (*argv)[*argc] = NULL;
113  } else {
114  *argc = 1;
115  (*argv)[1] = NULL;
116  }
117 
118  // Reset ignoring flag of TCLAP library. Otherwise, when a test
119  // uses the TCLAP library to parse its arguments, the labeled
120  // arguments will be immediately ignored.
121  // This required the addition of the stopIgnoring() method to TCLAP::Arg.
122  TCLAP::Arg::stopIgnoring();
123 
124  // -----------------------------------------------------------------------
125  // catch specification exceptions - parse errors are already taken care of
126  } catch (CmdLineException& e) {
127  cerr << e.error() << endl;
128  exit(1);
129  }
130 
131  // -----------------------------------------------------------------------
132  // add host name as Dart/CDash measurement
133  char hostname[256] = "unknown";
134  #if WINDOWS
135  WSADATA wsaData;
136  WSAStartup(MAKEWORD(2, 2), &wsaData);
137  gethostname(hostname, sizeof(hostname));
138  WSACleanup();
139  #else
140  gethostname(hostname, sizeof(hostname));
141  #endif
142  hostname[255] = '\0';
143 
144  cout << "<DartMeasurement name=\"Host Name\" type=\"string\">";
145  cout << hostname;
146  cout << "</DartMeasurement>" << endl;
147 
148  cout << "<DartMeasurement name=\"Working Directory\" type=\"string\">";
149  cout << os::getcwd();
150  cout << "</DartMeasurement>" << endl;
151 
152  #ifdef ITK_VERSION
153  cout << "<DartMeasurement name=\"ITK Version\" type=\"string\">";
154  cout << ITK_VERSION;
155  cout << "</DartMeasurement>" << endl;
156  #endif
157 
158  // -----------------------------------------------------------------------
159  // register ITK IO factories
160  #ifdef ITK_VERSION
162  #endif
163 }
164 
165 // ===========================================================================
166 // low-level file comparison
167 // ===========================================================================
168 
169 // ---------------------------------------------------------------------------
171 {
172  assert(diff.getValue().size() != 0);
173  assert((diff.getValue().size() % 2) == 0);
174 
175  RegressionTest regression_test;
176 
177  regression_test.test_file = diff.getValue()[diff.getValue().size() - 2];
178  regression_test.baseline_file = diff.getValue()[diff.getValue().size() - 1];
179  regression_test.intensity_tolerance = 0.0f;
180  regression_test.max_number_of_differences = 0;
181  regression_test.tolerance_radius = 0;
182  regression_test.orientation_insensitive = false;
183  regression_test.method = BINARY_DIFF;
184 
185  regression_tests.push_back(regression_test);
186 }
187 
188 // ---------------------------------------------------------------------------
189 int binary_diff(const char* testfile, const char* baseline)
190 {
191  int retval = 0;
192  ifstream ift(testfile, ios::binary);
193  ifstream ifb(baseline, ios::binary);
194  if (!ift) return -1;
195  if (!ifb) return -2;
196  istream_iterator<unsigned char> eos; // end-of-stream
197  istream_iterator<unsigned char> it(ift);
198  istream_iterator<unsigned char> ib(ifb);
199  while (it != eos && ib != eos) {
200  if (*it != *ib) break;
201  ++it;
202  ++ib;
203  }
204  if (it != eos || ib != eos) retval = 1;
205  ift.close();
206  ifb.close();
207  return retval;
208 }
209 
210 // ---------------------------------------------------------------------------
212 {
213  assert(diff_lines.getValue().size() != 0);
214  assert((diff_lines.getValue().size() % 2) == 0);
215 
216  RegressionTest regression_test;
217 
218  regression_test.test_file = diff_lines.getValue()[diff_lines.getValue().size() - 2];
219  regression_test.baseline_file = diff_lines.getValue()[diff_lines.getValue().size() - 1];
220  regression_test.intensity_tolerance = 0.0f;
221  regression_test.max_number_of_differences = max_number_of_differences.getValue();
222  regression_test.tolerance_radius = 0;
223  regression_test.orientation_insensitive = false;
224  regression_test.method = DIFF_LINES;
225 
226  regression_tests.push_back(regression_test);
227 }
228 
229 // ---------------------------------------------------------------------------
230 int text_diff_lines(const char* testfile, const char* baseline, unsigned int max_number_of_differences)
231 {
232  int retval = 0;
233  ifstream ift(testfile);
234  ifstream ifb(baseline);
235  if (!ift) return -1;
236  if (!ifb) return -2;
237  string tline, bline;
238  while (getline(ift, tline) && getline(ifb, bline)) {
239  if (tline != bline) retval++;
240  }
241  if (static_cast<unsigned int>(retval) <= max_number_of_differences) retval = 0;
242  if (getline(ift, tline) || getline(ifb, bline)) retval = 1000;
243  ift.close();
244  ifb.close();
245  return retval;
246 }
247 
248 // ===========================================================================
249 // image regression testing
250 // ===========================================================================
251 
252 // ---------------------------------------------------------------------------
254 {
255  assert(compare.getValue().size() != 0);
256  assert((compare.getValue().size() % 2) == 0);
257 
258  RegressionTest regression_test;
259 
260  regression_test.test_file = compare.getValue()[compare.getValue().size() - 2];
261  regression_test.baseline_file = compare.getValue()[compare.getValue().size() - 1];
262  regression_test.intensity_tolerance = intensity_tolerance.getValue();
263  regression_test.max_number_of_differences = max_number_of_differences.getValue();
264  regression_test.tolerance_radius = tolerance_radius.getValue();
265  regression_test.orientation_insensitive = orientation_insensitive.getValue();
266  regression_test.method = COMPARE_IMAGES;
267 
268  regression_tests.push_back(regression_test);
269 }
270 
271 // ---------------------------------------------------------------------------
272 vector<string> get_baseline_filenames(string filename_template)
273 {
274  vector<string> baselines;
275 
276  ifstream ifs(filename_template.c_str());
277  if (ifs) baselines.push_back(filename_template);
278 
279  int x = 0;
280  string::size_type pos = filename_template.rfind(".");
281  string suffix;
282 
283  if (pos != string::npos) {
284  suffix = filename_template.substr(pos);
285  filename_template.erase(pos);
286  }
287  while (++x) {
288  ostringstream filename;
289  filename << filename_template << '.' << x << suffix;
290  ifstream ifs(filename.str().c_str());
291  if (!ifs) break;
292  ifs.close();
293  baselines.push_back(filename.str());
294  }
295  return baselines;
296 }
297 
298 // ---------------------------------------------------------------------------
299 int image_regression_test(const char* imagefile,
300  const char* baseline,
301  double intensity_tolerance,
302  unsigned int max_number_of_differences,
303  unsigned int tolerance_radius,
305  int report)
306 {
307  #ifdef ITK_VERSION
308  return RegressionTestImage(imagefile,
309  baseline,
310  report,
311  intensity_tolerance,
312  max_number_of_differences,
313  tolerance_radius,
314  orientation_insensitive);
315  #else
316  BASIS_THROW(runtime_error,
317  "Not implemented yet! Use ITK implementation instead, i.e.,"
318  << " install ITK 3.14 or greater (including versions after 4.0)"
319  << " and reconfigure the build tree of " << PROJECT << ". Ensure that"
320  << " the ITK_DIR variable is set to the directory of the ITKConfig.cmake file"
321  << " and that the variable USE_ITK is set to ON. Then rebuild " << PROJECT
322  << " and optionally install it again.");
323  #endif
324 }
325 
326 
327 #endif // _BASIS_TESTDRIVER_HXX
PositionalArgs testcmd("testcmd", "The name of the test to run and optional arguments." " Displays a list of available tests if this argument is omitted" " and waits for the user to input the number of the test to run." " Exist with error if an invalid test was specified." " Note that if the -- option is not given before the test name," " labeled arguments following the test name will be considered" " to be options of the test driver if known by the test driver." " Otherwise, if the option is unknown to the test driver or the" " -- option has been given before the test name, the remaining" " arguments are passed on to the test.", false, "", "[--] [<test name> [<arg>...]]")
SwitchArg orientation_insensitive("", "orientation-insensitive", "Allow the test and baseline images to have different orientation." " When this option is given, the orientation of both images is made" " identical before they are compared. It is suitable if the test" " and baseline images are simply stored with different orientation," " but with proper orientation information in the file header.")
MultiStringArg diff("", "diff", "Compare the <test> file to the <baseline> file byte by byte." " Can by used to compare any files including text files." " For images, the --compare option should be used instead.", false, "<test> <baseline>", 2, false, &diff_visitor)
std::string getcwd()
Get absolute path of the (current) working directory.
Definition: os.cxx:47
int binary_diff(const char *testfile, const char *baseline)
Compare two files byte by byte.
Definition: testdriver.hxx:189
unsigned int tolerance_radius
Definition: testdriver.h:126
string PROJECT
Project name.
Definition: utilities.sh:79
#define assert(condition)
Assertion without custom message.
Definition: assert.h:44
SwitchArg full_output("", "full-output", "Causes the full output of the test to be passed to CDash.", false)
int text_diff_lines(const char *testfile, const char *baseline, unsigned int max_number_of_differences)
Compare two text files line by line.
Definition: testdriver.hxx:230
DoubleArg intensity_tolerance("", "intensity-tolerance", "The accepted maximum difference between image intensities" " to use for the following regression tests." " (default: 2.0)", false, 2.0, "<float>", true)
void testdriversetup(int *argc, char **argv[])
Parse command-line arguments and initialize test driver.
Definition: testdriver.hxx:42
#define BASIS_THROW(type, msg)
Throw exception with given message.
Definition: except.h:48
TCLAP::SpecificationException CmdLineException
Exception thrown when command-line specification is wrong.
Definition: except.h:73
TestMethod method
Definition: testdriver.h:128
SwitchArg clean_cwd_before_test("", "clean-cwd-before", "Request the removal of all files and directories from the current" " working directory before the execution of the test. This option is" " in particular useful if the test writes any results to the current" " working directory.", false)
UIntArg max_number_of_differences("", "max-number-of-differences", "When comparing images specified with the following --compare option(s)," " allow the given number of image elements to differ.", false, 0, "<n>", true)
string RELEASE
Project release.
Definition: utilities.sh:89
UIntArg max_number_of_threads("", "max-number-of-threads", "Use at most <n> threads. Set explicitly to n=1 to disable" " multi-threading. Note that the test itself still may use" " more threads, but the regression tests will not.", false, 0, "<n>")
unsigned int max_number_of_differences
Definition: testdriver.h:125
int RegressionTestImage(const char *testImageFilename, const char *baselineImageFilename, int reportErrors, double intensityTolerance, unsigned int numberOfPixelsTolerance, unsigned int radiusTolerance, bool orientationInsensitive)
void RegisterRequiredFactories()
MultiStringArg diff_lines("", "diff-lines", "Compare the <test> file to the <baseline> file line by line." " Can by used to compare text files. The current --max-number-of-differences" " setting determines the number of lines which may differ between the files." " For binary files, consider the --diff option instead.", false, "<test> <baseline>", 2, false, &diff_lines_visitor)
string test_file
Definition: testdriver.h:122
string baseline_file
Definition: testdriver.h:123
UIntArg tolerance_radius("", "tolerance-radius", "At most one image element in the neighborhood specified by the" " given radius has to fulfill the criteria of the following" " regression tests", false, 0, "<int>", true)
Structure holding arguments to regression test options and currently set tolerances to be used for th...
Definition: testdriver.h:121
StringArg redirect_output("", "redirect-output", "Redirects the test output to the specified file.", false, "", "<file>")
MultiSwitchArg verbose("v", "verbose", "Increase verbosity of output messages.", false)
MultiStringArg add_before_libpath("", "add-before-libpath", "Add a path to the library path environment. This option takes" " care of choosing the right environment variable for your system.", false, "<dir>")
ITK-based implementation of test driver.
int image_regression_test(const char *imagefile, const char *baseline, double intensity_tolerance, unsigned int max_number_of_differences, unsigned int tolerance_radius, bool orientation_insensitive, int report)
Compare output image to baseline image.
Definition: testdriver.hxx:299
MultiStringArg add_before_env("", "add-before-env", "Add an environment variable named <name> with the given value." " The seperator used is the default one on the system.", false, "<name> <value>", 2)
vector< string > get_baseline_filenames(string filename_template)
Definition: testdriver.hxx:272
SwitchArg clean_cwd_after_test("", "clean-cwd-after", "Request the removal of all files and directories from the current" " working directory after the successful execution of the test." " This option is in particular useful if the test writes any results" " to the current working directory.", false)
bool orientation_insensitive
Definition: testdriver.h:127
double intensity_tolerance
Definition: testdriver.h:124
MultiStringArg compare("", "compare", "Compare the <test> image to the <baseline> image using the" " current tolerances. If the test image should be compared to" " to more than one baseline image, specify the file name of" " the main baseline image and name the other baseline images" " similarly with only a numerical suffix appended to the" " basename of the image file path using a dot (.) as separator." " For example, name your baseline images baseline.nii," " baseline.1.nii, baseline.2.nii,..., and specify baseline.nii" " second argument value.", false, "<test> <baseline>", 2, false, &compare_visitor)
vector< RegressionTest > regression_tests
Container storing added regression tests.
Definition: testdriver.h:132