Simulant  21.12-574
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 
299  try {
300  func();
301  } catch(...) {
302  instance->tear_down();
303  throw;
304  }
305 
306  instance->tear_down();
307  });
308  }
309  }
310 
311  int32_t run(const std::string& test_case, const std::string& junit_output="") {
312  int failed = 0;
313  int skipped = 0;
314  int ran = 0;
315  int crashed = 0;
316 
317  auto new_tests = tests_;
318  auto new_names = names_;
319 
320  if(!test_case.empty()) {
321  new_tests.clear();
322  new_names.clear();
323 
324  for(uint32_t i = 0; i < names_.size(); ++i) {
325  if(names_[i].find(test_case) == 0) {
326  new_tests.push_back(tests_[i]);
327  new_names.push_back(names_[i]);
328  }
329  }
330  }
331 
332  std::cout << std::endl << "Running " << new_tests.size() << " tests" << std::endl << std::endl;
333 
334  std::vector<std::string> junit_lines;
335  junit_lines.push_back("<testsuites>\n");
336 
337  std::string klass = "";
338 
339  for(std::function<void ()> test: new_tests) {
340  std::string name = new_names[ran];
341  std::string this_klass(name.begin(), name.begin() + name.find_first_of(":"));
342  bool close_klass = ran == (int) new_tests.size() - 1;
343 
344  if(this_klass != klass) {
345  if(!klass.empty()) {
346  junit_lines.push_back(" </testsuite>\n");
347  }
348  klass = this_klass;
349  junit_lines.push_back(" <testsuite name=\"" + this_klass + "\">\n");
350  }
351 
352  try {
353  junit_lines.push_back(" <testcase name=\"" + new_names[ran] + "\">\n");
354  std::string output = " " + new_names[ran];
355 
356  for(int i = output.length(); i < 76; ++i) {
357  output += " ";
358  }
359 
360  std::cout << output;
361  test();
362  std::cout << "\033[32m" << " OK " << "\033[0m" << std::endl;
363  junit_lines.push_back(" </testcase>\n");
364  } catch(test::NotImplementedError& e) {
365  std::cout << "\033[34m" << " SKIPPED" << "\033[0m" << std::endl;
366  ++skipped;
367  junit_lines.push_back(" </testcase>\n");
368  } catch(test::SkippedTestError& e) {
369  std::cout << "\033[34m" << " SKIPPED" << "\033[0m" << std::endl;
370  ++skipped;
371  junit_lines.push_back(" </testcase>\n");
372  } catch(test::AssertionError& e) {
373  std::cout << "\033[33m" << " FAILED " << "\033[0m" << std::endl;
374  std::cout << " " << e.what() << std::endl;
375  if(!e.file.empty()) {
376  std::cout << " " << e.file << ":" << e.line << std::endl;
377 
378  std::ifstream ifs(e.file);
379  if(ifs.good()) {
380  std::string buffer;
381  std::vector<std::string> lines;
382  while(std::getline(ifs, buffer)) {
383  lines.push_back(buffer);
384  }
385 
386  int line_count = lines.size();
387  if(line_count && e.line <= line_count) {
388  std::cout << lines.at(e.line - 1) << std::endl << std::endl;
389  }
390  }
391  }
392  ++failed;
393 
394  junit_lines.push_back(" <failure message=\"" + std::string(e.what()) + "\"/>\n");
395  junit_lines.push_back(" </testcase>\n");
396  } catch(std::exception& e) {
397  std::cout << "\033[31m" << " EXCEPT " << std::endl;
398  std::cout << " " << e.what() << "\033[0m" << std::endl;
399  ++crashed;
400 
401  junit_lines.push_back(" <failure message=\"" + std::string(e.what()) + "\"/>\n");
402  junit_lines.push_back(" </testcase>\n");
403  }
404  std::cout << "\033[0m";
405  ++ran;
406 
407  if(close_klass) {
408  junit_lines.push_back(" </testsuite>\n");
409  }
410  }
411 
412  junit_lines.push_back("</testsuites>\n");
413 
414  if(!junit_output.empty()) {
415  FILE* f = fopen(junit_output.c_str(), "wt");
416  if(f) {
417  for(auto& line: junit_lines) {
418  fwrite(line.c_str(), sizeof(char), line.length(), f);
419  }
420  }
421 
422  fclose(f);
423  }
424 
425  std::cout << "-----------------------" << std::endl;
426  if(!failed && !crashed && !skipped) {
427  std::cout << "All tests passed" << std::endl << std::endl;
428  } else {
429  if(skipped) {
430  std::cout << skipped << " tests skipped";
431  }
432 
433  if(failed) {
434  if(skipped) {
435  std::cout << ", ";
436  }
437  std::cout << failed << " tests failed";
438  }
439 
440  if(crashed) {
441  if(failed) {
442  std::cout << ", ";
443  }
444  std::cout << crashed << " tests crashed";
445  }
446  std::cout << std::endl << std::endl;
447  }
448 
449  return failed + crashed;
450  }
451 
452 private:
453  std::vector<std::shared_ptr<TestCase>> instances_;
454  std::vector<std::function<void()> > tests_;
455  std::vector<std::string> names_;
456 };
457 
458 
459 class TestScene : public smlt::Scene<TestScene> {
460 public:
461  TestScene(Window* window):
462  smlt::Scene<TestScene>(window) {}
463 
464  void load() override {}
465 };
466 
467 class TestApp: public smlt::Application {
468 public:
469  TestApp(const AppConfig& config):
470  Application(config) {
471  }
472 
473 private:
474  bool init() {
475  scenes->register_scene<TestScene>("main");
476  return true;
477  }
478 };
479 
480 class SimulantTestCase: public TestCase {
481 private:
482  void set_app_and_window(std::shared_ptr<Application>* app, Window** window, SceneBase** scene) {
483  static std::shared_ptr<Application> application;
484 
485  if(!application) {
486  AppConfig config;
487  config.width = 640;
488  config.height = 480;
489  config.fullscreen = false;
490  config.log_level = LOG_LEVEL_WARN;
491 
492  // FIXME: This is a bit simulant-specific, you wouldn't necessarily want this
493  // path on user apps.
494  config.search_paths.push_back("assets");
495  config.search_paths.push_back("sample_data");
496 
497 #if defined(__DREAMCAST__)
498  config.search_paths.push_back("/cd");
499  config.search_paths.push_back("/cd/assets");
500  config.search_paths.push_back("/cd/sample_data");
501  config.search_paths.push_back("/pc");
502  config.search_paths.push_back("/pc/assets");
503  config.search_paths.push_back("/pc/sample_data");
504 #endif
505 
506  application.reset(new TestApp(config));
507  application->run_frame();
508  } else {
509  application->scenes->unload("main");
510  application->stop_all_coroutines();
511  application->update_coroutines();
512  application->window->reset();
513 
514  application->scenes->activate("main");
515 
516  /* We have to run a frame as activate doesn't kick in
517  * until late_update */
518  application->run_frame();
519  }
520 
521  *app = application;
522  *window = (*app)->window;
523  *scene = (*app)->scenes->active_scene().get();
524  }
525 
526 protected:
527  Window* window;
528  SceneBase* scene;
529  std::shared_ptr<Application> application;
530 
531 public:
532  virtual void set_up() {
533  TestCase::set_up();
534  set_app_and_window(&application, &window, &scene);
535  }
536 };
537 
538 } // test
539 } // smlt
540 
smlt::test::SimulantTestCase
Definition: test.h:480
smlt::Application
Definition: application.h:162
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:62
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:459
smlt::test::TestApp
Definition: test.h:467