Lumiera  0.pre.03
»edit your freedom«
suite.cpp
Go to the documentation of this file.
1 /*
2  Suite - helper class for running collections of tests
3 
4  Copyright (C)
5  2008, Hermann Vosseler <Ichthyostega@web.de>
6 
7   **Lumiera** is free software; you can redistribute it and/or modify it
8   under the terms of the GNU General Public License as published by the
9   Free Software Foundation; either version 2 of the License, or (at your
10   option) any later version. See the file COPYING for further details.
11 
12 * *****************************************************************/
13 
14 
21 #include "lib/error.hpp"
22 #include "lib/symbol.hpp"
23 #include "lib/format-cout.hpp"
24 #include "lib/test/suite.hpp"
25 #include "lib/test/run.hpp"
26 #include "lib/cmdline.hpp"
27 #include "lib/random.hpp"
28 #include "lib/util.hpp"
29 
30 #include <boost/algorithm/string.hpp>
31 #include <optional>
32 #include <sstream>
33 #include <string>
34 #include <memory>
35 #include <vector>
36 #include <map>
37 
38 
39 namespace test {
40 
41  using std::map;
42  using std::vector;
43  using std::optional;
44  using std::shared_ptr;
45  using std::string_literals::operator ""s;
46  using boost::algorithm::trim;
47 
48  using util::isnil;
49  using util::contains;
50  using util::toString;
51  using util::typeStr;
52  using lib::SeedNucleus;
53  using lib::Random;
54 
55  typedef map<string, Launcher*> TestMap;
56  typedef shared_ptr<TestMap> PTestMap;
57  typedef map<string,PTestMap> GroupMap;
58 
59 
60 
61  namespace {
70  class Registry
71  {
72  GroupMap groups_;
73 
74  public:
75  Registry() { };
76 
77  PTestMap&
78  getGroup (string grpID)
79  {
80  return groups_[grpID];
81  };
82 
83  void
84  add2group (Launcher* test, string testID, string groupID)
85  {
86  REQUIRE( test );
87  REQUIRE( !isnil(testID) );
88  REQUIRE( !isnil(groupID) );
89 
90  PTestMap& group = getGroup(groupID);
91  if (!group)
92  group.reset( new TestMap );
93  (*group)[testID] = test;
94  }
95  };
96 
98  : public SeedNucleus
99  {
100  public:
102  opt_uint64 fixedSeed;
103 
104  uint64_t
105  getSeed() override
106  {
107  auto seed = fixedSeed? *fixedSeed : lib::entropyGen.u64();
108  auto kind = fixedSeed? "!fix" : "rand";
109  NOTICE (test, " ++>>> SEED(%s) <<<: %s", kind, toString(seed).c_str());
110  return seed;
111  }
112  };
113 
114  /* ===== global implementation state ===== */
115  SuiteSeedNucleus suiteSeed;
116  Registry testcases;
117  }
118 
119 
120 
121 
130  void
131  Suite::enrol (Launcher* test, string testID, string groups)
132  {
133  REQUIRE( test );
134  REQUIRE( !isnil(testID) );
135 
136  std::istringstream ss(groups);
137  string group;
138  while (ss >> group )
139  testcases.add2group(test, testID, group);
140 
141  // Magic: always add any testcase to groupID="ALL"
142  testcases.add2group(test,testID, ALLGROUP);
143  }
144 
146  const string Suite::ALLGROUP = "ALL";
147 
149  const int Suite::EXCEPTION_THROWN = 5;
150  const int Suite::TEST_OK = 0;
151 
152 
153 
158  Suite::Suite (string groupID, opt_uint64 optSeed)
159  : groupID_(groupID)
160  , exitCode_(0)
161  {
162  REQUIRE( !isnil(groupID) );
163  TRACE(test, "Test-Suite( groupID=%s )\n", groupID.c_str () );
164 
165  // Seed random number generator
166  std::srand (std::time (nullptr));
167 
168  suiteSeed.fixedSeed = optSeed;
169 
170  if (!testcases.getGroup(groupID))
171  throw lumiera::error::Invalid ("empty testsuite");
172  }
173 
174 
175  int
176  Suite::getExitCode () const
177  {
178  return exitCode_;
179  }
180 
181 
182 
183 #define IS_VALID(test,testID) \
184  ASSERT ((test), "NULL testcase launcher for test '%s' found in testsuite '%s'", groupID_.c_str(),testID.c_str());
185 
186 
187  namespace { // internal helper for launching with error logging
188 
189  int
190  invokeTestCase (Test& theTest, Arg cmdline)
191  {
192  try {
193  INFO (test, "++------------------- invoking TEST: %s", cStr(typeStr (theTest)));
194  theTest.run (cmdline);
195  return Suite::TEST_OK;
196  }
197  catch (lumiera::Error& failure)
198  {
199  lumiera_err errorID = lumiera_error(); // reset error flag
200  cerr << "*** Test Failure " << theTest << endl;
201  cerr << "*** : " << failure.what() << endl;
202  ERROR (test, "Error state %s", errorID);
203  WARN (progress, "Caught exception %s", failure.what());
205  } }
206  }
207 
208 
209 
210  void
212  {
213  lib::defaultGen.reseed (suiteSeed);
214  }
215 
216 
217  Random
219  {
221  }
222 
223 
224  uint
225  Test::firstVal (Arg arg, uint someNumber)
226  {
227  if (not isnil(arg))
228  someNumber = boost::lexical_cast<uint> (arg[1]); // may throw
229  return someNumber;
230  }
231 
232  string
233  Test::firstTok (Arg arg)
234  {
235  return isnil(arg)? util::BOTTOM_INDICATOR
236  : arg[1];
237  }
238 
239 
240 
250  bool
251  Suite::run (Arg cmdline)
252  {
253  PTestMap tests = testcases.getGroup(groupID_);
254  if (!tests)
255  throw lumiera::error::Invalid ("No tests found for test group \""+groupID_+"\"");
256 
257  if (0 < cmdline.size())
258  {
259  string& testID (cmdline[0]);
260  trim(testID);
261  if ( contains (*tests, testID))
262  {
263  // first cmdline argument denotes a valid testcase registered in
264  // this group: invoke just this test with the remaining cmdline
265  Launcher* test = (*tests)[testID];
266  IS_VALID (test,testID);
267 
268  // Special contract: in case the cmdline holds no actual arguments
269  // beyond the test name, then it's cleared entirely.
270  if (1 == cmdline.size()) cmdline.clear(); // TODO this invalidates also testID -- really need to redesign the API ////TICKET #289
271 
272  exitCode_ |= invokeTestCase (*test->makeInstance(), cmdline); // TODO confusing statement, improve definition of test collection datatype Ticket #289
273  return true;
274  }
275  else
276  throw lumiera::error::Invalid ("unknown test : "+testID);
277  }
278 
279  // no test-ID was specified.
280  // Instantiate all tests cases and execute them.
281  for ( TestMap::iterator i=tests->begin(); i!=tests->end(); ++i )
282  {
283  cout << "\n ----------"<< i->first<< "----------\n";
284  Launcher* test = (i->second);
285  IS_VALID (test, i->first);
286  exitCode_ |= invokeTestCase (*test->makeInstance(), cmdline); // actually no cmdline arguments
287  }
288  return true;
289  }
290 
291 
298  void
300  {
301  lib::Cmdline noCmdline("");
302  PTestMap tests = testcases.getGroup(groupID_);
303  ASSERT (tests);
304 
305  cout << "TESTING \"Component Test Suite: " << groupID_ << "\" ./test-components\n\n";
306 
307  for ( TestMap::iterator i=tests->begin(); i!=tests->end(); ++i )
308  {
309  string key (i->first);
310  cout << "\n\n";
311  cout << "TEST \""<<key<<"\" "<<key<<" <<END\n";
312  Launcher* test = (i->second);
313  IS_VALID (test, i->first);
314  try
315  {
316  test->makeInstance()->run(noCmdline); // run it to insert test generated output
317  }
318  catch (...)
319  {
320  cout << "PLANNED ============= " << lumiera_error() << "\n";
321  }
322  cout << "END\n";
323  }
324  }
325 
326 
327 
328 } // namespace test
Automatically use custom string conversion in C++ stream output.
CStr cStr(std::string const &rendered)
convenience shortcut: forced conversion to c-String via string.
Definition: symbol.hpp:59
lib::Random makeRandGen()
build a dedicated new RandomGen, seeded from the default-Gen
Definition: suite.cpp:218
Definition: run.hpp:40
Suite(string groupID, opt_uint64 seed)
create a suite comprised of all the testcases previously registered with this this group...
Definition: suite.cpp:158
virtual CStr what() const noexcept override
std::exception interface : yield a diagnostic message
helper to collect and manage the test cases.
Definition: suite.cpp:70
Random entropyGen
a global RandomSequencer seeded with real entropy
Definition: random.cpp:71
static const int EXCEPTION_THROWN
exit code returned when any individual test threw
Definition: suite.hpp:73
SeedNucleus & seedFromDefaultGen()
draw seed another Generator from the default RandomSequencer
Definition: random.cpp:74
Derived specific exceptions within Lumiera&#39;s exception hierarchy.
Definition: error.hpp:190
Abstract Base Class for all testcases.
Definition: run.hpp:53
bool run(Arg cmdline)
run all testcases contained in this Suite.
Definition: suite.cpp:251
Class to encapsulate the typical C-style commandline definition.
RandomSequencer< std::mt19937_64 > Random
PRNG engine to use by default: 64bit Mersenne twister.
Definition: random.hpp:120
Marker types to indicate a literal string and a Symbol.
Simplistic test class runner.
void seedRand()
draw a new random seed from a common nucleus, and re-seed the default-Gen.
Definition: suite.cpp:211
static string firstTok(Arg)
conveniently pick the first token from the argument line
Definition: suite.cpp:233
void reseed(SeedNucleus &)
inject controlled randomisation
Definition: random.hpp:188
Building and running a suite of tests, implemented as test classes.
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...
ElementBoxWidget::Config::Qualifier kind(Kind kind)
qualify the basic use case for the new ElementBoxWidget
lumiera_err lumiera_error(void)
Get and clear current error state.
Definition: error-state.c:115
static const string ALLGROUP
"magic" groupID containing all registered testcases
Definition: suite.hpp:71
std::string toString(TY const &val) noexcept
get some string representation of any object, reliably.
Definition: format-obj.hpp:191
uint64_t u64()
random 64bit number from full range.
Definition: random.hpp:224
Establishes a seed point for any instance or performance.
Definition: random.hpp:48
static uint firstVal(Arg, uint=1)
conveniently use some number given as argument, with optional default
Definition: suite.cpp:225
interface: generic testcase creating functor.
Definition: run.hpp:68
Lumiera error handling (C++ interface).
Generating (pseudo) random numbers with controlled seed.
void describe()
print to stdout an enumeration of all testcases in this suite, in a format suitable for use with Ceht...
Definition: suite.cpp:299
opt_uint64 fixedSeed
optionally a fixed random seed to inject in each invoked test
Definition: suite.cpp:102
static void enrol(Launcher *test, string testID, string groups)
register the given test-launcher, so it can be later accessed either as a member of one of the specif...
Definition: suite.cpp:131
Random defaultGen
a global default RandomSequencer for mundane purposes
Definition: random.cpp:70
Abstraction of the usual int argc, int** argv-Commandline, to be able to treat it as a vector of stri...
Definition: cmdline.hpp:48
Interface and Base definition for all Lumiera Exceptions.
Definition: error.hpp:62
bool contains(SEQ const &cont, typename SEQ::const_reference val)
shortcut for brute-force containment test in any sequential container
Definition: util.hpp:255