Spinning Topp Logo BlackTopp Studios
inc
testdata.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 #ifndef _testdata_cpp
41 #define _testdata_cpp
42 
43 /// @file
44 /// @brief The definition of the string manipulation functions the unit tests use
45 
46 #include "mezztest.h"
47 
48 #include <vector>
49 #include <stdexcept>
50 #include <sstream>
51 #include <cassert>
52 
53 using namespace Mezzanine;
54 using namespace std;
55 
56 namespace Mezzanine
57 {
58  namespace Testing
59  {
60 
61  TestData::TestData(const String& Name,
62  TestResult Result,
63  const String& FuncName,
64  const String& File,
65  Mezzanine::Whole Line)
66  : TestName(Name), Results(Result), FunctionName(FuncName), FileName(File), LineNumber(Line)
67  {}
68 
69  TestData::TestData(pugi::xml_node Node)
70  : TestName( Node.attribute("TestName").as_string() ),
71  Results( StringToTestResult(Node.attribute ("Results").as_string())),
72  FunctionName( Node.attribute("FunctionName").as_string() ),
73  FileName( Node.attribute("FileName").as_string() ),
74  LineNumber( Node.attribute("LineNumber").as_uint() )
75  {}
76 
77  bool TestData::operator<(const TestData& Rhs) const
78  { return this->TestName < Rhs.TestName; }
79 
81  {
82  std::stringstream Snippet;
83  Snippet << "<TestData "
84  << "TestName=\"" << TestName << "\" "
85  << "Results=\"" << TestResultToString(Results) << "\" "
86  << "FunctionName=\"" << FunctionName << "\" "
87  << "FileName=\"" << FileName << "\" "
88  << "LineNumber=\"" << LineNumber << "\" "
89  << "/>";
90  return Snippet.str();
91  }
92 
93  int PrintList(CoreTestGroup& TestGroups)
94  {
95  for(CoreTestGroup::iterator Iter=TestGroups.begin(); Iter!=TestGroups.end(); ++Iter)
96  { cout << Iter->first << std::endl; }
97  return ExitSuccess;
98  }
99 
101  {
102  TestData Results;
103  size_t LastSpace=Line.rfind(' ');
104  Results.Results = StringToTestResult(Line.substr(LastSpace+1));
105  Results.TestName=rtrim(Line.substr(0,LastSpace));
106  return Results;
107  }
108 
110  {
111  CoutStreamBuf = cout.rdbuf();
112  cout.rdbuf(TestOutput.rdbuf());
113  CerrStreamBuf = cerr.rdbuf();
114  cerr.rdbuf(TestError.rdbuf());
115  }
116 
118  {
119  assert(CoutStreamBuf);
120  assert(CoutStreamBuf);
121  cout.rdbuf(CoutStreamBuf);
122  cout.rdbuf(CerrStreamBuf);
123  }
124 
126  : CoutStreamBuf(0),
127  CerrStreamBuf(0),
128  LongestNameLength(0),
129  DoSubProcessTest(false),
130  DoAutomaticTest(false),
131  DoInteractiveTest(false),
132  Completed(0)
133  {}
134 
136  : TestOutput(OtherGroup.TestOutput.str()),
137  TestError(OtherGroup.TestError.str()),
138  CoutStreamBuf(0),
139  CerrStreamBuf(0),
140  DoSubProcessTest(OtherGroup.DoSubProcessTest),
141  DoAutomaticTest(OtherGroup.DoAutomaticTest),
142  DoInteractiveTest(OtherGroup.DoInteractiveTest),
143  Completed(0)
144  { insert(OtherGroup.begin(),OtherGroup.end()); }
145 
146 
147 
149  {
150  if(DoSubProcessTest)
152  else
153  {
154  OutputCaptureManager Guard(this);
157  }
158  }
159 
161  {
162  TestOutput << std::endl << "<AutomaticTestOutput><![CDATA[" << std::endl;
163  TestError << std::endl << "<AutomaticTestError><![CDATA[" << std::endl;
164  try
165  {
166  if(DoAutomaticTest)
167  { RunAutomaticTests(); }
168  else if(HasAutomaticTests())
169  { AddTestResult( TestData("AutomaticTests",Testing::Skipped, "RunTests") );}
170  }catch(exception& e){
171  TestError << "Caught an unhandled exception while doing RunAutomaticTests()." << endl
172  << "Message: " << e.what() << endl;
173  AddTestResult( TestData("UnhandledException", Testing::Failed) );
174  }
175  TestOutput << std::endl << "]]></AutomaticTestOutput>" << std::endl;
176  TestError << std::endl << "]]></AutomaticTestError>" << std::endl;
177  }
178 
180  {
181  TestOutput << std::endl << "<InteractiveTestOutput><![CDATA[" << std::endl;
182  TestError << std::endl << "<InteractiveTestError><![CDATA[" << std::endl;
183  try
184  {
186  { RunInteractiveTests(); }
187  else if(HasInteractiveTests())
188  { AddTestResult( TestData("InteractiveTests",Testing::Skipped, "RunTests") );}
189  }catch(exception& e){
190  TestError << "Caught an unhandled exception while doing RunInteractiveTests()." << endl
191  << "Message: " << e.what() << endl;
192  AddTestResult( TestData("UnhandledException", Testing::Failed) );
193  }
194  TestOutput << std::endl << "]]></InteractiveTestOutput>" << std::endl;
195  TestError << std::endl << "]]></InteractiveTestError>" << std::endl;
196  }
197 
199  { return GetCommandResults(GetExecutableName() + String(" ") + SubTestPrefix + Name() + String(" ") + Argument); }
200 
202  {}
204  { return false; }
206  { DoAutomaticTest = true; }
207 
209  {}
211  { return false; }
213  { DoInteractiveTest = true; }
214 
216  {}
218  { return false; }
220  { DoSubProcessTest = true; }
221 
223  { return ""; }
224 
226  {
227  bool Added=false;
228 
229  if(CurrentTest.TestName.npos != CurrentTest.TestName.find(" "))
230  { throw std::invalid_argument("Invalid Test Name, contains one or more space character ( ), TestName: \"" + CurrentTest.TestName + "\""); }
231  if(CurrentTest.TestName.npos != CurrentTest.TestName.find("\""))
232  { throw std::invalid_argument("Invalid Test Name, contains one or more double quote (\") character(s), TestName: \"" + CurrentTest.TestName + "\""); }
233 
234  if(this->Name().length())
235  {
236  if(this->Name().npos != this->Name().find(" "))
237  { throw std::invalid_argument("Invalid UnitTestGroup Name, contains one or more space character ( ), name: \"" + this->Name() + "\""); }
238  if(this->Name().npos != this->Name().find("\""))
239  { throw std::invalid_argument("Invalid UnitTestGroup Name, contains one or more double quote (\"), name: \"" + this->Name() + "\""); }
240  CurrentTest.TestName = this->Name() + "::" + CurrentTest.TestName;
241  }
242 
243  TestDataStorage::iterator PreExisting = this->find(CurrentTest.TestName);
244  if(this->end()!=PreExisting)
245  {
246  switch(Behavior)
247  {
249  if (PreExisting->Results <= CurrentTest.Results)
250  {
251  this->erase(PreExisting);
252  this->insert(CurrentTest);
253  Added=true;
254  }
255  break;
256  case OverWrite:
257  this->erase(PreExisting);
258  this->insert(CurrentTest);
259  Added=true;
260  break;
262  if (PreExisting->Results >= CurrentTest.Results)
263  {
264  this->erase(PreExisting);
265  this->insert(CurrentTest);
266  Added=true;
267  }
268  break;
269  case DoNotOverWrite:
270  break;
271  }
272  }else{
273  this->insert(CurrentTest);
274  Added=true;
275  }
276 
277  if (Added)
278  {
279  if(CurrentTest.TestName.length()>LongestNameLength)
280  { LongestNameLength=CurrentTest.TestName.length(); }
281  }
282  }
283 
285  {
286  this->TestOutput << "Noting result of " << this->Name() + "::" + Fresh << " as " << TestResultToString(Meat) << std::endl;
287  AddTestResult(TestData(Fresh,Meat),Behavior);
288  }
289 
291  {
294 
295  insert(rhs.begin(),rhs.end());
296  this->TestOutput << rhs.TestOutput.str();
297  this->TestError << rhs.TestError.str();
298  return *this;
299  }
300 
301  void UnitTestGroup::AddTestsFromXML(pugi::xml_node Node)
302  {
303  if(!Node) //Basic Sanity Check
304  {
305  throw std::invalid_argument(
306  String("UnitTestGroup::AddTestsFromXML can only handle XML but was passed an empty file. Expected results from ")
307  + Node.name()
308  );
309  }
310 
311  if(String("UnitTestGroup")==Node.name())
312  {
313  for(pugi::xml_node::iterator Iter = Node.begin(); Iter!=Node.end(); Iter++)
314  {
315  String CurrentName(Iter->name());
316  if(String("TestData")==CurrentName)
317  { this->AddTestResult(TestData(*Iter)); }
318  else if(String("UnitTestOutput")==CurrentName)
319  {
320  TestOutput << std::endl;
321  Iter->print(TestOutput);
322  TestOutput << std::endl;
323  }
324  else if(String("UnitTestError")==CurrentName)
325  {
326  String Text(Iter->text().as_string());
327  if(Text.size()>0)
328  { TestError << std::endl << Text << std::endl; }
329  }
330  else
331  {
332  throw std::invalid_argument(
333  String("UnitTestGroup::AddTestsFromXML Invalid subelement passed from ")
334  + Node.name()
335  );
336  }
337  }
338  }
339  else
340  {
341  throw std::invalid_argument(
342  String("UnitTestGroup::AddTestsFromXML can only handle XML with \"UnitTestGroup\" as root element. Expected results from ")
343  + Node.name()
344  );
345  }
346  }
347 
349  {
350  String Results("\n<UnitTestGroup>");
351  for (iterator Iter=this->begin(); Iter!=this->end(); Iter++)
352  { Results += "\n " + Iter->GetAsXML(); }
353  Results += "\n<UnitTestOutput>";
354  Results += this->TestOutput.str();
355  Results += "\n</UnitTestOutput>";
356  Results += "\n<UnitTestError>";
357  Results += this->TestError.str();
358  Results += "\n</UnitTestError>";
359  Results += "\n</UnitTestGroup>";
360  return Results;
361  }
362 
363  void UnitTestGroup::DisplayResults(std::ostream& Output, std::ostream& Error, bool Summary, bool FullOutput, bool HeaderOutput)
364  {
365  std::vector<unsigned int> TestCounts; // This will store the counts of the Sucesses, failures, etc...
366  TestCounts.insert(TestCounts.end(),1+(unsigned int)NotApplicable, 0); //Fill with the exact amount of 0s
367 
368  if(FullOutput && HeaderOutput) // No point in displaying the header without the other content.
369  {
370  Mezzanine::String TestName("Test Name");
371  Output << std::endl << " " << TestName << MakePadding(TestName, LongestNameLength) << "Result" << std::endl;
372  }
373 
374  if(FullOutput)
375  { Output << TestOutput.str(); }
376 
377  for (TestDataStorage::iterator Iter=this->begin(); Iter!=this->end(); Iter++)
378  {
379  if(FullOutput)
380  {
381  Output << Iter->TestName << MakePadding(Iter->TestName, LongestNameLength+1) << TestResultToString(Iter->Results);
382  if(Iter->Results) // Not Testing::Success
383  {
384  Output << "\t";
385  if(Iter->FileName.length())
386  { Output << " File: " << Iter->FileName; }
387  if(Iter->FunctionName.length())
388  { Output << " Function: " << Iter->FunctionName; }
389  if(Iter->LineNumber)
390  { Output << " Line: " << Iter->LineNumber; }
391  if(Iter->FileName.length()==0 && Iter->FunctionName.length() == 0 && Iter->LineNumber==0)
392  { Output << " No Metadata available able issue, use TEST to capture"; }
393  }
394  Output << endl;
395  Output.flush();
396  }
397 
398  if (Iter->Results && Iter->FileName.length() && Iter->FunctionName.length() && Iter->LineNumber)
399  {
400  Error << Iter->FileName << ":" << Iter->LineNumber
401  << " Test " << TestResultToString(Iter->Results)
402  << " in function " << Iter->FunctionName << endl;
403  Error.flush();
404  }
405  TestCounts.at((unsigned int)Iter->Results)++; // Count this test result
406  }
407 
408  if(Summary)
409  {
410  Output << std::endl << " Results Summary:" << std::endl;
411  for(unsigned int c=0; c<TestCounts.size();++c)
412  {
414  Output << " " << ResultName << MakePadding(ResultName,16) << TestCounts.at(c) << std::endl;
415  }
416  Output << std::endl;
417  Output.flush();
418  }
419 
420  if(FullOutput && TestError.str().size()>5 ) // Sometimes the copying put "0\r\n" in TestError
421  { Error << "Errors: " << TestError.str(); }
422  }
423 
424  void UnitTestGroup::Test(bool TestCondition, const String& TestName, TestResult IfFalse, TestResult IfTrue, const String& FuncName, const String& File, Whole Line )
425  {
426  try
427  {
428  if(TestCondition)
429  {
430  AddTestResult( TestData(TestName, IfTrue, FuncName, File, Line) );
431  }else{
432  AddTestResult( TestData(TestName, IfFalse, FuncName, File, Line) );
433  }
434  }catch(exception& e){
435  TestError << "Caught an unhandled exception while adding results for " << TestName << endl
436  << "Message: " << e.what() << endl;
437  AddTestResult( TestData("UnhandledException", Testing::Failed, FuncName, File, Line) );
438  }
439  }
440 
441 
442 
443  }// Testing
444 }// Mezzanine
445 
446 #endif // \ _testdata_cpp
String LaunchSubProcessTest(const String &Argument=String(""))
Tests should use this to launch things that need sheltering from segfaults and similar faults...
Definition: testdata.cpp:198
Mezzanine::String GetExecutableName()
Get the command/executable name used to invoke this at the command prompt.
Definition: mezztest.cpp:77
void LaunchAutomaticTest()
This launches all the automated tests on the derived class if the flag is set to run them otherwise i...
Definition: testdata.cpp:160
String MakePadding(String Leader, unsigned int Column)
Creates some blank spaces, useful for controlling the vertical location of console text...
virtual void Test(bool TestCondition, const String &TestName, TestResult IfFalse=Testing::Failed, TestResult IfTrue=Testing::Success, const String &FuncName="", const String &File="", Mezzanine::Whole Line=0)
Interpret Boolean value as a test result. Also Prepends the name of the current test, as returned by Name() + "::", to ease test scoping.
Definition: testdata.cpp:424
std::streambuf * CoutStreamBuf
Used to store the buffer connected to the stdout while it is being redirected.
Definition: testdata.h:136
Used to aplly RAII to Stdout and STDERR buffers/streams.
Definition: testdata.h:287
Mezzanine::String TestResultToString(TestResult Convertable)
This converts A test result enum value into a String matching the identifier name.
virtual bool HasAutomaticTests() const
Used only to report skipped tests.
Definition: testdata.cpp:203
std::map< Mezzanine::String, UnitTestGroup * > CoreTestGroup
A group of testnames and the Actual test that implement those test(s).
Definition: testdata.h:108
virtual void ShouldRunAutomaticTests()
Sets a flag that indicatesz that is the process that should run this subprocess.
Definition: testdata.cpp:205
This is the default behavior, because it is presumed failures should be visible so they can be fixed...
Just Overwrite, useful if the test was defaulted to a low value.
STL namespace.
void CaptureOutputBuffers()
This will direct any output that would have gone to an external process via cout to TestOutput Instea...
Definition: testdata.cpp:109
String rtrim(const String &t)
Take the whitespace off the end of a String.
This is not even a kind of failure, This is used to when referencing a test, so if this winds up comi...
Test was simply not ran at the behest of the user.
void LaunchInteractiveTest()
This launches all the interactice tests on the derived class if the flag is set to run them otherwise...
Definition: testdata.cpp:179
unsigned int LongestNameLength
Some basic variable for tracking simple statistics.
Definition: testdata.h:146
Mezzanine::String FileName
The File The test happened in.
Definition: testdata.h:73
virtual void RunSubprocessTest(const Mezzanine::String &Arg)
Does nothing by default, tests which need to run code in a subprocess should override this...
Definition: testdata.cpp:215
Mezzanine::Whole LineNumber
What line in the file this test occurred when the test was compiled.
Definition: testdata.h:75
bool DoSubProcessTest
Set to false if subprocess tests should not be executed. True if they should.
Definition: testdata.h:149
virtual void RunTests()
This will call RunAutomaticTests based on the values passed.
Definition: testdata.cpp:148
bool DoInteractiveTest
Sets the flag to run interactive tests.
Definition: testdata.h:153
std::streambuf * CerrStreamBuf
Used to store the buffer connected to the stderr while it is being redirected.
Definition: testdata.h:138
bool operator<(const TestData &Rhs) const
Used to sort TestData in std::std and other sorted containers, by TestName.
Definition: testdata.cpp:77
A single group of tests, suitable for being all the tests of a small subsystem or single class...
Definition: testdata.h:125
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
OverWriteResults
This is used to control the behavior of the function UnitTestGroup::AddTestResult() ...
The information about a test and how to easily find it in the filesystem.
Definition: testdata.h:64
TestResult StringToTestResult(Mezzanine::String Text)
Roughly convert a String to a TestResult.
TestResult Results
How did the test turn out.
Definition: testdata.h:69
virtual void ShouldRunSubProcessTests()
Sets a flag that indicatesz that is the process that should run this subprocess.
Definition: testdata.cpp:219
bool DoAutomaticTest
Set the flag to run automatic tests.
Definition: testdata.h:151
TestResult
Return values from tests.
Normal exit all tests skipped or better.
String GetCommandResults(String Command)
Execute a command in a process, piping its standard output to a file.
String GetAsXML() const
Get the Whole UnitTestGroup as a valid XML document.
Definition: testdata.cpp:348
This is a supplementary running a test, if a results already exists, skip writing anything...
const UnitTestGroup & operator+=(const UnitTestGroup &rhs)
Add all the items in another UnitTestGroup to this one.
Definition: testdata.cpp:290
void RestoreOutputBuffers()
This will direct any error messages that would have gone to an external process via cerr to TestOutpu...
Definition: testdata.cpp:117
virtual void ShouldRunInteractiveTests()
Sets a flag that indicatesz that is the process that should run this subprocess.
Definition: testdata.cpp:212
The bulk of the engine components go in this namspace.
Definition: actor.cpp:56
unsigned long Whole
Whole is an unsigned integer, it will be at least 32bits in size.
Definition: datatypes.h:151
virtual bool HasInteractiveTests() const
Used only to report skipped tests.
Definition: testdata.cpp:210
TestData StringToTestData(Mezzanine::String Line)
Trim the whitespace from a line of text and try to interpret the remains as TestResults and a testnam...
Definition: testdata.cpp:100
UnitTestGroup()
Default constructor.
Definition: testdata.cpp:125
virtual Mezzanine::String Name()
Get Name of this UnitTestGroup.
Definition: testdata.cpp:222
TestData(const String &Name="", TestResult Result=Testing::Success, const String &FuncName="", const String &File="", Mezzanine::Whole Line=0)
Create a TestData.
Definition: testdata.cpp:61
virtual void RunAutomaticTests()
This should be overloaded to run all tests that do require not user interaction.
Definition: testdata.cpp:201
virtual void DisplayResults(std::ostream &Output=std::cout, std::ostream &Error=std::cerr, bool Summary=true, bool FullOutput=true, bool HeaderOutput=true)
Print the results or save them to a file.
Definition: testdata.cpp:363
std::stringstream TestError
A destination for errors.
Definition: testdata.h:133
Mezzanine::String FunctionName
The function the test was called from.
Definition: testdata.h:71
std::stringstream TestOutput
A destination for all normal ouput in the tests.
Definition: testdata.h:131
String GetAsXML() const
Return a snippet of xml describing this TestData.
Definition: testdata.cpp:80
std::string String
A datatype used to a series of characters.
Definition: datatypes.h:159
void AddTestsFromXML(pugi::xml_node Node)
Create and add all the tests in a given piece of parsed xml.
Definition: testdata.cpp:301
void AddTestResult(TestData CurrentTest, OverWriteResults Behavior=OverWriteIfLessSuccessful)
Its expected that tests will be inserted using this.
Definition: testdata.cpp:225
Mezzanine::String TestName
The name of a given test.
Definition: testdata.h:67
virtual bool HasSubprocessTest() const
If this returns false then the test suite treats it like any other test, if true then it enables some...
Definition: testdata.cpp:217
Overwrite only if the result is better than the old result.
virtual void RunInteractiveTests()
This should be overloaded to run all tests require user interaction.
Definition: testdata.cpp:208