Spinning Topp Logo BlackTopp Studios
inc
mezztest.cpp
Go to the documentation of this file.
1 // © Copyright 2010 - 2016 BlackTopp Studios Inc.
2 /* This file is part of The Mezzanine Engine.
3 
4  The Mezzanine Engine is free software: you can redistribute it and/or modify
5  it under the terms of the GNU General Public License as published by
6  the Free Software Foundation, either version 3 of the License, or
7  (at your option) any later version.
8 
9  The Mezzanine Engine is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  GNU General Public License for more details.
13 
14  You should have received a copy of the GNU General Public License
15  along with The Mezzanine Engine. If not, see <http://www.gnu.org/licenses/>.
16 */
17 /* The original authors have included a copy of the license specified above in the
18  'Docs' folder. See 'gpl.txt'
19 */
20 /* We welcome the use of the Mezzanine engine to anyone, including companies who wish to
21  Build professional software and charge for their product.
22 
23  However there are some practical restrictions, so if your project involves
24  any of the following you should contact us and we will try to work something
25  out:
26  - DRM or Copy Protection of any kind(except Copyrights)
27  - Software Patents You Do Not Wish to Freely License
28  - Any Kind of Linking to Non-GPL licensed Works
29  - Are Currently In Violation of Another Copyright Holder's GPL License
30  - If You want to change our code and not add a few hundred MB of stuff to
31  your distribution
32 
33  These and other limitations could cause serious legal problems if you ignore
34  them, so it is best to simply contact us or the Free Software Foundation, if
35  you have any questions.
36 
37  Joseph Toppi - toppij@gmail.com
38  John Blackwood - makoenergy02@gmail.com
39 */
40 
41 #include "autodetect.h"
42 #include "datatypes.h"
43 #include "mezztest.h"
44 
45 #include <cstdlib> // For system
46 #include <fstream>
47 #include <vector>
48 #include <stdexcept>
49 
50 /// @file
51 /// @brief This file is the entry point for the unit test framework.
52 /// @details If you need to change the nature of the executable this is the
53 /// file to change. This is where the simple (but robust) sub process
54 /// mechanism is implemented. Very little of the rest of the code in the
55 /// unit test frame work makes calls to this file, and everything that does
56 /// does so through the UnitTestGroup class via polymorphism.
57 
58 /// @internal
59 /// @brief ArgC as it was passed into Main.
60 /// @details This cannot be set statically, it must wait for main(int, char**) to
61 /// be initialized
62 int ArgC;
64  { return ArgC; }
65 
66 /// @internal
67 /// @brief ArgC as it was passed into Main.
68 /// @details This cannot be set statically, it must wait for main(int, char**) to
69 /// be initialized
70 char** ArgV;
72  { return ArgV; }
73 
74 /// @internal
75 /// @brief This will store the name of the command that launched this executable at run time
78  { return CommandName; }
79 
80 /// @internal
81 /// @brief The current process depth as interpretted by Main
82 ProcessDepth Depth;
83 ProcessDepth GetCurrentProcessDepth()
84  { return Depth; }
85 
86 /// @internal
87 /// @brief A string intended for use by any subsubprocess test
90  { return SubSubProcessArgument; }
91 
92 
93 /// @brief Write the passed UnitTestGroup to an XML temp file
94 /// @param TestsToWrite Teh group of tests to write.
95 void WriteTempFile(const UnitTestGroup &TestsToWrite)
96 {
97  std::ofstream File(TempFile.c_str());
98  File << TestsToWrite.GetAsXML();
99  File.close();
100 }
101 
102 /// @internal
103 /// @brief This will open then parse the contents of the file specified by TempFile and interpret any test results located
104 /// @throw This can throw any exception that the C++ filestream classes can throw.
105 /// @return This "reads" the temp file and interprets it. It tries to extract the name of the test as the whole of a line minus
106 /// the last word. The last word is then converted into a @ref TestResult using @ref StringToTestResult . Any Whitespace between
107 /// between the end of the last word and the end of the test name is dropped. All lines are interpretted this way and returned
108 /// as a single @ref UnitTestGroup.
109 UnitTestGroup GetResultsFromTempFile()
110 {
111  UnitTestGroup Results;
112 
113  pugi::xml_document Doc;
114 
115  std::ifstream InputFile(TempFile.c_str());
116  pugi::xml_parse_result LoadResults = Doc.load(InputFile);
117 
118  if(LoadResults)
119  {
120  Results.AddTestsFromXML(Doc.first_child());
121  }else{
122  std::stringstream FailStream;
123  FailStream << "Failure loading tempfile from SubProcess: "
124  << LoadResults.description() << std::endl
125  << "At " << LoadResults.offset << " bytes into the file.";
126  throw runtime_error(FailStream.str());
127  }
128 
129  return Results;
130 }
131 
132 /// @internal
133 /// @brief Empty the file specified by TempFile
134 /// @warning This doesn't ask for permission and can't easily be cancelled or recovered
135 /// from. This will open, then erase the contents of the file.
136 /// @throw This can throw any exception that the C++ filestream classes can throw.
138 {
139  std::ofstream FileToClear;
140  FileToClear.open(TempFile.c_str(),std::ios_base::out|std::ios_base::trunc); // Clear the work file.
141  FileToClear.close();
142 }
143 
144 /// @brief Attempts to delete TempFile. Silently fails if not possible.
146  { std::remove(TempFile.c_str()); }
147 
148 
149 /// @internal
150 /// @brief This aggregates the results of all the other test groups.
151 class AllUnitTestGroups : public UnitTestGroup
152 {
153  private:
154  /// @internal
155  /// @brief So no one uses it
156  void operator=(AllUnitTestGroups&)
157  {}
158 
159  /// @internal
160  /// @brief Should all tests be run.
161  bool RunAll;
162 
163  public:
164 
165  /// @internal
166  /// @brief This indicates we should run automatic tests rather than than launching a subprocess for that
167  virtual void ShouldRunAutomaticTests()
168  {
169  RunAll = false;
170  UnitTestGroup::ShouldRunAutomaticTests();
171  }
172 
173  /// @internal
174  /// @brief This indicates we should run interactive tests rather than than launching a subprocess for that
176  {
177  RunAll = false;
178  UnitTestGroup::ShouldRunInteractiveTests();
179  }
180 
181  /// @internal
182  /// @brief Tells this whether or not it should be used to run any tests that child tests might want to spwan in a subprocess
184  {
185  RunAll = false;
186  UnitTestGroup::ShouldRunSubProcessTests();
187  }
188 
189  /// @internal
190  /// @brief Used to signal
191  virtual void ShouldRunAllTests()
192  { RunAll = true; }
193  /// @internal
194  /// @brief Used to signal that none or only a subset of tests should run
195  virtual void DontRunAllTests()
196  { RunAll = false; }
197 
198  /// @internal
199  /// @brief A collection of all the test groups
200  GlobalCoreTestGroup& TestGroups;
201 
202  /// @internal
203  /// @brief Constructor
204  /// @param GlobalTestGroups The collection of tests that could be run
205  AllUnitTestGroups(GlobalCoreTestGroup& GlobalTestGroups)
206  : RunAll(true),
207  TestGroups(GlobalTestGroups)
208  {}
209 
210  /// @internal
211  /// @brief When determining what tests to run the name are aggregated here
212  std::vector<Mezzanine::String> TestGroupsToRun; //List of tests to run
213 
214  private:
215  /// @internal
216  /// @brief This is used when the passed flags at the command prompt or when a master process is executing a single tests.
217  void ExecuteSubTest()
218  {
219  for(std::vector<Mezzanine::String>::iterator CurrentTestName=TestGroupsToRun.begin(); CurrentTestName!=TestGroupsToRun.end(); ++CurrentTestName ) // Actually run the tests
220  {
221  if(DoInteractiveTest)
222  { TestGroups[*CurrentTestName]->ShouldRunInteractiveTests(); }
223  if(DoAutomaticTest)
224  { TestGroups[*CurrentTestName]->ShouldRunAutomaticTests(); }
225  TestGroups[*CurrentTestName]->RunTests();
226  (*this) += *(TestGroups[*CurrentTestName]);
227  }
228  }
229 
230  /// @internal
231  /// @brief this is used when there are test to execute and we need to loop over them and run each in a child process.
232  void IterateSubtests()
233  {
234  setvbuf(stdout, NULL, _IONBF, 0);
235  for(std::vector<Mezzanine::String>::iterator CurrentTestName=TestGroupsToRun.begin(); CurrentTestName!=TestGroupsToRun.end(); ++CurrentTestName )
236  {
237  ClearTempFile();
238  String SubprocessInvocation(CommandName + " " + *CurrentTestName + " " + MemSpaceArg + " " + Mezzanine::String(DoAutomaticTest?"automatic ":"") + Mezzanine::String(DoInteractiveTest?"interactive ":""));
239  if(system(SubprocessInvocation.c_str())) // Run a single unit test as another process
240  {
241  printf("%s", (SubprocessInvocation+String(" - Failure")).c_str() );
242  this->AddTestResult(String("Process::" + *CurrentTestName), Testing::Failed);
243  }else {
244  printf("%s", (SubprocessInvocation+String(" - Success")).c_str() );
245  this->AddTestResult(String("Process::" + *CurrentTestName), Success);
246  }
247 
248  try
249  {
250  (*this) += GetResultsFromTempFile();
251  } catch (std::exception& e) {
252  TestError << e.what() << endl;
253  }
254 
255  }
256  DeleteTempFile();
257  }
258  public:
259 
260  /// @internal
261  /// @brief Determine which tests need to be run and run them
262  virtual void RunTests()
263  {
264  if (DoAutomaticTest==DoInteractiveTest && DoInteractiveTest==false) // enforce running automatic tests if no type of test is specified
265  { DoAutomaticTest=true; }
266  if(RunAll)
267  {
268  // if Runall is set it is presumed that no tests have been added to list of tests to run yet.
269  for(map<String,UnitTestGroup*>::iterator Iter=TestGroups.begin(); Iter!=TestGroups.end(); ++Iter)
270  { TestGroupsToRun.push_back(Iter->first); }
271  }
272 
273  if(MainProcess == GetCurrentProcessDepth())
274  {
275  IterateSubtests();
276  }else{
277  ExecuteSubTest();
278  }
279  } // \function
280 
281  /// @internal
282  /// @brief Display the results either to the console or to the temp file for the main process to pick up.
283  /// @param Output Where to present the output, this only works for the main process. Defaults to std::cout.
284  /// @param Error A stream to send all errors to, defailts to std::cerr
285  /// @param Summary Passed onto the UnitTests UnitTestGroup::DisplayResults if run from the main process.
286  /// @param FullOutput Passed onto the UnitTests UnitTestGroup::DisplayResults if run from the main process.
287  /// @param HeaderOutput Passed onto the UnitTests UnitTestGroup::DisplayResults if run from the main process.
288  virtual void DisplayResults(std::ostream& Output=std::cout,
289  std::ostream& Error=std::cerr,
290  bool Summary = true,
291  bool FullOutput = true,
292  bool HeaderOutput = true)
293  {
294  //sleep_for(500000); // A half second sleep to allow subprocesses to finish output and not clobber stdout
295  if(DoSubProcessTest) // we are running a test in a seperate process, so we need to control the output for communcation purposes
296  {
297  WriteTempFile(*this);
298  UnitTestGroup::DisplayResults(Output, Error, Summary, FullOutput, HeaderOutput);
299  //std::ofstream OutputFile(TempFile.c_str(),std::ios_base::out|std::ios_base::trunc);
300  //UnitTestGroup::DisplayResults(OutputFile,false,true,false);
301  //OutputFile.close();
302  }else{
303  UnitTestGroup::DisplayResults(Output, Error, Summary, FullOutput, HeaderOutput);
304  }
305  }
306 };
307 
308 /// @brief Handles one argument at a time for main
309 /// @details This should be the only place that configures global state in the test suite
310 /// everywhere else should access global state through the accessors in mezztest.h
311 /// @param Arg The argument
313 {
314 
315 
316 }
317 
318 /// @brief This is the entry point for the unit test executable.
319 /// @details This will contruct an AllUnitTestGroups with the listing of unit tests
320 /// available from autodetect.h. It will then interpret any command line arguments
321 /// and direct the created AllUnitTestGroups about which tests to run and how to run
322 /// them. In addition to sending the results to the standard output a copy of the
323 /// test results will be written to TestResults.txt, if not configure not to.
324 /// @n @n
325 /// If no arguments are passed this will add all the tests to the AllUnitTestGroups
326 /// and execute all tests that are not interactive. Print out a default report of them.
327 /// @return This will return EXIT_SUCCESS if the tests ran, even if some or all failed,
328 /// even if a child process segfaulted, but will return other statuses only if the main
329 /// process fails. If the main process cannot create child processes it will return EXIT_FAILURE.
330 /// @param argc Is interpretted as the amount of passed arguments
331 /// @param argv Is interpretted as the arguments passed in from the launching shell.
332 int main(int argc, char** argv)
333 {
334  ArgC = argc;
335  ArgV = argv;
336  Depth = MainProcess;
337 
338  GlobalCoreTestGroup TestGroups;
339  bool WriteFile = true;
340 
341  if( !system( NULL ) ) // If this is not being run from a shell somehow, then using system() to run this task in a new process is not really possible.
342  {
343  std::cerr << "system() call not supported, missing command processor." << std::endl;
344  return EXIT_FAILURE;
345  }
346 
347  // Display everything, or just a Summary or neither? Should this process do the work, or should we spawn a new process.
348  bool FullDisplay = true, SummaryDisplay = true;
349 
350  if (argc > 0) //Not really sure how this would happen, but I would rather test for it than have it fail
351  { CommandName=argv[0]; }
352  else
353  { return Usage("UnitTestGroups", TestGroups); }
354 
355  AllUnitTestGroups Runner(TestGroups);
356 
357  for (int c=1; c<argc; ++c) // Check Command line for keywords and get all the test names
358  {
359  String ThisArg(AllLower(argv[c]));
360  if(ThisArg=="help")
361  { return Usage(CommandName, TestGroups); }
362  else if(ThisArg==MemSpaceArg) // Check to see if we do the work now or later
363  {
364  Runner.ShouldRunSubProcessTests();
365  Depth = TestSubSubProcess;
366  }
367  else if(ThisArg=="testlist")
368  { return PrintList(TestGroups); }
369  else if(ThisArg=="interactive")
370  { Runner.ShouldRunInteractiveTests(); }
371  else if(ThisArg=="automatic")
372  { Runner.ShouldRunAutomaticTests(); }
373  else if(ThisArg=="all")
374  { Runner.ShouldRunAllTests(); }
375  else if(ThisArg=="summary")
376  { FullDisplay = false, SummaryDisplay = true; }
377  else if(ThisArg=="skipfile")
378  { WriteFile = false; }
379  else // Wasn't a command so it is either gibberish or a test group or debug test group
380  {
381  if(ThisArg.size() > SubTestPrefix.size())
382  {
383  if( ThisArg.substr(0,SubTestPrefix.size()) == SubTestPrefix)
384  {
385  Depth = TestSubSubProcess;
386  ThisArg = ThisArg.substr(SubTestPrefix.size(), ThisArg.size()-SubTestPrefix.size());
387  GlobalCoreTestGroup::iterator SearchResult = TestGroups.find(ThisArg);
388  if(TestGroups.end()==SearchResult)
389  {
390  std::cerr << ThisArg << " appears to be a request to debug a sub-process that does not exist." << std::endl;
391  Usage(CommandName, TestGroups);
392  return ExitInvalidArguments;
393  }
394  if(!SearchResult->second->HasSubprocessTest())
395  {
396  std::cerr << ThisArg << " appears to be a request to debug a sub-process that does not have a sub process." << std::endl;
397  Usage(CommandName, TestGroups);
398  return ExitInvalidArguments;
399  }
400  SearchResult->second->ShouldRunSubProcessTests();
401  }
402  }
403  GlobalCoreTestGroup::iterator Found = TestGroups.find(String(ThisArg.c_str()));
404  if(Found != TestGroups.end())
405  {
406  Runner.DontRunAllTests();
407  Runner.TestGroupsToRun.push_back(ThisArg.c_str());
408  }else{
409  if(GetCurrentProcessDepth() == TestSubSubProcess)
410  {
411  SubSubProcessArgument = String(ThisArg);
412  }else{
413  std::cerr << ThisArg << " is not a valid testgroup or parameter." << std::endl;
414  Usage(CommandName, TestGroups);
415  return ExitInvalidArguments;
416  }
417  }
418  }
419  }
420 
421  Runner.RunTests();
422 
423  if(WriteFile)
424  {
425  String FileName("TestResults.txt");
426  std::ofstream OutFile(FileName.c_str());
427  Runner.DisplayResults(OutFile, OutFile, SummaryDisplay, FullDisplay);
428  OutFile.close();
429  }
430  if(MainProcess == GetCurrentProcessDepth())
431  { Runner.DisplayResults(cout, cerr, SummaryDisplay, FullDisplay); }
432 
433  for(AllUnitTestGroups::iterator Iter = Runner.begin(); Iter!=Runner.end(); Iter++)
434  {
435  if(Iter->Results>Skipped)
436  { return ExitFailure; }
437  }
438  return ExitSuccess;
439  }
Mezzanine::String GetExecutableName()
Get the command/executable name used to invoke this at the command prompt.
Definition: mezztest.cpp:77
char ** ArgV
ArgC as it was passed into Main.
Definition: mezztest.cpp:70
void ClearTempFile()
Empty the file specified by TempFile.
Definition: mezztest.cpp:137
int ArgC
ArgC as it was passed into Main.
Definition: mezztest.cpp:62
AllUnitTestGroups(GlobalCoreTestGroup &GlobalTestGroups)
Constructor.
Definition: mezztest.cpp:205
Mezzanine::String CommandName
This will store the name of the command that launched this executable at run time.
Definition: mezztest.cpp:76
At least one test return worse then skipped.
virtual void DisplayResults(std::ostream &Output=std::cout, std::ostream &Error=std::cerr, bool Summary=true, bool FullOutput=true, bool HeaderOutput=true)
Display the results either to the console or to the temp file for the main process to pick up...
Definition: mezztest.cpp:288
UnitTestGroup GetResultsFromTempFile()
This will open then parse the contents of the file specified by TempFile and interpret any test resul...
Definition: mezztest.cpp:109
void WriteTempFile(const UnitTestGroup &TestsToWrite)
Write the passed UnitTestGroup to an XML temp file.
Definition: mezztest.cpp:95
ProcessDepth GetCurrentProcessDepth()
Indicates what this test might attempt to do, based on what it thinks the process depth is...
Definition: mezztest.cpp:83
All the definitions for datatypes as well as some basic conversion functions are defined here...
At least some invalid args were passed on the command line.
Test was simply not ran at the behest of the user.
GlobalCoreTestGroup & TestGroups
A collection of all the test groups.
Definition: mezztest.cpp:200
Indicates an abnormal termination of a Workunit or other failure, Likely the whole application will n...
int Usage(Mezzanine::String ThisName, CoreTestGroup &TestGroups)
Print a message for the user onf the standard output that briefly describes hwo to use this...
virtual void ShouldRunSubProcessTests()
Tells this whether or not it should be used to run any tests that child tests might want to spwan in ...
Definition: mezztest.cpp:183
virtual void ShouldRunInteractiveTests()
This indicates we should run interactive tests rather than than launching a subprocess for that...
Definition: mezztest.cpp:175
virtual void ShouldRunAutomaticTests()
This indicates we should run automatic tests rather than than launching a subprocess for that...
Definition: mezztest.cpp:167
Mezzanine::String GetSubSubProcessArgument()
If a test needs to pass a string to a subsubprocess it will get stored here.
Definition: mezztest.cpp:89
int PrintList(CoreTestGroup &TestGroups)
Print all the groups that exist in a given CoreTestGroup.
Definition: testdata.cpp:93
ProcessDepth Depth
The current process depth as interpretted by Main.
Definition: mezztest.cpp:82
Mezzanine::String SubSubProcessArgument
A string intended for use by any subsubprocess test.
Definition: mezztest.cpp:88
Normal exit all tests skipped or better.
int main(int argc, char **argv)
This is the entry point for the unit test executable.
Definition: mezztest.cpp:332
virtual void RunTests()
Determine which tests need to be run and run them.
Definition: mezztest.cpp:262
void DeleteTempFile()
Attempts to delete TempFile. Silently fails if not possible.
Definition: mezztest.cpp:145
int GetMainArgumentCount()
Get the argument count as it was passed into Main.
Definition: mezztest.cpp:63
char * AllLower(char *StringToConvert)
Makes a c style stron all lowercase with respect to the current locale.
virtual void ShouldRunAllTests()
Used to signal.
Definition: mezztest.cpp:191
virtual void DontRunAllTests()
Used to signal that none or only a subset of tests should run.
Definition: mezztest.cpp:195
Test was ran and appeared to work.
char ** GetMainArgumentVector()
Get the argument vector as it was passed into Main.
Definition: mezztest.cpp:71
void HandleSingleArgument(Mezzanine::String Arg)
Handles one argument at a time for main.
Definition: mezztest.cpp:312
std::vector< Mezzanine::String > TestGroupsToRun
When determining what tests to run the name are aggregated here.
Definition: mezztest.cpp:212
std::string String
A datatype used to a series of characters.
Definition: datatypes.h:159
This aggregates the results of all the other test groups.
Definition: mezztest.cpp:151