diff --git a/Test/CMakeLists.txt b/Test/CMakeLists.txt index 11f42ad642b408f2810a03bb7bc921f46c70c635..f980e8e1e929bd4055c09f27b9ee15733a8d86f1 100644 --- a/Test/CMakeLists.txt +++ b/Test/CMakeLists.txt @@ -12,6 +12,8 @@ set(my_headers ${CMAKE_CURRENT_SOURCE_DIR}/TestChecksumLevel.hh ${CMAKE_CURRENT_SOURCE_DIR}/TestChecksumNumberT.hh ${CMAKE_CURRENT_SOURCE_DIR}/TestChecksumOutcome.hh + ${CMAKE_CURRENT_SOURCE_DIR}/TestError.hh + ${CMAKE_CURRENT_SOURCE_DIR}/TestErrorInc.hh ${CMAKE_CURRENT_SOURCE_DIR}/TestOutcome.hh ${CMAKE_CURRENT_SOURCE_DIR}/TestPaths.hh ${CMAKE_CURRENT_SOURCE_DIR}/TestReport.hh @@ -30,6 +32,7 @@ set(my_sources ${CMAKE_CURRENT_SOURCE_DIR}/TestChecksumFile.cc ${CMAKE_CURRENT_SOURCE_DIR}/TestChecksumIssueNumber.cc ${CMAKE_CURRENT_SOURCE_DIR}/TestChecksumOutcome.cc + ${CMAKE_CURRENT_SOURCE_DIR}/TestError.cc ${CMAKE_CURRENT_SOURCE_DIR}/TestOutcome.cc ${CMAKE_CURRENT_SOURCE_DIR}/TestPaths.cc ${CMAKE_CURRENT_SOURCE_DIR}/TestReport.cc diff --git a/Test/TestArgs.cc b/Test/TestArgs.cc index 04be492b4d47ba5cd0523c7cbd7816046335d937..b428e7885da580565d56a727e8bdeecaea71cc74 100755 --- a/Test/TestArgs.cc +++ b/Test/TestArgs.cc @@ -3,6 +3,7 @@ #ifdef TEST_ON #include "TestArgs.hh" +#include "TestError.hh" #include "Base/Test/TestChecksum.hh" #include "Base/Test/TestChecksumLevel.hh" @@ -97,7 +98,7 @@ Option::Option(const std::string& _name, const int _min_arg_nmbr, uint Option::parse(const size_t _arg_nmbr, const std::string* const _args) { // Check that enough arguments have been supplied - OPTION_THROW_if(min_argument_number() > _arg_nmbr, + TEST_ARGS_THROW_if(min_argument_number() > _arg_nmbr, "Too few arguments supplied for " << name() << "." << Base::ENDL << INDENT << "expected: (>=)" << min_argument_number() << "; supplied: " << _arg_nmbr << "." @@ -123,7 +124,7 @@ bool Option::check_and_set_mutex() void Option::throw_if_not_parsed() const { - OPTION_THROW_if(!parsed(), + TEST_ARGS_THROW_if(!parsed(), "The " << name() << " option is being accessed but it has not been parsed."); } @@ -148,9 +149,10 @@ bool ToggleGroup::off() const void ToggleGroup::throw_if_not_parsed() const { - OPTION_THROW_if(!parsed(), "The '" << name_ - << "' toggle option group is being " - "accessed but it has not been parsed."); + TEST_ARGS_THROW_if(!parsed(), "The '" + << name_ + << "' toggle option group is being " + "accessed but it has not been parsed."); } ToggleGroup::ToggleGroup(const std::string& _name, @@ -171,14 +173,14 @@ ToggleGroup::ToggleGroup(const std::string& _name, void Parser::add_option(Option& _option) { // Throw an error if the option attempts to override the -h or --help options. - OPTION_THROW_if(help_option(_option.name()), + TEST_ARGS_THROW_if(help_option(_option.name()), _option.name() << " cannot override -h or --help."); #ifdef __APPLE__ - OPTION_THROW_if(!options_.emplace(_option.name(), _option).second, + TEST_ARGS_THROW_if(!options_.emplace(_option.name(), _option).second, _option.name() << " has already been added to the argument parser."); -#else // __APPLE__ - OPTION_THROW_if(!options_.try_emplace(_option.name(), _option).second, +#else // __APPLE__ + TEST_ARGS_THROW_if(!options_.try_emplace(_option.name(), _option).second, _option.name() << " has already been added to the argument parser."); #endif // __APPLE__ } @@ -205,7 +207,7 @@ void Parser::parse() { // Throw error if the option is not recognised and the flag // FL_ALLOW_UNKNOWN is not on - OPTION_THROW_if( + TEST_ARGS_THROW_if( !flag_on<FL_ALLOW_UNKNOWN>(), args[i] << " is not a known option."); continue; } @@ -214,12 +216,12 @@ void Parser::parse() // Throw error if option has already been set and the flag FL_OVERWRITE is // not on - OPTION_THROW_if(option.parsed() && !flag_on<FL_OVERWRITE>(), + TEST_ARGS_THROW_if(option.parsed() && !flag_on<FL_OVERWRITE>(), args[i] << " has already been set."); // Throw error if option is mutually exclusive with an option that has // already been set - OPTION_THROW_if(!option.check_and_set_mutex(), + TEST_ARGS_THROW_if(!option.check_and_set_mutex(), "An option that is mutually exclusive with " << args[i] << " has already been set."); @@ -291,7 +293,7 @@ int DebugLevel::parse_level(const std::string& _arg) } catch (...) { - OPTION_THROW(name() << ": '" << _arg << "' is not a valid debug level."); + TEST_ARGS_THROW(name() << ": '" << _arg << "' is not a valid debug level."); } } @@ -367,7 +369,8 @@ Checksum::Level ChecksumLevel::parse_level(const std::string& _arg) return static_cast<Checksum::Level>(i); } - OPTION_THROW(name() << ": '" << _arg << "' is not a valid checksum level."); + TEST_ARGS_THROW( + name() << ": '" << _arg << "' is not a valid checksum level."); } } // namespace Args diff --git a/Test/TestArgs.hh b/Test/TestArgs.hh index f15f1d2e0914505edb44e1a3fa7ee3531c786cf7..a8d0012114b54fbc6d8552bd55aab115f6688b21 100755 --- a/Test/TestArgs.hh +++ b/Test/TestArgs.hh @@ -54,21 +54,6 @@ environment variable. Internally, this calls collect_from_string(). */ void collect_from_variable(const char* const _vrbl_name); -//! Throw a std::invalid_argument() exception -#define OPTION_THROW(EXPR) \ - { \ - Base::OStringStream strm; \ - strm << EXPR; \ - throw std::invalid_argument(strm.str); \ - } - -//! Throw a std::invalid_argument() exception is COND == true -#define OPTION_THROW_if(COND, EXPR) \ - { \ - if (COND) \ - OPTION_THROW(EXPR); \ - } - /*! Base class for test command-line options. Every option that we intend to parse must have an associated class derived from Option that implements the member @@ -149,7 +134,7 @@ protected: /*! Virtual function to parse the arguments for the current option. The return value should be the number of arguments that have been successfully parsed. - Use OPTION_THROW[_if]() to report errors. + Use TEST_ARGS_THROW[_if]() (defined in TestError.hh) to report errors. * const size_t [_arg_nmbr]: Number of available arguments (guaranteed to be at least min_argument_number()). diff --git a/Test/TestChecksumCompare.cc b/Test/TestChecksumCompare.cc index 732cd8d57cb313c3844acacbcfe2258a6534d429..fae9147db9864399f81f71390758202281005978 100755 --- a/Test/TestChecksumCompare.cc +++ b/Test/TestChecksumCompare.cc @@ -6,12 +6,13 @@ #include "TestChecksumCompare.hh" #include "TestChecksumCompletion.hh" +#include "TestError.hh" #include "LongestCommonSubsequenceT.hh" #include <Base/Paths/Filesystem.hh> -#include <Base/Utils/NullOutputStream.hh> #include <Base/Paths/PathLink.hh> +#include <Base/Utils/NullOutputStream.hh> #include <exception> #include <fstream> @@ -229,12 +230,8 @@ DifferenceDistribution Compare::Impl::run(Base::IOutputStream& _log_os) const const char* const RESULT_TAG = " "; // Throw an error if the report paths have not been set - if (left_path_.empty() || right_path_.empty()) - { - throw std::runtime_error( - "Unable to compare checksum reports: Report paths have not been set. " - "This should be done using Test::Checksum::Compare::set_reports()."); - } + TEST_THROW_ERROR_if(left_path_.empty() || right_path_.empty(), + TEST_CHECKSUM_COMPARE_REPORTS_NOT_SET); const Report rprts[2] = {Report(left_path_), Report(right_path_)}; @@ -318,12 +315,8 @@ void Compare::Impl::check_equal() const std::cout << "Info: Comparing checksums..."; // Throw an error if the report paths have not been set - if (left_path_.empty() || right_path_.empty()) - { - throw std::runtime_error( - "Unable to compare checksum reports: Report paths have not been set. " - "This should be done using Test::Checksum::Compare::set_reports()."); - } + TEST_THROW_ERROR_if(left_path_.empty() || right_path_.empty(), + TEST_CHECKSUM_COMPARE_REPORTS_NOT_SET); // Skip the comparison if one of these files doesn't exist if (!fs::exists(left_path_)) @@ -359,7 +352,8 @@ void Compare::Impl::check_equal() const std::cout << "Compare " << PathLink(left_path_) << " with " << PathLink(right_path_) << "." << std::endl; - throw std::runtime_error("Checksum comparison failed--differences found!"); + + TEST_THROW_ERROR(TEST_CHECKSUM_COMPARE_DIFFERENCES); } } @@ -376,17 +370,9 @@ void Compare::make() void Compare::set(Compare& _othr) { - if (_othr.impl_ != nullptr) - { - throw std::runtime_error( - "Unable to set implementation: incoming implementation is null."); - } - - if (impl_ != nullptr) - { - throw std::runtime_error( - "Unable to set implementation: implementation has already been set."); - } + TEST_THROW_ERROR_if( + _othr.impl_ != nullptr, TEST_CHECKSUM_COMPARE_INCOMING_IMPL_NULL); + TEST_THROW_ERROR_if(impl_ != nullptr, TEST_CHECKSUM_COMPARE_IMPL_ALREADY_SET); impl_ = _othr.impl_; } @@ -424,12 +410,7 @@ void Compare::check_equal() const void Compare::check_implementation() const { - if (impl_ == nullptr) - { - throw std::runtime_error("Implementation is null: Implementation should be " - "set using Test::Checksum::Compare::make() or " - "Test::Checksum::Compare::set()."); - } + TEST_THROW_ERROR_if(impl_ == nullptr, TEST_CHECKSUM_COMPARE_IMPL_NULL); } Compare compare; diff --git a/Test/TestError.cc b/Test/TestError.cc new file mode 100755 index 0000000000000000000000000000000000000000..cbea590a4b4203e83c6278a69d5a3655d474e628 --- /dev/null +++ b/Test/TestError.cc @@ -0,0 +1,21 @@ +// (C) Copyright 2021 by Autodesk, Inc. + +#ifdef TEST_ON + +#include "TestError.hh" + +namespace Test +{ + +static const char* ERROR_MESSAGE[] = +{ + #define DEFINE_ERROR(CODE, MSG) MSG, + #include "TestErrorInc.hh" + #undef DEFINE_ERROR +}; + +const char* Error::message() const { return ERROR_MESSAGE[idx_]; } + +} // namespace Test + +#endif // TEST_ON diff --git a/Test/TestError.hh b/Test/TestError.hh new file mode 100755 index 0000000000000000000000000000000000000000..cd8b4586db3b35c7f1cd0072d658a39a94926ce3 --- /dev/null +++ b/Test/TestError.hh @@ -0,0 +1,70 @@ +// (C) Copyright 2021 by Autodesk, Inc. + +#ifndef BASE_TESTERROR_HH_INCLUDED +#define BASE_TESTERROR_HH_INCLUDED + +#ifdef TEST_ON + +#include <Base/Utils/BaseError.hh> + +#include <exception> + +namespace Test +{ + +class BASEDLLEXPORT Error : public Base::Error +{ +public: + enum Index + { + #define DEFINE_ERROR(CODE, MSG) CODE, + #include <Base/Test/TestErrorInc.hh> + #undef DEFINE_ERROR + }; + +public: + //! Constructor. + Error(const Index _idx) : Base::Error((int)_idx) {} + + //! Return the error message + virtual const char* message() const; + +protected: + Error(const int _idx) : Base::Error(_idx) {} +}; + +} // namespace Test + +// Throw a Test::Error exception with a specific error index (defined in +// TestErrorInc.hh) +#define TEST_THROW_ERROR(INDEX) { THROW_ERROR_MODULE(Test, INDEX); } +#define TEST_THROW_ERROR_if(COND, INDEX) { if (COND) TEST_THROW_ERROR(INDEX); } + +// Throw a 'TODO' Test::Error exception and send a specific message to the debug +// output +#define TEST_THROW_ERROR_TODO(MSG) { THROW_ERROR_TODO_MODULE(Test, MSG); } +#define TEST_THROW_ERROR_TODO_if(COND, MSG) { if (COND) TEST_THROW_ERROR_TODO(MSG); } + +// Throw a std::runtime_error exception with a message defined by EXPR +#define TEST_THROW_MSG(EXPR) \ + { \ + Base::OStringStream strm; \ + strm << EXPR; \ + throw std::runtime_error(strm.str); \ + } + +#define TEST_THROW_MSG_if(COND, EXPR) { if (COND) TEST_THROW_MSG(EXPR); } + +// Throw a std::invalid_argument exception with a message defined by EXPR +#define TEST_ARGS_THROW(EXPR) \ + { \ + Base::OStringStream strm; \ + strm << EXPR; \ + throw std::invalid_argument(strm.str); \ + } + +#define TEST_ARGS_THROW_if(COND, EXPR) { if (COND) TEST_ARGS_THROW(EXPR); } + +#endif // TEST_ON + +#endif // BASE_TESTERROR_HH_INCLUDED diff --git a/Test/TestErrorInc.hh b/Test/TestErrorInc.hh new file mode 100755 index 0000000000000000000000000000000000000000..aad554c284ca7a9103704ad46ce723b53700080b --- /dev/null +++ b/Test/TestErrorInc.hh @@ -0,0 +1,32 @@ +// (C) Copyright 2021 by Autodesk, Inc. + +#ifndef DEFINE_ERROR +#error This file should not be included directly, include TestError.hh instead +#endif + +#ifdef TEST_ON + +#include <Base/Utils/BaseErrorInc.hh> + +// Error codes relating to TestChecksumCompare.(cc|hh) +DEFINE_ERROR(TEST_CHECKSUM_COMPARE_IMPL_NULL, + "Implementation cannot be null. It should be set using " + "Test::Checksum::Compare::make() or Test::Checksum::Compare::set().") +DEFINE_ERROR(TEST_CHECKSUM_COMPARE_REPORTS_NOT_SET, + "Report paths have not been set. This should be done using " + "Test::Checksum::Compare::set_reports().") +DEFINE_ERROR(TEST_CHECKSUM_COMPARE_INCOMING_IMPL_NULL, + "Incoming implementation cannot be null.") +DEFINE_ERROR(TEST_CHECKSUM_COMPARE_IMPL_ALREADY_SET, + "Implementation has already been set.") +DEFINE_ERROR(TEST_CHECKSUM_COMPARE_DIFFERENCES, + "Checksum comparison failed: differences found!") + +// Error codes relating to TestOutcome.(cc|hh) +DEFINE_ERROR(TEST_OUTCOME_UNEXPECTED, "Unexpected outcome.") + +// Error codes related to argument parsing +DEFINE_ERROR(TEST_TOO_FEW_ARGS, "Too few arguments have been supplied.") +DEFINE_ERROR(TEST_FAILED_CL_PARSE, "Failed to parse command-line arguments.") + +#endif // TEST_ON diff --git a/Test/TestOutcome.cc b/Test/TestOutcome.cc index decf45a64e155de6c42579ffdd5f616cae275523..41f2ee9c7d5a183fe63936b00e64c87e8fd1e10b 100755 --- a/Test/TestOutcome.cc +++ b/Test/TestOutcome.cc @@ -9,8 +9,8 @@ #include "TestChecksum.hh" #include "TestChecksumCondition.hh" #include "TestChecksumOutcome.hh" +#include "TestError.hh" -#include <exception> #include <functional> #include <iostream> #include <string> @@ -79,7 +79,7 @@ void expect_failure(const char* const _call, const Outcome& _oc) const Outcome::UnexpectedHandler Outcome::default_unexpected_handler = [] { - throw std::runtime_error("Unexpected outcome"); + TEST_THROW_ERROR(TEST_OUTCOME_UNEXPECTED); }; } // namespace Test diff --git a/Test/TestPaths.cc b/Test/TestPaths.cc index 572463e65b2436ce44ab8a968964091d29694512..bb9b9adad5602ec7bdfcee4e53792ef719894622 100644 --- a/Test/TestPaths.cc +++ b/Test/TestPaths.cc @@ -4,6 +4,7 @@ #include "TestPaths.hh" #include "TestChecksumNumberT.hh" +#include "TestError.hh" #ifdef __APPLE__ #include <boost/filesystem.hpp> @@ -29,22 +30,6 @@ namespace fs = boost::filesystem; namespace fs = std::filesystem; #endif // __APPLE__ -// Similar to BASE_THROW[_if] but the errors are not expected to be converted in -// error codes, so we just throw strings. Throwing exceptions through DLL -// boundaries is not a great idea in general, but in this case it is OK as we -// just want the test executable to fail immediately. -// TODO: Should we move this stuff somewhere more visible? -#define TEST_THROW(EXPR) \ - { \ - Base::OStringStream strm; \ - strm << EXPR; \ - throw std::runtime_error(strm.str); \ - } - -#define TEST_THROW_if(COND, EXPR) \ - if (COND) \ - TEST_THROW(EXPR) - Base::IOutputStream& operator<<(Base::IOutputStream& _os, const fs::path& _path) { return _os << _path.string(); @@ -65,7 +50,7 @@ void make_directory(const fs::path& _dir) // scheduled all together. In this case we can avoid a failure. // TODO: Make this even more secure, e.g., try this check a few more times // (5-10?), sleep the process before each attempt by 0.1sec. - TEST_THROW_if(!fs::exists(chk_dir), + TEST_THROW_MSG_if(!fs::exists(chk_dir), "Failed creating directory " << chk_dir << " in the requested directory path " << _dir); } @@ -95,8 +80,11 @@ void Paths::init(const char* const _in_root, const char* const _in_rel_path, fs::path in_path = in_root / in_rel_path; // Verify arguments - TEST_THROW_if(!fs::is_directory(in_root), in_root << " is not a directory."); - TEST_THROW_if(!fs::is_regular_file(in_path), in_path << " is not a file."); + TEST_THROW_MSG_if( + !fs::is_directory(in_root), in_root << " is not a directory."); + + TEST_THROW_MSG_if( + !fs::is_regular_file(in_path), in_path << " is not a file."); // Determine output directory (see comment in TestPaths.hh) auto out_dir = _out_root ? fs::path(_out_root) : fs::current_path(); @@ -135,7 +123,7 @@ Paths::Paths( #endif // TEST_WAIT_FOR_DEBUG_ATTACH // Check at least 2 arguments (i.e. test-bin arg) have been supplied. - TEST_THROW_if(_argc <= 1, "Test arguments have not been supplied."); + TEST_THROW_ERROR_if(_argc <= 1, TEST_TOO_FEW_ARGS); // Parse the first argument--this could be either the input root directory, or // a direct path to a test file @@ -160,7 +148,7 @@ Paths::Paths( else { // Command-line arguments are not in the correct format. - TEST_THROW("Failed to parse command-line arguments."); + TEST_THROW_ERROR(TEST_FAILED_CL_PARSE); } } diff --git a/Utils/BaseError.hh b/Utils/BaseError.hh index 693d560398247ae9268002e26abc42f3922ecdd4..1258956d9b6f0dcb368b9e552cc9b98318e3e616 100644 --- a/Utils/BaseError.hh +++ b/Utils/BaseError.hh @@ -45,7 +45,7 @@ protected: }//namespace BASE #define BASE_THROW_ERROR(INDEX) { THROW_ERROR_MODULE(Base, INDEX); } -#define BASE_THROW_ERROR_if(COND, INDEX) { if (COND) THROW_ERROR(INDEX); } +#define BASE_THROW_ERROR_if(COND, INDEX) { if (COND) BASE_THROW_ERROR(INDEX); } #define BASE_THROW_ERROR_TODO(MSG) { THROW_ERROR_TODO_MODULE(Base, MSG); } #define BASE_THROW_ERROR_TODO_if(COND, MSG) { if (COND) BASE_THROW_ERROR_TODO(MSG); } diff --git a/Utils/BaseErrorInc.hh b/Utils/BaseErrorInc.hh index e86287995bbaa430855ca5a2a1f071f94f716618..3c2b7827e11d5de8031d3e8dae71ecfe3d0a5225 100644 --- a/Utils/BaseErrorInc.hh +++ b/Utils/BaseErrorInc.hh @@ -4,7 +4,7 @@ #error This file should not be included directly, include the corresponding Outcome/Error header instead #endif -// Make sure the first error code starts from 1, 0 is reserved for success, +// Make sure the first error code starts from 1, 0 is reserved for success, // and it is not an error code. DEFINE_ERROR(SUCCESS, "Success") DEFINE_ERROR(TODO, "TODO: Undefined error message")