247 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			247 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /***************************************************************************************
 | |
|  * @file tinytest.cpp                                                                  *
 | |
|  *                                                                                     *
 | |
|  * @brief Defines structs and functions for implementing TinyTest.                     *
 | |
|  * @copyright Copyright 2023 Tom Hicks <headhunter3@gmail.com>                         *
 | |
|  * Licensed under the MIT license see the LICENSE file for details.                    *
 | |
|  ***************************************************************************************/
 | |
| 
 | |
| #define _XOPEN_SOURCE_EXTENDED
 | |
| #include "tinytest.h"
 | |
| 
 | |
| #include <cstdint>
 | |
| #include <cstdio>
 | |
| #include <iostream>
 | |
| #include <string>
 | |
| #include <vector>
 | |
| 
 | |
| namespace TinyTest {
 | |
| namespace {
 | |
| using std::endl;
 | |
| using std::string;
 | |
| using std::vector;
 | |
| }  // End namespace
 | |
| 
 | |
| // TODO: Add TShared(*)(string /*test_name*/, UUID /*testRunId*/)
 | |
| // allocateSharedData to the test tuple to make some shared data that can be
 | |
| // used in a thread safe way by setup, teardown, and evaluate steps of the test.
 | |
| // TODO: Add TShared to be returned by the setup functions, and consumed by the
 | |
| // evaluate and teardown functions.
 | |
| //   Suite setup/teardown functions should allocate/free.
 | |
| //   Test setup/teardown functions should consume the data allocated by Suite
 | |
| //   setup. Test setup functions may allocate additional resources. If they do
 | |
| //   then the allocated resources they should be freed by test teardown
 | |
| //   function. Suite and/or Test compare functions may consume this shared data,
 | |
| //   but it will not be shared with the execution of function_to_test.
 | |
| 
 | |
| // Begin TestResults methods
 | |
| TestResults::TestResults() : errors_(0), failed_(0), passed_(0), skipped_(0), total_(0) {}
 | |
| 
 | |
| TestResults::TestResults(const TestResults& other)
 | |
|     : error_messages_(other.error_messages_),
 | |
|       errors_(other.errors_),
 | |
|       failed_(other.failed_),
 | |
|       failure_messages_(other.failure_messages_),
 | |
|       passed_(other.passed_),
 | |
|       skip_messages_(other.skip_messages_),
 | |
|       skipped_(other.skipped_),
 | |
|       total_(other.total_) {}
 | |
| 
 | |
| TestResults::TestResults(uint32_t errors,
 | |
|                          uint32_t failed,
 | |
|                          uint32_t passed,
 | |
|                          uint32_t skipped,
 | |
|                          uint32_t total,
 | |
|                          vector<string> error_messages,
 | |
|                          vector<string> failure_messages,
 | |
|                          vector<string> skip_messages)
 | |
|     : error_messages_(error_messages),
 | |
|       errors_(errors),
 | |
|       failed_(failed),
 | |
|       failure_messages_(failure_messages),
 | |
|       passed_(passed),
 | |
|       skip_messages_(skip_messages),
 | |
|       skipped_(skipped),
 | |
|       total_(total) {}
 | |
| 
 | |
| TestResults& TestResults::Error() {
 | |
|   errors_++;
 | |
|   return *this;
 | |
| }
 | |
| 
 | |
| TestResults& TestResults::Error(string message) {
 | |
|   errors_++;
 | |
|   error_messages_.push_back(message);
 | |
|   return *this;
 | |
| }
 | |
| 
 | |
| TestResults& TestResults::Fail() {
 | |
|   total_++;
 | |
|   failed_++;
 | |
|   return *this;
 | |
| }
 | |
| 
 | |
| TestResults& TestResults::Fail(const string& message) {
 | |
|   total_++;
 | |
|   failed_++;
 | |
|   failure_messages_.push_back(message);
 | |
|   return *this;
 | |
| }
 | |
| 
 | |
| vector<string> TestResults::FailureMessages() const {
 | |
|   return failure_messages_;
 | |
| }
 | |
| 
 | |
| TestResults& TestResults::Pass() {
 | |
|   total_++;
 | |
|   passed_++;
 | |
|   return *this;
 | |
| }
 | |
| 
 | |
| TestResults& TestResults::Skip() {
 | |
|   total_++;
 | |
|   skipped_++;
 | |
|   return *this;
 | |
| }
 | |
| 
 | |
| TestResults& TestResults::Skip(const string& message) {
 | |
|   total_++;
 | |
|   skipped_++;
 | |
|   skip_messages_.push_back(message);
 | |
|   return *this;
 | |
| }
 | |
| 
 | |
| vector<string> TestResults::SkipMessages() const {
 | |
|   return skip_messages_;
 | |
| }
 | |
| 
 | |
| vector<string> TestResults::ErrorMessages() const {
 | |
|   return error_messages_;
 | |
| }
 | |
| 
 | |
| uint32_t TestResults::Errors() const {
 | |
|   return errors_;
 | |
| }
 | |
| 
 | |
| uint32_t TestResults::Failed() const {
 | |
|   return failed_;
 | |
| }
 | |
| 
 | |
| uint32_t TestResults::Passed() const {
 | |
|   return passed_;
 | |
| }
 | |
| 
 | |
| uint32_t TestResults::Skipped() const {
 | |
|   return skipped_;
 | |
| }
 | |
| 
 | |
| uint32_t TestResults::Total() const {
 | |
|   return total_;
 | |
| }
 | |
| 
 | |
| TestResults TestResults::operator+(const TestResults& other) const {
 | |
|   vector<string> error_messages;
 | |
|   error_messages.insert(error_messages.end(), error_messages_.begin(), error_messages_.end());
 | |
|   error_messages.insert(error_messages.end(), other.error_messages_.begin(), other.error_messages_.end());
 | |
|   vector<string> failure_messages;
 | |
|   failure_messages.insert(failure_messages.end(), failure_messages_.begin(), failure_messages_.end());
 | |
|   failure_messages.insert(failure_messages.end(), other.failure_messages_.begin(), other.failure_messages_.end());
 | |
|   vector<string> skip_messages;
 | |
|   skip_messages.insert(skip_messages.end(), skip_messages_.begin(), skip_messages_.end());
 | |
|   skip_messages.insert(skip_messages.end(), other.skip_messages_.begin(), other.skip_messages_.end());
 | |
| 
 | |
|   return TestResults(errors_ + other.errors_,
 | |
|                      failed_ + other.failed_,
 | |
|                      passed_ + other.passed_,
 | |
|                      skipped_ + other.skipped_,
 | |
|                      total_ + other.total_,
 | |
|                      error_messages,
 | |
|                      failure_messages,
 | |
|                      skip_messages);
 | |
| }
 | |
| 
 | |
| TestResults& TestResults::operator+=(const TestResults& other) {
 | |
|   error_messages_.insert(error_messages_.end(), other.error_messages_.begin(), other.error_messages_.end());
 | |
|   errors_ += other.errors_;
 | |
|   failed_ += other.failed_;
 | |
|   failure_messages_.insert(failure_messages_.end(), other.failure_messages_.begin(), other.failure_messages_.end());
 | |
|   passed_ += other.passed_;
 | |
|   skip_messages_.insert(skip_messages_.end(), other.skip_messages_.begin(), other.skip_messages_.end());
 | |
|   skipped_ += other.skipped_;
 | |
|   total_ += other.total_;
 | |
|   return *this;
 | |
| }
 | |
| 
 | |
| void PrintResults(std::ostream& os, TestResults results) {
 | |
|   auto skip_messages = results.SkipMessages();
 | |
|   if (skip_messages.size() > 0) {
 | |
|     os << "Skipped:" << endl;
 | |
|     for_each(skip_messages.begin(), skip_messages.end(), [&os](const string& message) {
 | |
|       os << "🚧Skipped: " << message << endl;
 | |
|     });
 | |
|   }
 | |
|   auto failure_messages = results.FailureMessages();
 | |
|   if (failure_messages.size() > 0) {
 | |
|     os << "Failures:" << endl;
 | |
|     for_each(failure_messages.begin(), failure_messages.end(), [&os](const string& message) {
 | |
|       os << "❌FAILED: " << message << endl;
 | |
|     });
 | |
|   }
 | |
|   auto error_messages = results.ErrorMessages();
 | |
|   if (error_messages.size() > 0) {
 | |
|     os << "Errors:" << endl;
 | |
|     for_each(error_messages.begin(), error_messages.end(), [&os](const string& message) {
 | |
|       os << "🔥ERROR: " << message << endl;
 | |
|     });
 | |
|   }
 | |
|   os << "Total tests: " << results.Total() << endl;
 | |
|   os << "Passed:      " << results.Passed() << " ✅" << endl;
 | |
|   os << "Failed:      " << results.Failed() << " ❌" << endl;
 | |
|   os << "Skipped:     " << results.Skipped() << " 🚧" << endl;
 | |
|   os << "Errors:      " << results.Errors() << " 🔥" << endl;
 | |
| }
 | |
| 
 | |
| // End TestResults methods.
 | |
| 
 | |
| MaybeTestConfigureFunction DefaultTestConfigureFunction() {
 | |
|   return std::nullopt;
 | |
| }
 | |
| 
 | |
| MaybeTestConfigureFunction Coalesce(MaybeTestConfigureFunction first, MaybeTestConfigureFunction second) {
 | |
|   if (first.has_value()) {
 | |
|     if (second.has_value()) {
 | |
|       // This is the only place we actually need to combine them.
 | |
|       return [&first, &second]() {
 | |
|         first.value()();
 | |
|         second.value()();
 | |
|       };
 | |
|     } else {
 | |
|       return first;
 | |
|     }
 | |
|   } else {
 | |
|     return second;
 | |
|   }
 | |
| }
 | |
| 
 | |
| // Utility functions.
 | |
| TestResults& SkipTest(TestResults& results,
 | |
|                       const std::string& suite_label,
 | |
|                       const std::string& test_label,
 | |
|                       std::optional<const std::string> reason) {
 | |
|   std::string qualified_test_label = suite_label + "::" + test_label;
 | |
|   std::cout << "  🚧Skipping Test: " << test_label;
 | |
|   if (reason.has_value()) {
 | |
|     std::cout << " because " << reason.value();
 | |
|   }
 | |
|   std::cout << std::endl;
 | |
|   results.Skip(qualified_test_label + (reason.has_value() ? " because " + reason.value() : ""));
 | |
|   return results;
 | |
| }
 | |
| 
 | |
| // TODO: Factor out the pretty printing into a separate module so it can be tested separately.
 | |
| // TODO: Consider making separate files for test suite, tests, test cases, and test results.
 | |
| // TODO: Come up with a way to autogenerat a main function that runs all tests in a *_test.cpp file.
 | |
| // TODO: Come up with a way to aggregate TestResults over multiple c++ files when running under bazel.
 | |
| // TODO: Create a Makefile to build as a library.
 | |
| }  // namespace TinyTest
 |