Simulant  21.12-246
A portable game engine for Windows, OSX, Linux, Dreamcast, and PSP
test.h
1 /* * Copyright (c) 2011-2017 Luke Benstead https://simulant-engine.appspot.com
2  *
3  * This file is part of Simulant.
4  *
5  * Simulant is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * Simulant is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with Simulant. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #pragma once
20 
21 #include <vector>
22 #include <functional>
23 #include <stdexcept>
24 #include <iostream>
25 #include <sstream>
26 #include <algorithm>
27 #include <fstream>
28 #include <memory>
29 
30 #include "application.h"
31 #include "window.h"
32 #include "asset_manager.h"
33 
34 #define assert_equal(expected, actual) _assert_equal((expected), (actual), __FILE__, __LINE__)
35 #define assert_not_equal(expected, actual) _assert_not_equal((expected), (actual), __FILE__, __LINE__)
36 #define assert_false(actual) _assert_false((actual), __FILE__, __LINE__)
37 #define assert_true(actual) _assert_true((actual), __FILE__, __LINE__)
38 #define assert_close(expected, actual, difference) _assert_close((expected), (actual), (difference), __FILE__, __LINE__)
39 #define assert_is_null(actual) _assert_is_null((actual), __FILE__, __LINE__)
40 #define assert_is_not_null(actual) _assert_is_not_null((actual), __FILE__, __LINE__)
41 #define assert_raises(exception, func) _assert_raises<exception>((func), __FILE__, __LINE__)
42 #define assert_items_equal(expected, actual) _assert_items_equal((actual), (expected), __FILE__, __LINE__)
43 #define not_implemented() _not_implemented(__FILE__, __LINE__)
44 
45 
46 namespace smlt {
47 namespace test {
48 
49 
51 public:
52  StringFormatter(const std::string& templ):
53  templ_(templ) { }
54 
55  struct Counter {
56  Counter(uint32_t c): c(c) {}
57  uint32_t c;
58  };
59 
60  template<typename T>
61  std::string format(T value) {
62  std::stringstream ss;
63  ss << value;
64  return _do_format(0, ss.str());
65  }
66 
67  template<typename T>
68  std::string format(Counter count, T value) {
69  std::stringstream ss;
70  ss << value;
71  return _do_format(count.c, ss.str());
72  }
73 
74  template<typename T, typename... Args>
75  std::string format(T value, const Args&... args) {
76  std::stringstream ss;
77  ss << value;
78  return StringFormatter(_do_format(0, ss.str())).format(Counter(1), args...);
79  }
80 
81  template<typename T, typename... Args>
82  std::string format(Counter count, T value, const Args&... args) {
83  std::stringstream ss;
84  ss << value;
85  return StringFormatter(_do_format(count.c, ss.str())).format(Counter(count.c + 1), args...);
86  }
87 
88  std::string _do_format(uint32_t counter, const std::string& value) {
89  std::stringstream ss; // Can't use to_string on all platforms
90  ss << counter;
91 
92  const std::string to_replace = "{" + ss.str() + "}";
93  std::string output = templ_;
94 
95  auto replace = [](std::string& str, const std::string& from, const std::string& to) -> bool {
96  size_t start_pos = str.find(from);
97  if(start_pos == std::string::npos)
98  return false;
99  str.replace(start_pos, from.length(), to);
100  return true;
101  };
102 
103  replace(output, to_replace, value);
104  return output;
105  }
106 
107 private:
108  std::string templ_;
109 };
110 
112 public:
113  StringSplitter(const std::string& str):
114  str_(str) {
115 
116  }
117 
118  std::vector<std::string> split() {
119  std::vector<std::string> result;
120  std::string buffer;
121 
122  for(auto c: str_) {
123  if(c == '\n') {
124  if(!buffer.empty()) {
125  result.push_back(buffer);
126  buffer.clear();
127  }
128  } else {
129  buffer.push_back(c);
130  }
131  }
132 
133  if(!buffer.empty()) {
134  result.push_back(buffer);
135  }
136 
137  return result;
138  }
139 
140 private:
141  std::string str_;
142 };
143 
144 typedef StringFormatter _Format;
145 
146 class AssertionError : public std::logic_error {
147 public:
148  AssertionError(const std::string& what):
149  std::logic_error(what),
150  file(""),
151  line(-1) {
152  }
153 
154  AssertionError(const std::pair<std::string, int> file_and_line, const std::string& what):
155  std::logic_error(what),
156  file(file_and_line.first),
157  line(file_and_line.second) {
158 
159  }
160 
161  ~AssertionError() noexcept (true) {
162 
163  }
164 
165  std::string file;
166  int line;
167 };
168 
169 
170 class NotImplementedError: public std::logic_error {
171 public:
172  NotImplementedError(const std::string& file, int line):
173  std::logic_error(_Format("Not implemented at {0}:{1}").format(file, line)) {}
174 };
175 
176 
177 class SkippedTestError: public std::logic_error {
178 public:
179  SkippedTestError(const std::string& reason):
180  std::logic_error(reason) {
181 
182  }
183 };
184 
185 class TestCase {
186 public:
187  virtual ~TestCase() {}
188 
189  virtual void set_up() {}
190  virtual void tear_down() {}
191 
192  void skip_if(const bool& flag, const std::string& reason) {
193  if(flag) { throw test::SkippedTestError(reason); }
194  }
195 
196  template<typename T, typename U>
197  void _assert_equal(T expected, U actual, std::string file, int line) {
198  if(expected != actual) {
199  auto file_and_line = std::make_pair(file, line);
200  throw test::AssertionError(file_and_line, test::_Format("{0} does not match {1}").format(actual, expected));
201  }
202  }
203 
204  template<typename T, typename U>
205  void _assert_not_equal(T lhs, U rhs, std::string file, int line) {
206  if(lhs == (T) rhs) {
207  auto file_and_line = std::make_pair(file, line);
208  throw test::AssertionError(file_and_line, test::_Format("{0} should not match {1}").format(lhs, rhs));
209  }
210  }
211 
212  template<typename T>
213  void _assert_true(T actual, std::string file, int line) {
214  if(!bool(actual)) {
215  auto file_and_line = std::make_pair(file, line);
216  throw test::AssertionError(file_and_line, test::_Format("{0} is not true").format(bool(actual) ? "true" : "false"));
217  }
218  }
219 
220  template<typename T>
221  void _assert_false(T actual, std::string file, int line) {
222  if(bool(actual)) {
223  auto file_and_line = std::make_pair(file, line);
224  throw test::AssertionError(file_and_line, test::_Format("{0} is not false").format(bool(actual) ? "true" : "false"));
225  }
226  }
227 
228  template<typename T, typename U, typename V>
229  void _assert_close(T expected, U actual, V difference, std::string file, int line) {
230  if(actual < expected - difference ||
231  actual > expected + difference) {
232  auto file_and_line = std::make_pair(file, line);
233  throw test::AssertionError(file_and_line, test::_Format("{0} is not close enough to {1}").format(actual, expected));
234  }
235  }
236 
237  template<typename T>
238  void _assert_is_null(T* thing, std::string file, int line) {
239  if(thing != nullptr) {
240  auto file_and_line = std::make_pair(file, line);
241  throw test::AssertionError(file_and_line, "Pointer was not NULL");
242  }
243  }
244 
245  template<typename T>
246  void _assert_is_not_null(T* thing, std::string file, int line) {
247  if(thing == nullptr) {
248  auto file_and_line = std::make_pair(file, line);
249  throw test::AssertionError(file_and_line, "Pointer was unexpectedly NULL");
250  }
251  }
252 
253  template<typename T, typename Func>
254  void _assert_raises(Func func, std::string file, int line) {
255  try {
256  func();
257  auto file_and_line = std::make_pair(file, line);
258  throw test::AssertionError(file_and_line, test::_Format("Expected exception ({0}) was not thrown").format(typeid(T).name()));
259  } catch(T& e) {}
260  }
261 
262  template<typename T, typename U>
263  void _assert_items_equal(const T& lhs, const U& rhs, std::string file, int line) {
264  auto file_and_line = std::make_pair(file, line);
265 
266  if(lhs.size() != rhs.size()) {
267  throw test::AssertionError(file_and_line, "Containers are not the same length");
268  }
269 
270  for(auto item: lhs) {
271  if(std::find(rhs.begin(), rhs.end(), item) == rhs.end()) {
272  throw test::AssertionError(file_and_line, test::_Format("Container does not contain {0}").format(item));
273  }
274  }
275  }
276 
277  void _not_implemented(std::string file, int line) {
278  throw test::NotImplementedError(file, line);
279  }
280 };
281 
282 class TestRunner {
283 public:
284  template<typename T, typename U>
285  void register_case(std::vector<U> methods, std::vector<std::string> names) {
286  std::shared_ptr<TestCase> instance = std::make_shared<T>();
287 
288  instances_.push_back(instance); //Hold on to it
289 
290  for(std::string name: names) {
291  names_.push_back(name);
292  }
293 
294  for(U& method: methods) {
295  std::function<void()> func = std::bind(method, dynamic_cast<T*>(instance.get()));
296  tests_.push_back([=]() {
297  instance->set_up();
298  func();
299  instance->tear_down();
300  });
301  }
302  }
303 
304  int32_t run(const std::string& test_case, const std::string& junit_output="") {
305  int failed = 0;
306  int skipped = 0;
307  int ran = 0;
308  int crashed = 0;
309 
310  auto new_tests = tests_;
311  auto new_names = names_;
312 
313  if(!test_case.empty()) {
314  new_tests.clear();
315  new_names.clear();
316 
317  for(uint32_t i = 0; i < names_.size(); ++i) {
318  if(names_[i].find(test_case) == 0) {
319  new_tests.push_back(tests_[i]);
320  new_names.push_back(names_[i]);
321  }
322  }
323  }
324 
325  std::cout << std::endl << "Running " << new_tests.size() << " tests" << std::endl << std::endl;
326 
327  std::vector<std::string> junit_lines;
328  junit_lines.push_back("<testsuites>\n");
329 
330  std::string klass = "";
331 
332  for(std::function<void ()> test: new_tests) {
333  std::string name = new_names[ran];
334  std::string this_klass(name.begin(), name.begin() + name.find_first_of(":"));
335  bool close_klass = ran == (int) new_tests.size() - 1;
336 
337  if(this_klass != klass) {
338  if(!klass.empty()) {
339  junit_lines.push_back(" </testsuite>\n");
340  }
341  klass = this_klass;
342  junit_lines.push_back(" <testsuite name=\"" + this_klass + "\">\n");
343  }
344 
345  try {
346  junit_lines.push_back(" <testcase name=\"" + new_names[ran] + "\">\n");
347  std::string output = " " + new_names[ran];
348 
349  for(int i = output.length(); i < 76; ++i) {
350  output += " ";
351  }
352 
353  std::cout << output;
354  test();
355  std::cout << "\033[32m" << " OK " << "\033[0m" << std::endl;
356  junit_lines.push_back(" </testcase>\n");
357  } catch(test::NotImplementedError& e) {
358  std::cout << "\033[34m" << " SKIPPED" << "\033[0m" << std::endl;
359  ++skipped;
360  junit_lines.push_back(" </testcase>\n");
361  } catch(test::SkippedTestError& e) {
362  std::cout << "\033[34m" << " SKIPPED" << "\033[0m" << std::endl;
363  ++skipped;
364  junit_lines.push_back(" </testcase>\n");
365  } catch(test::AssertionError& e) {
366  std::cout << "\033[33m" << " FAILED " << "\033[0m" << std::endl;
367  std::cout << " " << e.what() << std::endl;
368  if(!e.file.empty()) {
369  std::cout << " " << e.file << ":" << e.line << std::endl;
370 
371  std::ifstream ifs(e.file);
372  if(ifs.good()) {
373  std::string buffer;
374  std::vector<std::string> lines;
375  while(std::getline(ifs, buffer)) {
376  lines.push_back(buffer);
377  }
378 
379  int line_count = lines.size();
380  if(line_count && e.line <= line_count) {
381  std::cout << lines.at(e.line - 1) << std::endl << std::endl;
382  }
383  }
384  }
385  ++failed;
386 
387  junit_lines.push_back(" <failure message=\"" + std::string(e.what()) + "\"/>\n");
388  junit_lines.push_back(" </testcase>\n");
389  } catch(std::exception& e) {
390  std::cout << "\033[31m" << " EXCEPT " << std::endl;
391  std::cout << " " << e.what() << "\033[0m" << std::endl;
392  ++crashed;
393 
394  junit_lines.push_back(" <failure message=\"" + std::string(e.what()) + "\"/>\n");
395  junit_lines.push_back(" </testcase>\n");
396  }
397  std::cout << "\033[0m";
398  ++ran;
399 
400  if(close_klass) {
401  junit_lines.push_back(" </testsuite>\n");
402  }
403  }
404 
405  junit_lines.push_back("</testsuites>\n");
406 
407  if(!junit_output.empty()) {
408  FILE* f = fopen(junit_output.c_str(), "wt");
409  if(f) {
410  for(auto& line: junit_lines) {
411  fwrite(line.c_str(), sizeof(char), line.length(), f);
412  }
413  }
414 
415  fclose(f);
416  }
417 
418  std::cout << "-----------------------" << std::endl;
419  if(!failed && !crashed && !skipped) {
420  std::cout << "All tests passed" << std::endl << std::endl;
421  } else {
422  if(skipped) {
423  std::cout << skipped << " tests skipped";
424  }
425 
426  if(failed) {
427  if(skipped) {
428  std::cout << ", ";
429  }
430  std::cout << failed << " tests failed";
431  }
432 
433  if(crashed) {
434  if(failed) {
435  std::cout << ", ";
436  }
437  std::cout << crashed << " tests crashed";
438  }
439  std::cout << std::endl << std::endl;
440  }
441 
442  return failed + crashed;
443  }
444 
445 private:
446  std::vector<std::shared_ptr<TestCase>> instances_;
447  std::vector<std::function<void()> > tests_;
448  std::vector<std::string> names_;
449 };
450 
451 
452 class TestScene : public smlt::Scene<TestScene> {
453 public:
454  TestScene(Window* window):
455  smlt::Scene<TestScene>(window) {}
456 
457  void load() override {}
458 };
459 
460 class TestApp: public smlt::Application {
461 public:
462  TestApp(const AppConfig& config):
463  Application(config) {
464  }
465 
466 private:
467  bool init() {
468  scenes->register_scene<TestScene>("main");
469  return true;
470  }
471 };
472 
473 class SimulantTestCase: public TestCase {
474 private:
475  void set_app_and_window(std::shared_ptr<Application>* app, Window** window, SceneBase** scene) {
476  static std::shared_ptr<Application> application;
477 
478  if(!application) {
479  AppConfig config;
480  config.width = 640;
481  config.height = 480;
482  config.fullscreen = false;
483 
484  // FIXME: This is a bit simulant-specific, you wouldn't necessarily want this
485  // path on user apps.
486  config.search_paths.push_back("assets");
487  config.search_paths.push_back("sample_data");
488 
489 #if defined(__DREAMCAST__)
490  config.search_paths.push_back("/cd");
491  config.search_paths.push_back("/cd/assets");
492  config.search_paths.push_back("/cd/sample_data");
493  config.search_paths.push_back("/pc");
494  config.search_paths.push_back("/pc/assets");
495  config.search_paths.push_back("/pc/sample_data");
496 #endif
497 
498  application.reset(new TestApp(config));
499  application->run_frame();
500  } else {
501  application->scenes->unload("main");
502  application->stop_all_coroutines();
503  application->update_coroutines();
504  application->window->reset();
505 
506  application->scenes->activate("main");
507 
508  /* We have to run a frame as activate doesn't kick in
509  * until late_update */
510  application->run_frame();
511  }
512 
513  *app = application;
514  *window = (*app)->window;
515  *scene = (*app)->scenes->active_scene().get();
516  }
517 
518 protected:
519  Window* window;
520  SceneBase* scene;
521  std::shared_ptr<Application> application;
522 
523 public:
524  virtual void set_up() {
525  TestCase::set_up();
526  set_app_and_window(&application, &window, &scene);
527  }
528 };
529 
530 } // test
531 } // smlt
532 
smlt::test::SimulantTestCase
Definition: test.h:473
smlt::Application
Definition: application.h:167
smlt::SceneBase
Definition: scene.h:65
smlt::test::StringFormatter
Definition: test.h:50
smlt
Definition: animation.cpp:25
smlt::Window
Definition: window.h:65
smlt::test::TestCase
Definition: test.h:185
smlt::Scene
Definition: scene.h:165
smlt::test::StringSplitter
Definition: test.h:111
smlt::test::AssertionError
Definition: test.h:146
smlt::test::SkippedTestError
Definition: test.h:177
smlt::AppConfig
Definition: application.h:61
smlt::test::StringFormatter::Counter
Definition: test.h:55
smlt::test::NotImplementedError
Definition: test.h:170
smlt::test::TestRunner
Definition: test.h:282
smlt::test::TestScene
Definition: test.h:452
smlt::test::TestApp
Definition: test.h:460