/*************************************************************************** * GTestSuite.cpp - Abstract test suite base class * * ----------------------------------------------------------------------- * * copyright (C) 2012-2013 by Jean-Baptiste Cayrou * * ----------------------------------------------------------------------- * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ /** * @file GTestSuite.cpp * @brief Abstract test suite base class implementation * @author Jean-Baptiste Cayrou */ /* __ Includes ___________________________________________________________ */ #ifdef HAVE_CONFIG_H #include #endif #include #include "GTestSuite.hpp" #include "GTools.hpp" #include "GLog.hpp" /* __ OpenMP section _____________________________________________________ */ #ifdef _OPENMP #include #ifdef HAVE_OPENMP_DARWIN_KLUGE #include pthread_attr_t gomp_thread_attr; #endif #endif /* __ Method name definitions ____________________________________________ */ #define G_OP_ACCESS "GTestSuite::operator[](int&)" #define G_TRY_SUCCESS "GTestSuite::test_try_success()" #define G_TRY_FAILURE1 "GTestSuite::test_try_failure(std::string&,"\ " std::string&)" #define G_TRY_FAILURE2 "GTestSuite::test_try_failure(std::exception&)" /* __ Macros _____________________________________________________________ */ /* __ Coding definitions _________________________________________________ */ /* __ Debug definitions __________________________________________________ */ /*========================================================================== = = = Constructors/destructors = = = ==========================================================================*/ /***********************************************************************//** * @brief Void constructor ***************************************************************************/ GTestSuite::GTestSuite(void) { // Initialise members init_members(); //Return return; } /***********************************************************************//** * @brief Copy constructor * * @param[in] suite Test Suite. ***************************************************************************/ GTestSuite::GTestSuite(const GTestSuite& suite) { // Initialise members init_members(); // Copy members copy_members(suite); //Return return; } /***********************************************************************//** * @brief Name constructor * * @param[in] name Test suite name. ***************************************************************************/ GTestSuite::GTestSuite(const std::string& name) { // Initialise members init_members(); // Set name m_name = name; //Return return; } /***********************************************************************//** * @brief Destructor ***************************************************************************/ GTestSuite::~GTestSuite(void) { // Free members free_members(); // Return return; } /*========================================================================== = = = Operators = = = ==========================================================================*/ /***********************************************************************//** * @brief Assignment operator * * @param[in] suite Test suite. * @return Test suite. ***************************************************************************/ GTestSuite& GTestSuite::operator=(const GTestSuite& suite) { // Execute only if object is not identical if (this != &suite) { // Free members free_members(); // Initialise members init_members(); // Copy members copy_members(suite); } // endif: object was not identical // Return return *this; } /***********************************************************************//** * @brief Returns reference to test case * * @param[in] index Test case index [0,...,size()-1]. * * @exception GException::out_of_range * Test case index is out of range. ***************************************************************************/ GTestCase& GTestSuite::operator[](const int& index) { // Compile option: raise exception if index is out of range #if defined(G_RANGE_CHECK) if (index < 0 || index >= size()) { throw GException::out_of_range(G_OP_ACCESS, "Test case index", index, size()); } #endif // Return reference return *(m_tests[index]); } /***********************************************************************//** * @brief Returns reference to test case * * @param[in] index Test case index [0,...,size()-1]. * * @exception GException::out_of_range * Test case index is out of range. ***************************************************************************/ const GTestCase& GTestSuite::operator[](const int& index) const { // Compile option: raise exception if index is out of range #if defined(G_RANGE_CHECK) if (index < 0 || index >= size()) { throw GException::out_of_range(G_OP_ACCESS, "Test case index", index, size()); } #endif // Return reference return *(m_tests[index]); } /*========================================================================== = = = Public methods = = = ==========================================================================*/ /***********************************************************************//** * @brief Clear test suite ***************************************************************************/ void GTestSuite::clear(void) { // Free members free_members(); // Initialise members init_members(); // Return return; } /***********************************************************************//** * @brief Append test functions to test suite * * @param[in] function Test function pointer. * @param[in] name Test name. * * This method adds test functions to the test suite. The test functions will * be executed when the run method is called. ***************************************************************************/ void GTestSuite::append(const pfunction function, const std::string& name) { // Add test function pointer and name to suite m_functions.push_back(function); m_names.push_back(name); // Return return; } /***********************************************************************//** * @brief Run all tests in test suite * * @return True is all tests were successful, false otherwise. * * Executes all test functions that have been appended to the test suite. * For each test function a test case is added to the test suite. ***************************************************************************/ bool GTestSuite::run(void) { // Setup the test functions. This is a pure virtual function that needs // to be implemented in the derived class. It sets the function // pointers and function names for all test functions that should be // executed. set(); // Initialise success flag bool success = true; // Loop over all functions in suite for (m_index = 0; m_index < m_functions.size(); ++m_index) { // Continue only if function is valid if (m_functions[m_index] != NULL) { // Save the number of errors and failures before test // execution. We use this after the test to see if // any failures occured. int old_errors = errors(); int old_failures = failures(); // Log the name of the test std::cout << m_names[m_index] << ": "; // Create a test of error type for function testing GTestCase* test = new GTestCase(GTestCase::ERROR_TEST, m_names[m_index]); // Set start time #ifdef _OPENMP double t_start = omp_get_wtime(); #else clock_t t_start = clock(); #endif // Execute test function try { (this->*(m_functions[m_index]))(); } catch (std::exception& e) { // Signal that test did not succeed test->has_passed(false); // Set test message to exception message test->message(e.what()); // Set type as class name test->type(typeid(e).name()); } catch (...) { // For other exceptions test->has_passed(false); } // Compute elapsed time #ifdef _OPENMP double t_elapse = omp_get_wtime()-t_start; #else double t_elapse = (double)(clock() - t_start) / (double)CLOCKS_PER_SEC; #endif // Set test duration test->duration(t_elapse); // Increment number of errors if the test did not pass if (!test->has_passed()) { m_errors++; } // Log the result (".","F" or, "E") std::cout << test->print(); // Log if there are errors or failures if ((m_errors == old_errors && m_failures == old_failures)) { std::cout << " ok" << std::endl; } else { std::cout << " NOK" << std::endl; success = false; } // Add test case to test suite m_tests.push_back(test); } // endif: test case has a function pointer } // endwhile: there were tests on the stack // Reset index m_index = 0; // Return success flag return success; } /***********************************************************************//** * @brief Test an assert * * @param[in] assert Assert (true/false). * @param[in] name Test case name (defaults to ""). * @param[in] message Test case name (defaults to ""). * * Tests if a condition is true or false. This method adds a test case of * type "failure" to the test suite. * * Examples: * test_assert(x>3, "Test if x > 3"); * test_assert(x>3 && x<10, "Test if 3 < x < 10 "); ***************************************************************************/ void GTestSuite::test_assert(const bool& assert, const std::string& name, const std::string& message) { // Create a test case of failure type GTestCase* testcase = new GTestCase(GTestCase::FAIL_TEST, format_name(name)); // If assert is false then signal that the test is not passed and // increment the number of failures in this test suite if (!assert) { testcase->has_passed(false); m_failures++; } // Set message testcase->message(message); // Log the result (".","F" or, "E") std::cout << testcase->print(); // Add test case to test suite m_tests.push_back(testcase); // Return return; } /***********************************************************************//** * @brief Test a value * * @param[in] value Value to test. * @param[in] expected Expected value. * @param[in] name Test case name (defaults to ""). * @param[in] message Test case message (defaults to ""). * * Test if the value is comprised in the interval * [expected-eps, expected+eps]. ***************************************************************************/ void GTestSuite::test_value(const int& value, const int& expected, const std::string& name, const std::string& message) { // Set test case name. If no name is specify then build the name from // the actual test parameters. std::string formated_name; if (name != "") { formated_name = format_name(name); } else { formated_name = format_name("Test if " + gammalib::str(value) + " is " + gammalib::str(expected)); } // Create a test case of failure type GTestCase* testcase = new GTestCase(GTestCase::FAIL_TEST, formated_name); // If value is not the expected one then signal test as failed and // increment the number of failures if (value != expected) { testcase->has_passed(false); m_failures++; } // If no message is specified then build message from test result std::string formated_message; if (message != "") { formated_message = message; } else { formated_message = "Value " + gammalib::str(value) + " equals not the " + "expected value of " + gammalib::str(expected) + "."; } // Set message testcase->message(formated_message); // Log the result (".","F" or, "E") std::cout << testcase->print(); // Add test case to test suite m_tests.push_back(testcase); // Return return; } /***********************************************************************//** * @brief Test a value * * @param[in] value Value to test. * @param[in] expected Expected value. * @param[in] eps Precision of the test (default 1e-30). * @param[in] name Test case name (defaults to ""). * @param[in] message Test case message (defaults to ""). * * Test if the value is comprised in the interval * [expected-eps, expected+eps]. ***************************************************************************/ void GTestSuite::test_value(const double& value, const double& expected, const double& eps, const std::string& name, const std::string& message) { // Set test case name. If no name is specify then build the name from // the actual test parameters. std::string formated_name; if (name != "") { formated_name = format_name(name); } else { formated_name = format_name("Test if " + gammalib::str(value) + " is within " + gammalib::str(expected) + " +/- " + gammalib::str(eps)); } // Create a test case of failure type GTestCase* testcase = new GTestCase(GTestCase::FAIL_TEST, formated_name); // If value is not between in interval [expected-eps, expected+eps] // then signal test as failed and increment the number of failures if (value > expected + eps || value < expected - eps) { testcase->has_passed(false); m_failures++; } // If no message is specified then build message from test result std::string formated_message; if (message != "") { formated_message = message; } else { formated_message = "Value " + gammalib::str(value) + " not within " + gammalib::str(expected) + " +/- " + gammalib::str(eps) + " (value-expected = " + gammalib::str(value-expected) + ")."; } // Set message testcase->message(formated_message); // Log the result (".","F" or, "E") std::cout << testcase->print(); // Add test case to test suite m_tests.push_back(testcase); // Return return; } /***********************************************************************//** * @brief Test an try block * * @param[in] name Test case name (defaults to ""). * * @see test_try_sucess() * @see test_try_failure(const std::string& message,const std::string& type) * @see test_try_failure(const std::exception& e) * * Call before testing a try/catch block. * * Example: * test_try("Test a try block"); * try { * ... //someting to test * test_try_success(); * } * catch(...) { * test_try_failure(); * } ***************************************************************************/ void GTestSuite::test_try(const std::string& name) { // Create a test case of error type GTestCase* testcase = new GTestCase(GTestCase::ERROR_TEST, format_name(name)); // Add test case to try stack of test suite m_stack_try.push_back(testcase); // Return return; } /***********************************************************************//** * @brief Notice when a try block succeeded * * @exception GException::test_nested_try_error * Test case index is out of range. * * @see test_try(const std::string& name) * @see test_try_failure(const std::string& message, const std::string& type) * @see test_try_failure(const std::exception& e) * * Call this method at the last line of a try * * Example: * test_try("Test a try block"); * try { * ... //someting to test * test_try_success(); * } * catch(...) { * test_try_failure(); * } ***************************************************************************/ void GTestSuite::test_try_success(void) { // If the stack is empty if (m_stack_try.empty()) { throw GException::test_nested_try_error(G_TRY_SUCCESS, "Called \"G_TRY_SUCCESS\" without a previous call to test_try()"); } // Add test case to test suite m_tests.push_back(m_stack_try.back()); // Delete the test case from the try stack m_stack_try.pop_back(); // Log the result (".","F" or, "E") std::cout << m_tests.back()->print(); // Return return; } /***********************************************************************//** * @brief Notice when a try block failed * * @param[in] message Message to explain why test failed (defaults to ""). * @param[in] type Type of message (defaults to ""). * * @exception GException::test_nested_try_error * Test case index is out of range. * * @see test_try_sucess() * @see test_try(const std::string& name) * @see test_try_failure(const std::exception& e) * * Call this method in the catch block. * * Example: * test_try("Test a try block"); * try { * ... //someting to test * test_try_success(); * } * catch(...) { * test_try_failure(); * } ***************************************************************************/ void GTestSuite::test_try_failure(const std::string& message, const std::string& type) { // If the stack is empty then create an eception test case if (m_stack_try.empty()) { GTestCase* testcase = new GTestCase(GTestCase::ERROR_TEST, "Exception test"); m_stack_try.push_back(testcase); } // Signal that test is not ok m_stack_try.back()->has_passed(false); // Increment the number of errors m_errors++; // Set test type m_stack_try.back()->kind(GTestCase::ERROR_TEST); // Set message m_stack_try.back()->message(message); // Set type of message m_stack_try.back()->type(type); // Add test case to test suite m_tests.push_back(m_stack_try.back()); // Delete the test case from the stack m_stack_try.pop_back(); // Log the result ( ".","F" or, "E") std::cout << m_tests.back()->print(); //Return return; } /***********************************************************************//** * @brief Notice when a try block failed * * @param[in] e Exception. * * @exception GException::test_nested_try_error * Test case index is out of range. * * @see test_try_sucess() * @see test_try(const std::string& name) * @see test_try_failure(const std::string& message, const std::string& type) * * Call this method in a catch block. * * Example: * test_try("Test a try block"); * try { * ... //someting to test * test_try_success(); * } * catch(exception& e) { * test_try_failure(e); * } ***************************************************************************/ void GTestSuite::test_try_failure(const std::exception& e) { // Extract message of exception and class name test_try_failure(e.what(), typeid(e).name()); // Return return; } /***********************************************************************//** * @brief Return a failure exception * * @param[in] message Message. * * @see test_try() * @see test_error(const std::string& message) * * It can be use in a try test * * Example: * test_try("Test a try block"); * try { * throw exception_failure("a failure"); * test_try_success(); * } * catch(exception& e) { * test_try_failure(e); * } ***************************************************************************/ GException::test_failure& GTestSuite::exception_failure(const std::string& message) { // Return exception return *(new GException::test_failure(m_stack_try.back()->name(), message)); } /***********************************************************************//** * @brief Return an error exception * * @param[in] message Message. * * @see test_try() * @see test_failure(const std::string& message) * * It can be use in a try test * * Example: * test_try("Test a try block"); * try { * throw exception_error("an error"); * test_try_success(); * } * catch(exception& e) { * test_try_failure(e); * } ***************************************************************************/ GException::test_error& GTestSuite::exception_error(const std::string& message) { // Return exception return *(new GException::test_error(m_stack_try.back()->name(),message)); } /***********************************************************************//** * @brief Return the number of successful tests ***************************************************************************/ int GTestSuite::success(void) const { // Return successes return size()-(m_errors+m_failures); } /***********************************************************************//** * @brief Return the total duration of all tests * * This method sums up all test durations and returns the result. ***************************************************************************/ double GTestSuite::duration(void) const { // Initialise duration double duration = 0.0; // Add up the durations of all tests for (int i = 0; i < m_tests.size(); ++i) { duration += m_tests[i]->duration(); } // Return duration return duration; } /***********************************************************************//** * @brief Print test suite information * * @param[in] chatter Chattiness (defaults to NORMAL). * @return String containing test suite information. ***************************************************************************/ std::string GTestSuite::print(const GChatter& chatter) const { // Initialise result string std::string result; // Continue only if chatter is not silent if (chatter != SILENT) { // Append header result.append("=== GTestSuite ==="); // Append information result.append("\n"+gammalib::parformat("Name")+m_name); result.append("\n"+gammalib::parformat("Number of functions")); result.append(gammalib::str(m_names.size())); result.append("\n"+gammalib::parformat("Number of executed tests")); result.append(gammalib::str(size())); result.append("\n"+gammalib::parformat("Number of errors")); result.append(gammalib::str(errors())); result.append("\n"+gammalib::parformat("Number of failures")); result.append(gammalib::str(failures())); } // endif: chatter was not silent // Return result return result; } /*========================================================================== = = = Private methods = = = ==========================================================================*/ /***********************************************************************//** * @brief Initialise class members ***************************************************************************/ void GTestSuite::init_members(void) { // Initialise members m_name = "Unnamed Test Suite"; m_names.clear(); m_functions.clear(); m_tests.clear(); m_stack_try.clear(); m_index = 0; m_failures = 0; m_errors = 0; m_log.clear(); m_timestamp = time(NULL); // Set logger parameters cout(true); m_log.max_size(1); // Return return; } /***********************************************************************//** * @brief Copy class members * * @param[in] suite Test suite. * * This method just clone the container not the test case. ***************************************************************************/ void GTestSuite::copy_members(const GTestSuite& suite) { // Copy members m_name = suite.m_name; m_names = suite.m_names; m_functions = suite.m_functions; m_index = suite.m_index; m_failures = suite.m_failures; m_errors = suite.m_errors; m_log = suite.m_log; m_timestamp = suite.m_timestamp; // Clone test cases for (int i = 0; i < suite.m_tests.size(); ++i) { m_tests[i] = suite.m_tests[i]->clone(); } // Clone try stack for (int i = 0; i < suite.m_stack_try.size(); ++i) { m_stack_try[i] = suite.m_stack_try[i]->clone(); } // Return return; } /***********************************************************************//** * @brief Delete class members ***************************************************************************/ void GTestSuite::free_members(void) { // Delete test cases for (int i = 0; i < m_tests.size(); ++i) { delete m_tests[i]; m_tests[i] = NULL; } // Delete try stack for (int i = 0; i < m_stack_try.size(); ++i) { delete m_stack_try[i]; m_stack_try[i] = NULL; } // Close logger m_log.close(); // Return return; } /***********************************************************************//** * @brief Format Name * * Return a string with the format * "TestFunctionName:TestTryname1:TestTryName2: name" ***************************************************************************/ std::string GTestSuite::format_name(const std::string& name) { // Initialise format name std::string format_name; // Set name of the try blocks if (!m_stack_try.empty()) { format_name = m_stack_try.back()->name(); } else { // Set name of the test format_name = m_names[m_index]; } // Append test suite name format_name += ": " + name; // Return format return format_name; }