From c88413dd58fcbfa1d9b4d34d132029c6025b6c02 Mon Sep 17 00:00:00 2001 From: Malcolm Noyes Date: Fri, 22 Nov 2013 11:47:21 +0000 Subject: [PATCH 1/6] register all tests, begin work on running all tests for 'tag' --- .gitignore | 1 + include/internal/catch_registry_hub.hpp | 94 ++++++++------- include/internal/catch_runner_impl.hpp | 3 +- .../catch_test_case_registry_impl.hpp | 10 +- include/internal/catch_vs_managed_impl.hpp | 66 ++++++++-- include/internal/catch_vs_test_registry.hpp | 113 +++++++----------- include/reporters/catch_vs_reporter.hpp | 6 + 7 files changed, 167 insertions(+), 126 deletions(-) diff --git a/.gitignore b/.gitignore index 45fc0fa8..f256957d 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ Debug Release ipch TestResults +projects/VS2010/TestCatch/Visual Lint *.user *.xcuserstate *.o diff --git a/include/internal/catch_registry_hub.hpp b/include/internal/catch_registry_hub.hpp index 303a843a..a0d1df1e 100644 --- a/include/internal/catch_registry_hub.hpp +++ b/include/internal/catch_registry_hub.hpp @@ -16,64 +16,68 @@ namespace Catch { - namespace { + class RegistryHub : public IRegistryHub, public IMutableRegistryHub { - class RegistryHub : public IRegistryHub, public IMutableRegistryHub { + RegistryHub( RegistryHub const& ); + void operator=( RegistryHub const& ); - RegistryHub( RegistryHub const& ); - void operator=( RegistryHub const& ); + public: // IRegistryHub + RegistryHub() { + } + virtual IReporterRegistry const& getReporterRegistry() const { + return m_reporterRegistry; + } + virtual ITestCaseRegistry const& getTestCaseRegistry() const { + return m_testCaseRegistry; + } + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() { + return m_exceptionTranslatorRegistry; + } - public: // IRegistryHub - RegistryHub() { - } - virtual IReporterRegistry const& getReporterRegistry() const { - return m_reporterRegistry; - } - virtual ITestCaseRegistry const& getTestCaseRegistry() const { - return m_testCaseRegistry; - } - virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() { - return m_exceptionTranslatorRegistry; - } + public: // IMutableRegistryHub + virtual void registerReporter( std::string const& name, IReporterFactory* factory ) { + m_reporterRegistry.registerReporter( name, factory ); + } + virtual void registerTest( TestCase const& testInfo ) { + m_testCaseRegistry.registerTest( testInfo ); + } + virtual void registerTranslator( const IExceptionTranslator* translator ) { + m_exceptionTranslatorRegistry.registerTranslator( translator ); + } - public: // IMutableRegistryHub - virtual void registerReporter( std::string const& name, IReporterFactory* factory ) { - m_reporterRegistry.registerReporter( name, factory ); - } - virtual void registerTest( TestCase const& testInfo ) { - m_testCaseRegistry.registerTest( testInfo ); - } - virtual void registerTranslator( const IExceptionTranslator* translator ) { - m_exceptionTranslatorRegistry.registerTranslator( translator ); - } + private: + TestRegistry m_testCaseRegistry; + ReporterRegistry m_reporterRegistry; + ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; + }; - private: - TestRegistry m_testCaseRegistry; - ReporterRegistry m_reporterRegistry; - ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; - }; - - // Single, global, instance - inline RegistryHub*& getTheRegistryHub() { - static RegistryHub* theRegistryHub = NULL; + // Single, global, instance + template + struct GlobalRegistryHub + { + static T*& instance() + { if( !theRegistryHub ) - theRegistryHub = new RegistryHub(); + theRegistryHub = new T(); return theRegistryHub; } - } + static T* theRegistryHub; + }; + template + T* GlobalRegistryHub::theRegistryHub = NULL; - IRegistryHub& getRegistryHub() { - return *getTheRegistryHub(); + INTERNAL_CATCH_INLINE IRegistryHub& getRegistryHub() { + return *GlobalRegistryHub::instance(); } - IMutableRegistryHub& getMutableRegistryHub() { - return *getTheRegistryHub(); + INTERNAL_CATCH_INLINE IMutableRegistryHub& getMutableRegistryHub() { + return *GlobalRegistryHub::instance(); } - void cleanUp() { - delete getTheRegistryHub(); - getTheRegistryHub() = NULL; + INTERNAL_CATCH_INLINE void cleanUp() { + delete GlobalRegistryHub::instance(); + GlobalRegistryHub::instance() = NULL; cleanUpContext(); } - std::string translateActiveException() { + INTERNAL_CATCH_INLINE std::string translateActiveException() { return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); } diff --git a/include/internal/catch_runner_impl.hpp b/include/internal/catch_runner_impl.hpp index bd392f94..eff066f6 100644 --- a/include/internal/catch_runner_impl.hpp +++ b/include/internal/catch_runner_impl.hpp @@ -279,7 +279,8 @@ namespace Catch { } #ifdef INTERNAL_CATCH_VS_MANAGED // detect CLR catch(AssertFailedException^) { - throw; // CLR always rethrows - stop on first assert + if( aborting() ) + throw; // CLR always rethrows - stop on first assert } #else catch( INTERNAL_CATCH_TEST_FAILURE_EXCEPTION ) { diff --git a/include/internal/catch_test_case_registry_impl.hpp b/include/internal/catch_test_case_registry_impl.hpp index 41714bc8..3fc0680c 100644 --- a/include/internal/catch_test_case_registry_impl.hpp +++ b/include/internal/catch_test_case_registry_impl.hpp @@ -8,7 +8,11 @@ #ifndef TWOBLUECUBES_CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED #define TWOBLUECUBES_CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED +#if defined(INTERNAL_CATCH_VS_MANAGED) || defined(INTERNAL_CATCH_VS_NATIVE) +#include "internal/catch_vs_test_registry.hpp" +#else #include "catch_test_registry.hpp" +#endif #include "catch_test_case_info.h" #include "catch_test_spec.h" #include "catch_context.h" @@ -124,15 +128,15 @@ namespace Catch { /////////////////////////////////////////////////////////////////////////// - AutoReg::AutoReg( TestFunction function, + INTERNAL_CATCH_INLINE AutoReg::AutoReg( TestFunction function, SourceLineInfo const& lineInfo, NameAndDesc const& nameAndDesc ) { registerTestCase( new FreeFunctionTestCase( function ), "", nameAndDesc, lineInfo ); } - AutoReg::~AutoReg() {} + INTERNAL_CATCH_INLINE AutoReg::~AutoReg() {} - void AutoReg::registerTestCase( ITestCase* testCase, + INTERNAL_CATCH_INLINE void AutoReg::registerTestCase( ITestCase* testCase, char const* classOrQualifiedMethodName, NameAndDesc const& nameAndDesc, SourceLineInfo const& lineInfo ) { diff --git a/include/internal/catch_vs_managed_impl.hpp b/include/internal/catch_vs_managed_impl.hpp index df7043a3..7991091f 100644 --- a/include/internal/catch_vs_managed_impl.hpp +++ b/include/internal/catch_vs_managed_impl.hpp @@ -27,11 +27,12 @@ namespace Catch { } #include "internal/catch_timer.hpp" -#include "internal/catch_vs_test_registry.hpp" +#include "internal/catch_reporter_registrars.hpp" #include "reporters/catch_vs_reporter.hpp" +#include "catch_registry_hub.hpp" -#include "internal/catch_exception_translator_registry.hpp" - +//#define OLD (1) +#ifdef OLD namespace Catch { class ExceptionRegistryHub : public IRegistryHub, public IMutableRegistryHub { @@ -70,11 +71,11 @@ namespace Catch { template struct GlobalRegistryHub { - static T& instance() + static T*& instance() { if( !theRegistryHub ) theRegistryHub = new T(); - return *theRegistryHub; + return theRegistryHub; } static T* theRegistryHub; }; @@ -82,13 +83,64 @@ namespace Catch { T* GlobalRegistryHub::theRegistryHub = NULL; INTERNAL_CATCH_INLINE IMutableRegistryHub& getMutableRegistryHub() { - return GlobalRegistryHub::instance(); + return *GlobalRegistryHub::instance(); } INTERNAL_CATCH_INLINE std::string translateActiveException() { - return GlobalRegistryHub::instance().getExceptionTranslatorRegistry().translateActiveException(); + return GlobalRegistryHub::instance()->getExceptionTranslatorRegistry().translateActiveException(); } } +#endif // OLD + +namespace Catch { + inline NonCopyable::~NonCopyable() {} + inline IShared::~IShared() {} + inline StreamBufBase::~StreamBufBase() throw() {} + inline IContext::~IContext() {} + inline IResultCapture::~IResultCapture() {} + inline ITestCase::~ITestCase() {} + inline ITestCaseRegistry::~ITestCaseRegistry() {} + inline IRegistryHub::~IRegistryHub() {} + inline IMutableRegistryHub::~IMutableRegistryHub() {} + inline IExceptionTranslator::~IExceptionTranslator() {} + inline IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() {} + inline IReporter::~IReporter() {} + inline IReporterFactory::~IReporterFactory() {} + inline IReporterRegistry::~IReporterRegistry() {} + inline IStreamingReporter::~IStreamingReporter() {} + inline AssertionStats::~AssertionStats() {} + inline SectionStats::~SectionStats() {} + inline TestCaseStats::~TestCaseStats() {} + inline TestGroupStats::~TestGroupStats() {} + inline TestRunStats::~TestRunStats() {} + //CumulativeReporterBase::SectionNode::~SectionNode() {} + //CumulativeReporterBase::~CumulativeReporterBase() {} + + //StreamingReporterBase::~StreamingReporterBase() {} + //ConsoleReporter::~ConsoleReporter() {} + inline IRunner::~IRunner() {} + inline IMutableContext::~IMutableContext() {} + inline IConfig::~IConfig() {} + //XmlReporter::~XmlReporter() {} + //JunitReporter::~JunitReporter() {} + inline TestRegistry::~TestRegistry() {} + inline FreeFunctionTestCase::~FreeFunctionTestCase() {} + inline IGeneratorInfo::~IGeneratorInfo() {} + inline IGeneratorsForTest::~IGeneratorsForTest() {} + inline TagParser::~TagParser() {} + inline TagExtracter::~TagExtracter() {} + inline TagExpressionParser::~TagExpressionParser() {} + + inline Matchers::Impl::StdString::Equals::~Equals() {} + inline Matchers::Impl::StdString::Contains::~Contains() {} + inline Matchers::Impl::StdString::StartsWith::~StartsWith() {} + inline Matchers::Impl::StdString::EndsWith::~EndsWith() {} + + inline void Config::dummy() {} + + //INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( "xml", XmlReporter ) +} + #endif #endif // TWOBLUECUBES_CATCH_VS_MANAGED_HPP_INCLUDED diff --git a/include/internal/catch_vs_test_registry.hpp b/include/internal/catch_vs_test_registry.hpp index ee1e022b..dfc09c20 100644 --- a/include/internal/catch_vs_test_registry.hpp +++ b/include/internal/catch_vs_test_registry.hpp @@ -16,27 +16,11 @@ #include "catch_common.h" #include "catch_interfaces_testcase.h" #include "internal/catch_compiler_capabilities.h" +#include "internal/clara.h" #include namespace Catch { - typedef void(*TestFunction)(); - - class FreeFunctionTestCase : public SharedImpl { - public: - - FreeFunctionTestCase( TestFunction fun ) : m_fun( fun ) {} - - virtual void invoke() const { - m_fun(); - } - - private: - virtual ~FreeFunctionTestCase(); - - TestFunction m_fun; - }; - class MethodTestCase : public SharedImpl { struct placeholder @@ -105,6 +89,35 @@ struct NameAndDesc { std::string description; }; +struct AutoReg { + + AutoReg( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ); + + template + AutoReg( void (C::*method)(), + char const* className, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ) { + registerTestCase( new MethodTestCase( method ), + className, + nameAndDesc, + lineInfo ); + } + + void registerTestCase( ITestCase* testCase, + char const* className, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ); + + ~AutoReg(); + +private: + AutoReg( AutoReg const& ); + void operator= ( AutoReg const& ); +}; + } // end namespace Catch #if (_MANAGED == 1) || (_M_CEE == 1) // detect CLR @@ -213,27 +226,34 @@ struct NameAndDesc { #define CATCH_INTERNAL_RUN_SINGLE_TEST( Method ) \ { Catch::ConfigData cd; \ cd.name = name_desc.name; \ + cd.abortAfter = 1; \ Catch::Ptr config(new Catch::Config(cd)); \ Catch::MSTestReporter* rep = new Catch::MSTestReporter(config.get()); \ Catch::RunContext tr(config.get(), rep); \ - Catch::TestCase tc = Catch::makeTestCase( new Catch::FreeFunctionTestCase( & Method ), "", name_desc.name, name_desc.description, CATCH_INTERNAL_LINEINFO ); \ - tr.runTest(tc); \ + std::vector testCase = Catch::getRegistryHub().getTestCaseRegistry().getMatchingTestCases(name_desc.name); \ + if( testCase.empty() ) Assert::Fail("No tests match"); \ + if( testCase.size() > 1 ) Assert::Fail("More than one test with the same name"); \ + tr.runTest(*testCase.begin()); \ } #define CATCH_INTERNAL_RUN_SINGLE_CLASS_TEST( ClassMethod ) \ { Catch::ConfigData cd; \ cd.name = name_desc.name; \ + cd.abortAfter = 1; \ Catch::Ptr config(new Catch::Config(cd)); \ Catch::MSTestReporter* rep = new Catch::MSTestReporter(config.get()); \ Catch::RunContext tr(config.get(), rep); \ - Catch::TestCase tc = Catch::makeTestCase( new Catch::MethodTestCase( & ClassMethod ), # ClassMethod, name_desc.name, name_desc.description, CATCH_INTERNAL_LINEINFO ); \ - tr.runTest(tc); \ + std::vector testCase = Catch::getRegistryHub().getTestCaseRegistry().getMatchingTestCases(name_desc.name); \ + if( testCase.empty() ) Assert::Fail("No tests match"); \ + if( testCase.size() > 1 ) Assert::Fail("More than one test with the same name"); \ + tr.runTest(*testCase.begin()); \ } #define INTERNAL_CATCH_TESTCASE2( UniqueExt, Name, Desc ) \ CHECK_FOR_TEST_CASE_CLASH \ static void INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____, UniqueExt )(); \ namespace CATCH_INTERNAL_NAMESPACE( UniqueExt ) { \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( & INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____, UniqueExt ), CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc(Name, Desc) ); \ INTERNAL_CATCH_CLASS_DEFINITION( INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____C_L_A_S_S___, UniqueExt ) ) \ { \ INTERNAL_CATCH_CLASS_CONTEXT \ @@ -245,6 +265,7 @@ struct NameAndDesc { #define INTERNAL_CATCH_METHOD_AS_TEST_CASE2( QualifiedMethod, UniqueExt, Name, Desc ) \ CHECK_FOR_TEST_CASE_CLASH \ namespace CATCH_INTERNAL_NAMESPACE( UniqueExt ) { \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( & QualifiedMethod, "&" # QualifiedMethod, Catch::NameAndDesc(Name, Desc), CATCH_INTERNAL_LINEINFO ); \ INTERNAL_CATCH_CLASS_DEFINITION( INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____C_L_A_S_S___, UniqueExt ) ) \ { \ INTERNAL_CATCH_CLASS_CONTEXT \ @@ -259,6 +280,7 @@ struct NameAndDesc { static void invoke() { INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____, UniqueExt ) tmp; tmp.test(); } \ }; \ namespace CATCH_INTERNAL_NAMESPACE( UniqueExt ) { \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( & INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____, UniqueExt )::invoke, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc(TestName, Desc) ); \ INTERNAL_CATCH_CLASS_DEFINITION( INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____C_L_A_S_S___, UniqueExt ) ) \ { \ INTERNAL_CATCH_CLASS_CONTEXT \ @@ -320,55 +342,6 @@ struct NameAndDesc { #include "catch_exception_translator_registry.hpp" -namespace Catch { - inline NonCopyable::~NonCopyable() {} - inline IShared::~IShared() {} - inline StreamBufBase::~StreamBufBase() throw() {} - inline IContext::~IContext() {} - inline IResultCapture::~IResultCapture() {} - inline ITestCase::~ITestCase() {} - inline ITestCaseRegistry::~ITestCaseRegistry() {} - inline IRegistryHub::~IRegistryHub() {} - inline IMutableRegistryHub::~IMutableRegistryHub() {} - inline IExceptionTranslator::~IExceptionTranslator() {} - inline IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() {} - inline IReporter::~IReporter() {} - inline IReporterFactory::~IReporterFactory() {} - inline IReporterRegistry::~IReporterRegistry() {} - inline IStreamingReporter::~IStreamingReporter() {} - inline AssertionStats::~AssertionStats() {} - inline SectionStats::~SectionStats() {} - inline TestCaseStats::~TestCaseStats() {} - inline TestGroupStats::~TestGroupStats() {} - inline TestRunStats::~TestRunStats() {} - //CumulativeReporterBase::SectionNode::~SectionNode() {} - //CumulativeReporterBase::~CumulativeReporterBase() {} - - //StreamingReporterBase::~StreamingReporterBase() {} - //ConsoleReporter::~ConsoleReporter() {} - inline IRunner::~IRunner() {} - inline IMutableContext::~IMutableContext() {} - inline IConfig::~IConfig() {} - //XmlReporter::~XmlReporter() {} - //JunitReporter::~JunitReporter() {} - //TestRegistry::~TestRegistry() {} - inline FreeFunctionTestCase::~FreeFunctionTestCase() {} - inline IGeneratorInfo::~IGeneratorInfo() {} - inline IGeneratorsForTest::~IGeneratorsForTest() {} - inline TagParser::~TagParser() {} - inline TagExtracter::~TagExtracter() {} - inline TagExpressionParser::~TagExpressionParser() {} - - inline Matchers::Impl::StdString::Equals::~Equals() {} - inline Matchers::Impl::StdString::Contains::~Contains() {} - inline Matchers::Impl::StdString::StartsWith::~StartsWith() {} - inline Matchers::Impl::StdString::EndsWith::~EndsWith() {} - - inline void Config::dummy() {} - - //INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( "xml", XmlReporter ) -} - #ifdef __clang__ #pragma clang diagnostic pop #endif diff --git a/include/reporters/catch_vs_reporter.hpp b/include/reporters/catch_vs_reporter.hpp index 29f0be57..0f9c9966 100644 --- a/include/reporters/catch_vs_reporter.hpp +++ b/include/reporters/catch_vs_reporter.hpp @@ -46,6 +46,12 @@ namespace Catch { #endif // detect CLR struct MSTestReporter : SharedImpl { + MSTestReporter( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + m_headerPrinted( false ), + m_atLeastOneTestCasePrinted( false ) + {} + MSTestReporter( Ptr const& _fullConfig ) : m_config( _fullConfig ), m_headerPrinted( false ), From 7d7ecc130fce9c9c6fc09c7433068e204be7ef0d Mon Sep 17 00:00:00 2001 From: Malcolm Noyes Date: Mon, 9 Dec 2013 13:58:16 +0000 Subject: [PATCH 2/6] Excluide Mbcs from repository --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index f256957d..9ed4427e 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,11 @@ Release ipch TestResults projects/VS2010/TestCatch/Visual Lint +projects/VS2010/ManagedTestCatch/Visual Lint +projects/VS2012/ManagedTestCatch/Visual Lint +projects/VS2012/NativeTestCatch/Visual Lint +DebugMbcs +ReleaseMbcs *.user *.xcuserstate *.o From dd8c13a9e1764e4454f5ea24a91a59195d4d39b1 Mon Sep 17 00:00:00 2001 From: Malcolm Noyes Date: Mon, 9 Dec 2013 14:01:22 +0000 Subject: [PATCH 3/6] Draft for filtering using tags --- docs/vs/VScommandlinetags.md | 8 ++++++++ docs/vs/vs-index.md | 12 ++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 docs/vs/VScommandlinetags.md diff --git a/docs/vs/VScommandlinetags.md b/docs/vs/VScommandlinetags.md new file mode 100644 index 00000000..6a38134d --- /dev/null +++ b/docs/vs/VScommandlinetags.md @@ -0,0 +1,8 @@ +If you've used the Catch command line, you might know that Catch can selectively include and exclude tests using tags. For example, you might want to exclude long running tests from each check-in to your CI server, so you might tag those tests with `[slow]` and run Catch with `example.exe ~[slow]`. + +It doesn't look like it's possible to reproduce the flexibility of Catch but it is possible to do some basic filtering using Catch tags. + + +--- + +[Home](../../README.md) \ No newline at end of file diff --git a/docs/vs/vs-index.md b/docs/vs/vs-index.md index a117b0d3..7b47e7a7 100644 --- a/docs/vs/vs-index.md +++ b/docs/vs/vs-index.md @@ -20,7 +20,15 @@ We can do the same for VS2012 - [see this page for instructions on how to do thi # Running tests from the command line -Tests can also be run from the command line. [For VS2010 see these details](VS2010commandline.md) and [for VS2012 see these](VS2012commandline.md) +Tests can also be run from the command line. [For VS2010 see these details](VS2010commandline.md) and [for VS2012 see these](VS2012commandline.md). + +# Running tests using Catch tags + +If you've used the Catch command line, you'll know that Catch can selectively include and exclude tests using tags. For example, you might want to exclude long running tests from each check-in to your CI server, so you might tag those tests with `[slow]` and run Catch with `example.exe ~[slow]`. + +It doesn't look like it's possible to reproduce the flexibility of Catch but it is possible to do some basic filtering using Catch tags. + +[This page details how to do this.](VScommandlinetags.md) # Differences in behaviour @@ -34,7 +42,7 @@ There are some minor differences in behaviour between Catch and Visual Studio pr You can still use the same names that you would normally use for Catch TEST_CASE names, however we use an internal name for the actual function name that the test lives in. This means that you won't see the Catch names in the Test View (VS2010) unless you 'Group By' Description, or in VS2012 you select the 'Traits' view Test Explorer - see the screen shots above. -## Catch tests always stop on first failure. +## When running from the GUI, Catch tests always stop on first failure. A Catch test that uses SECTIONS will continue to run further sections if one fails; in VS this doesn't make much sense because in a visual environment we want to capture the context of where the test failed. If we allowed the test to continue then we lose this context, so instead we always stop on each failure. You can still use the CHECK macros if you don't want the test to stop. From 36cb9672207b013ea0efb3d6d2a122524289020a Mon Sep 17 00:00:00 2001 From: Malcolm Noyes Date: Mon, 9 Dec 2013 14:18:35 +0000 Subject: [PATCH 4/6] Update to allow all self tests to be run and to allow running with tags --- include/catch.hpp | 13 +- include/catch_runner.hpp | 4 + include/internal/catch_capture.hpp | 20 +- include/internal/catch_config.hpp | 147 +++ include/internal/catch_console_colour.hpp | 10 - .../internal/catch_console_colour_impl.hpp | 51 +- include/internal/catch_context_impl.hpp | 30 +- include/internal/catch_interfaces_capture.h | 17 + include/internal/catch_interfaces_runner.h | 4 + include/internal/catch_runner_impl.hpp | 171 ++- include/internal/catch_timer.hpp | 20 +- include/internal/catch_tostring.hpp | 4 + include/internal/catch_vs_managed_impl.hpp | 75 +- include/internal/catch_vs_native_impl.hpp | 96 +- include/internal/catch_vs_test_registry.hpp | 234 ++-- include/reporters/catch_vs_reporter.hpp | 15 +- projects/SelfTest/RunAllTests.cpp | 187 +++ projects/SelfTest/TestMain.cpp | 1033 +++++++++-------- projects/SelfTest/VisualStudioTests.cpp | 110 ++ projects/SelfTest/catch_self_test.cpp | 2 + projects/SelfTest/makefile | 2 - 21 files changed, 1402 insertions(+), 843 deletions(-) create mode 100644 projects/SelfTest/RunAllTests.cpp create mode 100644 projects/SelfTest/VisualStudioTests.cpp diff --git a/include/catch.hpp b/include/catch.hpp index 778f2c8e..9da979f6 100644 --- a/include/catch.hpp +++ b/include/catch.hpp @@ -19,6 +19,7 @@ #if (_MANAGED == 1) || (_M_CEE == 1) // detect CLR #define INTERNAL_CATCH_VS_MANAGED + #define INTERNAL_CATCH_INLINE inline #else #if defined(_WINDLL) @@ -26,6 +27,7 @@ // It's possible that this is not enough for someone so allow it to be overridden... #if !defined( CATCH_CONFIG_MAIN ) && !defined( CATCH_CONFIG_RUNNER ) #define INTERNAL_CATCH_VS_NATIVE + #define INTERNAL_CATCH_INLINE inline #endif #endif @@ -52,7 +54,6 @@ #endif #if defined(INTERNAL_CATCH_VS_MANAGED) || defined(INTERNAL_CATCH_VS_NATIVE) - #define INTERNAL_CATCH_INLINE inline #ifdef INTERNAL_CATCH_VS_MANAGED #include "internal/catch_vs_managed_impl.hpp" #else // INTERNAL_CATCH_VS_MANAGED @@ -203,6 +204,16 @@ #define THEN( desc ) SECTION( " Then: " desc, "" ) #define AND_THEN( desc ) SECTION( " And: " desc, "" ) +#if defined(INTERNAL_CATCH_VS_MANAGED) || defined(INTERNAL_CATCH_VS_NATIVE) +#define CATCH_MAP_CATEGORY_TO_TAG( Category, Tag ) INTERNAL_CATCH_MAP_CATEGORY_TO_TAG( Category, Tag ) +#define CATCH_CONFIG_SHOW_SUCCESS( v ) CATCH_INTERNAL_CONFIG_SHOW_SUCCESS( v ) +#define CATCH_CONFIG_WARN_MISSING_ASSERTIONS( v ) CATCH_INTERNAL_CONFIG_WARN_MISSING_ASSERTIONS( v ) +#else +#define CATCH_MAP_CATEGORY_TO_TAG( Category, Tag ) +#define CATCH_CONFIG_SHOW_SUCCESS( v ) +#define CATCH_CONFIG_WARN_MISSING_ASSERTIONS( v ) +#endif + using Catch::Detail::Approx; #ifdef __clang__ diff --git a/include/catch_runner.hpp b/include/catch_runner.hpp index d72ab5aa..52a475e8 100644 --- a/include/catch_runner.hpp +++ b/include/catch_runner.hpp @@ -108,6 +108,8 @@ namespace Catch { std::set m_testsAlreadyRun; }; +#if !defined(INTERNAL_CATCH_VS_MANAGED) && !defined(INTERNAL_CATCH_VS_NATIVE) + class Session { static bool alreadyInstantiated; @@ -232,6 +234,8 @@ namespace Catch { bool Session::alreadyInstantiated = false; +#endif // !VS_MANAGED && !VS_NATIVE + } // end namespace Catch #endif // TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED diff --git a/include/internal/catch_capture.hpp b/include/internal/catch_capture.hpp index eaef9237..7f4f0468 100644 --- a/include/internal/catch_capture.hpp +++ b/include/internal/catch_capture.hpp @@ -56,11 +56,7 @@ namespace Catch { .setResultType( matcher.match( arg ) ); } -#if defined(INTERNAL_CATCH_VS_MANAGED) - // TestFailureException not defined for CLR -#else // detect CLR -struct TestFailureException{}; -#endif + struct TestFailureException{}; } // end namespace Catch @@ -70,7 +66,6 @@ struct TestFailureException{}; #if !defined(INTERNAL_CATCH_VS_MANAGED) && !defined(INTERNAL_CATCH_VS_NATIVE) // normal Catch - #define INTERNAL_CATCH_TEST_FAILURE_EXCEPTION const Catch::TestFailureException& #define INTERNAL_CATCH_TEST_THROW_FAILURE throw Catch::TestFailureException(); #else // VS integration @@ -84,10 +79,9 @@ struct TestFailureException{}; std::stringstream _sf; \ _sf << r->getExpressionInMacro().c_str() << ", " << r->getMessage().c_str(); \ std::string fail = _sf.str(); \ - Assert::Fail(Catch::convert_string_to_managed(fail)); \ + Assert::Fail(Catch::convert_string_for_assert(fail)); \ } - #define INTERNAL_CATCH_TEST_FAILURE_EXCEPTION AssertFailedException^ #else #if defined(INTERNAL_CATCH_VS_NATIVE) @@ -106,8 +100,6 @@ struct TestFailureException{}; Assert::Fail(ws2.c_str(), &li); \ } - #define INTERNAL_CATCH_TEST_FAILURE_EXCEPTION const Catch::TestFailureException& - #endif // INTERNAL_CATCH_VS_MANAGED #endif // detect CLR @@ -119,7 +111,7 @@ struct TestFailureException{}; if( Catch::ResultAction::Value internal_catch_action = Catch::getResultCapture().acceptExpression( evaluatedExpr, INTERNAL_CATCH_ASSERTIONINFO_NAME ) ) { \ if( internal_catch_action & Catch::ResultAction::Debug ) BreakIntoDebugger(); \ if( internal_catch_action & Catch::ResultAction::Abort ) { INTERNAL_CATCH_TEST_THROW_FAILURE } \ - if( !Catch::shouldContinueOnFailure( resultDisposition ) ) { INTERNAL_CATCH_TEST_THROW_FAILURE } \ + if( !Catch::shouldContinueOnFailure( resultDisposition ) ) { throw Catch::TestFailureException(); } \ Catch::isTrue( false && originalExpr ); \ } @@ -133,7 +125,7 @@ struct TestFailureException{}; INTERNAL_CATCH_ACCEPT_INFO( #expr, macroName, resultDisposition ); \ try { \ INTERNAL_CATCH_ACCEPT_EXPR( ( Catch::ExpressionDecomposer()->*expr ).endExpression( resultDisposition ), resultDisposition, expr ); \ - } catch( INTERNAL_CATCH_TEST_FAILURE_EXCEPTION ) { \ + } catch( const Catch::TestFailureException& ) { \ throw; \ } catch( ... ) { \ INTERNAL_CATCH_ACCEPT_EXPR( Catch::ExpressionResultBuilder( Catch::ResultWas::ThrewException ) << Catch::translateActiveException(), \ @@ -172,7 +164,7 @@ struct TestFailureException{}; INTERNAL_CATCH_ACCEPT_EXPR( Catch::ExpressionResultBuilder( Catch::ResultWas::DidntThrowException ), resultDisposition, false ); \ } \ } \ - catch( INTERNAL_CATCH_TEST_FAILURE_EXCEPTION ) { \ + catch( const Catch::TestFailureException& ) { \ throw; \ } \ catch( exceptionType ) { \ @@ -216,7 +208,7 @@ struct TestFailureException{}; INTERNAL_CATCH_ACCEPT_INFO( #arg " " #matcher, macroName, resultDisposition ); \ try { \ INTERNAL_CATCH_ACCEPT_EXPR( ( Catch::expressionResultBuilderFromMatcher( ::Catch::Matchers::matcher, arg, #matcher ) ), resultDisposition, false ); \ - } catch( INTERNAL_CATCH_TEST_FAILURE_EXCEPTION ) { \ + } catch( const Catch::TestFailureException& ) { \ throw; \ } catch( ... ) { \ INTERNAL_CATCH_ACCEPT_EXPR( ( Catch::ExpressionResultBuilder( Catch::ResultWas::ThrewException ) << Catch::translateActiveException() ), \ diff --git a/include/internal/catch_config.hpp b/include/internal/catch_config.hpp index f561b260..301d3979 100644 --- a/include/internal/catch_config.hpp +++ b/include/internal/catch_config.hpp @@ -22,6 +22,138 @@ #define CATCH_CONFIG_CONSOLE_WIDTH 80 #endif +namespace CatchOverrides { + + class ConfigGuard + { + public: + ConfigGuard() + : origConfig(Catch::getCurrentContext().getConfig()) + {} + ~ConfigGuard() + { + Catch::getCurrentMutableContext().setConfig(origConfig); + } + const Catch::Ptr& value() const {return origConfig;} + private: + ConfigGuard(const ConfigGuard&); + ConfigGuard& operator=(const ConfigGuard&); + + const Catch::Ptr origConfig; + }; + + template + class Config + { + typedef std::map LineData; + typedef std::map FileLineData; + public: + bool includeSuccessfulResults(const std::string& file, int c) const + { + bool result(false); + FileLineData::const_iterator it = showSuccessfulTestsData.find(file); + if( it != showSuccessfulTestsData.end() ) + { + for( LineData::const_iterator lineIt = it->second.begin(); lineIt != it->second.end(); ++lineIt ) + { + if( c <= lineIt->first ) + break; + result = lineIt->second; + } + } + return result; + } + void insertSuccessfulResults(const std::string& file, int c, bool v) + { + FileLineData::iterator it = showSuccessfulTestsData.find(file); + if( it == showSuccessfulTestsData.end() ) + { + LineData tmp; + tmp.insert(std::make_pair(c,v)); + showSuccessfulTestsData.insert(std::make_pair(file, tmp)); + } + else + { + it->second.insert(std::make_pair(c,v)); + } + } + bool warnAboutMissingAssertions(const std::string& file, int c) const + { + bool result(false); + FileLineData::const_iterator it = missingAssertionData.find(file); + if( it != missingAssertionData.end() ) + { + for( LineData::const_iterator lineIt = it->second.begin(); lineIt != it->second.end(); ++lineIt ) + { + if( c <= lineIt->first ) + break; + result = lineIt->second; + } + } + return result; + } + void insertMissingAssertions(const std::string& file, int c, bool v) + { + FileLineData::iterator it = missingAssertionData.find(file); + if( it == missingAssertionData.end() ) + { + LineData tmp; + tmp.insert(std::make_pair(c,v)); + missingAssertionData.insert(std::make_pair(file, tmp)); + } + else + { + it->second.insert(std::make_pair(c,v)); + } + } + static Config& instance() + { + if( !s_instance ) + { + s_instance = new Config(); + } + return *s_instance; + } + private: + FileLineData showSuccessfulTestsData; + FileLineData missingAssertionData; + + static Config* s_instance; + }; + template + Config* Config::s_instance = NULL; + + template + struct ConfigReset + { + ConfigReset( const std::string& file, int c ) + { + Config::instance().insertSuccessfulResults(file, c, false); + Config::instance().insertMissingAssertions(file, c, false); + } + }; + + template + struct ConfigShowSuccessfulTests + { + template + ConfigShowSuccessfulTests( const std::string& file, int c, U v ) + { + Config::instance().insertSuccessfulResults(file, c, v ? true : false); + } + }; + + template + struct ConfigWarnMissingAssertions + { + template + ConfigWarnMissingAssertions( const std::string& file, int c, U v ) + { + Config::instance().insertMissingAssertions(file, c, v ? true : false); + } + }; +} + namespace Catch { struct ConfigData { @@ -40,6 +172,21 @@ namespace Catch { showDurations( ShowDurations::DefaultForReporter ) {} + explicit ConfigData(const IConfig* other) + : listTests( false ), + listTags( false ), + listReporters( false ), + showSuccessfulTests( other ? other->includeSuccessfulResults() : false ), + shouldDebugBreak( false ), + noThrow( other ? !other->allowThrows() : false ), + showHelp( false ), + abortAfter( -1 ), + verbosity( Verbosity::Normal ), + warnings( other ? (other->warnAboutMissingAssertions() ? WarnAbout::NoAssertions : WarnAbout::Nothing) : WarnAbout::Nothing ), + showDurations( other ? other->showDurations() : ShowDurations::DefaultForReporter ), + name( other ? other->name() : std::string() ) + {} + bool listTests; bool listTags; bool listReporters; diff --git a/include/internal/catch_console_colour.hpp b/include/internal/catch_console_colour.hpp index 3fc2e38b..40a6c999 100644 --- a/include/internal/catch_console_colour.hpp +++ b/include/internal/catch_console_colour.hpp @@ -12,10 +12,6 @@ namespace Catch { - namespace Detail { - struct IColourImpl; - } - struct Colour { enum Code { None = 0, @@ -53,12 +49,6 @@ namespace Catch { // Use constructed object for RAII guard Colour( Code _colourCode ); ~Colour(); - - // Use static method for one-shot changes - static void use( Code _colourCode ); - - private: - static Detail::IColourImpl* impl; }; } // end namespace Catch diff --git a/include/internal/catch_console_colour_impl.hpp b/include/internal/catch_console_colour_impl.hpp index 0183f785..bfe37b03 100644 --- a/include/internal/catch_console_colour_impl.hpp +++ b/include/internal/catch_console_colour_impl.hpp @@ -10,13 +10,6 @@ #include "catch_console_colour.hpp" -namespace Catch { namespace Detail { - struct IColourImpl { - virtual ~IColourImpl() {} - virtual void use( Colour::Code _colourCode ) = 0; - }; -}} - #if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// #ifndef NOMINMAX @@ -32,7 +25,7 @@ namespace Catch { namespace Detail { namespace Catch { namespace { - class Win32ColourImpl : public Detail::IColourImpl { + class Win32ColourImpl { public: Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) { @@ -41,7 +34,7 @@ namespace { originalAttributes = csbiInfo.wAttributes; } - virtual void use( Colour::Code _colourCode ) { + void use( Colour::Code _colourCode ) { switch( _colourCode ) { case Colour::None: return setTextAttribute( originalAttributes ); case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); @@ -73,7 +66,7 @@ namespace { return true; } - Win32ColourImpl platformColourImpl; + typedef Win32ColourImpl PlatformColourImpl; } // end anon namespace } // end namespace Catch @@ -89,9 +82,9 @@ namespace { // Thanks to Adam Strzelecki for original contribution // (http://github.com/nanoant) // https://github.com/philsquared/Catch/pull/131 - class PosixColourImpl : public Detail::IColourImpl { + class PosixColourImpl { public: - virtual void use( Colour::Code _colourCode ) { + void use( Colour::Code _colourCode ) { switch( _colourCode ) { case Colour::None: case Colour::White: return setColour( "[0m" ); @@ -120,7 +113,7 @@ namespace { return isatty( fileno(stdout) ); } - PosixColourImpl platformColourImpl; + typedef PosixColourImpl PlatformColourImpl; } // end anon namespace } // end namespace Catch @@ -129,24 +122,24 @@ namespace { namespace Catch { - namespace { - struct NoColourImpl : Detail::IColourImpl { - void use( Colour::Code ) {} - }; - NoColourImpl noColourImpl; - static const bool shouldUseColour = shouldUseColourForPlatform() && - !isDebuggerActive(); - } + template + struct ColourChange + { + static Impl impl; + static const bool shouldUseColour; + }; + template + Impl ColourChange::impl; + template + const bool ColourChange::shouldUseColour = shouldUseColourForPlatform() && + !isDebuggerActive();; - Colour::Colour( Code _colourCode ){ use( _colourCode ); } - Colour::~Colour(){ use( None ); } - void Colour::use( Code _colourCode ) { - impl->use( _colourCode ); + INTERNAL_CATCH_INLINE Colour::Colour( Code _colourCode ) { + if( ColourChange::shouldUseColour ) ColourChange::impl.use( _colourCode ); + } + INTERNAL_CATCH_INLINE Colour::~Colour() { + if( ColourChange::shouldUseColour ) ColourChange::impl.use( Colour::None ); } - - Detail::IColourImpl* Colour::impl = shouldUseColour - ? static_cast( &platformColourImpl ) - : static_cast( &noColourImpl ); } // end namespace Catch diff --git a/include/internal/catch_context_impl.hpp b/include/internal/catch_context_impl.hpp index d70ea358..7d217e5c 100644 --- a/include/internal/catch_context_impl.hpp +++ b/include/internal/catch_context_impl.hpp @@ -15,9 +15,10 @@ namespace Catch { + template class Context : public IMutableContext { - Context() : m_config( NULL ), m_runner( NULL ), m_resultCapture( NULL ) {} + Context() : m_config( NULL ), m_runner( &nullRunner ), m_resultCapture( &nullResultCapture ) {} Context( Context const& ); void operator=( Context const& ); @@ -81,15 +82,28 @@ namespace Catch { IRunner* m_runner; IResultCapture* m_resultCapture; std::map m_generatorsByTestName; + + static ResultCapture nullResultCapture; + static Runner nullRunner; + public: + static Context* currentContext; }; - namespace { + template + ResultCapture Context::nullResultCapture; + template + Runner Context::nullRunner; + template + Context* Context::currentContext = NULL; + + /*namespace { Context* currentContext = NULL; - } + }*/ + typedef Context DefaultContext; INTERNAL_CATCH_INLINE IMutableContext& getCurrentMutableContext() { - if( !currentContext ) - currentContext = new Context(); - return *currentContext; + if( !DefaultContext::currentContext ) + DefaultContext::currentContext = new DefaultContext(); + return *DefaultContext::currentContext; } INTERNAL_CATCH_INLINE IContext& getCurrentContext() { return getCurrentMutableContext(); @@ -104,8 +118,8 @@ namespace Catch { } INTERNAL_CATCH_INLINE void cleanUpContext() { - delete currentContext; - currentContext = NULL; + delete DefaultContext::currentContext; + DefaultContext::currentContext = NULL; } } diff --git a/include/internal/catch_interfaces_capture.h b/include/internal/catch_interfaces_capture.h index 2601ab46..db9958ec 100644 --- a/include/internal/catch_interfaces_capture.h +++ b/include/internal/catch_interfaces_capture.h @@ -41,6 +41,23 @@ namespace Catch { virtual std::string getCurrentTestName() const = 0; virtual const AssertionResult* getLastResult() const = 0; }; + + struct NullResultCapture : public IResultCapture { + + virtual void assertionEnded( AssertionResult const& ) {} + virtual bool sectionStarted( SectionInfo const& , + Counts& ) {return false;} + virtual void sectionEnded( SectionInfo const& , Counts const& , double ) {} + virtual void pushScopedMessage( MessageInfo const& ) {} + virtual void popScopedMessage( MessageInfo const& ) {} + + virtual bool shouldDebugBreak() const {return false;} + + virtual ResultAction::Value acceptExpression( ExpressionResultBuilder const& , AssertionInfo const& ) {return ResultAction::Abort;} + + virtual std::string getCurrentTestName() const {return std::string();} + virtual const AssertionResult* getLastResult() const {return NULL;} + }; } #endif // TWOBLUECUBES_CATCH_INTERFACES_CAPTURE_H_INCLUDED diff --git a/include/internal/catch_interfaces_runner.h b/include/internal/catch_interfaces_runner.h index ab127b9f..48c836aa 100644 --- a/include/internal/catch_interfaces_runner.h +++ b/include/internal/catch_interfaces_runner.h @@ -18,6 +18,10 @@ namespace Catch { struct IRunner { virtual ~IRunner(); }; + + struct NullRunner : public IRunner + { + }; } #endif // TWOBLUECUBES_CATCH_INTERFACES_RUNNER_H_INCLUDED diff --git a/include/internal/catch_runner_impl.hpp b/include/internal/catch_runner_impl.hpp index eff066f6..20694851 100644 --- a/include/internal/catch_runner_impl.hpp +++ b/include/internal/catch_runner_impl.hpp @@ -48,7 +48,6 @@ namespace Catch { }; /////////////////////////////////////////////////////////////////////////// - class RunContext : public IResultCapture, public IRunner { RunContext( RunContext const& ); @@ -105,18 +104,16 @@ namespace Catch { } Totals runTest( TestCase const& testCase ) { - Totals prevTotals = m_totals; std::string redirectedCout; std::string redirectedCerr; TestCaseInfo testInfo = testCase.getTestCaseInfo(); - m_reporter->testCaseStarting( testInfo ); + UnwindTestCaseOnCompletion finaliser(*this, m_totals, m_reporter, testInfo, redirectedCout, redirectedCerr); m_activeTestCase = &testCase; m_testCaseTracker = TestCaseTracker( testInfo.name ); - do { do { runCurrentTest( redirectedCout, redirectedCerr ); @@ -125,18 +122,10 @@ namespace Catch { } while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() ); - Totals deltaTotals = m_totals.delta( prevTotals ); - m_totals.testCases += deltaTotals.testCases; - m_reporter->testCaseEnded( TestCaseStats( testInfo, - deltaTotals, - redirectedCout, - redirectedCerr, - aborting() ) ); - m_activeTestCase = NULL; m_testCaseTracker.reset(); - return deltaTotals; + return finaliser.report(); } Ptr config() const { @@ -194,12 +183,7 @@ namespace Catch { return true; } - virtual void sectionEnded( SectionInfo const& info, Counts const& prevAssertions, double _durationInSeconds ) { - if( std::uncaught_exception() ) { - m_unfinishedSections.push_back( UnfinishedSections( info, prevAssertions, _durationInSeconds ) ); - return; - } - + void unwindSection(SectionInfo const& info, Counts const& prevAssertions, double _durationInSeconds ) { Counts assertions = m_totals.assertions - prevAssertions; bool missingAssertions = testForMissingAssertions( assertions ); @@ -209,6 +193,15 @@ namespace Catch { m_messages.clear(); } + virtual void sectionEnded( SectionInfo const& info, Counts const& prevAssertions, double _durationInSeconds ) { + if( std::uncaught_exception() ) { + m_unfinishedSections.push_back( UnfinishedSections( info, prevAssertions, _durationInSeconds ) ); + return; + } + + unwindSection(info, prevAssertions, _durationInSeconds); + } + virtual void pushScopedMessage( MessageInfo const& message ) { m_messages.push_back( message ); } @@ -257,16 +250,13 @@ namespace Catch { void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ) { TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); - SectionInfo testCaseSection( testCaseInfo.name, testCaseInfo.description, testCaseInfo.lineInfo ); - m_reporter->sectionStarting( testCaseSection ); - Counts prevAssertions = m_totals.assertions; - double duration = 0; + + UnwindSectionOnCompletion finaliser(*this, m_totals, m_reporter, testCaseInfo, m_unfinishedSections, m_messages); try { m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal ); TestCaseTracker::Guard guard( *m_testCaseTracker ); - Timer timer; - timer.start(); + finaliser.startTimer(); if( m_reporter->getPreferences().shouldRedirectStdOut ) { StreamRedirect coutRedir( std::cout, redirectedCout ); StreamRedirect cerrRedir( std::cerr, redirectedCerr ); @@ -275,38 +265,16 @@ namespace Catch { else { m_activeTestCase->invoke(); } - duration = timer.getElapsedSeconds(); + finaliser.stopTimer(); } -#ifdef INTERNAL_CATCH_VS_MANAGED // detect CLR - catch(AssertFailedException^) { - if( aborting() ) - throw; // CLR always rethrows - stop on first assert - } -#else - catch( INTERNAL_CATCH_TEST_FAILURE_EXCEPTION ) { + catch( const Catch::TestFailureException& ) { // This just means the test was aborted due to failure } -#endif catch(...) { ExpressionResultBuilder exResult( ResultWas::ThrewException ); exResult << translateActiveException(); actOnCurrentResult( exResult.buildResult( m_lastAssertionInfo ) ); } - // If sections ended prematurely due to an exception we stored their - // infos here so we can tear them down outside the unwind process. - for( std::vector::const_iterator it = m_unfinishedSections.begin(), - itEnd = m_unfinishedSections.end(); - it != itEnd; - ++it ) - sectionEnded( it->info, it->prevAssertions, it->durationInSeconds ); - m_unfinishedSections.clear(); - m_messages.clear(); - - Counts assertions = m_totals.assertions - prevAssertions; - bool missingAssertions = testForMissingAssertions( assertions ); - - SectionStats testCaseSectionStats( testCaseSection, assertions, duration, missingAssertions ); - m_reporter->sectionEnded( testCaseSectionStats ); } private: @@ -320,6 +288,111 @@ namespace Catch { double durationInSeconds; }; + class UnwindSectionOnCompletion + { + public: + UnwindSectionOnCompletion(RunContext& context, Totals& totals, Ptr& reporter, TestCaseInfo const& testCaseInfo, + std::vector& unfinishedSections, std::vector& messages) + : m_context(context) + , m_totals(totals) + , m_reporter(reporter) + , m_testCaseSection( testCaseInfo.name, testCaseInfo.description, testCaseInfo.lineInfo ) + , m_unfinishedSections(unfinishedSections) + , m_messages(messages) + , m_duration(0.0) + { + m_prevAssertions = m_totals.assertions; + m_reporter->sectionStarting( m_testCaseSection ); + } + ~UnwindSectionOnCompletion() + { + // If sections ended prematurely due to an exception we stored their + // infos here so we can tear them down. + for( std::vector::const_iterator it = m_unfinishedSections.begin(), + itEnd = m_unfinishedSections.end(); + it != itEnd; + ++it ) { + m_context.unwindSection( it->info, it->prevAssertions, it->durationInSeconds ); + } + m_unfinishedSections.clear(); + m_messages.clear(); + + Counts assertions = m_totals.assertions - m_prevAssertions; + bool missingAssertions = m_context.testForMissingAssertions( assertions ); + + SectionStats testCaseSectionStats( m_testCaseSection, assertions, m_duration, missingAssertions ); + m_reporter->sectionEnded( testCaseSectionStats ); + } + void startTimer() + { + m_timer.start(); + } + void stopTimer() + { + m_duration = m_timer.getElapsedSeconds(); + } + private: + // non-copyable + UnwindSectionOnCompletion(const UnwindSectionOnCompletion&); + UnwindSectionOnCompletion& operator=(const UnwindSectionOnCompletion&); + + RunContext& m_context; + Totals& m_totals; + Ptr& m_reporter; + SectionInfo m_testCaseSection; + std::vector& m_unfinishedSections; + std::vector& m_messages; + Timer m_timer; + Counts m_prevAssertions; + double m_duration; + }; + + class UnwindTestCaseOnCompletion + { + public: + UnwindTestCaseOnCompletion(RunContext& context, Totals& totals, Ptr& reporter, TestCaseInfo& testInfo, + std::string& redirectedCout, std::string& redirectedCerr) + : m_context(context), m_totals(totals), m_reporter(reporter), m_testInfo(testInfo) + , m_redirectedCout(redirectedCout), m_redirectedCerr(redirectedCerr) + , m_reported(false) + { + m_prevTotals = m_totals; + m_reporter->testCaseStarting( m_testInfo ); + } + ~UnwindTestCaseOnCompletion() + { + if( !m_reported ) + { + report(); + } + } + Totals report() + { + m_reported = true; + Totals deltaTotals = m_totals.delta( m_prevTotals ); + m_totals.testCases += deltaTotals.testCases; + m_reporter->testCaseEnded( TestCaseStats( m_testInfo, + deltaTotals, + m_redirectedCout, + m_redirectedCerr, + m_context.aborting() ) ); + return deltaTotals; + } + private: + // non-copyable + UnwindTestCaseOnCompletion(const UnwindTestCaseOnCompletion&); + UnwindTestCaseOnCompletion& operator=(const UnwindTestCaseOnCompletion&); + + RunContext& m_context; + Totals& m_totals; + Ptr& m_reporter; + TestCaseInfo& m_testInfo; + std::string& m_redirectedCout; + std::string& m_redirectedCerr; + bool m_reported; + Totals m_prevTotals; + }; + TestRunInfo m_runInfo; IMutableContext& m_context; TestCase const* m_activeTestCase; diff --git a/include/internal/catch_timer.hpp b/include/internal/catch_timer.hpp index 141c1def..eaf396e8 100644 --- a/include/internal/catch_timer.hpp +++ b/include/internal/catch_timer.hpp @@ -24,15 +24,25 @@ namespace Catch { namespace { #ifdef CATCH_PLATFORM_WINDOWS + template + struct CounterDefaults + { + static T hz; + static T hzo; + }; + template + T CounterDefaults::hz = 0; + template + T CounterDefaults::hzo = 0; + INTERNAL_CATCH_INLINE uint64_t getCurrentTicks() { - static uint64_t hz=0, hzo=0; - if (!hz) { - QueryPerformanceFrequency((LARGE_INTEGER*)&hz); - QueryPerformanceCounter((LARGE_INTEGER*)&hzo); + if (!CounterDefaults::hz) { + QueryPerformanceFrequency((LARGE_INTEGER*)&CounterDefaults::hz); + QueryPerformanceCounter((LARGE_INTEGER*)&CounterDefaults::hzo); } uint64_t t; QueryPerformanceCounter((LARGE_INTEGER*)&t); - return ((t-hzo)*1000000)/hz; + return ((t-CounterDefaults::hzo)*1000000)/CounterDefaults::hz; } #else INTERNAL_CATCH_INLINE uint64_t getCurrentTicks() { diff --git a/include/internal/catch_tostring.hpp b/include/internal/catch_tostring.hpp index cec4cfed..5e098aa6 100644 --- a/include/internal/catch_tostring.hpp +++ b/include/internal/catch_tostring.hpp @@ -95,7 +95,11 @@ struct StringMaker { if( !p ) return INTERNAL_CATCH_STRINGIFY( NULL ); std::ostringstream oss; +#ifdef _MSC_VER + oss << "0x" << p; +#else oss << p; +#endif return oss.str(); } }; diff --git a/include/internal/catch_vs_managed_impl.hpp b/include/internal/catch_vs_managed_impl.hpp index 7991091f..7edeaf13 100644 --- a/include/internal/catch_vs_managed_impl.hpp +++ b/include/internal/catch_vs_managed_impl.hpp @@ -18,7 +18,7 @@ using namespace System::Collections::Generic; using namespace Microsoft::VisualStudio::TestTools::UnitTesting; namespace Catch { - inline String^ convert_string_to_managed(const std::string& s) + inline String^ convert_string_for_assert(const std::string& s) { String^ result = gcnew String(s.c_str()); return result; @@ -26,71 +26,12 @@ namespace Catch { } -#include "internal/catch_timer.hpp" #include "internal/catch_reporter_registrars.hpp" #include "reporters/catch_vs_reporter.hpp" #include "catch_registry_hub.hpp" - -//#define OLD (1) -#ifdef OLD -namespace Catch { - - class ExceptionRegistryHub : public IRegistryHub, public IMutableRegistryHub { - - ExceptionRegistryHub( ExceptionRegistryHub const& ); - void operator=( ExceptionRegistryHub const& ); - - public: // IRegistryHub - ExceptionRegistryHub() { - } - virtual IReporterRegistry const& getReporterRegistry() const { - throw std::runtime_error("can't do this for Visual Studio tests!"); - } - virtual ITestCaseRegistry const& getTestCaseRegistry() const { - throw std::runtime_error("can't do this for Visual Studio tests!"); - } - virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() { - return m_exceptionTranslatorRegistry; - } - - public: // IMutableRegistryHub - virtual void registerReporter( std::string const&, IReporterFactory* ) { - throw std::runtime_error("can't do this for Visual Studio tests!"); - } - virtual void registerTest( TestCase const& ) { - throw std::runtime_error("can't do this for Visual Studio tests!"); - } - virtual void registerTranslator( const IExceptionTranslator* translator ) { - m_exceptionTranslatorRegistry.registerTranslator( translator ); - } - - private: - ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; - }; - - template - struct GlobalRegistryHub - { - static T*& instance() - { - if( !theRegistryHub ) - theRegistryHub = new T(); - return theRegistryHub; - } - static T* theRegistryHub; - }; - template - T* GlobalRegistryHub::theRegistryHub = NULL; - - INTERNAL_CATCH_INLINE IMutableRegistryHub& getMutableRegistryHub() { - return *GlobalRegistryHub::instance(); - } - INTERNAL_CATCH_INLINE std::string translateActiveException() { - return GlobalRegistryHub::instance()->getExceptionTranslatorRegistry().translateActiveException(); - } - -} -#endif // OLD +#include "internal/catch_timer.hpp" +#include "internal/catch_console_colour_impl.hpp" +#include "catch_runner.hpp" namespace Catch { inline NonCopyable::~NonCopyable() {} @@ -113,16 +54,10 @@ namespace Catch { inline TestCaseStats::~TestCaseStats() {} inline TestGroupStats::~TestGroupStats() {} inline TestRunStats::~TestRunStats() {} - //CumulativeReporterBase::SectionNode::~SectionNode() {} - //CumulativeReporterBase::~CumulativeReporterBase() {} - //StreamingReporterBase::~StreamingReporterBase() {} - //ConsoleReporter::~ConsoleReporter() {} inline IRunner::~IRunner() {} inline IMutableContext::~IMutableContext() {} inline IConfig::~IConfig() {} - //XmlReporter::~XmlReporter() {} - //JunitReporter::~JunitReporter() {} inline TestRegistry::~TestRegistry() {} inline FreeFunctionTestCase::~FreeFunctionTestCase() {} inline IGeneratorInfo::~IGeneratorInfo() {} @@ -137,8 +72,6 @@ namespace Catch { inline Matchers::Impl::StdString::EndsWith::~EndsWith() {} inline void Config::dummy() {} - - //INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( "xml", XmlReporter ) } #endif diff --git a/include/internal/catch_vs_native_impl.hpp b/include/internal/catch_vs_native_impl.hpp index f0c2e242..dbb1e56a 100644 --- a/include/internal/catch_vs_native_impl.hpp +++ b/include/internal/catch_vs_native_impl.hpp @@ -16,71 +16,55 @@ using Microsoft::VisualStudio::CppUnitTestFramework::Logger; using Microsoft::VisualStudio::CppUnitTestFramework::Assert; using Microsoft::VisualStudio::CppUnitTestFramework::__LineInfo; -#define INTERNAL_CATCH_INLINE inline - #include #include -#include "internal/catch_timer.hpp" -#include "internal/catch_vs_test_registry.hpp" +#include "internal/catch_reporter_registrars.hpp" #include "reporters/catch_vs_reporter.hpp" +#include "catch_registry_hub.hpp" +#include "internal/catch_timer.hpp" +#include "internal/catch_console_colour_impl.hpp" +#include "catch_runner.hpp" namespace Catch { + inline NonCopyable::~NonCopyable() {} + inline IShared::~IShared() {} + inline StreamBufBase::~StreamBufBase() throw() {} + inline IContext::~IContext() {} + inline IResultCapture::~IResultCapture() {} + inline ITestCase::~ITestCase() {} + inline ITestCaseRegistry::~ITestCaseRegistry() {} + inline IRegistryHub::~IRegistryHub() {} + inline IMutableRegistryHub::~IMutableRegistryHub() {} + inline IExceptionTranslator::~IExceptionTranslator() {} + inline IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() {} + inline IReporter::~IReporter() {} + inline IReporterFactory::~IReporterFactory() {} + inline IReporterRegistry::~IReporterRegistry() {} + inline IStreamingReporter::~IStreamingReporter() {} + inline AssertionStats::~AssertionStats() {} + inline SectionStats::~SectionStats() {} + inline TestCaseStats::~TestCaseStats() {} + inline TestGroupStats::~TestGroupStats() {} + inline TestRunStats::~TestRunStats() {} - class ExceptionRegistryHub : public IRegistryHub, public IMutableRegistryHub { + inline IRunner::~IRunner() {} + inline IMutableContext::~IMutableContext() {} + inline IConfig::~IConfig() {} + inline TestRegistry::~TestRegistry() {} + inline FreeFunctionTestCase::~FreeFunctionTestCase() {} + inline IGeneratorInfo::~IGeneratorInfo() {} + inline IGeneratorsForTest::~IGeneratorsForTest() {} + inline TagParser::~TagParser() {} + inline TagExtracter::~TagExtracter() {} + inline TagExpressionParser::~TagExpressionParser() {} - ExceptionRegistryHub( ExceptionRegistryHub const& ); - void operator=( ExceptionRegistryHub const& ); - - public: // IRegistryHub - ExceptionRegistryHub() { - } - virtual IReporterRegistry const& getReporterRegistry() const { - throw std::runtime_error("can't do this for Visual Studio tests!"); - } - virtual ITestCaseRegistry const& getTestCaseRegistry() const { - throw std::runtime_error("can't do this for Visual Studio tests!"); - } - virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() { - return m_exceptionTranslatorRegistry; - } - - public: // IMutableRegistryHub - virtual void registerReporter( std::string const&, IReporterFactory* ) { - throw std::runtime_error("can't do this for Visual Studio tests!"); - } - virtual void registerTest( TestCase const& ) { - throw std::runtime_error("can't do this for Visual Studio tests!"); - } - virtual void registerTranslator( const IExceptionTranslator* translator ) { - m_exceptionTranslatorRegistry.registerTranslator( translator ); - } - - private: - ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; - }; - - template - struct GlobalRegistryHub - { - static T& instance() - { - if( !theRegistryHub ) - theRegistryHub = new T(); - return *theRegistryHub; - } - static T* theRegistryHub; - }; - template - T* GlobalRegistryHub::theRegistryHub = NULL; - - INTERNAL_CATCH_INLINE IMutableRegistryHub& getMutableRegistryHub() { - return GlobalRegistryHub::instance(); - } - INTERNAL_CATCH_INLINE std::string translateActiveException() { - return GlobalRegistryHub::instance().getExceptionTranslatorRegistry().translateActiveException(); - } + inline Matchers::Impl::StdString::Equals::~Equals() {} + inline Matchers::Impl::StdString::Contains::~Contains() {} + inline Matchers::Impl::StdString::StartsWith::~StartsWith() {} + inline Matchers::Impl::StdString::EndsWith::~EndsWith() {} + inline void Config::dummy() {} } #endif // INTERNAL_CATCH_VS_NATIVE diff --git a/include/internal/catch_vs_test_registry.hpp b/include/internal/catch_vs_test_registry.hpp index dfc09c20..6071ac69 100644 --- a/include/internal/catch_vs_test_registry.hpp +++ b/include/internal/catch_vs_test_registry.hpp @@ -63,7 +63,7 @@ private: typedef void(*TestFunction)(); struct NameAndDesc { -#if (_MANAGED == 1) || (_M_CEE == 1) // detect CLR +#ifdef INTERNAL_CATCH_VS_MANAGED NameAndDesc( const char* _name = "", const char* _description= "" ) : name( _name ), description( _description ) {} @@ -74,14 +74,35 @@ struct NameAndDesc { NameAndDesc( const wchar_t* _name, const char* _description= "" ) : name(), description( _description ) { - stdext::cvt::wstring_convert > conv1; - name = conv1.to_bytes(_name); + assignName(_name); } NameAndDesc( const wchar_t* _name, int ) : name(), description( "" ) + { + assignName(_name); + } + void assignName(const wchar_t* _name) { stdext::cvt::wstring_convert > conv1; - name = conv1.to_bytes(_name); + std::string tmp = conv1.to_bytes(_name); + if( tmp.empty() ) + { + name = tmp; + } + else + { + std::string::iterator startIter = tmp.begin(); + if(*startIter == '\"') + { + ++startIter; + } + std::string::reverse_iterator endIter = tmp.rbegin(); + if(*endIter == '\"') + { + ++endIter; + } + name.assign(startIter, endIter.base()); + } } #endif @@ -120,7 +141,7 @@ private: } // end namespace Catch -#if (_MANAGED == 1) || (_M_CEE == 1) // detect CLR +#ifdef INTERNAL_CATCH_VS_MANAGED #define CATCH_INTERNAL_HANDLE_EMPTY_PARAM2( name ) name##"" #define CATCH_INTERNAL_HANDLE_EMPTY_PARAM(...) CATCH_INTERNAL_HANDLE_EMPTY_PARAM2( INTERNAL_CATCH_SPLIT_ARGS_2(__VA_ARGS__) ) @@ -147,7 +168,7 @@ private: #define CATCH_INTERNAL_NAMESPACE( Ext ) -#define INTERNAL_CATCH_TEST_METHOD( Method, UniqueExt, Name, Desc ) \ +#define INTERNAL_CATCH_TEST_METHOD( Count, UniqueExt, Name, Desc ) \ public: \ [TestMethod] \ [Description( CATCH_INTERNAL_HANDLE_EMPTY_PARAM(Name) )] \ @@ -155,22 +176,24 @@ private: void INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H___M_E_T_H_O_D___, UniqueExt) () \ { \ Catch::NameAndDesc name_desc( CATCH_INTERNAL_HANDLE_EMPTY_PARAM(Name), Desc ); \ - CATCH_INTERNAL_RUN_SINGLE_TEST( Method ); \ + CATCH_INTERNAL_RUN_SINGLE_TEST(Count); \ } -#define INTERNAL_CATCH_TEST_CLASS_METHOD( Method, UniqueExt, Name, Desc ) \ +#define BEGIN_INTERNAL_CATCH_BATCH_METHOD( Tags, UniqueExt ) \ public: \ [TestMethod] \ - [Description( CATCH_INTERNAL_HANDLE_EMPTY_PARAM(Name) )] \ - [TestProperty( "Description", CATCH_INTERNAL_HANDLE_EMPTY_PARAM(Name) )] \ - void INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H___M_E_T_H_O_D___, UniqueExt) () \ - { \ - Catch::NameAndDesc name_desc( CATCH_INTERNAL_HANDLE_EMPTY_PARAM(Name), Desc ); \ - CATCH_INTERNAL_RUN_SINGLE_CLASS_TEST( Method ); \ - } + [TestCategory( CATCH_INTERNAL_HANDLE_EMPTY_PARAM(Tags) )] \ + [Description( CATCH_INTERNAL_HANDLE_EMPTY_PARAM(Tags) )] \ + [TestProperty( "Description", CATCH_INTERNAL_HANDLE_EMPTY_PARAM(Tags) )] \ + void INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H___M_E_T_H_O_D___, UniqueExt) () #define CHECK_FOR_TEST_CASE_CLASH +#define INTERNAL_CATCH_MAP_CATEGORY_TO_TAG( Category, Tag ) \ + INTERNAL_CATCH_MAP_CATEGORY_TO_TAG2( #Category, Tag, __COUNTER__ ) + +#define FAIL_STRING( str ) _T( str ) + #else // detect CLR // Native tests @@ -186,108 +209,161 @@ private: #define TEST_IMPL_2(tuple) TEST_IMPL2 tuple #define TEST_IMPL2( INTERNAL_CATCH_SPLIT_ARG_1,INTERNAL_CATCH_SPLIT_ARG_2,N,...) L#INTERNAL_CATCH_SPLIT_ARG_1 -#define CATCH_INTERNAL_HANDLE_EMPTY_PARAM(...) CATCH_INTERNAL_HANDLE_EMPTY_PARAM_IMPL( (__VA_ARGS__, 2, 1) ) -#define CATCH_INTERNAL_HANDLE_EMPTY_PARAM_IMPL(tuple) CATCH_INTERNAL_HANDLE_EMPTY_PARAM_IMPL2 tuple -#define CATCH_INTERNAL_HANDLE_EMPTY_PARAM_IMPL2( INTERNAL_CATCH_SPLIT_ARG_1,INTERNAL_CATCH_SPLIT_ARG_2,N,...) #INTERNAL_CATCH_SPLIT_ARG_1 -#define CATCH_INTERNAL_HANDLE_EMPTY_PARAMW(...) CATCH_INTERNAL_HANDLE_EMPTY_PARAM_IMPLW( (__VA_ARGS__, 2, 1) ) +#define CATCH_INTERNAL_HANDLE_EMPTY_PARAM(...) CATCH_INTERNAL_HANDLE_EMPTY_PARAM_IMPLW( (__VA_ARGS__, 2, 1) ) #define CATCH_INTERNAL_HANDLE_EMPTY_PARAM_IMPLW(tuple) CATCH_INTERNAL_HANDLE_EMPTY_PARAM_IMPL2W tuple #define CATCH_INTERNAL_HANDLE_EMPTY_PARAM_IMPL2W( INTERNAL_CATCH_SPLIT_ARG_1,INTERNAL_CATCH_SPLIT_ARG_2,N,...) L#INTERNAL_CATCH_SPLIT_ARG_1 -#define INTERNAL_CATCH_TEST_METHOD( Method, UniqueExt, Name, Desc ) \ +#define INTERNAL_CATCH_TEST_METHOD( Count, UniqueExt, Name, Desc ) \ public: \ BEGIN_TEST_METHOD_ATTRIBUTE( INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H___M_E_T_H_O_D___, UniqueExt) ) \ - TEST_OWNER( CATCH_INTERNAL_HANDLE_EMPTY_PARAMW(Name) ) \ - TEST_DESCRIPTION( CATCH_INTERNAL_HANDLE_EMPTY_PARAMW(Name) ) \ + TEST_OWNER( CATCH_INTERNAL_HANDLE_EMPTY_PARAM(Name) ) \ + TEST_DESCRIPTION( CATCH_INTERNAL_HANDLE_EMPTY_PARAM(Name) ) \ END_TEST_METHOD_ATTRIBUTE() \ TEST_METHOD( INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H___M_E_T_H_O_D___, UniqueExt) ) \ { \ - Catch::NameAndDesc name_desc(CATCH_INTERNAL_HANDLE_EMPTY_PARAMW(Name), Desc ); \ - CATCH_INTERNAL_RUN_SINGLE_TEST( Method ); \ + Catch::NameAndDesc name_desc(CATCH_INTERNAL_HANDLE_EMPTY_PARAM(Name), Desc ); \ + CATCH_INTERNAL_RUN_SINGLE_TEST(Count); \ } -#define INTERNAL_CATCH_TEST_CLASS_METHOD( Method, UniqueExt, Name, Desc ) \ +#define BEGIN_INTERNAL_CATCH_BATCH_METHOD( Tags, UniqueExt ) \ public: \ - BEGIN_TEST_METHOD_ATTRIBUTE( INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H___M_E_T_H_O_D___, UniqueExt) ) \ - TEST_OWNER( CATCH_INTERNAL_HANDLE_EMPTY_PARAMW(Name) ) \ - TEST_DESCRIPTION( CATCH_INTERNAL_HANDLE_EMPTY_PARAMW(Name) ) \ - END_TEST_METHOD_ATTRIBUTE() \ - TEST_METHOD( INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H___M_E_T_H_O_D___, UniqueExt) ) \ - { \ - Catch::NameAndDesc name_desc( CATCH_INTERNAL_HANDLE_EMPTY_PARAMW(Name), Desc ); \ - CATCH_INTERNAL_RUN_SINGLE_CLASS_TEST( Method ); \ - } + BEGIN_TEST_METHOD_ATTRIBUTE( INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H___M_E_T_H_O_D___, UniqueExt) ) \ + TEST_OWNER( CATCH_INTERNAL_HANDLE_EMPTY_PARAM(Tags) ) \ + TEST_DESCRIPTION( CATCH_INTERNAL_HANDLE_EMPTY_PARAM(Tags) ) \ + END_TEST_METHOD_ATTRIBUTE() \ + TEST_METHOD( INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H___M_E_T_H_O_D___, UniqueExt) ) #define CHECK_FOR_TEST_CASE_CLASH void INTERNAL_CATCH_UNIQUE_NAME_LINE( if_you_get_this_error_you_have_a_test_case_name_clash_please_put_a_namespace_around_the_test_case_at_line_, __LINE__ )() {} +#define INTERNAL_CATCH_MAP_CATEGORY_TO_TAG( Category, Tag ) \ + INTERNAL_CATCH_MAP_CATEGORY_TO_TAG2( Category, Tag, __COUNTER__ ) + +#define FAIL_STRING( str ) WIDEN( str ) + #endif // detect CLR -#define INTERNAL_CATCH_CONCAT_LINE_COUNTER INTERNAL_CATCH_UNIQUE_NAME_LINE( INTERNAL_CATCH_UNIQUE_NAME_LINE( __LINE__, _ ), __COUNTER__ ) +#define INTERNAL_CATCH_CONCAT_LINE_COUNTER( count ) INTERNAL_CATCH_UNIQUE_NAME_LINE( INTERNAL_CATCH_UNIQUE_NAME_LINE( __LINE__, _ ), count ) -#define CATCH_INTERNAL_RUN_SINGLE_TEST( Method ) \ - { Catch::ConfigData cd; \ +#define CATCH_INTERNAL_CONFIG_SHOW_SUCCESS2( v, Count ) \ + namespace { CatchOverrides::ConfigShowSuccessfulTests INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H_____O_V_E_R_R_I_D_E____, INTERNAL_CATCH_CONCAT_LINE_COUNTER( Count ) )(__FILE__, Count, v); } + +#define CATCH_INTERNAL_CONFIG_WARN_MISSING_ASSERTIONS2( v, Count ) \ + namespace { CatchOverrides::ConfigWarnMissingAssertions INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H_____O_V_E_R_R_I_D_E____, INTERNAL_CATCH_CONCAT_LINE_COUNTER( Count ) )(__FILE__, Count, v); } + +#define CATCH_INTERNAL_CONFIG_SHOW_SUCCESS( v ) \ + CATCH_INTERNAL_CONFIG_SHOW_SUCCESS2( v, __COUNTER__) + +#define CATCH_INTERNAL_CONFIG_WARN_MISSING_ASSERTIONS( v ) \ + CATCH_INTERNAL_CONFIG_WARN_MISSING_ASSERTIONS2( v, __COUNTER__) + +#define CATCH_INTERNAL_RUN_SINGLE_TEST( Count ) \ + { CatchOverrides::ConfigGuard cg; \ + Catch::ConfigData cd(cg.value().get()); \ cd.name = name_desc.name; \ cd.abortAfter = 1; \ + cd.showSuccessfulTests = CatchOverrides::Config::instance().includeSuccessfulResults(__FILE__, Count ); \ + cd.warnings = (CatchOverrides::Config::instance().warnAboutMissingAssertions(__FILE__, Count ) ? Catch::WarnAbout::NoAssertions : Catch::WarnAbout::Nothing); \ Catch::Ptr config(new Catch::Config(cd)); \ Catch::MSTestReporter* rep = new Catch::MSTestReporter(config.get()); \ Catch::RunContext tr(config.get(), rep); \ std::vector testCase = Catch::getRegistryHub().getTestCaseRegistry().getMatchingTestCases(name_desc.name); \ - if( testCase.empty() ) Assert::Fail("No tests match"); \ - if( testCase.size() > 1 ) Assert::Fail("More than one test with the same name"); \ - tr.runTest(*testCase.begin()); \ + if( testCase.empty() ) Assert::Fail(FAIL_STRING("No tests match")); \ + if( testCase.size() > 1 ) Assert::Fail(FAIL_STRING("More than one test with the same name")); \ + Catch::Totals totals = tr.runTest(*testCase.begin()); \ + if( totals.assertions.failed > 0 ) { \ + INTERNAL_CATCH_TEST_THROW_FAILURE \ + } \ } -#define CATCH_INTERNAL_RUN_SINGLE_CLASS_TEST( ClassMethod ) \ - { Catch::ConfigData cd; \ - cd.name = name_desc.name; \ - cd.abortAfter = 1; \ - Catch::Ptr config(new Catch::Config(cd)); \ - Catch::MSTestReporter* rep = new Catch::MSTestReporter(config.get()); \ - Catch::RunContext tr(config.get(), rep); \ - std::vector testCase = Catch::getRegistryHub().getTestCaseRegistry().getMatchingTestCases(name_desc.name); \ - if( testCase.empty() ) Assert::Fail("No tests match"); \ - if( testCase.size() > 1 ) Assert::Fail("More than one test with the same name"); \ - tr.runTest(*testCase.begin()); \ - } - -#define INTERNAL_CATCH_TESTCASE2( UniqueExt, Name, Desc ) \ +#define INTERNAL_CATCH_TESTCASE2( Count, Name, Desc ) \ CHECK_FOR_TEST_CASE_CLASH \ - static void INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____, UniqueExt )(); \ - namespace CATCH_INTERNAL_NAMESPACE( UniqueExt ) { \ - Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( & INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____, UniqueExt ), CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc(Name, Desc) ); \ - INTERNAL_CATCH_CLASS_DEFINITION( INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____C_L_A_S_S___, UniqueExt ) ) \ + static void INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____, INTERNAL_CATCH_CONCAT_LINE_COUNTER( Count ) )(); \ + namespace CATCH_INTERNAL_NAMESPACE( INTERNAL_CATCH_CONCAT_LINE_COUNTER( Count ) ) { \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( & INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____, INTERNAL_CATCH_CONCAT_LINE_COUNTER( Count ) ), CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc(CATCH_INTERNAL_HANDLE_EMPTY_PARAM(Name), Desc) ); \ + CatchOverrides::ConfigReset INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____C_O_N_F_I_G___, INTERNAL_CATCH_CONCAT_LINE_COUNTER( Count ) )(__FILE__, Count); \ + INTERNAL_CATCH_CLASS_DEFINITION( INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____C_L_A_S_S___, INTERNAL_CATCH_CONCAT_LINE_COUNTER( Count ) ) ) \ { \ INTERNAL_CATCH_CLASS_CONTEXT \ - INTERNAL_CATCH_TEST_METHOD( INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____, UniqueExt ), UniqueExt, Name, Desc ) \ + INTERNAL_CATCH_TEST_METHOD( Count, INTERNAL_CATCH_CONCAT_LINE_COUNTER( Count ), Name, Desc ) \ }; \ } \ - void INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____, UniqueExt )() + void INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____, INTERNAL_CATCH_CONCAT_LINE_COUNTER( Count ) )() -#define INTERNAL_CATCH_METHOD_AS_TEST_CASE2( QualifiedMethod, UniqueExt, Name, Desc ) \ +#define INTERNAL_CATCH_METHOD_AS_TEST_CASE2( QualifiedMethod, Count, Name, Desc ) \ CHECK_FOR_TEST_CASE_CLASH \ - namespace CATCH_INTERNAL_NAMESPACE( UniqueExt ) { \ - Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( & QualifiedMethod, "&" # QualifiedMethod, Catch::NameAndDesc(Name, Desc), CATCH_INTERNAL_LINEINFO ); \ - INTERNAL_CATCH_CLASS_DEFINITION( INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____C_L_A_S_S___, UniqueExt ) ) \ + namespace CATCH_INTERNAL_NAMESPACE( INTERNAL_CATCH_CONCAT_LINE_COUNTER( Count ) ) { \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( & QualifiedMethod, "&" # QualifiedMethod, Catch::NameAndDesc(CATCH_INTERNAL_HANDLE_EMPTY_PARAM(Name), Desc), CATCH_INTERNAL_LINEINFO ); \ + CatchOverrides::ConfigReset INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____C_O_N_F_I_G___, INTERNAL_CATCH_CONCAT_LINE_COUNTER( Count ) )(__FILE__, Count); \ + INTERNAL_CATCH_CLASS_DEFINITION( INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____C_L_A_S_S___, INTERNAL_CATCH_CONCAT_LINE_COUNTER( Count ) ) ) \ { \ INTERNAL_CATCH_CLASS_CONTEXT \ - INTERNAL_CATCH_TEST_CLASS_METHOD( QualifiedMethod, UniqueExt, Name, Desc ) \ + INTERNAL_CATCH_TEST_METHOD( Count, INTERNAL_CATCH_CONCAT_LINE_COUNTER( Count ), Name, Desc ) \ }; \ }; -#define INTERNAL_CATCH_TEST_CASE_METHOD2( ClassName, UniqueExt, TestName, Desc ) \ +#define INTERNAL_CATCH_TEST_CASE_METHOD2( ClassName, Count, TestName, Desc ) \ CHECK_FOR_TEST_CASE_CLASH \ - struct INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____, UniqueExt ) : ClassName { \ + struct INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____, INTERNAL_CATCH_CONCAT_LINE_COUNTER( Count ) ) : ClassName { \ void test(); \ - static void invoke() { INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____, UniqueExt ) tmp; tmp.test(); } \ + static void invoke() { INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____, INTERNAL_CATCH_CONCAT_LINE_COUNTER( Count ) ) tmp; tmp.test(); } \ }; \ - namespace CATCH_INTERNAL_NAMESPACE( UniqueExt ) { \ - Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( & INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____, UniqueExt )::invoke, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc(TestName, Desc) ); \ - INTERNAL_CATCH_CLASS_DEFINITION( INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____C_L_A_S_S___, UniqueExt ) ) \ + namespace CATCH_INTERNAL_NAMESPACE( INTERNAL_CATCH_CONCAT_LINE_COUNTER( Count ) ) { \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( & INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____, INTERNAL_CATCH_CONCAT_LINE_COUNTER( Count ) )::invoke, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc(CATCH_INTERNAL_HANDLE_EMPTY_PARAM(TestName), Desc) ); \ + CatchOverrides::ConfigReset INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____C_O_N_F_I_G___, INTERNAL_CATCH_CONCAT_LINE_COUNTER( Count ) )(__FILE__, Count); \ + INTERNAL_CATCH_CLASS_DEFINITION( INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____C_L_A_S_S___, INTERNAL_CATCH_CONCAT_LINE_COUNTER( Count ) ) ) \ { \ INTERNAL_CATCH_CLASS_CONTEXT \ - INTERNAL_CATCH_TEST_METHOD( INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____, UniqueExt )::invoke, UniqueExt, TestName, Desc ) \ + INTERNAL_CATCH_TEST_METHOD( Count, INTERNAL_CATCH_CONCAT_LINE_COUNTER( Count ), TestName, Desc ) \ }; \ } \ - void INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____, UniqueExt )::test() + void INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____, INTERNAL_CATCH_CONCAT_LINE_COUNTER( Count ) )::test() + +#if defined(INTERNAL_CATCH_VS_MANAGED) + + #define INTERNAL_CATCH_TEST_REPORT_BATCH_FAILURE( count ) \ + { \ + std::stringstream _sf; \ + _sf << count << " assertions failed - check output for results."; \ + std::string fail = _sf.str(); \ + Assert::Fail(Catch::convert_string_for_assert(fail)); \ + } + +#else + + #define INTERNAL_CATCH_TEST_REPORT_BATCH_FAILURE( count ) \ + { \ + std::wstringstream _s; \ + _s << count << " assertions failed - check output for results."; \ + std::wstring ws = _s.str(); \ + Assert::Fail(ws.c_str()); \ + } +#endif + +#define INTERNAL_CATCH_MAP_CATEGORY_TO_TAG2( Category, Tag, Count ) \ + CHECK_FOR_TEST_CASE_CLASH \ + namespace CATCH_INTERNAL_NAMESPACE( INTERNAL_CATCH_CONCAT_LINE_COUNTER( Count ) ) { \ + CatchOverrides::ConfigReset INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____C_O_N_F_I_G___, INTERNAL_CATCH_CONCAT_LINE_COUNTER( Count ) )(__FILE__, Count); \ + INTERNAL_CATCH_CLASS_DEFINITION( INTERNAL_CATCH_UNIQUE_NAME_LINE( C_A_T_C_H____T_E_S_T____C_L_A_S_S___, INTERNAL_CATCH_CONCAT_LINE_COUNTER( Count ) ) ) \ + { \ + INTERNAL_CATCH_CLASS_CONTEXT \ + BEGIN_INTERNAL_CATCH_BATCH_METHOD( Category, INTERNAL_CATCH_CONCAT_LINE_COUNTER( Count ) ) \ + { \ + Catch::ConfigData cd; \ + cd.showSuccessfulTests = CatchOverrides::Config::instance().includeSuccessfulResults(__FILE__, Count ); \ + cd.warnings = (CatchOverrides::Config::instance().warnAboutMissingAssertions(__FILE__, Count ) ? Catch::WarnAbout::NoAssertions : Catch::WarnAbout::Nothing); \ + cd.reporterName = "vs_reporter"; \ + cd.name = "Batch run using tag : " Tag; \ + cd.testsOrTags.push_back( Tag ); \ + Catch::Ptr config(new Catch::Config(cd)); \ + Catch::ReporterRegistrar reporterReg("vs_reporter"); \ + Catch::Runner runner(config); \ + Catch::Totals totals = runner.runTests(); \ + if( totals.assertions.failed > 0 ) { \ + INTERNAL_CATCH_TEST_REPORT_BATCH_FAILURE(totals.assertions.failed) \ + } \ + } \ + }; \ + } //#undef CATCH_CONFIG_VARIADIC_MACROS @@ -301,29 +377,29 @@ private: #define INTERNAL_CATCH_SPLIT_TAGS_IMPL_(INTERNAL_CATCH_SPLIT_ARG_1,INTERNAL_CATCH_SPLIT_ARG_2,N,...) INTERNAL_CATCH_SPLIT_ARG_2 #define INTERNAL_CATCH_TESTCASE( ... ) \ - INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_CONCAT_LINE_COUNTER, INTERNAL_CATCH_SPLIT_ARGS_2(__VA_ARGS__), INTERNAL_CATCH_SPLIT_TAGS(__VA_ARGS__) ) + INTERNAL_CATCH_TESTCASE2( __COUNTER__ , INTERNAL_CATCH_SPLIT_ARGS_2(__VA_ARGS__), INTERNAL_CATCH_SPLIT_TAGS(__VA_ARGS__) ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ - INTERNAL_CATCH_METHOD_AS_TEST_CASE2( QualifiedMethod, INTERNAL_CATCH_CONCAT_LINE_COUNTER, INTERNAL_CATCH_SPLIT_ARGS_2(__VA_ARGS__), INTERNAL_CATCH_SPLIT_TAGS(__VA_ARGS__) ) + INTERNAL_CATCH_METHOD_AS_TEST_CASE2( QualifiedMethod, __COUNTER__, INTERNAL_CATCH_SPLIT_ARGS_2(__VA_ARGS__), INTERNAL_CATCH_SPLIT_TAGS(__VA_ARGS__) ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... )\ - INTERNAL_CATCH_TEST_CASE_METHOD2(ClassName, INTERNAL_CATCH_CONCAT_LINE_COUNTER, INTERNAL_CATCH_SPLIT_ARGS_2(__VA_ARGS__), INTERNAL_CATCH_SPLIT_TAGS(__VA_ARGS__) ) + INTERNAL_CATCH_TEST_CASE_METHOD2(ClassName, __COUNTER__, INTERNAL_CATCH_SPLIT_ARGS_2(__VA_ARGS__), INTERNAL_CATCH_SPLIT_TAGS(__VA_ARGS__) ) #else /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \ - INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_CONCAT_LINE_COUNTER, Name, Desc ) + INTERNAL_CATCH_TESTCASE2( __COUNTER__ , Name, Desc ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \ - INTERNAL_CATCH_METHOD_AS_TEST_CASE2( QualifiedMethod, INTERNAL_CATCH_CONCAT_LINE_COUNTER, Name, Desc ) + INTERNAL_CATCH_METHOD_AS_TEST_CASE2( QualifiedMethod, __COUNTER__, Name, Desc ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\ - INTERNAL_CATCH_TEST_CASE_METHOD2(ClassName, INTERNAL_CATCH_CONCAT_LINE_COUNTER, TestName, Desc ) + INTERNAL_CATCH_TEST_CASE_METHOD2(ClassName, __COUNTER__, TestName, Desc ) #endif diff --git a/include/reporters/catch_vs_reporter.hpp b/include/reporters/catch_vs_reporter.hpp index 0f9c9966..9b7d2368 100644 --- a/include/reporters/catch_vs_reporter.hpp +++ b/include/reporters/catch_vs_reporter.hpp @@ -49,18 +49,24 @@ namespace Catch { MSTestReporter( ReporterConfig const& _config ) : m_config( _config.fullConfig() ), m_headerPrinted( false ), - m_atLeastOneTestCasePrinted( false ) + m_atLeastOneTestCasePrinted( false ), + m_failed(0) {} MSTestReporter( Ptr const& _fullConfig ) : m_config( _fullConfig ), m_headerPrinted( false ), - m_atLeastOneTestCasePrinted( false ) + m_atLeastOneTestCasePrinted( false ), + m_failed(0) {} virtual ~MSTestReporter() { if( m_atLeastOneTestCasePrinted ) { write_output_message(stream.str()); + /*if( m_failed ) + { + Assert::IsTrue(false, L"At least one test failed - examine output for failures."); + }*/ } } @@ -126,9 +132,6 @@ namespace Catch { write_output_message(_testCaseStats.stdErr); write_output_message(getDoubleDashes()); } - if( _testCaseStats.totals.assertions.failed ) { - Assert::IsTrue(false, L"At least one test failed - examine output for CHECK failures."); - } m_headerPrinted = false; currentTestCaseInfo.reset(); assert( m_sectionStack.empty() ); @@ -147,6 +150,7 @@ namespace Catch { printTotalsDivider(); printTotals( _testRunStats.totals ); stream << "\r\n" << "\r\n"; + m_failed = _testRunStats.totals.testCases.failed; currentTestCaseInfo.reset(); currentGroupInfo.reset(); currentTestRunInfo.reset(); @@ -454,6 +458,7 @@ namespace Catch { std::vector m_sectionStack; bool m_headerPrinted; bool m_atLeastOneTestCasePrinted; + size_t m_failed; }; } // end namespace Catch diff --git a/projects/SelfTest/RunAllTests.cpp b/projects/SelfTest/RunAllTests.cpp new file mode 100644 index 00000000..eca4e0bc --- /dev/null +++ b/projects/SelfTest/RunAllTests.cpp @@ -0,0 +1,187 @@ +/* + * Created by Phil on 22/10/2010. + * Copyright 2010 Two Blue Cubes Ltd + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ + +#include "catch.hpp" +#include "internal/catch_text.h" +#include "internal/catch_console_colour.hpp" + +namespace AllTestsRunner { + + class NullStreamingReporter : public Catch::SharedImpl { + public: + + virtual ~NullStreamingReporter(); + + static std::string getDescription() { + return "null reporter"; + } + + private: // IStreamingReporter + + virtual Catch::ReporterPreferences getPreferences() const { + return Catch::ReporterPreferences(); + } + + virtual void noMatchingTestCases( std::string const& ) {} + virtual void testRunStarting( Catch::TestRunInfo const& ) {} + virtual void testGroupStarting( Catch::GroupInfo const& ) {} + virtual void testCaseStarting( Catch::TestCaseInfo const& ) {} + virtual void sectionStarting( Catch::SectionInfo const& ) {} + virtual void assertionStarting( Catch::AssertionInfo const& ) {} + virtual bool assertionEnded( Catch::AssertionStats const& ) { return false; } + virtual void sectionEnded( Catch::SectionStats const& ) {} + virtual void testCaseEnded( Catch::TestCaseStats const& ) {} + virtual void testGroupEnded( Catch::TestGroupStats const& ) {} + virtual void testRunEnded( Catch::TestRunStats const& ) {} + }; + + class EmbeddedRunner { + + public: + EmbeddedRunner() : m_reporter( new NullStreamingReporter() ) {} + + Catch::Totals runMatching( const std::string& rawTestSpec, + std::size_t groupIndex, + std::size_t groupsCount, + const std::string& reporter = "console" ); + + private: + Catch::Ptr m_reporter; + }; + + class MetaTestRunner { + + public: + struct Expected { enum Result { + ToSucceed, + ToFail + }; }; + + MetaTestRunner( Expected::Result expectedResult, std::size_t groupIndex, std::size_t groupsCount ) + : m_expectedResult( expectedResult ), + m_groupIndex( groupIndex ), + m_groupsCount( groupsCount ) + {} + + static void runMatching( const std::string& testSpec, + Expected::Result expectedResult, + std::size_t groupIndex, + std::size_t groupsCount ) { + forEach( Catch::getRegistryHub().getTestCaseRegistry().getMatchingTestCases( testSpec ), + MetaTestRunner( expectedResult, groupIndex, groupsCount ) ); + } + + void operator()( const Catch::TestCase& testCase ) { + std::string name; + Catch::Totals totals; + { + EmbeddedRunner runner; + name = testCase.getTestCaseInfo().name; + totals = runner.runMatching( name, m_groupIndex, m_groupsCount ); + } + switch( m_expectedResult ) { + case Expected::ToSucceed: + if( totals.assertions.failed > 0 ) { + FAIL( "Expected test case '" + << name + << "' to succeed but there was/ were " + << totals.assertions.failed << " failure(s)" ); + } + else { + SUCCEED( "Tests passed, as expected" ); + } + break; + case Expected::ToFail: + if( totals.assertions.failed == 0 ) { + FAIL( "Expected test case '" + << name + << "' to fail but there was/ were " + << totals.assertions.passed << " success(es)" ); + } + else { + SUCCEED( "Tests failed, as expected" ); + } + break; + } + } + + private: + Expected::Result m_expectedResult; + std::size_t m_groupIndex; + std::size_t m_groupsCount; + }; + + NullStreamingReporter::~NullStreamingReporter() {} + + Catch::Totals EmbeddedRunner::runMatching( const std::string& rawTestSpec, std::size_t groupIndex, std::size_t groupsCount, const std::string& ) { + std::ostringstream oss; + Catch::Ptr config = new Catch::Config(); + config->setStreamBuf( oss.rdbuf() ); + + Catch::Totals totals; + + // Scoped because RunContext doesn't report EndTesting until its destructor + { + Catch::RunContext runner( config.get(), m_reporter.get() ); + totals = runner.runMatching( rawTestSpec, groupIndex, groupsCount ); + } + return totals; + } + + TEST_CASE( "Run all failing and succeeding tests", "[vsall]" ) { + + /////////////////////////////////////////////////////////////////////////// + SECTION( "selftest/expected result", + "Tests do what they claim" ) { + +#ifdef _UNICODE + std::cout << "using Unicode..." << std::endl; +#else + std::cout << "using Mbcs..." << std::endl; +#endif + + SECTION( "selftest/expected result/failing tests", + "Tests in the 'failing' branch fail" ) { + std::cout << "Tests in the 'failing' branch fail" << std::endl; + MetaTestRunner::runMatching( "./failing/*", MetaTestRunner::Expected::ToFail, 0, 2 ); + } + + SECTION( "selftest/expected result/succeeding tests", + "Tests in the 'succeeding' branch succeed" ) { + std::cout << "Tests in the 'succeeding' branch succeed" << std::endl; + MetaTestRunner::runMatching( "./succeeding/*", MetaTestRunner::Expected::ToSucceed, 1, 2 ); + } + } + + /////////////////////////////////////////////////////////////////////////// + SECTION( "selftest/test counts", + "Number of test cases that run is fixed" ) { + EmbeddedRunner runner; + + SECTION( "selftest/test counts/succeeding tests", + "Number of 'succeeding' tests is fixed" ) { + std::cout << "Number of 'succeeding' tests is fixed" << std::endl; + Catch::Totals totals = runner.runMatching( "./succeeding/*", 0, 2 ); + CHECK( totals.assertions.passed == 298 ); + CHECK( totals.assertions.failed == 0 ); + } + + SECTION( "selftest/test counts/failing tests", + "Number of 'failing' tests is fixed" ) { + std::cout << "Number of 'failing' tests is fixed" << std::endl; + Catch::Totals totals = runner.runMatching( "./failing/*", 1, 2 ); + CHECK( totals.assertions.passed == 2 ); + CHECK( totals.assertions.failed == 77 ); + } + } + } + +#if defined(INTERNAL_CATCH_VS_MANAGED) || defined(INTERNAL_CATCH_VS_NATIVE) + CATCH_MAP_CATEGORY_TO_TAG(all, "[vsall]"); +#endif +} diff --git a/projects/SelfTest/TestMain.cpp b/projects/SelfTest/TestMain.cpp index dc2684ed..f793ce6b 100644 --- a/projects/SelfTest/TestMain.cpp +++ b/projects/SelfTest/TestMain.cpp @@ -13,53 +13,55 @@ #include "internal/catch_text.h" #include "internal/catch_console_colour.hpp" -TEST_CASE( "selftest/main", "Runs all Catch self tests and checks their results" ) { - using namespace Catch; +namespace TestMain { - /////////////////////////////////////////////////////////////////////////// - SECTION( "selftest/expected result", - "Tests do what they claim" ) { + TEST_CASE( "selftest/main", "Runs all Catch self tests and checks their results" ) { + using namespace Catch; + + /////////////////////////////////////////////////////////////////////////// + SECTION( "selftest/expected result", + "Tests do what they claim" ) { - SECTION( "selftest/expected result/failing tests", - "Tests in the 'failing' branch fail" ) { - MetaTestRunner::runMatching( "./failing/*", MetaTestRunner::Expected::ToFail, 0, 2 ); - } + SECTION( "selftest/expected result/failing tests", + "Tests in the 'failing' branch fail" ) { + MetaTestRunner::runMatching( "./failing/*", MetaTestRunner::Expected::ToFail, 0, 2 ); + } - SECTION( "selftest/expected result/succeeding tests", - "Tests in the 'succeeding' branch succeed" ) { - MetaTestRunner::runMatching( "./succeeding/*", MetaTestRunner::Expected::ToSucceed, 1, 2 ); + SECTION( "selftest/expected result/succeeding tests", + "Tests in the 'succeeding' branch succeed" ) { + MetaTestRunner::runMatching( "./succeeding/*", MetaTestRunner::Expected::ToSucceed, 1, 2 ); + } + } + + /////////////////////////////////////////////////////////////////////////// + SECTION( "selftest/test counts", + "Number of test cases that run is fixed" ) { + EmbeddedRunner runner; + + SECTION( "selftest/test counts/succeeding tests", + "Number of 'succeeding' tests is fixed" ) { + Totals totals = runner.runMatching( "./succeeding/*", 0, 2 ); + CHECK( totals.assertions.passed == 298 ); + CHECK( totals.assertions.failed == 0 ); + } + + SECTION( "selftest/test counts/failing tests", + "Number of 'failing' tests is fixed" ) { + Totals totals = runner.runMatching( "./failing/*", 1, 2 ); + CHECK( totals.assertions.passed == 2 ); + CHECK( totals.assertions.failed == 77 ); + } } } - /////////////////////////////////////////////////////////////////////////// - SECTION( "selftest/test counts", - "Number of test cases that run is fixed" ) { - EmbeddedRunner runner; - - SECTION( "selftest/test counts/succeeding tests", - "Number of 'succeeding' tests is fixed" ) { - Totals totals = runner.runMatching( "./succeeding/*", 0, 2 ); - CHECK( totals.assertions.passed == 298 ); - CHECK( totals.assertions.failed == 0 ); - } - - SECTION( "selftest/test counts/failing tests", - "Number of 'failing' tests is fixed" ) { - Totals totals = runner.runMatching( "./failing/*", 1, 2 ); - CHECK( totals.assertions.passed == 2 ); - CHECK( totals.assertions.failed == 77 ); - } - } -} - -TEST_CASE( "meta/Misc/Sections", "looped tests" ) { - Catch::EmbeddedRunner runner; + TEST_CASE( "meta/Misc/Sections", "looped tests" ) { + Catch::EmbeddedRunner runner; - Catch::Totals totals = runner.runMatching( "./mixed/Misc/Sections/nested2", 0, 1 ); - CHECK( totals.assertions.passed == 2 ); - CHECK( totals.assertions.failed == 1 ); + Catch::Totals totals = runner.runMatching( "./mixed/Misc/Sections/nested2", 0, 1 ); + CHECK( totals.assertions.passed == 2 ); + CHECK( totals.assertions.failed == 1 ); + } } - #ifdef __clang__ #pragma clang diagnostic ignored "-Wweak-vtables" #endif @@ -68,528 +70,531 @@ TEST_CASE( "meta/Misc/Sections", "looped tests" ) { #include "../../include/internal/catch_test_spec.h" #include "../../include/reporters/catch_reporter_xml.hpp" -template -void parseIntoConfig( const char * (&argv)[size], Catch::ConfigData& config ) { - Clara::CommandLine parser = Catch::makeCommandLineParser(); - parser.parseInto( size, argv, config ); -} +namespace TestMain { -template -std::string parseIntoConfigAndReturnError( const char * (&argv)[size], Catch::ConfigData& config ) { - try { - parseIntoConfig( argv, config ); - FAIL( "expected exception" ); + template + void parseIntoConfig( const char * (&argv)[size], Catch::ConfigData& config ) { + Clara::CommandLine parser = Catch::makeCommandLineParser(); + parser.parseInto( size, argv, config ); } - catch( std::exception& ex ) { - return ex.what(); + + template + std::string parseIntoConfigAndReturnError( const char * (&argv)[size], Catch::ConfigData& config ) { + try { + parseIntoConfig( argv, config ); + FAIL( "expected exception" ); + } + catch( std::exception& ex ) { + return ex.what(); + } + return ""; } - return ""; -} -inline Catch::TestCase fakeTestCase( const char* name, const char* desc = "" ){ return Catch::makeTestCase( NULL, "", name, desc, CATCH_INTERNAL_LINEINFO ); } + inline Catch::TestCase fakeTestCase( const char* name, const char* desc = "" ){ return Catch::makeTestCase( NULL, "", name, desc, CATCH_INTERNAL_LINEINFO ); } -TEST_CASE( "Process can be configured on command line", "[config][command-line]" ) { + TEST_CASE( "Process can be configured on command line", "[config][command-line]" ) { - Catch::ConfigData config; + Catch::ConfigData config; - SECTION( "default - no arguments", "" ) { - const char* argv[] = { "test" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + SECTION( "default - no arguments", "" ) { + const char* argv[] = { "test" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - CHECK( config.shouldDebugBreak == false ); - CHECK( config.abortAfter == -1 ); - CHECK( config.noThrow == false ); - CHECK( config.reporterName.empty() ); - } + CHECK( config.shouldDebugBreak == false ); + CHECK( config.abortAfter == -1 ); + CHECK( config.noThrow == false ); + CHECK( config.reporterName.empty() ); + } - SECTION( "test lists", "" ) { - SECTION( "1 test", "Specify one test case using" ) { - const char* argv[] = { "test", "test1" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + SECTION( "test lists", "" ) { + SECTION( "1 test", "Specify one test case using" ) { + const char* argv[] = { "test", "test1" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - Catch::Config cfg( config ); - REQUIRE( cfg.filters().size() == 1 ); - REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "notIncluded" ) ) == false ); - REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "test1" ) ) ); + Catch::Config cfg( config ); + REQUIRE( cfg.filters().size() == 1 ); + REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "notIncluded" ) ) == false ); + REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "test1" ) ) ); + } + SECTION( "Specify one test case exclusion using exclude:", "" ) { + const char* argv[] = { "test", "exclude:test1" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + + Catch::Config cfg( config ); + REQUIRE( cfg.filters().size() == 1 ); + REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "test1" ) ) == false ); + REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "alwaysIncluded" ) ) ); + } + + SECTION( "Specify one test case exclusion using ~", "" ) { + const char* argv[] = { "test", "~test1" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + + Catch::Config cfg( config ); + REQUIRE( cfg.filters().size() == 1 ); + REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "test1" ) ) == false ); + REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "alwaysIncluded" ) ) ); + } + + SECTION( "Specify two test cases using -t", "" ) { + const char* argv[] = { "test", "-t", "test1", "test2" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + + Catch::Config cfg( config ); + REQUIRE( cfg.filters().size() == 1 ); + REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "notIncluded" ) ) == false ); + REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "test1" ) ) ); + REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "test2" ) ) ); + } } - SECTION( "Specify one test case exclusion using exclude:", "" ) { - const char* argv[] = { "test", "exclude:test1" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - - Catch::Config cfg( config ); - REQUIRE( cfg.filters().size() == 1 ); - REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "test1" ) ) == false ); - REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "alwaysIncluded" ) ) ); + + SECTION( "reporter", "" ) { + SECTION( "-r/console", "" ) { + const char* argv[] = { "test", "-r", "console" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + + REQUIRE( config.reporterName == "console" ); + } + SECTION( "-r/xml", "" ) { + const char* argv[] = { "test", "-r", "xml" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + + REQUIRE( config.reporterName == "xml" ); + } + SECTION( "--reporter/junit", "" ) { + const char* argv[] = { "test", "--reporter", "junit" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + + REQUIRE( config.reporterName == "junit" ); + } } - - SECTION( "Specify one test case exclusion using ~", "" ) { - const char* argv[] = { "test", "~test1" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - - Catch::Config cfg( config ); - REQUIRE( cfg.filters().size() == 1 ); - REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "test1" ) ) == false ); - REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "alwaysIncluded" ) ) ); + + SECTION( "debugger", "" ) { + SECTION( "-b", "" ) { + const char* argv[] = { "test", "-b" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + + REQUIRE( config.shouldDebugBreak == true ); + } + SECTION( "--break", "" ) { + const char* argv[] = { "test", "--break" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + + REQUIRE( config.shouldDebugBreak ); + } } - SECTION( "Specify two test cases using -t", "" ) { - const char* argv[] = { "test", "-t", "test1", "test2" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + SECTION( "abort", "" ) { + SECTION( "-a aborts after first failure", "" ) { + const char* argv[] = { "test", "-a" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - Catch::Config cfg( config ); - REQUIRE( cfg.filters().size() == 1 ); - REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "notIncluded" ) ) == false ); - REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "test1" ) ) ); - REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "test2" ) ) ); + REQUIRE( config.abortAfter == 1 ); + } + SECTION( "-x 2 aborts after two failures", "" ) { + const char* argv[] = { "test", "-x", "2" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + + REQUIRE( config.abortAfter == 2 ); + } + SECTION( "-x must be greater than zero", "" ) { + const char* argv[] = { "test", "-x", "0" }; + REQUIRE_THAT( parseIntoConfigAndReturnError( argv, config ), Contains( "greater than zero" ) ); + } + SECTION( "-x must be numeric", "" ) { + const char* argv[] = { "test", "-x", "oops" }; + REQUIRE_THAT( parseIntoConfigAndReturnError( argv, config ), Contains( "-x" ) ); + } } - } - SECTION( "reporter", "" ) { - SECTION( "-r/console", "" ) { - const char* argv[] = { "test", "-r", "console" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - - REQUIRE( config.reporterName == "console" ); + SECTION( "nothrow", "" ) { + SECTION( "-e", "" ) { + const char* argv[] = { "test", "-e" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + + REQUIRE( config.noThrow == true ); + } + SECTION( "--nothrow", "" ) { + const char* argv[] = { "test", "--nothrow" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + + REQUIRE( config.noThrow == true ); + } } - SECTION( "-r/xml", "" ) { - const char* argv[] = { "test", "-r", "xml" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - - REQUIRE( config.reporterName == "xml" ); - } - SECTION( "--reporter/junit", "" ) { - const char* argv[] = { "test", "--reporter", "junit" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - - REQUIRE( config.reporterName == "junit" ); + + SECTION( "output filename", "" ) { + SECTION( "-o filename", "" ) { + const char* argv[] = { "test", "-o", "filename.ext" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + + REQUIRE( config.outputFilename == "filename.ext" ); + } + SECTION( "--out", "" ) { + const char* argv[] = { "test", "--out", "filename.ext" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + + REQUIRE( config.outputFilename == "filename.ext" ); + } } + + SECTION( "combinations", "" ) { + SECTION( "Single character flags can be combined", "" ) { + const char* argv[] = { "test", "-abe" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + + CHECK( config.abortAfter == 1 ); + CHECK( config.shouldDebugBreak ); + CHECK( config.noThrow == true ); + } + } } + + TEST_CASE( "selftest/test filter", "Individual filters" ) { + + Catch::TestCaseFilter matchAny( "*" ); + Catch::TestCaseFilter matchNone( "*", Catch::IfFilterMatches::ExcludeTests ); + CHECK( matchAny.shouldInclude( fakeTestCase( "any" ) )); + CHECK( matchNone.shouldInclude( fakeTestCase( "any" ) ) == false ); + + Catch::TestCaseFilter matchHidden( "./*" ); + Catch::TestCaseFilter matchNonHidden( "./*", Catch::IfFilterMatches::ExcludeTests ); + + CHECK( matchHidden.shouldInclude( fakeTestCase( "any" ) ) == false ); + CHECK( matchNonHidden.shouldInclude( fakeTestCase( "any" ) ) ); + + CHECK( matchHidden.shouldInclude( fakeTestCase( "./any" ) ) ); + CHECK( matchNonHidden.shouldInclude( fakeTestCase( "./any" ) ) == false ); + } + + TEST_CASE( "selftest/test filters", "Sets of filters" ) { + + Catch::TestCaseFilter matchHidden( "./*" ); + Catch::TestCaseFilter dontMatchA( "./a*", Catch::IfFilterMatches::ExcludeTests ); + Catch::TestCaseFilters filters( "" ); + filters.addFilter( matchHidden ); + filters.addFilter( dontMatchA ); + + CHECK( matchHidden.shouldInclude( fakeTestCase( "./something" ) ) ); + + CHECK( filters.shouldInclude( fakeTestCase( "any" ) ) == false ); + CHECK( filters.shouldInclude( fakeTestCase( "./something" ) ) ); + CHECK( filters.shouldInclude( fakeTestCase( "./anything" ) ) == false ); + } + + TEST_CASE( "selftest/filter/prefix wildcard", "Individual filters with wildcards at the start" ) { + Catch::TestCaseFilter matchBadgers( "*badger" ); + + CHECK( matchBadgers.shouldInclude( fakeTestCase( "big badger" ) )); + CHECK( matchBadgers.shouldInclude( fakeTestCase( "little badgers" ) ) == false ); + } + TEST_CASE( "selftest/filter/wildcard at both ends", "Individual filters with wildcards at both ends" ) { + Catch::TestCaseFilter matchBadgers( "*badger*" ); + + CHECK( matchBadgers.shouldInclude( fakeTestCase( "big badger" ) )); + CHECK( matchBadgers.shouldInclude( fakeTestCase( "little badgers" ) ) ); + CHECK( matchBadgers.shouldInclude( fakeTestCase( "badgers are big" ) ) ); + CHECK( matchBadgers.shouldInclude( fakeTestCase( "hedgehogs" ) ) == false ); + } + + + template + int getArgc( const char * (&)[size] ) { + return size; + } + + TEST_CASE( "selftest/tags", "[tags]" ) { + + std::string p1 = "[one]"; + std::string p2 = "[one],[two]"; + std::string p3 = "[one][two]"; + std::string p4 = "[one][two],[three]"; + std::string p5 = "[one][two]~[.],[three]"; - SECTION( "debugger", "" ) { - SECTION( "-b", "" ) { - const char* argv[] = { "test", "-b" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - - REQUIRE( config.shouldDebugBreak == true ); + SECTION( "single [one] tag", "" ) { + Catch::TestCase oneTag = makeTestCase( NULL, "", "test", "[one]", CATCH_INTERNAL_LINEINFO ); + + CHECK( oneTag.getTestCaseInfo().description == "" ); + CHECK( oneTag.hasTag( "one" ) ); + CHECK( oneTag.getTags().size() == 1 ); + + CHECK( oneTag.matchesTags( p1 ) == true ); + CHECK( oneTag.matchesTags( p2 ) == true ); + CHECK( oneTag.matchesTags( p3 ) == false ); + CHECK( oneTag.matchesTags( p4 ) == false ); + CHECK( oneTag.matchesTags( p5 ) == false ); } - SECTION( "--break", "" ) { - const char* argv[] = { "test", "--break" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - - REQUIRE( config.shouldDebugBreak ); + + SECTION( "single [two] tag", "" ) { + Catch::TestCase oneTag = makeTestCase( NULL, "", "test", "[two]", CATCH_INTERNAL_LINEINFO ); + + CHECK( oneTag.getTestCaseInfo().description == "" ); + CHECK( oneTag.hasTag( "two" ) ); + CHECK( oneTag.getTags().size() == 1 ); + + CHECK( oneTag.matchesTags( p1 ) == false ); + CHECK( oneTag.matchesTags( p2 ) == true ); + CHECK( oneTag.matchesTags( p3 ) == false ); + CHECK( oneTag.matchesTags( p4 ) == false ); + CHECK( oneTag.matchesTags( p5 ) == false ); + } + + SECTION( "two tags", "" ) { + Catch::TestCase twoTags= makeTestCase( NULL, "", "test", "[one][two]", CATCH_INTERNAL_LINEINFO ); + + CHECK( twoTags.getTestCaseInfo().description == "" ); + CHECK( twoTags.hasTag( "one" ) ); + CHECK( twoTags.hasTag( "two" ) ); + CHECK( twoTags.hasTag( "Two" ) ); + CHECK( twoTags.hasTag( "three" ) == false ); + CHECK( twoTags.getTags().size() == 2 ); + + CHECK( twoTags.matchesTags( p1 ) == true ); + CHECK( twoTags.matchesTags( p2 ) == true ); + CHECK( twoTags.matchesTags( p3 ) == true ); + CHECK( twoTags.matchesTags( p4 ) == true ); + CHECK( twoTags.matchesTags( p5 ) == true ); + } + SECTION( "complex", "" ) { + CHECK( fakeTestCase( "test", "[one][.]" ).matchesTags( p1 ) ); + CHECK_FALSE( fakeTestCase( "test", "[one][.]" ).matchesTags( p5 ) ); + CHECK( fakeTestCase( "test", "[three]" ).matchesTags( p4 ) ); + CHECK( fakeTestCase( "test", "[three]" ).matchesTags( p5 ) ); + CHECK( fakeTestCase( "test", "[three]" ).matchesTags( "[three]~[one]" ) ); + CHECK( fakeTestCase( "test", "[unit][not_apple]" ).matchesTags( "[unit]" ) ); + CHECK_FALSE( fakeTestCase( "test", "[unit][not_apple]" ).matchesTags( "[unit]~[not_apple]" ) ); + } + + SECTION( "one tag with characters either side", "" ) { + + Catch::TestCase oneTagWithExtras = makeTestCase( NULL, "", "test", "12[one]34", CATCH_INTERNAL_LINEINFO ); + CHECK( oneTagWithExtras.getTestCaseInfo().description == "1234" ); + CHECK( oneTagWithExtras.hasTag( "one" ) ); + CHECK( oneTagWithExtras.hasTag( "two" ) == false ); + CHECK( oneTagWithExtras.getTags().size() == 1 ); + } + + SECTION( "start of a tag, but not closed", "" ) { + + Catch::TestCase oneTagOpen = makeTestCase( NULL, "", "test", "[one", CATCH_INTERNAL_LINEINFO ); + + CHECK( oneTagOpen.getTestCaseInfo().description == "[one" ); + CHECK( oneTagOpen.hasTag( "one" ) == false ); + CHECK( oneTagOpen.getTags().size() == 0 ); + } + + SECTION( "hidden", "" ) { + Catch::TestCase oneTag = makeTestCase( NULL, "", "test", "[.]", CATCH_INTERNAL_LINEINFO ); + + CHECK( oneTag.getTestCaseInfo().description == "" ); + CHECK( oneTag.hasTag( "." ) ); + CHECK( oneTag.isHidden() ); + + CHECK( oneTag.matchesTags( "~[.]" ) == false ); + } } + + TEST_CASE( "Long strings can be wrapped", "[wrap]" ) { + + using namespace Catch; + SECTION( "plain string", "" ) { + // guide: 123456789012345678 + std::string testString = "one two three four"; - SECTION( "abort", "" ) { - SECTION( "-a aborts after first failure", "" ) { - const char* argv[] = { "test", "-a" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + SECTION( "No wrapping", "" ) { + CHECK( Catch::Text( testString, TextAttributes().setWidth( 80 ) ).toString() == testString ); + CHECK( Catch::Text( testString, TextAttributes().setWidth( 18 ) ).toString() == testString ); + } + SECTION( "Wrapped once", "" ) { + CHECK( Catch::Text( testString, TextAttributes().setWidth( 17 ) ).toString() == "one two three\nfour" ); + CHECK( Catch::Text( testString, TextAttributes().setWidth( 16 ) ).toString() == "one two three\nfour" ); + CHECK( Catch::Text( testString, TextAttributes().setWidth( 14 ) ).toString() == "one two three\nfour" ); + CHECK( Catch::Text( testString, TextAttributes().setWidth( 13 ) ).toString() == "one two three\nfour" ); + CHECK( Catch::Text( testString, TextAttributes().setWidth( 12 ) ).toString() == "one two\nthree four" ); + } + SECTION( "Wrapped twice", "" ) { + CHECK( Catch::Text( testString, TextAttributes().setWidth( 9 ) ).toString() == "one two\nthree\nfour" ); + CHECK( Catch::Text( testString, TextAttributes().setWidth( 8 ) ).toString() == "one two\nthree\nfour" ); + CHECK( Catch::Text( testString, TextAttributes().setWidth( 7 ) ).toString() == "one two\nthree\nfour" ); + } + SECTION( "Wrapped three times", "" ) { + CHECK( Catch::Text( testString, TextAttributes().setWidth( 6 ) ).toString() == "one\ntwo\nthree\nfour" ); + CHECK( Catch::Text( testString, TextAttributes().setWidth( 5 ) ).toString() == "one\ntwo\nthree\nfour" ); + } + SECTION( "Short wrap", "" ) { + CHECK( Catch::Text( "abcdef", TextAttributes().setWidth( 4 ) ).toString() == "abc-\ndef" ); + CHECK( Catch::Text( "abcdefg", TextAttributes().setWidth( 4 ) ).toString() == "abc-\ndefg" ); + CHECK( Catch::Text( "abcdefgh", TextAttributes().setWidth( 4 ) ).toString() == "abc-\ndef-\ngh" ); - REQUIRE( config.abortAfter == 1 ); + CHECK( Catch::Text( testString, TextAttributes().setWidth( 4 ) ).toString() == "one\ntwo\nthr-\nee\nfour" ); + CHECK( Catch::Text( testString, TextAttributes().setWidth( 3 ) ).toString() == "one\ntwo\nth-\nree\nfo-\nur" ); + } + SECTION( "As container", "" ) { + Catch::Text text( testString, TextAttributes().setWidth( 6 ) ); + REQUIRE( text.size() == 4 ); + CHECK( text[0] == "one" ); + CHECK( text[1] == "two" ); + CHECK( text[2] == "three" ); + CHECK( text[3] == "four" ); + } + SECTION( "Indent first line differently", "" ) { + Catch::Text text( testString, TextAttributes() + .setWidth( 10 ) + .setIndent( 4 ) + .setInitialIndent( 1 ) ); + CHECK( text.toString() == " one two\n three\n four" ); + } + } - SECTION( "-x 2 aborts after two failures", "" ) { - const char* argv[] = { "test", "-x", "2" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - - REQUIRE( config.abortAfter == 2 ); - } - SECTION( "-x must be greater than zero", "" ) { - const char* argv[] = { "test", "-x", "0" }; - REQUIRE_THAT( parseIntoConfigAndReturnError( argv, config ), Contains( "greater than zero" ) ); - } - SECTION( "-x must be numeric", "" ) { - const char* argv[] = { "test", "-x", "oops" }; - REQUIRE_THAT( parseIntoConfigAndReturnError( argv, config ), Contains( "-x" ) ); - } - } - SECTION( "nothrow", "" ) { - SECTION( "-e", "" ) { - const char* argv[] = { "test", "-e" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - - REQUIRE( config.noThrow == true ); + SECTION( "With newlines", "" ) { + + // guide: 1234567890123456789 + std::string testString = "one two\nthree four"; + + SECTION( "No wrapping" , "" ) { + CHECK( Catch::Text( testString, TextAttributes().setWidth( 80 ) ).toString() == testString ); + CHECK( Catch::Text( testString, TextAttributes().setWidth( 18 ) ).toString() == testString ); + CHECK( Catch::Text( testString, TextAttributes().setWidth( 10 ) ).toString() == testString ); + } + SECTION( "Trailing newline" , "" ) { + CHECK( Catch::Text( "abcdef\n", TextAttributes().setWidth( 10 ) ).toString() == "abcdef\n" ); + CHECK( Catch::Text( "abcdef", TextAttributes().setWidth( 6 ) ).toString() == "abcdef" ); + CHECK( Catch::Text( "abcdef\n", TextAttributes().setWidth( 6 ) ).toString() == "abcdef\n" ); + } + SECTION( "Wrapped once", "" ) { + CHECK( Catch::Text( testString, TextAttributes().setWidth( 9 ) ).toString() == "one two\nthree\nfour" ); + CHECK( Catch::Text( testString, TextAttributes().setWidth( 8 ) ).toString() == "one two\nthree\nfour" ); + CHECK( Catch::Text( testString, TextAttributes().setWidth( 7 ) ).toString() == "one two\nthree\nfour" ); + } + SECTION( "Wrapped twice", "" ) { + CHECK( Catch::Text( testString, TextAttributes().setWidth( 6 ) ).toString() == "one\ntwo\nthree\nfour" ); + } } - SECTION( "--nothrow", "" ) { - const char* argv[] = { "test", "--nothrow" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - - REQUIRE( config.noThrow == true ); - } - } - - SECTION( "output filename", "" ) { - SECTION( "-o filename", "" ) { - const char* argv[] = { "test", "-o", "filename.ext" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - - REQUIRE( config.outputFilename == "filename.ext" ); - } - SECTION( "--out", "" ) { - const char* argv[] = { "test", "--out", "filename.ext" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - - REQUIRE( config.outputFilename == "filename.ext" ); - } - } - - SECTION( "combinations", "" ) { - SECTION( "Single character flags can be combined", "" ) { - const char* argv[] = { "test", "-abe" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - - CHECK( config.abortAfter == 1 ); - CHECK( config.shouldDebugBreak ); - CHECK( config.noThrow == true ); - } - } -} - -TEST_CASE( "selftest/test filter", "Individual filters" ) { - - Catch::TestCaseFilter matchAny( "*" ); - Catch::TestCaseFilter matchNone( "*", Catch::IfFilterMatches::ExcludeTests ); - CHECK( matchAny.shouldInclude( fakeTestCase( "any" ) )); - CHECK( matchNone.shouldInclude( fakeTestCase( "any" ) ) == false ); - - Catch::TestCaseFilter matchHidden( "./*" ); - Catch::TestCaseFilter matchNonHidden( "./*", Catch::IfFilterMatches::ExcludeTests ); - - CHECK( matchHidden.shouldInclude( fakeTestCase( "any" ) ) == false ); - CHECK( matchNonHidden.shouldInclude( fakeTestCase( "any" ) ) ); - - CHECK( matchHidden.shouldInclude( fakeTestCase( "./any" ) ) ); - CHECK( matchNonHidden.shouldInclude( fakeTestCase( "./any" ) ) == false ); -} - -TEST_CASE( "selftest/test filters", "Sets of filters" ) { - - Catch::TestCaseFilter matchHidden( "./*" ); - Catch::TestCaseFilter dontMatchA( "./a*", Catch::IfFilterMatches::ExcludeTests ); - Catch::TestCaseFilters filters( "" ); - filters.addFilter( matchHidden ); - filters.addFilter( dontMatchA ); - - CHECK( matchHidden.shouldInclude( fakeTestCase( "./something" ) ) ); - - CHECK( filters.shouldInclude( fakeTestCase( "any" ) ) == false ); - CHECK( filters.shouldInclude( fakeTestCase( "./something" ) ) ); - CHECK( filters.shouldInclude( fakeTestCase( "./anything" ) ) == false ); -} - -TEST_CASE( "selftest/filter/prefix wildcard", "Individual filters with wildcards at the start" ) { - Catch::TestCaseFilter matchBadgers( "*badger" ); - - CHECK( matchBadgers.shouldInclude( fakeTestCase( "big badger" ) )); - CHECK( matchBadgers.shouldInclude( fakeTestCase( "little badgers" ) ) == false ); -} -TEST_CASE( "selftest/filter/wildcard at both ends", "Individual filters with wildcards at both ends" ) { - Catch::TestCaseFilter matchBadgers( "*badger*" ); - - CHECK( matchBadgers.shouldInclude( fakeTestCase( "big badger" ) )); - CHECK( matchBadgers.shouldInclude( fakeTestCase( "little badgers" ) ) ); - CHECK( matchBadgers.shouldInclude( fakeTestCase( "badgers are big" ) ) ); - CHECK( matchBadgers.shouldInclude( fakeTestCase( "hedgehogs" ) ) == false ); -} - - -template -int getArgc( const char * (&)[size] ) { - return size; -} - -TEST_CASE( "selftest/tags", "[tags]" ) { - - std::string p1 = "[one]"; - std::string p2 = "[one],[two]"; - std::string p3 = "[one][two]"; - std::string p4 = "[one][two],[three]"; - std::string p5 = "[one][two]~[.],[three]"; - SECTION( "single [one] tag", "" ) { - Catch::TestCase oneTag = makeTestCase( NULL, "", "test", "[one]", CATCH_INTERNAL_LINEINFO ); + SECTION( "With tabs", "" ) { - CHECK( oneTag.getTestCaseInfo().description == "" ); - CHECK( oneTag.hasTag( "one" ) ); - CHECK( oneTag.getTags().size() == 1 ); - - CHECK( oneTag.matchesTags( p1 ) == true ); - CHECK( oneTag.matchesTags( p2 ) == true ); - CHECK( oneTag.matchesTags( p3 ) == false ); - CHECK( oneTag.matchesTags( p4 ) == false ); - CHECK( oneTag.matchesTags( p5 ) == false ); - } - - SECTION( "single [two] tag", "" ) { - Catch::TestCase oneTag = makeTestCase( NULL, "", "test", "[two]", CATCH_INTERNAL_LINEINFO ); - - CHECK( oneTag.getTestCaseInfo().description == "" ); - CHECK( oneTag.hasTag( "two" ) ); - CHECK( oneTag.getTags().size() == 1 ); - - CHECK( oneTag.matchesTags( p1 ) == false ); - CHECK( oneTag.matchesTags( p2 ) == true ); - CHECK( oneTag.matchesTags( p3 ) == false ); - CHECK( oneTag.matchesTags( p4 ) == false ); - CHECK( oneTag.matchesTags( p5 ) == false ); - } - - SECTION( "two tags", "" ) { - Catch::TestCase twoTags= makeTestCase( NULL, "", "test", "[one][two]", CATCH_INTERNAL_LINEINFO ); - - CHECK( twoTags.getTestCaseInfo().description == "" ); - CHECK( twoTags.hasTag( "one" ) ); - CHECK( twoTags.hasTag( "two" ) ); - CHECK( twoTags.hasTag( "Two" ) ); - CHECK( twoTags.hasTag( "three" ) == false ); - CHECK( twoTags.getTags().size() == 2 ); - - CHECK( twoTags.matchesTags( p1 ) == true ); - CHECK( twoTags.matchesTags( p2 ) == true ); - CHECK( twoTags.matchesTags( p3 ) == true ); - CHECK( twoTags.matchesTags( p4 ) == true ); - CHECK( twoTags.matchesTags( p5 ) == true ); - } - SECTION( "complex", "" ) { - CHECK( fakeTestCase( "test", "[one][.]" ).matchesTags( p1 ) ); - CHECK_FALSE( fakeTestCase( "test", "[one][.]" ).matchesTags( p5 ) ); - CHECK( fakeTestCase( "test", "[three]" ).matchesTags( p4 ) ); - CHECK( fakeTestCase( "test", "[three]" ).matchesTags( p5 ) ); - CHECK( fakeTestCase( "test", "[three]" ).matchesTags( "[three]~[one]" ) ); - CHECK( fakeTestCase( "test", "[unit][not_apple]" ).matchesTags( "[unit]" ) ); - CHECK_FALSE( fakeTestCase( "test", "[unit][not_apple]" ).matchesTags( "[unit]~[not_apple]" ) ); - } - - SECTION( "one tag with characters either side", "" ) { - - Catch::TestCase oneTagWithExtras = makeTestCase( NULL, "", "test", "12[one]34", CATCH_INTERNAL_LINEINFO ); - CHECK( oneTagWithExtras.getTestCaseInfo().description == "1234" ); - CHECK( oneTagWithExtras.hasTag( "one" ) ); - CHECK( oneTagWithExtras.hasTag( "two" ) == false ); - CHECK( oneTagWithExtras.getTags().size() == 1 ); - } + // guide: 1234567890123456789 + std::string testString = "one two \tthree four five six"; + + CHECK( Catch::Text( testString, TextAttributes().setWidth( 15 ) ).toString() + == "one two three\n four\n five\n six" ); + } + - SECTION( "start of a tag, but not closed", "" ) { - - Catch::TestCase oneTagOpen = makeTestCase( NULL, "", "test", "[one", CATCH_INTERNAL_LINEINFO ); - - CHECK( oneTagOpen.getTestCaseInfo().description == "[one" ); - CHECK( oneTagOpen.hasTag( "one" ) == false ); - CHECK( oneTagOpen.getTags().size() == 0 ); } - SECTION( "hidden", "" ) { - Catch::TestCase oneTag = makeTestCase( NULL, "", "test", "[.]", CATCH_INTERNAL_LINEINFO ); - - CHECK( oneTag.getTestCaseInfo().description == "" ); - CHECK( oneTag.hasTag( "." ) ); - CHECK( oneTag.isHidden() ); - - CHECK( oneTag.matchesTags( "~[.]" ) == false ); - - } -} - -TEST_CASE( "Long strings can be wrapped", "[wrap]" ) { - using namespace Catch; - SECTION( "plain string", "" ) { - // guide: 123456789012345678 - std::string testString = "one two three four"; - - SECTION( "No wrapping", "" ) { - CHECK( Text( testString, TextAttributes().setWidth( 80 ) ).toString() == testString ); - CHECK( Text( testString, TextAttributes().setWidth( 18 ) ).toString() == testString ); - } - SECTION( "Wrapped once", "" ) { - CHECK( Text( testString, TextAttributes().setWidth( 17 ) ).toString() == "one two three\nfour" ); - CHECK( Text( testString, TextAttributes().setWidth( 16 ) ).toString() == "one two three\nfour" ); - CHECK( Text( testString, TextAttributes().setWidth( 14 ) ).toString() == "one two three\nfour" ); - CHECK( Text( testString, TextAttributes().setWidth( 13 ) ).toString() == "one two three\nfour" ); - CHECK( Text( testString, TextAttributes().setWidth( 12 ) ).toString() == "one two\nthree four" ); - } - SECTION( "Wrapped twice", "" ) { - CHECK( Text( testString, TextAttributes().setWidth( 9 ) ).toString() == "one two\nthree\nfour" ); - CHECK( Text( testString, TextAttributes().setWidth( 8 ) ).toString() == "one two\nthree\nfour" ); - CHECK( Text( testString, TextAttributes().setWidth( 7 ) ).toString() == "one two\nthree\nfour" ); - } - SECTION( "Wrapped three times", "" ) { - CHECK( Text( testString, TextAttributes().setWidth( 6 ) ).toString() == "one\ntwo\nthree\nfour" ); - CHECK( Text( testString, TextAttributes().setWidth( 5 ) ).toString() == "one\ntwo\nthree\nfour" ); - } - SECTION( "Short wrap", "" ) { - CHECK( Text( "abcdef", TextAttributes().setWidth( 4 ) ).toString() == "abc-\ndef" ); - CHECK( Text( "abcdefg", TextAttributes().setWidth( 4 ) ).toString() == "abc-\ndefg" ); - CHECK( Text( "abcdefgh", TextAttributes().setWidth( 4 ) ).toString() == "abc-\ndef-\ngh" ); - CHECK( Text( testString, TextAttributes().setWidth( 4 ) ).toString() == "one\ntwo\nthr-\nee\nfour" ); - CHECK( Text( testString, TextAttributes().setWidth( 3 ) ).toString() == "one\ntwo\nth-\nree\nfo-\nur" ); - } - SECTION( "As container", "" ) { - Text text( testString, TextAttributes().setWidth( 6 ) ); - REQUIRE( text.size() == 4 ); - CHECK( text[0] == "one" ); - CHECK( text[1] == "two" ); - CHECK( text[2] == "three" ); - CHECK( text[3] == "four" ); - } - SECTION( "Indent first line differently", "" ) { - Text text( testString, TextAttributes() - .setWidth( 10 ) - .setIndent( 4 ) - .setInitialIndent( 1 ) ); - CHECK( text.toString() == " one two\n three\n four" ); - } - - } - - SECTION( "With newlines", "" ) { - - // guide: 1234567890123456789 - std::string testString = "one two\nthree four"; - - SECTION( "No wrapping" , "" ) { - CHECK( Text( testString, TextAttributes().setWidth( 80 ) ).toString() == testString ); - CHECK( Text( testString, TextAttributes().setWidth( 18 ) ).toString() == testString ); - CHECK( Text( testString, TextAttributes().setWidth( 10 ) ).toString() == testString ); - } - SECTION( "Trailing newline" , "" ) { - CHECK( Text( "abcdef\n", TextAttributes().setWidth( 10 ) ).toString() == "abcdef\n" ); - CHECK( Text( "abcdef", TextAttributes().setWidth( 6 ) ).toString() == "abcdef" ); - CHECK( Text( "abcdef\n", TextAttributes().setWidth( 6 ) ).toString() == "abcdef\n" ); - } - SECTION( "Wrapped once", "" ) { - CHECK( Text( testString, TextAttributes().setWidth( 9 ) ).toString() == "one two\nthree\nfour" ); - CHECK( Text( testString, TextAttributes().setWidth( 8 ) ).toString() == "one two\nthree\nfour" ); - CHECK( Text( testString, TextAttributes().setWidth( 7 ) ).toString() == "one two\nthree\nfour" ); - } - SECTION( "Wrapped twice", "" ) { - CHECK( Text( testString, TextAttributes().setWidth( 6 ) ).toString() == "one\ntwo\nthree\nfour" ); - } - } - - SECTION( "With tabs", "" ) { + class ColourString { + public: - // guide: 1234567890123456789 - std::string testString = "one two \tthree four five six"; - - CHECK( Text( testString, TextAttributes().setWidth( 15 ) ).toString() - == "one two three\n four\n five\n six" ); - } - - -} + struct ColourIndex { + ColourIndex( Colour::Code _colour, std::size_t _fromIndex, std::size_t _toIndex ) + : colour( _colour ), + fromIndex( _fromIndex ), + toIndex( _toIndex ) + {} -using namespace Catch; + Colour::Code colour; + std::size_t fromIndex; + std::size_t toIndex; + }; -class ColourString { -public: - - struct ColourIndex { - ColourIndex( Colour::Code _colour, std::size_t _fromIndex, std::size_t _toIndex ) - : colour( _colour ), - fromIndex( _fromIndex ), - toIndex( _toIndex ) + ColourString( std::string const& _string ) + : string( _string ) {} + ColourString( std::string const& _string, std::vector const& _colours ) + : string( _string ), colours( _colours ) + {} + + ColourString& addColour( Colour::Code colour, int _index ) { + colours.push_back( ColourIndex( colour, + resolveRelativeIndex( _index ), + resolveRelativeIndex( _index )+1 ) ); + return *this; + } + ColourString& addColour( Colour::Code colour, int _fromIndex, int _toIndex ) { + colours.push_back( ColourIndex( colour, + resolveRelativeIndex(_fromIndex), + resolveLastRelativeIndex( _toIndex ) ) ); + return *this; + } + + void writeToStream( std::ostream& _stream ) const { + std::size_t last = 0; + for( std::size_t i = 0; i < colours.size(); ++i ) { + ColourIndex const& index = colours[i]; + if( index.fromIndex > last ) + _stream << string.substr( last, index.fromIndex-last ); + { + Colour colourGuard( index.colour ); + _stream << string.substr( index.fromIndex, index.toIndex-index.fromIndex ); + } + last = index.toIndex; + } + if( last < string.size() ) + _stream << string.substr( last ); + } + friend std::ostream& operator << ( std::ostream& _stream, ColourString const& _colourString ) { + _colourString.writeToStream( _stream ); + return _stream; + } - Colour::Code colour; - std::size_t fromIndex; - std::size_t toIndex; + private: + + std::size_t resolveLastRelativeIndex( int _index ) { + std::size_t index = resolveRelativeIndex( _index ); + return index == 0 ? string.size() : index; + } + std::size_t resolveRelativeIndex( int _index ) { + return static_cast( _index >= 0 + ? _index + : static_cast( string.size() )+_index ); + } + std::string string; + std::vector colours; }; - ColourString( std::string const& _string ) - : string( _string ) - {} - ColourString( std::string const& _string, std::vector const& _colours ) - : string( _string ), colours( _colours ) - {} + // !TBD: This will be folded into Text class + TEST_CASE( "Strings can be rendered with colour", "[colour]" ) { - ColourString& addColour( Colour::Code colour, int _index ) { - colours.push_back( ColourIndex( colour, - resolveRelativeIndex( _index ), - resolveRelativeIndex( _index )+1 ) ); - return *this; - } - ColourString& addColour( Colour::Code colour, int _fromIndex, int _toIndex ) { - colours.push_back( ColourIndex( colour, - resolveRelativeIndex(_fromIndex), - resolveLastRelativeIndex( _toIndex ) ) ); - return *this; - } - - void writeToStream( std::ostream& _stream ) const { - std::size_t last = 0; - for( std::size_t i = 0; i < colours.size(); ++i ) { - ColourIndex const& index = colours[i]; - if( index.fromIndex > last ) - _stream << string.substr( last, index.fromIndex-last ); - { - Colour colourGuard( index.colour ); - _stream << string.substr( index.fromIndex, index.toIndex-index.fromIndex ); - } - last = index.toIndex; + { + ColourString cs( "hello" ); + cs .addColour( Colour::Red, 0 ) + .addColour( Colour::Green, -1 ); + + std::cout << cs << std::endl; } - if( last < string.size() ) - _stream << string.substr( last ); - } - friend std::ostream& operator << ( std::ostream& _stream, ColourString const& _colourString ) { - _colourString.writeToStream( _stream ); - return _stream; - } -private: - - std::size_t resolveLastRelativeIndex( int _index ) { - std::size_t index = resolveRelativeIndex( _index ); - return index == 0 ? string.size() : index; - } - std::size_t resolveRelativeIndex( int _index ) { - return static_cast( _index >= 0 - ? _index - : static_cast( string.size() )+_index ); - } - std::string string; - std::vector colours; -}; - -// !TBD: This will be folded into Text class -TEST_CASE( "Strings can be rendered with colour", "[colour]" ) { - - { - ColourString cs( "hello" ); - cs .addColour( Colour::Red, 0 ) - .addColour( Colour::Green, -1 ); - - std::cout << cs << std::endl; - } - - { - ColourString cs( "hello" ); - cs .addColour( Colour::Blue, 1, -2 ); + { + ColourString cs( "hello" ); + cs .addColour( Colour::Blue, 1, -2 ); - std::cout << cs << std::endl; + std::cout << cs << std::endl; + } + } + + TEST_CASE( "Text can be formatted using the Text class", "" ) { -} - -TEST_CASE( "Text can be formatted using the Text class", "" ) { - - CHECK( Text( "hi there" ).toString() == "hi there" ); - - TextAttributes narrow; - narrow.setWidth( 6 ); - - CHECK( Text( "hi there", narrow ).toString() == "hi\nthere" ); -} - -TEST_CASE( "Long text is truncted", "[Text][Truncated]" ) { - - std::string longLine( 90, '*' ); - - std::ostringstream oss; - for(int i = 0; i < 600; ++i ) - oss << longLine << longLine << "\n"; - Text t( oss.str() ); - CHECK_THAT( t.toString(), EndsWith( "... message truncated due to excessive size" ) ); - + CHECK( Catch::Text( "hi there" ).toString() == "hi there" ); + + TextAttributes narrow; + narrow.setWidth( 6 ); + + CHECK( Catch::Text( "hi there", narrow ).toString() == "hi\nthere" ); + } + + TEST_CASE( "Long text is truncted", "[Text][Truncated]" ) { + + std::string longLine( 90, '*' ); + + std::ostringstream oss; + for(int i = 0; i < 600; ++i ) + oss << longLine << longLine << "\n"; + Catch::Text t( oss.str() ); + CHECK_THAT( t.toString(), EndsWith( "... message truncated due to excessive size" ) ); + + } } diff --git a/projects/SelfTest/VisualStudioTests.cpp b/projects/SelfTest/VisualStudioTests.cpp new file mode 100644 index 00000000..b972a4ea --- /dev/null +++ b/projects/SelfTest/VisualStudioTests.cpp @@ -0,0 +1,110 @@ +/* + * Created by Phil on 22/10/2010. + * Copyright 2010 Two Blue Cubes Ltd + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ + +#include "catch.hpp" + +#if defined(INTERNAL_CATCH_VS_MANAGED) || defined(INTERNAL_CATCH_VS_NATIVE) + +namespace VisualStudioTests +{ + class UniqueTestsFixture { + private: + static int uniqueID; + public: + UniqueTestsFixture() { } + protected: + int getID() { + return ++uniqueID; + } + }; + + int UniqueTestsFixture::uniqueID = 0; + TEST_CASE("M00", "[m_off]") + { + bool show = Catch::getCurrentContext().getConfig()->includeSuccessfulResults(); + REQUIRE(!show); + } + + CATCH_CONFIG_SHOW_SUCCESS(true) + TEST_CASE("M01", "[m_on]") + { + bool show = Catch::getCurrentContext().getConfig()->includeSuccessfulResults(); + REQUIRE(show); + } + + TEST_CASE("M02", "[m_off]") + { + bool show = Catch::getCurrentContext().getConfig()->includeSuccessfulResults(); + REQUIRE(!show); + } + + TEST_CASE_METHOD(UniqueTestsFixture, "M10", "[m_off]") + { + bool show = Catch::getCurrentContext().getConfig()->includeSuccessfulResults(); + REQUIRE(!show); + getID(); + } + + CATCH_CONFIG_WARN_MISSING_ASSERTIONS(true) + CATCH_CONFIG_SHOW_SUCCESS(true) + TEST_CASE_METHOD(UniqueTestsFixture, "M11", "[m_on]") + { + bool show = Catch::getCurrentContext().getConfig()->includeSuccessfulResults(); + REQUIRE(show); + getID(); + } + + CATCH_CONFIG_WARN_MISSING_ASSERTIONS(true) + CATCH_CONFIG_SHOW_SUCCESS(true) + TEST_CASE_METHOD(UniqueTestsFixture, "M99", "[m_on]") + { + bool show = Catch::getCurrentContext().getConfig()->includeSuccessfulResults(); + REQUIRE(show); + WARN("Warning message"); + getID(); + } + + TEST_CASE_METHOD(UniqueTestsFixture, "M12", "[m_off]") + { + bool show = Catch::getCurrentContext().getConfig()->includeSuccessfulResults(); + REQUIRE(!show); + getID(); + } + + class ConfigTest + { + public: + void run1() + { + bool show = Catch::getCurrentContext().getConfig()->includeSuccessfulResults(); + REQUIRE(!show); + } + void run2() + { + bool show = Catch::getCurrentContext().getConfig()->includeSuccessfulResults(); + REQUIRE(show); + } + void run3() + { + bool show = Catch::getCurrentContext().getConfig()->includeSuccessfulResults(); + REQUIRE(!show); + } + }; + METHOD_AS_TEST_CASE(ConfigTest::run1,"M20", "[m_off]"); + + CATCH_CONFIG_SHOW_SUCCESS(true) + METHOD_AS_TEST_CASE(ConfigTest::run2,"M21", "[m_on]"); + + METHOD_AS_TEST_CASE(ConfigTest::run3,"M22", "[m_off]"); + + CATCH_MAP_CATEGORY_TO_TAG(vstestsCheckOutputOff, "[m_off]"); + CATCH_CONFIG_SHOW_SUCCESS(true) + CATCH_MAP_CATEGORY_TO_TAG(vstestsCheckOutputOn, "[m_on]"); + CATCH_MAP_CATEGORY_TO_TAG(vstestsCheckOutputOff2, "[m_off]"); +} +#endif diff --git a/projects/SelfTest/catch_self_test.cpp b/projects/SelfTest/catch_self_test.cpp index c5153d68..4dce6fda 100644 --- a/projects/SelfTest/catch_self_test.cpp +++ b/projects/SelfTest/catch_self_test.cpp @@ -6,7 +6,9 @@ * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ +#if !defined(_WINDLL) #define CATCH_CONFIG_MAIN +#endif #include "catch_self_test.hpp" namespace Catch{ diff --git a/projects/SelfTest/makefile b/projects/SelfTest/makefile index 9be05008..61ed50ec 100644 --- a/projects/SelfTest/makefile +++ b/projects/SelfTest/makefile @@ -4,8 +4,6 @@ SOURCES = ApproxTests.cpp \ ExceptionTests.cpp \ GeneratorTests.cpp \ MessageTests.cpp \ - MessageInstantiationTests1.cpp \ - MessageInstantiationTests2.cpp \ MiscTests.cpp \ TestMain.cpp \ TrickyTests.cpp \ From a0af453cda74fd55f41e15a55721e9eeff8b0e15 Mon Sep 17 00:00:00 2001 From: Malcolm Noyes Date: Mon, 9 Dec 2013 14:32:35 +0000 Subject: [PATCH 5/6] scripts for validation on windows --- .../ManagedTestCatch/ManagedTestCatch.sln | 6 + .../ManagedTestCatch/ManagedTestCatch.vcxproj | 64 +- .../ManagedTestCatch.vcxproj.filters | 12 + .../TestCatch/TestCatch/TestCatch.vcxproj | 2 - .../TestCatch/TestCatch.vcxproj.filters | 6 - .../ManagedTestCatch/ManagedTestCatch.sln | 6 + .../ManagedTestCatch/ManagedTestCatch.vcxproj | 75 +- .../ManagedTestCatch.vcxproj.filters | 12 + .../NativeTestCatch/NativeTestCatch.sln | 6 + .../NativeTestCatch/NativeTestCatch.vcxproj | 70 ++ .../NativeTestCatch.vcxproj.filters | 12 + scripts/approvalVSTests.py | 716 ++++++++++++++++++ scripts/catch_conditions.py | 398 ++++++++++ scripts/catch_test_case.py | 584 ++++++++++++++ scripts/catch_test_run.py | 430 +++++++++++ scripts/test_conditions.py | 698 +++++++++++++++++ scripts/test_test_case.py | 341 +++++++++ scripts/test_test_run.py | 334 ++++++++ 18 files changed, 3761 insertions(+), 11 deletions(-) create mode 100644 scripts/approvalVSTests.py create mode 100644 scripts/catch_conditions.py create mode 100644 scripts/catch_test_case.py create mode 100644 scripts/catch_test_run.py create mode 100644 scripts/test_conditions.py create mode 100644 scripts/test_test_case.py create mode 100644 scripts/test_test_run.py diff --git a/projects/VS2010/ManagedTestCatch/ManagedTestCatch.sln b/projects/VS2010/ManagedTestCatch/ManagedTestCatch.sln index 8d265189..7cfd3e6c 100644 --- a/projects/VS2010/ManagedTestCatch/ManagedTestCatch.sln +++ b/projects/VS2010/ManagedTestCatch/ManagedTestCatch.sln @@ -16,13 +16,19 @@ Global EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 + DebugMbcs|Win32 = DebugMbcs|Win32 Release|Win32 = Release|Win32 + ReleaseMbcs|Win32 = ReleaseMbcs|Win32 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {7CC06A6B-763E-42B3-AF6C-8F1E340372A1}.Debug|Win32.ActiveCfg = Debug|Win32 {7CC06A6B-763E-42B3-AF6C-8F1E340372A1}.Debug|Win32.Build.0 = Debug|Win32 + {7CC06A6B-763E-42B3-AF6C-8F1E340372A1}.DebugMbcs|Win32.ActiveCfg = DebugMbcs|Win32 + {7CC06A6B-763E-42B3-AF6C-8F1E340372A1}.DebugMbcs|Win32.Build.0 = DebugMbcs|Win32 {7CC06A6B-763E-42B3-AF6C-8F1E340372A1}.Release|Win32.ActiveCfg = Release|Win32 {7CC06A6B-763E-42B3-AF6C-8F1E340372A1}.Release|Win32.Build.0 = Release|Win32 + {7CC06A6B-763E-42B3-AF6C-8F1E340372A1}.ReleaseMbcs|Win32.ActiveCfg = ReleaseMbcs|Win32 + {7CC06A6B-763E-42B3-AF6C-8F1E340372A1}.ReleaseMbcs|Win32.Build.0 = ReleaseMbcs|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/projects/VS2010/ManagedTestCatch/ManagedTestCatch.vcxproj b/projects/VS2010/ManagedTestCatch/ManagedTestCatch.vcxproj index b14d3a5f..84f8baf6 100644 --- a/projects/VS2010/ManagedTestCatch/ManagedTestCatch.vcxproj +++ b/projects/VS2010/ManagedTestCatch/ManagedTestCatch.vcxproj @@ -1,17 +1,25 @@  + + DebugMbcs + Win32 + Debug Win32 + + ReleaseMbcs + Win32 + Release Win32 - DefaultTest + $(ProjectName) {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942} {7CC06A6B-763E-42B3-AF6C-8F1E340372A1} ManagedCProj @@ -23,24 +31,42 @@ true Unicode + + DynamicLibrary + true + MultiByte + DynamicLibrary Safe Unicode + + DynamicLibrary + Safe + MultiByte + + + + + + + if exist app.config copy app.config "$(OutDir)app.config" true + true false + false @@ -58,6 +84,22 @@ + + + Level4 + Disabled + WIN32;_DEBUG;%(PreprocessorDefinitions) + NotUsing + ..\..\..\include + true + MultiThreadedDebugDLL + + + true + + + + Level3 @@ -72,6 +114,20 @@ + + + Level3 + WIN32;NDEBUG;%(PreprocessorDefinitions) + NotUsing + true + ../../../include + + + true + + + + @@ -81,6 +137,7 @@ + @@ -90,13 +147,18 @@ + + + Create + Create Create + Create diff --git a/projects/VS2010/ManagedTestCatch/ManagedTestCatch.vcxproj.filters b/projects/VS2010/ManagedTestCatch/ManagedTestCatch.vcxproj.filters index 0f1c9922..fe8b5077 100644 --- a/projects/VS2010/ManagedTestCatch/ManagedTestCatch.vcxproj.filters +++ b/projects/VS2010/ManagedTestCatch/ManagedTestCatch.vcxproj.filters @@ -63,6 +63,18 @@ Source Files + + Source Files + + + Source Files + + + Source Files + + + Source Files + diff --git a/projects/VS2010/TestCatch/TestCatch/TestCatch.vcxproj b/projects/VS2010/TestCatch/TestCatch/TestCatch.vcxproj index d9105496..d7b89498 100644 --- a/projects/VS2010/TestCatch/TestCatch/TestCatch.vcxproj +++ b/projects/VS2010/TestCatch/TestCatch/TestCatch.vcxproj @@ -94,8 +94,6 @@ - - diff --git a/projects/VS2010/TestCatch/TestCatch/TestCatch.vcxproj.filters b/projects/VS2010/TestCatch/TestCatch/TestCatch.vcxproj.filters index b358c13d..c74faac3 100644 --- a/projects/VS2010/TestCatch/TestCatch/TestCatch.vcxproj.filters +++ b/projects/VS2010/TestCatch/TestCatch/TestCatch.vcxproj.filters @@ -28,12 +28,6 @@ Sources - - Sources - - - Sources - Sources diff --git a/projects/VS2012/ManagedTestCatch/ManagedTestCatch.sln b/projects/VS2012/ManagedTestCatch/ManagedTestCatch.sln index b41b17a4..a0be7336 100644 --- a/projects/VS2012/ManagedTestCatch/ManagedTestCatch.sln +++ b/projects/VS2012/ManagedTestCatch/ManagedTestCatch.sln @@ -6,13 +6,19 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 + DebugMbcs|Win32 = DebugMbcs|Win32 Release|Win32 = Release|Win32 + ReleaseMbcs|Win32 = ReleaseMbcs|Win32 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {9757CB21-B840-49A6-B057-9F322E543DD6}.Debug|Win32.ActiveCfg = Debug|Win32 {9757CB21-B840-49A6-B057-9F322E543DD6}.Debug|Win32.Build.0 = Debug|Win32 + {9757CB21-B840-49A6-B057-9F322E543DD6}.DebugMbcs|Win32.ActiveCfg = DebugMbcs|Win32 + {9757CB21-B840-49A6-B057-9F322E543DD6}.DebugMbcs|Win32.Build.0 = DebugMbcs|Win32 {9757CB21-B840-49A6-B057-9F322E543DD6}.Release|Win32.ActiveCfg = Release|Win32 {9757CB21-B840-49A6-B057-9F322E543DD6}.Release|Win32.Build.0 = Release|Win32 + {9757CB21-B840-49A6-B057-9F322E543DD6}.ReleaseMbcs|Win32.ActiveCfg = ReleaseMbcs|Win32 + {9757CB21-B840-49A6-B057-9F322E543DD6}.ReleaseMbcs|Win32.Build.0 = ReleaseMbcs|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/projects/VS2012/ManagedTestCatch/ManagedTestCatch.vcxproj b/projects/VS2012/ManagedTestCatch/ManagedTestCatch.vcxproj index e5bcc201..d96ad74a 100644 --- a/projects/VS2012/ManagedTestCatch/ManagedTestCatch.vcxproj +++ b/projects/VS2012/ManagedTestCatch/ManagedTestCatch.vcxproj @@ -1,17 +1,25 @@  + + DebugMbcs + Win32 + Debug Win32 + + ReleaseMbcs + Win32 + Release Win32 - DefaultTest + $(ProjectName) {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942} {9757CB21-B840-49A6-B057-9F322E543DD6} ManagedCProj @@ -24,30 +32,54 @@ Safe Unicode - + DynamicLibrary v110 Safe + MultiByte + + + DynamicLibrary + v110 + true Unicode + + DynamicLibrary + v110 + true + MultiByte + + + + + + + if exist app.config copy app.config "$(OutDir)app.config" true + + true + false + + false + Level3 @@ -66,6 +98,25 @@ taskkill /F /IM vstest.executionengine.x86.exe /FI "MEMUSAGE gt 1" + + + Level3 + Disabled + WIN32;_DEBUG;%(PreprocessorDefinitions) + NotUsing + ../../../include + true + MultiThreadedDebugDLL + + + true + + + + + taskkill /F /IM vstest.executionengine.x86.exe /FI "MEMUSAGE gt 1" + + Level3 @@ -79,6 +130,20 @@ + + + Level3 + WIN32;NDEBUG;%(PreprocessorDefinitions) + NotUsing + ../../../include + true + + + true + + + + ..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\PublicAssemblies\Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll @@ -91,6 +156,7 @@ + @@ -100,13 +166,18 @@ + + + Create + Create Create + Create diff --git a/projects/VS2012/ManagedTestCatch/ManagedTestCatch.vcxproj.filters b/projects/VS2012/ManagedTestCatch/ManagedTestCatch.vcxproj.filters index f32e36cb..54f6763d 100644 --- a/projects/VS2012/ManagedTestCatch/ManagedTestCatch.vcxproj.filters +++ b/projects/VS2012/ManagedTestCatch/ManagedTestCatch.vcxproj.filters @@ -63,6 +63,18 @@ Source Files + + Source Files + + + Source Files + + + Source Files + + + Source Files + diff --git a/projects/VS2012/NativeTestCatch/NativeTestCatch.sln b/projects/VS2012/NativeTestCatch/NativeTestCatch.sln index 18ec7dc0..9e250bd9 100644 --- a/projects/VS2012/NativeTestCatch/NativeTestCatch.sln +++ b/projects/VS2012/NativeTestCatch/NativeTestCatch.sln @@ -6,13 +6,19 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 + DebugMbcs|Win32 = DebugMbcs|Win32 Release|Win32 = Release|Win32 + ReleaseMbcs|Win32 = ReleaseMbcs|Win32 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {977CE524-3FC7-4281-9C1B-77C210F24A9B}.Debug|Win32.ActiveCfg = Debug|Win32 {977CE524-3FC7-4281-9C1B-77C210F24A9B}.Debug|Win32.Build.0 = Debug|Win32 + {977CE524-3FC7-4281-9C1B-77C210F24A9B}.DebugMbcs|Win32.ActiveCfg = DebugMbcs|Win32 + {977CE524-3FC7-4281-9C1B-77C210F24A9B}.DebugMbcs|Win32.Build.0 = DebugMbcs|Win32 {977CE524-3FC7-4281-9C1B-77C210F24A9B}.Release|Win32.ActiveCfg = Release|Win32 {977CE524-3FC7-4281-9C1B-77C210F24A9B}.Release|Win32.Build.0 = Release|Win32 + {977CE524-3FC7-4281-9C1B-77C210F24A9B}.ReleaseMbcs|Win32.ActiveCfg = ReleaseMbcs|Win32 + {977CE524-3FC7-4281-9C1B-77C210F24A9B}.ReleaseMbcs|Win32.Build.0 = ReleaseMbcs|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/projects/VS2012/NativeTestCatch/NativeTestCatch.vcxproj b/projects/VS2012/NativeTestCatch/NativeTestCatch.vcxproj index 87bdb6d4..eb1ff325 100644 --- a/projects/VS2012/NativeTestCatch/NativeTestCatch.vcxproj +++ b/projects/VS2012/NativeTestCatch/NativeTestCatch.vcxproj @@ -1,10 +1,18 @@  + + DebugMbcs + Win32 + Debug Win32 + + ReleaseMbcs + Win32 + Release Win32 @@ -23,6 +31,20 @@ Unicode false + + DynamicLibrary + true + v110 + MultiByte + false + + + DynamicLibrary + true + v110 + MultiByte + false + DynamicLibrary false @@ -37,6 +59,12 @@ + + + + + + @@ -44,6 +72,12 @@ true + + true + + + true + true @@ -62,6 +96,36 @@ $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + + NotUsing + Level3 + Disabled + ../../../include;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;%(PreprocessorDefinitions) + true + + + Windows + true + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + + + + NotUsing + Level3 + Disabled + ../../../include;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;%(PreprocessorDefinitions) + true + + + Windows + true + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + Level3 @@ -88,6 +152,7 @@ + @@ -97,11 +162,16 @@ + + + Create + Create + Create Create diff --git a/projects/VS2012/NativeTestCatch/NativeTestCatch.vcxproj.filters b/projects/VS2012/NativeTestCatch/NativeTestCatch.vcxproj.filters index 24538c37..a95b17b3 100644 --- a/projects/VS2012/NativeTestCatch/NativeTestCatch.vcxproj.filters +++ b/projects/VS2012/NativeTestCatch/NativeTestCatch.vcxproj.filters @@ -68,5 +68,17 @@ Source Files + + Source Files + + + Source Files + + + Source Files + + + Source Files + \ No newline at end of file diff --git a/scripts/approvalVSTests.py b/scripts/approvalVSTests.py new file mode 100644 index 00000000..bef1cb79 --- /dev/null +++ b/scripts/approvalVSTests.py @@ -0,0 +1,716 @@ +#!/c/Python27/python + +import os +import sys +import subprocess +import re + +import xml.etree.cElementTree as etree + +from scriptCommon import catchPath +#from rawfile import writeRawFile +#from rawfile import parseRawFileIntoTree +from catch_test_run import TestRunApprovedHandler +from catch_test_run import TestRunData +from catch_test_run import TestRunResultHandler +from catch_test_case import TestCaseResultParser +from catch_test_case import TestCaseData + +rootPath = os.path.join(os.path.join(os.path.join( catchPath, 'projects'), 'SelfTest'), 'Baselines' ) + +if len(sys.argv) == 2: + cmdPath = sys.argv[1] +else: + if sys.platform == 'win32': + cmdPath = os.path.join( catchPath, 'projects\\VS2010\\TestCatch\\Release\\TestCatch.exe' ) + dllPath = os.path.join( catchPath, 'projects\\VS2010\\ManagedTestCatch\\Release\\ManagedTestCatch.dll' ) + else: + cmdPath = os.path.join( catchPath, 'projects/XCode4/CatchSelfTest/DerivedData/CatchSelfTest/Build/Products/Debug/CatchSelfTest' ) + +print cmdPath + +overallResult = 0 + +def approve( baseName, args ): + global overallResult + args[0:0] = [cmdPath] + baselinesPath = os.path.join( rootPath, '{0}.approved.txt'.format( baseName ) ) + baselinesSortedPath = os.path.join( rootPath, '{0}.sorted.approved.txt'.format( baseName ) ) + rawResultsPath = os.path.join( rootPath, '_{0}.tmp'.format( baseName ) ) + if os.path.exists( baselinesPath ): + approvedFileHandler = TestRunApprovedHandler(baselinesPath) + baselinesPathNew = os.path.join( rootPath, '{0}.approved.new.txt'.format( baseName ) ) + approvedFileHandler.writeRawFile(baselinesPathNew) + approvedFileHandler.writeSortedRawFile(baselinesSortedPath) + else: + raise Exception("Base file does not exist: '" + baselinesPath + "'") + + if not(os.path.exists( args[0] )): + raise Exception("Executable does not exist: '" + args[0] + "'") + + f = open( rawResultsPath, 'w' ) + subprocess.call( args, stdout=f, stderr=f ) + f.close() + + if os.path.exists( rawResultsPath ): + resultFileHandler = TestRunResultHandler(rawResultsPath) + rawPathNew = os.path.join( rootPath, '{0}.rewrite.txt'.format( baseName ) ) + #print "F:",rawPathNew,",",approvedFileHandler.current.outputLine + resultFileHandler.writeRawFile(rawPathNew) + rawPathNewSorted = os.path.join( rootPath, '{0}.sorted.unapproved.txt'.format( baseName ) ) + resultFileHandler.writeSortedUnapprovedFile(rawPathNewSorted, approvedFileHandler.current.outputLine) + else: + raise Exception("Results file does not exist: '" + rawResultsPath + "'") + + #os.remove( rawResultsPath ) + print + print baseName + ":" + if os.path.exists( baselinesSortedPath ) and os.path.exists( rawPathNewSorted ): + diffResult = subprocess.call([ "diff", "--ignore-all-space", baselinesSortedPath, rawPathNewSorted ] ) + if diffResult == 0: + #os.remove( filteredResultsPath ) + if not(sys.platform == 'win32'): + print " \033[92mResults matched" + else: + print " Results matched" + else: + if not(sys.platform == 'win32'): + print " \n****************************\n \033[91mResults differed" + else: + print " \n****************************\n Results differed" + if diffResult > overallResult: + overallResult = diffResult + if not(sys.platform == 'win32'): + print "\033[0m" + +def approveJunit( baseName, args ): + global overallResult + args[0:0] = [cmdPath] + baselinesPath = os.path.join( rootPath, '{0}.approved.txt'.format( baseName ) ) + baselinesSortedPath = os.path.join( rootPath, '{0}.sorted.approved.txt'.format( baseName ) ) + #baselinesFixedPath = os.path.join( rootPath, '{0}.rewrite.approved.txt'.format( baseName ) ) + rawResultsPath = os.path.join( rootPath, '_{0}.tmp'.format( baseName ) ) + if os.path.exists( baselinesPath ): + xml = "" + f = open( baselinesPath, 'r' ) + for line in f: + xml += line + xml = xml.replace("", "<line number>") + xml = xml.replace("", "<hex digits>") + #f2 = open( baselinesFixedPath, 'wb' ) + #f2.write(xml) + #f2.close() + + # ClassTests.cpp: + otherApprovedTestParser = re.compile( r'(.*\..pp).*:<(.*).*>' ) + testRun = TestRunData() + testcase = None + root = etree.fromstring(xml) + for testsuites in root: + if testsuites.tag == "testsuite": + testRun = TestRunData() + testRun.appname = testsuites.get("name") + testRun.errors = testsuites.get("errors") + testRun.failures = testsuites.get("failures") + testRun.tests = testsuites.get("tests") + for tc in testsuites: + if tc.tag == "testcase": + cls = tc.get("classname") + #print "C:",cls,tc + if len(cls): + testcase = testRun.addClassTestCase(cls, tc.get("name")) + else: + testcase = testRun.addTestCase(tc.get("name")) + for prop in tc: + if prop.tag == "failure": + text = prop.text.strip() + lines = text.splitlines() + filename = "" + lineNumber = "" + output = [] + for l in lines: + m = otherApprovedTestParser.match(l) + if m: + filename = m.group(1) + lineNumber = m.group(2) + else: + output.append(l) + testcase.addFailure(filename, lineNumber, output, prop.get("message"), prop.get("type")) + elif prop.tag == "error": + text = prop.text.strip() + lines = text.splitlines() + filename = "" + lineNumber = "" + output = [] + for l in lines: + m = otherApprovedTestParser.match(l) + if m: + filename = m.group(1) + lineNumber = m.group(2) + else: + output.append(l) + testcase.addError(filename, lineNumber, output, prop.get("message"), prop.get("type")) + elif prop.tag == "system-out": + text = prop.text.strip() + lines = text.splitlines() + testcase.addSysout(lines) + elif prop.tag == "system-err": + text = prop.text.strip() + lines = text.splitlines() + testcase.addSyserr(lines) + elif tc.tag == "system-out": + text = tc.text.strip() + lines = text.splitlines() + testRun.addSysout(lines) + elif tc.tag == "system-err": + text = tc.text.strip() + lines = text.splitlines() + testRun.addSyserr(lines) + else: + print tc.tag + + lines = testRun.generateSortedUnapprovedJunit() + + rawWriteFile = open( baselinesSortedPath, 'wb' ) + for line in lines: + #print "L:",line + rawWriteFile.write(line + "\n") + rawWriteFile.close() + + if not(os.path.exists( args[0] )): + raise Exception("Executable does not exist: '" + args[0] + "'") + + f = open( rawResultsPath, 'w' ) + subprocess.call( args, stdout=f, stderr=f ) + f.close() + + rawSortedPath = os.path.join( rootPath, '{0}.sorted.unapproved.txt'.format( baseName ) ) + if os.path.exists( rawResultsPath ): + xml = "" + f = open( rawResultsPath, 'r' ) + for line in f: + xml += line + #xml = xml.replace("", "<line number>") + #xml = xml.replace("", "<hex digits>") + + # ClassTests.cpp: + otherResultsTestParser = re.compile( r'(.*\\)(.*\..pp).*\((.*).*\)' ) + testRun = TestRunData() + testcase = None + root = etree.fromstring(xml) + for testsuites in root: + if testsuites.tag == "testsuite": + testRun = TestRunData() + testRun.appname = testsuites.get("name") + testRun.errors = testsuites.get("errors") + testRun.failures = testsuites.get("failures") + testRun.tests = testsuites.get("tests") + for tc in testsuites: + if tc.tag == "testcase": + cls = tc.get("classname") + #print "C:",cls,tc + if len(cls): + if cls.startswith("::"): + cls = cls[2:] + testcase = testRun.addClassTestCase(cls, tc.get("name")) + else: + testcase = testRun.addTestCase(tc.get("name")) + for prop in tc: + if prop.tag == "failure": + text = prop.text.strip() + lines = text.splitlines() + filename = "" + lineNumber = "" + output = [] + for l in lines: + m = otherResultsTestParser.match(l) + if m: + filename = m.group(2) + lineNumber = "line number" + else: + output.append(l) + testcase.addFailure(filename, lineNumber, output, prop.get("message"), prop.get("type")) + elif prop.tag == "error": + text = prop.text.strip() + lines = text.splitlines() + filename = "" + lineNumber = "" + output = [] + for l in lines: + m = otherResultsTestParser.match(l) + if m: + filename = m.group(2) + lineNumber = "line number" + else: + output.append(l) + testcase.addError(filename, lineNumber, output, prop.get("message"), prop.get("type")) + elif prop.tag == "system-out": + text = prop.text.strip() + lines = text.splitlines() + testcase.addSysout(lines) + elif prop.tag == "system-err": + text = prop.text.strip() + lines = text.splitlines() + testcase.addSyserr(lines) + elif tc.tag == "system-out": + text = tc.text.strip() + lines = text.splitlines() + testRun.addSysout(lines) + elif tc.tag == "system-err": + text = tc.text.strip() + lines = text.splitlines() + testRun.addSyserr(lines) + else: + print tc.tag + + lines = testRun.generateSortedUnapprovedJunit() + + rawWriteFile = open( rawSortedPath, 'wb' ) + for line in lines: + #print "L:",line + rawWriteFile.write(line + "\n") + rawWriteFile.close() + +def addSubSection(testcase, section, exp): + r = exp.find("OverallResults") + if r != None: + ores = [] + ores.append(r.get("successes")) + ores.append(r.get("failures")) + if section == None: + section = testcase.addSection(exp.get("name"), exp.get("description"), ores) + else: + section = testcase.addSubSection(section, exp.get("name"), exp.get("description"), ores) + e1 = False + for tmp in exp: + if tmp.tag == "OverallResults": + pass + elif tmp.tag == "Exception": + filename = tmp.get("filename") + text = tmp.text + ls = text.splitlines() + testcase.addSubException(section, filename, ls) + elif tmp.tag == "Section": + addSubSection(testcase, section, tmp) + elif tmp.tag == "Failure": + text = tmp.text + ls = text.splitlines() + testcase.addSubFailure(section, ls) + elif tmp.tag == "Expression": + #print "Exp:",tmp + e1 = True + result = tmp.get("success") + filename = tmp.get("filename") + subSection = testcase.addSubExpression(section,result,filename) + subExp = [] + for cond in tmp: + if cond.tag == "Original": + text = cond.text + ls = text.splitlines() + subExp.append(ls) + elif cond.tag == "Expanded" and len(subExp) == 1: + text = cond.text + ls = text.splitlines() + subExp.append(ls) + elif cond.tag == "Exception" and len(subExp) == 2: + subExp.append(cond.get("filename")) + text = cond.text + ls = text.splitlines() + subExp.append(ls) + else: + print "SX:",cond.tag + if len(subExp) >= 2: + testcase.addExpressionDetails(subSection, subExp) + else: + print "Z:",tmp.tag + #if e1: + # print "Section:",section + +def addResultsSubSection(otherResultsTestParser, testcase, section, exp): + r = exp.find("OverallResults") + if r != None: + ores = [] + ores.append(r.get("successes")) + ores.append(r.get("failures")) + if section == None: + section = testcase.addSection(exp.get("name"), exp.get("description"), ores) + else: + section = testcase.addSubSection(section, exp.get("name"), exp.get("description"), ores) + e1 = False + for tmp in exp: + if tmp.tag == "OverallResults": + pass + elif tmp.tag == "Exception": + filename = tmp.get("filename") + m = otherResultsTestParser.match(filename) + if m: + filename = "/Users/philnash/Dev/OSS/Catch/projects/SelfTest/" + m.group(2) + text = tmp.text + ls = text.splitlines() + testcase.addSubException(section, filename, ls) + elif tmp.tag == "Section": + addResultsSubSection(otherResultsTestParser, testcase, section, tmp) + elif tmp.tag == "Failure": + text = tmp.text + ls = text.splitlines() + testcase.addSubFailure(section, ls) + elif tmp.tag == "Expression": + #print "Exp:",tmp + e1 = True + result = tmp.get("success") + filename = tmp.get("filename") + m = otherResultsTestParser.match(filename) + if m: + filename = "/Users/philnash/Dev/OSS/Catch/projects/SelfTest/" + m.group(2) + subSection = testcase.addSubExpression(section,result,filename) + subExp = [] + for cond in tmp: + if cond.tag == "Original": + text = cond.text + ls = text.splitlines() + subExp.append(ls) + elif cond.tag == "Expanded" and len(subExp) == 1: + text = cond.text + ls = text.splitlines() + subExp.append(ls) + elif cond.tag == "Exception" and len(subExp) == 2: + filename = cond.get("filename") + m = otherResultsTestParser.match(filename) + if m: + filename = "/Users/philnash/Dev/OSS/Catch/projects/SelfTest/" + m.group(2) + subExp.append(filename) + text = cond.text + ls = text.splitlines() + subExp.append(ls) + else: + print "SX:",cond.tag + if len(subExp) >= 2: + testcase.addExpressionDetails(subSection, subExp) + else: + print "Z:",tmp.tag + #if e1: + # print "Section:",section + +def approveXml( baseName, args ): + global overallResult + args[0:0] = [cmdPath] + baselinesPath = os.path.join( rootPath, '{0}.approved.txt'.format( baseName ) ) + baselinesSortedPath = os.path.join( rootPath, '{0}.sorted.approved.txt'.format( baseName ) ) + #baselinesFixedPath = os.path.join( rootPath, '{0}.rewrite.approved.txt'.format( baseName ) ) + rawResultsPath = os.path.join( rootPath, '_{0}.tmp'.format( baseName ) ) + if os.path.exists( baselinesPath ): + xml = "" + f = open( baselinesPath, 'r' ) + for line in f: + xml += line + xml = xml.replace("", "<hex digits>") + + #otherApprovedTestParser = re.compile( r'(.*\..pp).*:<(.*).*>' ) + testRun = TestRunData() + testcase = None + root = etree.fromstring(xml) + testRun.appname = root.get("name") + for ts in root: + #print ts.tag + for tc in ts: + if tc.tag == "TestCase": + testcase = testRun.addTestCase(tc.get("name")) + for exp in tc: + if exp.tag == "Expression": + result = exp.get("success") + filename = exp.get("filename") + section = testcase.addExpression(result,filename) + subExp = [] + for cond in exp: + if cond.tag == "Original": + text = cond.text + ls = text.splitlines() + subExp.append(ls) + elif cond.tag == "Expanded" and len(subExp) == 1: + text = cond.text + ls = text.splitlines() + subExp.append(ls) + elif cond.tag == "Exception" and len(subExp) == 2: + subExp.append(cond.get("filename")) + text = cond.text + ls = text.splitlines() + subExp.append(ls) + else: + print "X:",cond.tag + if len(subExp) >= 2: + testcase.addExpressionDetails(section, subExp) + elif exp.tag == "Exception": + filename = exp.get("filename") + text = exp.text + ls = text.splitlines() + section = testcase.addException(filename,ls) + elif exp.tag == "Section": + addSubSection(testcase, None, exp) + elif exp.tag == "Info": + text = exp.text + ls = text.splitlines() + section = testcase.addInfo(ls) + elif exp.tag == "Warning": + text = exp.text + ls = text.splitlines() + section = testcase.addWarning(ls) + elif exp.tag == "Failure": + text = exp.text + ls = text.splitlines() + section = testcase.addSimpleFailure(ls) + elif exp.tag == "OverallResult": + testcase.addOverallResult(exp.get("success")) + else: + print "E:",exp.tag + elif tc.tag == "OverallResults": + testRun.tests = tc.get("successes") + testRun.failures = tc.get("failures") + else: + print "U:",tc.tag + + lines = testRun.generateSortedUnapprovedXml() + + rawWriteFile = open( baselinesSortedPath, 'wb' ) + for line in lines: + #print "L:",line + rawWriteFile.write(line + "\n") + rawWriteFile.close() + + if not(os.path.exists( args[0] )): + raise Exception("Executable does not exist: '" + args[0] + "'") + + f = open( rawResultsPath, 'w' ) + subprocess.call( args, stdout=f, stderr=f ) + f.close() + + rawSortedPath = os.path.join( rootPath, '{0}.sorted.unapproved.txt'.format( baseName ) ) + if os.path.exists( rawResultsPath ): + xml = "" + f = open( rawResultsPath, 'r' ) + for line in f: + xml += line + #xml = xml.replace("", "<hex digits>") + + otherResultsTestParser = re.compile( r'(.*\\)(.*\..pp)' ) + hexParser = re.compile( r'(.*)\b(0[xX][0-9a-fA-F]+)\b(.*)' ) + testRun = TestRunData() + testcase = None + root = etree.fromstring(xml) + testRun.appname = root.get("name") + if testRun.appname == "TestCatch.exe": + testRun.appname = "CatchSelfTest" + for ts in root: + #print ts.tag + for tc in ts: + if tc.tag == "TestCase": + testcase = testRun.addTestCase(tc.get("name")) + for exp in tc: + if exp.tag == "Expression": + result = exp.get("success") + filename = exp.get("filename") + m = otherResultsTestParser.match(filename) + if m: + filename = "/Users/philnash/Dev/OSS/Catch/projects/SelfTest/" + m.group(2) + section = testcase.addExpression(result,filename) + subExp = [] + for cond in exp: + if cond.tag == "Original": + text = cond.text + tmp = text.splitlines() + ls = [] + for li in tmp: + m = hexParser.match(li) + if m: + while m: + #print li, m.group(1), m.group(3) + li = m.group(1) + "0x" + m.group(3) + m = hexParser.match(li) + ls.append(li) + subExp.append(ls) + elif cond.tag == "Expanded" and len(subExp) == 1: + text = cond.text + tmp = text.splitlines() + ls = [] + for li in tmp: + m = hexParser.match(li) + if m: + while m: + #print li, m.group(1), m.group(3) + li = m.group(1) + "0x" + m.group(3) + m = hexParser.match(li) + ls.append(li) + subExp.append(ls) + elif cond.tag == "Exception" and len(subExp) == 2: + filename = cond.get("filename") + m = otherResultsTestParser.match(filename) + if m: + filename = "/Users/philnash/Dev/OSS/Catch/projects/SelfTest/" + m.group(2) + subExp.append(filename) + text = cond.text + ls = text.splitlines() + subExp.append(ls) + else: + print "X:",cond.tag + if len(subExp) >= 2: + testcase.addExpressionDetails(section, subExp) + elif exp.tag == "Exception": + filename = exp.get("filename") + m = otherResultsTestParser.match(filename) + if m: + filename = "/Users/philnash/Dev/OSS/Catch/projects/SelfTest/" + m.group(2) + text = exp.text + ls = text.splitlines() + section = testcase.addException(filename,ls) + elif exp.tag == "Section": + addResultsSubSection(otherResultsTestParser, testcase, None, exp) + elif exp.tag == "Info": + text = exp.text + ls = text.splitlines() + section = testcase.addInfo(ls) + elif exp.tag == "Warning": + text = exp.text + ls = text.splitlines() + section = testcase.addWarning(ls) + elif exp.tag == "Failure": + text = exp.text + ls = text.splitlines() + section = testcase.addSimpleFailure(ls) + elif exp.tag == "OverallResult": + testcase.addOverallResult(exp.get("success")) + else: + print "E:",exp.tag + elif tc.tag == "OverallResults": + testRun.tests = tc.get("successes") + testRun.failures = tc.get("failures") + else: + print "U:",tc.tag + + lines = testRun.generateSortedUnapprovedXml() + + rawWriteFile = open( rawSortedPath, 'wb' ) + for line in lines: + #print "L:",line + rawWriteFile.write(line + "\n") + rawWriteFile.close() + +def parseTrxFile(trxFile): + print "TRX file:" ,trxFile + if os.path.exists( trxFile ): + xml = "" + f = open( trxFile, 'r' ) + for line in f: + xml += line + + #otherResultsTestParser = re.compile( r'(.*\\)(.*\..pp)' ) + #hexParser = re.compile( r'(.*)\b(0[xX][0-9a-fA-F]+)\b(.*)' ) + testRun = TestRunData() + testRun.appname = "CatchSelfTest" + root = etree.fromstring(xml) + if testRun.appname == "TestCatch.exe": + testRun.appname = "CatchSelfTest" + qname=re.compile("{(?P.*)}(?P.*)") + ids = [] + for ts in root: + m = qname.match(ts.tag) + if m: + tag = m.group(2) + print tag + if tag != None: + if tag == "TestDefinitions": + for tc in ts: + m = qname.match(tc.tag) + if m: + tag = m.group(2) + if tag != None and tag == "UnitTest": + name = tc.get("name") + id = tc.get("id") + for item in tc: + m = qname.match(item.tag) + if m: + tag = m.group(2) + if tag != None and tag == "Description": + desc = item.text + #print desc, id + ids.append([id,desc]) + elif tag == "Results": + #print ids + ids = dict(ids) + #print ids["87ec526a-e414-1a3f-ba0f-e210b204bb42"] + resultParser = TestCaseResultParser() + for tc in ts: + m = qname.match(tc.tag) + if m: + tag = m.group(2) + if tag != None and tag == "UnitTestResult": + outcome = tc.get("outcome") + id = tc.get("testId") + if len(id) > 0: + for item in tc: + m = qname.match(item.tag) + if m: + tag = m.group(2) + if tag != None and tag == "Output": + for sub in item: + m = qname.match(sub.tag) + if m: + tag = m.group(2) + if tag != None and tag == "StdOut": + desc = sub.text + lines = desc.splitlines() + if (len(lines) > 2 and + lines[0].startswith("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") and + lines[1].startswith("Using Catch v") ): + lines = lines[2:-1] + #print "*******",desc + #print lines + for line in lines: + testcase = resultParser.parseResultLine(line) + if isinstance(testcase, TestCaseData): + testRun.testcases.append(testcase) + lines = testRun.generateSortedUnapprovedLines(0) + + rawSortedPath = os.path.join( rootPath, 'mstest.trx.sorted.unapproved.txt' ) + rawWriteFile = open( rawSortedPath, 'wb' ) + for line in lines: + #print "L:",line + rawWriteFile.write(line + "\n") + rawWriteFile.close() + + +def approveMsTest( baseName ): + rawResultsPath = os.path.join( rootPath, '_{0}.tmp'.format( baseName ) ) + if not(os.path.exists( dllPath )): + raise Exception("Managed DLL does not exist: '" + dllPath + "'") + + args = [] + args.append("MSTest.exe") + args.append("/testcontainer:" + dllPath) + #f = open( rawResultsPath, 'w' ) + #subprocess.call( args, stdout=f, stderr=f ) + #f.close() + + #if os.path.exists( rawResultsPath ): + # f = open( rawResultsPath, 'r' ) + # for line in f: + line = "Results file: c:\Projects\Catch\TestResults\NoyesMa_SACHDEW7 2013-12-09 11_43_57.trx" + + if line.startswith("Results file:"): + trxFile = line[13:].strip() + parseTrxFile(trxFile) + +# Standard console reporter +#approve( "console.std", ["~_"] ) +# console reporter, include passes, warn about No Assertions +#approve( "console.sw", ["~_", "-s", "-w", "NoAssertions"] ) +# console reporter, include passes, warn about No Assertions, limit failures to first 4 +#approve( "console.swa4", ["~_", "-s", "-w", "NoAssertions", "-x", "4"] ) +# junit reporter, include passes, warn about No Assertions +#approveJunit( "junit.sw", ["~_", "-s", "-w", "NoAssertions", "-r", "junit"] ) +# xml reporter, include passes, warn about No Assertions +#approveXml( "xml.sw", ["~_", "-s", "-w", "NoAssertions", "-r", "xml"] ) +#mstest runner, xml output +approveMsTest( "mstest.sw") + +if overallResult <> 0: + print "run approve.py to approve new baselines" +exit( overallResult) diff --git a/scripts/catch_conditions.py b/scripts/catch_conditions.py new file mode 100644 index 00000000..e7cc87b1 --- /dev/null +++ b/scripts/catch_conditions.py @@ -0,0 +1,398 @@ +import re + +class RandomOutput: + + def __init__(self): + output = [] + +class TestConditionData: + NONE = 0 + CONDITION = 1 + EXPANSION = 2 + MESSAGES = 3 + + hexParser = re.compile( r'(.*)\b(0[xX][0-9a-fA-F]+)\b(.*)' ) + + def __init__(self): + self.state = self.NONE + self.filenamePrefix = "" + self.filename = "" + self.lineNumber = "" + self.reason = "" + self.condition = "" + self.expansionPrefix = "" + self.expansion = [] + self.messagePrefix = "" + self.messages = [] + self.output = [] + self.noAssertions = "" + + def __eq__(self, other): + return (self.filenamePrefix == other.filenamePrefix and + self.filename == other.filename and self.lineNumber == other.lineNumber and + self.reason == other.reason and self.condition == other.condition and + self.expansion == other.expansion and self.messagePrefix == other.messagePrefix and + self.output == other.output and self.noAssertions == other.noAssertions) + + + def empty(self): + if self.state == self.NONE: + return True + return False + + def __repr__(self): + result = ("[" + self.reason + ", " + self.filename + ", " + self.lineNumber + ", " + self.condition + + ", " + self.expansionPrefix + ", [ ") + suffix = "" + for msg in self.expansion: + result += suffix + result += repr(msg) + suffix = ", " + result += "], " + self.messagePrefix + ", [ " + suffix = "" + for msg in self.messages: + result += suffix + result += repr(msg) + suffix = ", " + result += " ], [ " + suffix = "" + for msg in self.output: + result += suffix + result += repr(msg) + suffix = ", " + result += " ] ]" + return result + + def parseCondition(self,line): + if len(line): + if self.state == self.CONDITION and line.startswith(" "): + self.condition = line.strip() + elif self.state == self.CONDITION: + if len(self.reason) == 0 and line.startswith("PASSED:"): + self.reason = line.strip() + elif line.startswith("warning:") or line.startswith("with message"): + self.messagePrefix = line.strip() + self.state = self.MESSAGES + elif not(line.startswith(" ")): + self.expansionPrefix = line.strip() + self.state = self.EXPANSION + else: + raise Exception("Unknown condition parse line: '" + line + "'") + elif self.state == self.EXPANSION: + if line.startswith("with message"): + self.messagePrefix = line.strip() + self.state = self.MESSAGES + elif line.startswith(" "): + #print "***LINE: ", line + self.expansion.append(line[2:].rstrip()) + elif line.startswith("... message truncated due"): + #print "***MSG: ", line + self.messagePrefix = line.strip() + else: + #print "***OUTPUT: ", line + self.output.append(line.strip()) + elif self.state == self.MESSAGES: + if line.startswith(" "): + #print "***MESSAGE: ", line + self.messages.append(line.strip()) + else: + #print "***OUTPUT: ", line + self.output.append(line.strip()) + else: + raise Exception("Unknown parse line: '" + line + "'") + + if len(self.output) == 10: + if (self.output[0] == "Message from section one" and self.output[1] == "Message from section two" and + self.output[2] == "Some information" and self.output[3] == "An error" and + self.output[4] == "Message from section one" and self.output[5] == "Message from section two" and + self.output[6] == "Some information" and self.output[7] == "An error" and + self.output[8] == "hello" and self.output[9] == "hello" ): + obj = RandomOutput() + obj.output = self.output + self.output = [] + raise obj + + elif len(self.output) == 2: + if (self.output[0] == "hello" and self.output[1] == "hello" ): + obj = RandomOutput() + obj.output = self.output + self.output = [] + raise obj + + def generateApprovedLines(self): + if self.empty(): + raise Exception("Empty condition..." + repr(self)) + lines = [] + extraLine = False + if len(self.filename): + line = self.filename + ":<" + self.lineNumber + ">:" + reasonOnSameLine = False + if self.reason == "FAILED": + line += " " + "FAILED:" + reasonOnSameLine = True + lines.append(line) + if not(reasonOnSameLine) and len(self.reason): + lines.append(self.reason) + if len(self.condition): + lines.append(" " + self.condition) + if len(self.expansionPrefix): + lines.append(self.expansionPrefix) + extraLine = True + if len(self.expansion): + for line in self.expansion: + m = self.hexParser.match(line) + if m: + while m: + line = m.group(1) + "0x" + m.group(3) + m = self.hexParser.match(line) + lines.append(" " + line) + if len(self.messagePrefix): + lines.append(self.messagePrefix) + extraLine = True + if len(self.messages): + for msg in self.messages: + lines.append(" " + msg) + lines.append("") + if len(self.noAssertions) > 0: + if extraLine: + lines.append("") + lines.append(self.noAssertions) + lines.append("") + if len(self.output): + for msg in self.output: + lines.append(msg) + return lines + + def generateResultLines(self): + if self.empty(): + raise Exception("Empty condition..." + repr(self)) + lines = [] + extraLine = False + if len(self.filename): + if len(self.filenamePrefix): + line = self.filenamePrefix + self.filename + "(" + self.lineNumber + "):" + else: + line = self.filename + "(" + self.lineNumber + "):" + reasonOnSameLine = False + if self.reason == "FAILED": + line += " " + "FAILED:" + reasonOnSameLine = True + if (len(self.messagePrefix) > 0 and self.messagePrefix == "warning:" or + self.reason == "PASSED:" or self.expansionPrefix.startswith("FAILED - but was ok") ): + line += " " + lines.append(line) + if not(reasonOnSameLine) and len(self.reason): + lines.append(self.reason) + if len(self.condition): + lines.append(" " + self.condition) + if len(self.expansionPrefix): + lines.append(self.expansionPrefix) + extraLine = True + if len(self.expansion): + for line in self.expansion: + lines.append(" " + line) + if len(self.messagePrefix): + lines.append(self.messagePrefix) + extraLine = True + if len(self.messages): + for msg in self.messages: + lines.append(" " + msg) + lines.append("") + if len(self.noAssertions) > 0: + if extraLine: + lines.append("") + lines.append(self.noAssertions) + lines.append("") + if len(self.output): + for msg in self.output: + lines.append(msg) + return lines + + def generateUnapprovedLines(self): + if self.empty(): + raise Exception("Empty condition..." + repr(self)) + lines = [] + extraLine = False + if len(self.filename): + line = self.filename + ":<" + "line number" + ">:" + reasonOnSameLine = False + if self.reason == "FAILED": + line += " " + "FAILED:" + reasonOnSameLine = True + lines.append(line) + if not(reasonOnSameLine) and len(self.reason): + lines.append(self.reason) + if len(self.condition): + lines.append(" " + self.condition) + if len(self.expansionPrefix): + lines.append(self.expansionPrefix) + extraLine = True + if len(self.expansion): + for line in self.expansion: + m = self.hexParser.match(line) + if m: + while m: + line = m.group(1) + "0x" + m.group(3) + m = self.hexParser.match(line) + lines.append(" " + line) + if len(self.messagePrefix): + lines.append(self.messagePrefix) + extraLine = True + if len(self.messages): + for msg in self.messages: + lines.append(" " + msg) + lines.append("") + if len(self.noAssertions) > 0: + if extraLine: + lines.append("") + lines.append(self.noAssertions) + lines.append("") + if len(self.output): + for msg in self.output: + lines.append(msg) + return lines + + def addFailure(self, filename, lineNumber, output, message, type): + self.reason = "failure" + self.filename = filename + self.lineNumber = lineNumber + self.condition = type + if message != None: + self.expansion.append(message) + self.output = output + + def addError(self, filename, lineNumber, output, message, type): + self.reason = "error" + self.filename = filename + self.lineNumber = lineNumber + self.condition = type + if message != None: + self.expansion.append(message) + self.output = output + + def generateUnapprovedJunit(self): + lines = [] + msg = "" + for m in self.expansion: + msg += m + msg = msg.replace("\"", """) + msg = msg.replace("<", "<") + msg = msg.replace("<hex digits>", "") + #msg = msg.replace(">", ">") + + #print "R:",self.reason,msg,self.condition + if len(self.reason) > 0: + l = " <" + self.reason + if len(msg) > 0: + m = self.hexParser.match(msg) + if m: + while m: + msg = m.group(1) + "0x" + m.group(3) + m = self.hexParser.match(msg) + l += " message=\"" + msg + "\"" + if self.condition != None: + l += " type=\"" + self.condition + "\"" + l += ">" + lines.append(l) + if len(self.output) > 0: + for o in self.output: + lines.append(o) + if len(self.filename) > 0: + lines.append(self.filename + ":<" + self.lineNumber + ">") + lines.append(" ") + return lines + +class TestConditionApprovedParser: + failedApprovedTestParser = re.compile( r'(.*\..pp).*:<(.*).*>:(.*FAILED)' ) + otherApprovedTestParser = re.compile( r'(.*\..pp).*:<(.*).*>:' ) + + def __init__(self): + self.current = TestConditionData() + + def parseApprovedLine(self,line): + result = None + if line.startswith("==============================================================================="): + if not(self.current.empty()): + result = self.current + self.current = TestConditionData() + + elif line.startswith("-------------------------------------------------------------------------------"): + if not(self.current.empty()): + result = self.current + self.current = TestConditionData() + + else: + if line.startswith("No assertions in"): + self.current.noAssertions = line.strip() + self.current.state = self.current.MESSAGES + else: + m = self.failedApprovedTestParser.match(line) + if m: + if not(self.current.empty()): + result = self.current + self.current = TestConditionData() + self.current.filename = m.group(1).strip() + self.current.lineNumber = m.group(2).strip() + self.current.reason = m.group(3).strip() + self.current.state = self.current.CONDITION + else: + m = self.otherApprovedTestParser.match(line) + if m: + if not(self.current.empty()): + result = self.current + self.current = TestConditionData() + self.current.filename = m.group(1).strip() + self.current.lineNumber = m.group(2).strip() + self.current.state = self.current.CONDITION + elif not(self.current.empty()): + self.current.parseCondition(line) + return result + +class TestConditionResultParser: + failedResultsTestParser = re.compile( r'(.*\\)(.*\..pp).*\((.*).*\):(.*FAILED)' ) + otherResultsTestParser = re.compile( r'(.*\\)(.*\..pp).*\((.*).*\):' ) + + def __init__(self): + self.current = TestConditionData() + + def parseResultLine(self,line): + result = None + if line.startswith("==============================================================================="): + if not(self.current.empty()): + result = self.current + self.current = TestConditionData() + + elif line.startswith("-------------------------------------------------------------------------------"): + if not(self.current.empty()): + result = self.current + self.current = TestConditionData() + + else: + if line.startswith("No assertions in"): + self.current.noAssertions = line.strip() + self.current.state = self.current.MESSAGES + else: + m = self.failedResultsTestParser.match(line) + if m: + if not(self.current.empty()): + result = self.current + self.current = TestConditionData() + self.current.filenamePrefix = m.group(1).strip() + self.current.filename = m.group(2).strip() + self.current.lineNumber = m.group(3).strip() + self.current.reason = m.group(4).strip() + self.current.state = self.current.CONDITION + else: + m = self.otherResultsTestParser.match(line) + if m: + if not(self.current.empty()): + result = self.current + self.current = TestConditionData() + self.current.filenamePrefix = m.group(1).strip() + self.current.filename = m.group(2).strip() + self.current.lineNumber = m.group(3).strip() + self.current.state = self.current.CONDITION + elif not(self.current.empty()): + self.current.parseCondition(line) + return result + diff --git a/scripts/catch_test_case.py b/scripts/catch_test_case.py new file mode 100644 index 00000000..b685c9e8 --- /dev/null +++ b/scripts/catch_test_case.py @@ -0,0 +1,584 @@ +import re + +from catch_conditions import TestConditionData +from catch_conditions import TestConditionApprovedParser +from catch_conditions import TestConditionResultParser + +class EndOfClassName: + @staticmethod + def parseRawLine(line): + if line.startswith("..............................................................................."): + return ParseResult.END_OF_CLASS_NAME + return ParseResult.NONE + +class TestCaseData: + + def __init__(self): + self.name = "" + self.nameParts = [] + self.classname = "global" + self.sections = [] + self.filenamePrefix = "" + self.filename = "" + self.lineNumber = "" + self.conditions = [] + self.sysout = [] + self.syserr = [] + self.result = "" + + def __eq__(self, other): + return self.__dict__ == other.__dict__ + + def __repr__(self): + result = "[" + self.name + ", [ " + suffix = "" + for section in self.sections: + result += suffix + result += repr(section) + suffix = ", " + result + " ] " + result += self.filename + ", " + self.lineNumber + " [ " + suffix = "" + for cond in self.conditions: + result += suffix + result += repr(cond) + suffix = ", " + result + " ] ]" + return result + + def empty(self): + if len(self.name): + return False + return True + + def generateApprovedLines(self): + if self.empty(): + raise Exception("Empty test case..." + repr(self)) + lines = [] + if len(self.name): + lines.append("-------------------------------------------------------------------------------") + for n in self.nameParts: + lines.append(n) + if len(self.sections) > 0: + for section in self.sections: + lines.append(" " + section) + lines.append("-------------------------------------------------------------------------------") + if len(self.filename): + lines.append(self.filename + ":<" + self.lineNumber + ">") + lines.append("...............................................................................") + lines.append("") + for cond in self.conditions: + lines += cond.generateApprovedLines() + return lines + + def generateResultLines(self): + if self.empty(): + raise Exception("Empty test case..." + repr(self)) + lines = [] + if len(self.name): + lines.append("-------------------------------------------------------------------------------") + for n in self.nameParts: + lines.append(n) + if len(self.sections) > 0: + for section in self.sections: + lines.append(" " + section) + lines.append("-------------------------------------------------------------------------------") + if len(self.filename): + lines.append(self.filenamePrefix + self.filename + "(" + self.lineNumber + ")") + lines.append("...............................................................................") + lines.append("") + for cond in self.conditions: + lines += cond.generateResultLines() + return lines + + def generateUnapprovedLines(self): + if self.empty(): + raise Exception("Empty test case..." + repr(self)) + lines = [] + if len(self.name): + lines.append("-------------------------------------------------------------------------------") + for n in self.nameParts: + lines.append(n) + if len(self.sections) > 0: + for section in self.sections: + lines.append(" " + section) + lines.append("-------------------------------------------------------------------------------") + if len(self.filename): + lines.append(self.filename + ":<" + "line number" + ">") + lines.append("...............................................................................") + lines.append("") + for cond in self.conditions: + lines += cond.generateUnapprovedLines() + return lines + + def generateUnapprovedJunit(self): + lines = [] + + condLines = [] + for cond in self.conditions: + condLines += cond.generateUnapprovedJunit() + + if len(self.name): + l = " 0 or len(self.sysout) > 0 or len(self.syserr) > 0: + l += ">" + else: + l += "/>" + lines.append(l) + #if len(self.sections) > 0: + # for section in self.sections: + # lines.append(" " + section) + #if len(self.filename): + # lines.append(self.filename + ":<" + "line number" + ">") + # lines.append("...............................................................................") + lines += condLines + if len(self.sysout) > 0: + lines.append(" ") + for l in self.sysout: + lines.append(l) + lines.append(" ") + if len(self.syserr) > 0: + lines.append(" ") + for l in self.syserr: + lines.append(l) + lines.append(" ") + if len(condLines) > 0 or len(self.sysout) > 0 or len(self.syserr) > 0: + lines.append(" ") + return lines + + def generateRecursiveSection(self, prefix, section): + lines = [] + #print "S:",section + if section[0] == "S": + l = " " + prefix + "
","") + li = li.replace("\"",""") + l += " description=\"" + li + "\"" + l += ">" + lines.append(l) + if len(section) > 4: + index = 4 + while index < len(section): + tmp = section[index] + if len(tmp) > 0: + if tmp[0] == "E": + l = " " + prefix + " 3: + cond = tmp[3] + if len(cond[0]) > 0: + lines.append(" " + prefix + "") + for li in cond[0]: + if len(li.strip()) > 0: + li = li.replace("<","<") + li = li.replace("<hex digits>","") + li = li.replace("\"",""") + lines.append(li) + lines.append(" " + prefix + "") + if len(cond[1]) > 0: + lines.append(" " + prefix + "") + for li in cond[1]: + if len(li.strip()) > 0: + li = li.replace("<","<") + li = li.replace("<hex digits>","") + li = li.replace("\"",""") + lines.append(li) + lines.append(" " + prefix + "") + if len(cond) > 2: + filename = cond[2] + lines.append(" " + prefix + "") + if len(cond) > 3: + tmp = cond[3] + for li in tmp: + if len(li.strip()) > 0: + lines.append(li) + lines.append(" " + prefix + "") + elif len(tmp) > 4: + print "RE:",tmp[4] + + l = " " + prefix + "" + lines.append(l) + elif tmp[0] == "X": + l = " " + prefix + "" + lines.append(l) + for li in tmp[2]: + if len(li.strip()) > 0: + lines.append(li) + l = " " + prefix + "" + lines.append(l) + elif tmp[0] == "F": + l = " " + prefix + "" + lines.append(l) + for li in tmp[1]: + if len(li.strip()) > 0: + lines.append(li) + l = " " + prefix + "" + lines.append(l) + elif tmp[0] == "S": + lines += self.generateRecursiveSection(prefix + " ", tmp) + else: + print "RS2:",tmp[0] + else: + print "RS:",section[index] + index += 1 + + lines.append(" " + prefix + "") + l = " " + prefix + "
" + lines.append(l) + return lines + + def generateSection(self, prefix, sections): + lines = [] + for section in sections: + #print "S:",section + if section[0] == "S": + lines += self.generateRecursiveSection(prefix, section) + elif section[0] == "E": + l = " " + prefix + " 3: + cond = section[3] + if len(cond[0]) > 0: + lines.append(" " + prefix + "") + for li in cond[0]: + if len(li.strip()) > 0: + li = li.replace("&","&") + li = li.replace("<","<") + li = li.replace("<hex digits>","") + li = li.replace("\"",""") + lines.append(li) + lines.append(" " + prefix + "") + if len(cond[1]) > 0: + lines.append(" " + prefix + "") + for li in cond[1]: + if len(li.strip()) > 0: + li = li.replace("<","<") + li = li.replace("<hex digits>","") + li = li.replace("\"",""") + lines.append(li) + lines.append(" " + prefix + "") + if len(cond) > 2: + filename = cond[2] + lines.append(" " + prefix + "") + if len(cond) > 3: + tmp = cond[3] + for li in tmp: + if len(li.strip()) > 0: + lines.append(li) + lines.append(" " + prefix + "") + elif len(section) > 4: + print "RE:",section[4] + + l = " " + prefix + "" + lines.append(l) + elif section[0] == "X": + l = " " + prefix + "" + lines.append(l) + for li in section[2]: + if len(li.strip()) > 0: + lines.append(li) + l = " " + prefix + "" + lines.append(l) + elif section[0] == "I": + l = " " + prefix + "" + lines.append(l) + for li in section[1]: + if len(li.strip()) > 0: + lines.append(li) + l = " " + prefix + "" + lines.append(l) + elif section[0] == "W": + l = " " + prefix + "" + lines.append(l) + for li in section[1]: + if len(li.strip()) > 0: + lines.append(li) + l = " " + prefix + "" + lines.append(l) + elif section[0] == "F": + l = " " + prefix + "" + lines.append(l) + for li in section[1]: + if len(li.strip()) > 0: + lines.append(li) + l = " " + prefix + "" + lines.append(l) + return lines + + def generateUnapprovedXml(self): + lines = [] + + if len(self.name): + l = " " + lines.append(l) + + if len(self.sections) > 0: + prefix = " " + lines += self.generateSection(prefix, self.sections) + + if len(self.result) > 0: + lines.append(" ") + + if len(self.name): + l = " " + lines.append(l) + return lines + + def addFailure(self, filename, lineNumber, output, message, type): + self.filename = filename + self.lineNumber = lineNumber + condition = TestConditionData() + condition.addFailure(filename, lineNumber, output, message, type) + self.conditions.append(condition) + + def addError(self, filename, lineNumber, output, message, type): + self.filename = filename + self.lineNumber = lineNumber + condition = TestConditionData() + condition.addError(filename, lineNumber, output, message, type) + self.conditions.append(condition) + + def addSysout(self, output): + self.sysout = output + + def addSyserr(self, output): + self.syserr = output + + def addOverallResult(self,r): + self.result = r + + def addSection(self,name,desc, results): + section = [] + section.append("S") + section.append(name) + section.append(desc) + section.append(results) + self.sections.append(section) + return section + + def addExpression(self,result,filename): + section = [] + section.append("E") + section.append(result) + section.append(filename) + self.sections.append(section) + return section + + def addException(self,filename,text): + section = [] + section.append("X") + section.append(filename) + section.append(text) + #print section + self.sections.append(section) + return section + + def addInfo(self,text): + section = [] + section.append("I") + section.append(text) + self.sections.append(section) + return section + + def addWarning(self,text): + section = [] + section.append("W") + section.append(text) + self.sections.append(section) + return section + + def addSimpleFailure(self,text): + section = [] + section.append("F") + section.append(text) + self.sections.append(section) + return section + + def addExpressionDetails(self, section, subExp): + section.append(subExp) + + def addSubException(self, section, filename, text): + tmp = [] + tmp.append("X") + tmp.append(filename) + tmp.append(text) + section.append(tmp) + + def addSubFailure(self, section, text): + tmp = [] + tmp.append("F") + tmp.append(text) + section.append(tmp) + + def addSubExpression(self,section,result,filename): + tmp = [] + tmp.append("E") + tmp.append(result) + tmp.append(filename) + section.append(tmp) + #print "Section:",section + return tmp + + def addSubSection(self,section,name,desc,results): + tmp = [] + tmp.append("S") + tmp.append(name) + tmp.append(desc) + tmp.append(results) + section.append(tmp) + #print "Section:",section + return tmp + +class TestCaseApprovedParser: + NONE = 0 + TEST_CASE_NAME_EXPECTED = 1 + TEST_CASE_NAME = 2 + TEST_CLASS_EXPECTED = 3 + END_OF_CLASS_NAME_EXPECTED = 4 + TEST_CONDITION_EXPECTED = 5 + + testFilenameParser = re.compile( r'(.*\..pp).*:<(.*).*>' ) + + def __init__(self): + self.state = self.NONE + self.current = TestCaseData() + self.conditionParser = TestConditionApprovedParser() + + def parseApprovedLine(self,line): + result = None + if self.state == self.NONE: + if line.startswith("-------------------------------------------------------------------------------"): + self.state = self.TEST_CASE_NAME_EXPECTED + elif len(line): + raise Exception("Unknown parse line: '" + line + "'") + elif self.state == self.TEST_CASE_NAME_EXPECTED: + if len(line): + self.current.name = line.strip() + self.current.nameParts.append(line.strip()) + self.state = self.TEST_CASE_NAME + elif len(line): + raise Exception("Unknown parse line: '" + line + "'") + elif self.state == self.TEST_CASE_NAME: + if line.startswith("-------------------------------------------------------------------------------"): + self.state = self.TEST_CLASS_EXPECTED + elif line.startswith(" "): + #print "***SECTION: ",line + self.current.sections.append(line[2:]) + elif len(line): + if len(self.current.name) > 0: + self.current.name += line.strip() + else: + self.current.name = line.strip() + self.current.nameParts.append(line.strip()) + elif self.state == self.TEST_CLASS_EXPECTED: + m = self.testFilenameParser.match(line) + if m: + self.current.filename = m.group(1).strip() + self.current.lineNumber = m.group(2).strip() + self.state = self.END_OF_CLASS_NAME_EXPECTED + elif len(line): + raise Exception("Unknown parse line: '" + line + "'") + elif self.state == self.END_OF_CLASS_NAME_EXPECTED: + if line.startswith("..............................................................................."): + self.state = self.TEST_CONDITION_EXPECTED + elif len(line): + raise Exception("Unknown parse line: '" + line + "'") + elif self.state == self.TEST_CONDITION_EXPECTED: + #print "**** LINE " + line + condition = self.conditionParser.parseApprovedLine(line) + if isinstance(condition, TestConditionData): + #print "**** CASE " + repr(condition) + self.current.conditions.append(condition) + if line.startswith("-------------------------------------------------------------------------------"): + result = self.current + self.current = TestCaseData() + self.state = self.TEST_CASE_NAME_EXPECTED + elif line.startswith("==============================================================================="): + result = self.current + self.current = TestCaseData() + self.state = self.NONE + + return result + +class TestCaseResultParser: + NONE = 0 + TEST_CASE_NAME_EXPECTED = 1 + TEST_CASE_NAME = 2 + TEST_CLASS_EXPECTED = 3 + END_OF_CLASS_NAME_EXPECTED = 4 + TEST_CONDITION_EXPECTED = 5 + + testFilenameParser = re.compile( r'(.*\\)(.*\..pp).*\((.*).*\)' ) + + def __init__(self): + self.state = self.NONE + self.current = TestCaseData() + self.conditionParser = TestConditionResultParser() + + def parseResultLine(self,line): + result = None + if self.state == self.NONE: + if line.startswith("-------------------------------------------------------------------------------"): + self.state = self.TEST_CASE_NAME_EXPECTED + elif len(line): + raise Exception("Unknown parse line: '" + line + "'") + elif self.state == self.TEST_CASE_NAME_EXPECTED: + if len(line): + self.current.name = line.strip() + self.current.nameParts.append(line.strip()) + self.state = self.TEST_CASE_NAME + elif len(line): + raise Exception("Unknown parse line: '" + line + "'") + elif self.state == self.TEST_CASE_NAME: + if line.startswith("-------------------------------------------------------------------------------"): + self.state = self.TEST_CLASS_EXPECTED + elif line.startswith(" "): + #print "***SECTION: ",line + self.current.sections.append(line[2:]) + elif len(line): + if len(self.current.name) > 0: + self.current.name += line.strip() + else: + self.current.name = line.strip() + self.current.nameParts.append(line.strip()) + elif self.state == self.TEST_CLASS_EXPECTED: + m = self.testFilenameParser.match(line) + if m: + self.current.filenamePrefix = m.group(1).strip() + self.current.filename = m.group(2).strip() + self.current.lineNumber = m.group(3).strip() + self.state = self.END_OF_CLASS_NAME_EXPECTED + elif len(line): + raise Exception("Unknown parse line: '" + line + "'") + elif self.state == self.END_OF_CLASS_NAME_EXPECTED: + if line.startswith("..............................................................................."): + self.state = self.TEST_CONDITION_EXPECTED + elif len(line): + raise Exception("Unknown parse line: '" + line + "'") + elif self.state == self.TEST_CONDITION_EXPECTED: + #print "**** LINE " + line + condition = self.conditionParser.parseResultLine(line) + if isinstance(condition, TestConditionData): + #print "**** CASE " + repr(condition) + self.current.conditions.append(condition) + if line.startswith("-------------------------------------------------------------------------------"): + result = self.current + self.current = TestCaseData() + self.state = self.TEST_CASE_NAME_EXPECTED + elif line.startswith("==============================================================================="): + result = self.current + self.current = TestCaseData() + self.state = self.NONE + + return result + diff --git a/scripts/catch_test_run.py b/scripts/catch_test_run.py new file mode 100644 index 00000000..833d68a0 --- /dev/null +++ b/scripts/catch_test_run.py @@ -0,0 +1,430 @@ +import re +import os + +from catch_test_case import TestCaseApprovedParser +from catch_test_case import TestCaseResultParser +from catch_test_case import TestCaseData +from catch_conditions import RandomOutput + +class TestRunData: + + def __init__(self): + self.appname = "" + self.version = "" + self.testcases = [] + self.results = "" + self.output = [] + self.outputLine = 0 + self.writtenOutput = False + self.sysout = [] + self.syserr = [] + self.errors = "" + self.failures = "" + self.tests = "" + + def __eq__(self, other): + return self.__dict__ == other.__dict__ + + def __repr__(self): + result = "[" + self.appname + ", " + self.version + " [ " + suffix = "" + for case in self.testcases: + result += suffix + result += repr(case) + suffix = ", " + result += " ]" + result += self.results + result += " ]" + return result + + def empty(self): + if len(self.appname): + return False + return True + + def generateApprovedLines(self): + if self.empty(): + raise Exception("Empty test run..." + repr(self)) + lines = [] + self.writtenOutput = False + if not(self.writtenOutput) and len(self.output) > 0 and self.outputLine == 0: + lines += self.output + self.writtenOutput = True + if len(self.appname): + lines.append("") + lines.append("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") + lines.append(self.appname + " is a " + self.version + " host application.") + lines.append("Run with -? for options") + lines.append("") + for case in self.testcases: + lines += case.generateApprovedLines() + if not(self.writtenOutput) and len(self.output) > 0 and len(lines) >= self.outputLine: + lines += self.output + self.writtenOutput = True + lines.append("===============================================================================") + lines.append(self.results) + lines.append("") + + return lines + + def generateSortedApprovedLines(self): + if self.empty(): + raise Exception("Empty test run..." + repr(self)) + lines = [] + self.writtenOutput = False + if not(self.writtenOutput) and len(self.output) > 0 and self.outputLine == 0: + lines += self.output + self.writtenOutput = True + if len(self.appname): + lines.append("") + lines.append("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") + lines.append(self.appname + " is a " + self.version + " host application.") + lines.append("Run with -? for options") + lines.append("") + sortedTestcases = sorted(self.testcases, key=lambda x: x.name, reverse=False) + for case in sortedTestcases: + lines += case.generateApprovedLines() + if not(self.writtenOutput) and len(self.output) > 0 and len(lines) >= self.outputLine: + lines += self.output + self.writtenOutput = True + lines.append("===============================================================================") + lines.append(self.results) + lines.append("") + + return lines + + def generateResultLines(self): + if self.empty(): + raise Exception("Empty test run..." + repr(self)) + lines = [] + self.writtenOutput = False + if not(self.writtenOutput) and len(self.output) > 0 and self.outputLine == 0: + lines += self.output + self.writtenOutput = True + if len(self.appname): + lines.append("") + lines.append("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") + lines.append(self.appname + " is a " + self.version + " host application.") + lines.append("Run with -? for options") + lines.append("") + for case in self.testcases: + lines += case.generateResultLines() + if not(self.writtenOutput) and len(self.output) > 0 and len(lines) >= self.outputLine: + lines += self.output + self.writtenOutput = True + lines.append("===============================================================================") + lines.append(self.results) + lines.append("") + + return lines + + def generateUnapprovedLines(self, outputLine): + if self.empty(): + raise Exception("Empty test run..." + repr(self)) + lines = [] + self.writtenOutput = False + #print "U:",outputLine,",",self.output + if not(self.writtenOutput) and len(self.output) > 0 and outputLine == 0: + lines += self.output + self.writtenOutput = True + if len(self.appname): + lines.append("") + lines.append("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") + lines.append("CatchSelfTest" + " is a " + "" + " host application.") + lines.append("Run with -? for options") + lines.append("") + for case in self.testcases: + lines += case.generateUnapprovedLines() + if not(self.writtenOutput) and len(self.output) > 0 and len(lines) >= outputLine: + lines += self.output + self.writtenOutput = True + lines.append("===============================================================================") + lines.append(self.results) + lines.append("") + + return lines + + def generateSortedUnapprovedLines(self, outputLine): + if self.empty(): + raise Exception("Empty test run..." + repr(self)) + lines = [] + self.writtenOutput = False + #print "U:",outputLine,",",self.output + if not(self.writtenOutput) and len(self.output) > 0 and outputLine == 0: + lines += self.output + self.writtenOutput = True + if len(self.appname): + lines.append("") + lines.append("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") + lines.append("CatchSelfTest" + " is a " + "" + " host application.") + lines.append("Run with -? for options") + lines.append("") + sortedTestcases = sorted(self.testcases, key=lambda x: x.name, reverse=False) + for case in sortedTestcases: + lines += case.generateUnapprovedLines() + if not(self.writtenOutput) and len(self.output) > 0 and len(lines) >= outputLine: + lines += self.output + self.writtenOutput = True + lines.append("===============================================================================") + lines.append(self.results) + lines.append("") + + return lines + + def generateSortedUnapprovedJunit(self): + lines = [] + #print "U:",outputLine,",",self.output + lines.append("") + l = " " + lines.append(l) + sortedTestcases = sorted(self.testcases, key=lambda x: x.classname, reverse=False) + sortedTestcases = sorted(sortedTestcases, key=lambda x: x.name, reverse=False) + #sortedTestcases = self.testcases + for case in sortedTestcases: + lines += case.generateUnapprovedJunit() + + if len(self.sysout) > 0: + lines.append(" ") + for l in self.sysout: + lines.append(l) + lines.append(" ") + if len(self.syserr) > 0: + lines.append(" ") + for l in self.syserr: + lines.append(l) + lines.append(" ") + + lines.append(" ") + lines.append("") + return lines + + def generateSortedUnapprovedXml(self): + lines = [] + #print "U:",outputLine,",",self.output + lines.append("") + lines.append(" ") + + sortedTestcases = sorted(self.testcases, key=lambda x: x.classname, reverse=False) + sortedTestcases = sorted(sortedTestcases, key=lambda x: x.name, reverse=False) + #sortedTestcases = self.testcases + for case in sortedTestcases: + lines += case.generateUnapprovedXml() + + l = "" + lines.append(" " + l) + lines.append(" ") + lines.append(" " + l) + lines.append("") + return lines + + def addTestCase(self, name): + testcase = TestCaseData() + testcase.name = name + testcase.nameParts.append(name) + self.testcases.append(testcase) + return testcase + + def addClassTestCase(self, cls, name): + testcase = TestCaseData() + testcase.classname = cls + testcase.name = name + testcase.nameParts.append(name) + self.testcases.append(testcase) + return testcase + + def addSysout(self, output): + self.sysout = output + + def addSyserr(self, output): + self.syserr = output + +class TestRunApprovedParser: + NONE = 0 + VERSION_EXPECTED = 1 + TEST_CASE_EXPECTED = 2 + END_RUN_INFO = 3 + + versionParser = re.compile( r'(.*)is a (*).*' ) + + def __init__(self): + self.state = self.NONE + self.current = TestRunData() + self.testcaseParser = TestCaseApprovedParser() + self.lineNumber = 0 + + def parseApprovedLine(self,line): + result = None + if self.state == self.NONE: + if line.startswith("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"): + self.state = self.VERSION_EXPECTED + elif len(line): + raise Exception("Unknown parse line: '" + line + "'") + elif self.state == self.VERSION_EXPECTED: + m = self.versionParser.match(line) + if m: + self.current.appname = m.group(1).strip() + self.current.version = m.group(2).strip() + self.state = self.TEST_CASE_EXPECTED + elif len(line): + raise Exception("Unknown parse line: '" + line + "'") + elif self.state == self.TEST_CASE_EXPECTED: + if line == "Run with -? for options": + pass + else: + testcase = None + try: + testcase = self.testcaseParser.parseApprovedLine(line) + except RandomOutput as e: + #print "E:", self.lineNumber, ", ",e.output + self.current.output = e.output + self.current.outputLine = self.lineNumber - 10 + + if isinstance(testcase, TestCaseData): + self.current.testcases.append(testcase) + if line.startswith("==============================================================================="): + self.state = self.END_RUN_INFO + elif self.state == self.END_RUN_INFO: + if len(line): + self.current.results = line.strip() + result = self.current + + self.lineNumber += 1 + return result + +class TestRunApprovedHandler: + + def __init__(self, filePath): + rawFile = open( filePath, 'r' ) + parser = TestRunApprovedParser() + lineNumber = 0 + self.current = None + for line in rawFile: + line = line.rstrip() + #print "L:", lineNumber, "'",line,"'" + result = parser.parseApprovedLine(line) + if isinstance(result, TestRunData): + self.current = result + lineNumber += 1 + if not(isinstance(self.current, TestRunData) ): + raise Exception("File could not be parsed: '" + filePath + "'") + + def writeRawFile(self,filePath): + rawWriteFile = open( filePath, 'wb' ) + lines = self.current.generateApprovedLines() + for line in lines: + rawWriteFile.write(line + "\n") + + def writeSortedRawFile(self,filePath): + rawWriteFile = open( filePath, 'wb' ) + lines = self.current.generateSortedApprovedLines() + for line in lines: + rawWriteFile.write(line + "\n") + +class TestRunResultParser: + NONE = 0 + VERSION_EXPECTED = 1 + TEST_CASE_EXPECTED = 2 + END_RUN_INFO = 3 + + versionParser = re.compile( r'(.*)is a (Catch v[0-9]*.[0-9]* b[0-9]*).*' ) + + def __init__(self): + self.state = self.NONE + self.current = TestRunData() + self.testcaseParser = TestCaseResultParser() + self.lineNumber = 0 + + def parseResultLine(self,line): + result = None + if self.state == self.NONE: + if line.startswith("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"): + self.state = self.VERSION_EXPECTED + elif len(line): + self.current.output.append(line.strip()) + if len(self.current.output) == 10: + if (self.current.output[0] == "Message from section one" and self.current.output[1] == "Message from section two" and + self.current.output[2] == "Some information" and self.current.output[3] == "An error" and + self.current.output[4] == "Message from section one" and self.current.output[5] == "Message from section two" and + self.current.output[6] == "Some information" and self.current.output[7] == "An error" and + self.current.output[8] == "hello" and self.current.output[9] == "hello" ): + + self.current.outputLine = self.lineNumber - 9 + + elif self.state == self.VERSION_EXPECTED: + m = self.versionParser.match(line) + if m: + self.current.appname = m.group(1).strip() + self.current.version = m.group(2).strip() + self.state = self.TEST_CASE_EXPECTED + elif len(line): + raise Exception("Unknown parse line: '" + line + "'") + elif self.state == self.TEST_CASE_EXPECTED: + if line == "Run with -? for options": + pass + else: + testcase = None + try: + testcase = self.testcaseParser.parseResultLine(line) + except RandomOutput as e: + #print "E:", self.lineNumber, ", ",e.output + self.current.output = e.output + self.current.outputLine = self.lineNumber - 10 + + if isinstance(testcase, TestCaseData): + self.current.testcases.append(testcase) + if line.startswith("==============================================================================="): + self.state = self.END_RUN_INFO + elif self.state == self.END_RUN_INFO: + if len(line): + self.current.results = line.strip() + result = self.current + + self.lineNumber += 1 + return result + +class TestRunResultHandler: + + def __init__(self, filePath): + rawFile = open( filePath, 'r' ) + parser = TestRunResultParser() + lineNumber = 0 + self.current = None + for line in rawFile: + line = line.rstrip() + #print "L:", lineNumber, "'",line,"'" + result = parser.parseResultLine(line) + if isinstance(result, TestRunData): + self.current = result + lineNumber += 1 + if not(isinstance(self.current, TestRunData) ): + raise Exception("File could not be parsed: '" + filePath + "'") + + def writeRawFile(self,filePath): + rawWriteFile = open( filePath, 'wb' ) + lines = self.current.generateResultLines() + for line in lines: + rawWriteFile.write(line + os.linesep) + + def writeUnapprovedFile(self,filePath,outputLine): + rawWriteFile = open( filePath, 'wb' ) + lines = self.current.generateUnapprovedLines(outputLine) + for line in lines: + rawWriteFile.write(line + "\n") + def writeSortedUnapprovedFile(self,filePath,outputLine): + rawWriteFile = open( filePath, 'wb' ) + lines = self.current.generateSortedUnapprovedLines(outputLine) + for line in lines: + rawWriteFile.write(line + "\n") diff --git a/scripts/test_conditions.py b/scripts/test_conditions.py new file mode 100644 index 00000000..2b11bf7b --- /dev/null +++ b/scripts/test_conditions.py @@ -0,0 +1,698 @@ +import unittest +import catch_conditions + +from catch_conditions import TestConditionApprovedParser +from catch_conditions import TestConditionResultParser +from catch_conditions import TestConditionData +from catch_conditions import RandomOutput + +class ConditionTest(unittest.TestCase): + + def testConditionEquality(self): + c1 = TestConditionData() + c2 = TestConditionData() + c1.state = TestConditionData.CONDITION + c2.state = TestConditionData.EXPANSION + c1.filenamePrefix = "..\\..\\Test" + c2.filenamePrefix = "..\\..\\Test" + self.assertTrue(c1 == c2) + c2.filenamePrefix = "..\\..\\Junk" + self.assertFalse(c1 == c2) + self.assertTrue(c1 != c2) + + def testEndOfTestRunIsFound(self): + obj = TestConditionApprovedParser() + line = "===============================================================================" + result = obj.parseApprovedLine(line) + self.assertTrue(result == None) + self.assertTrue(obj.current.empty()) + + def testEndOfTestCaseIsFound(self): + obj = TestConditionApprovedParser() + line = "-------------------------------------------------------------------------------" + result = obj.parseApprovedLine(line) + self.assertTrue(result == None) + self.assertTrue(obj.current.empty()) + + def testFailedConditionIsFound(self): + obj = TestConditionApprovedParser() + line = "ClassTests.cpp:: FAILED:" + result = obj.parseApprovedLine(line) + self.assertTrue(result == None) + self.assertTrue( not(obj.current.empty()) ) + + def testOtherConditionIsFound(self): + obj = TestConditionApprovedParser() + line = "ClassTests.cpp::" + result = obj.parseApprovedLine(line) + self.assertTrue(result == None) + self.assertTrue( not(obj.current.empty()) ) + + def testFailedConditionSetsReason(self): + obj = TestConditionApprovedParser() + line = "ClassTests.cpp:: FAILED:" + result = obj.parseApprovedLine(line) + self.assertTrue(obj.current.reason == "FAILED") + self.assertTrue(obj.current.filename == "ClassTests.cpp") + self.assertTrue(obj.current.lineNumber == "line number") + + def testOtherConditionSetsFileNameAndLine(self): + obj = TestConditionApprovedParser() + line = "MessageTests.cpp::" + result = obj.parseApprovedLine(line) + self.assertTrue(obj.current.filename == "MessageTests.cpp") + self.assertTrue(obj.current.lineNumber == "line number") + + def testFailedConditionSetsCondition(self): + obj = TestConditionApprovedParser() + lines = ["ClassTests.cpp:: FAILED:", + " REQUIRE( s == \"world\" )", + ""] + for line in lines: + result = obj.parseApprovedLine(line) + self.assertTrue(result == None) + + self.assertTrue(obj.current.condition == "REQUIRE( s == \"world\" )") + newLines = obj.current.generateApprovedLines() + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testExpansionConditionReturnsExpansion(self): + obj = TestConditionApprovedParser() + lines = ["ClassTests.cpp:: FAILED:", + " REQUIRE( s == \"world\" )", + "with expansion:" ] + for line in lines: + result = obj.parseApprovedLine(line) + self.assertTrue(result == None) + + def testExpansionSetsExpansion(self): + obj = TestConditionApprovedParser() + lines = [ "ClassTests.cpp:: FAILED:", + " REQUIRE( s == \"world\" )", + "with expansion:", + " 1 == 2", + "", + "-------------------------------------------------------------------------------" + ] + for line in lines: + result = obj.parseApprovedLine(line) + #print lines + self.assertTrue(isinstance(result, TestConditionData)) + self.assertTrue(len(result.expansion) == 1) + self.assertTrue(result.expansion[0] == "1 == 2") + newLines = result.generateApprovedLines() + newLines.append("-------------------------------------------------------------------------------") + #print newLines + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testTwoConditions(self): + obj = TestConditionApprovedParser() + lines = [ "ConditionTests.cpp:: FAILED:", + " CHECK( data.int_seven == 6 )", + "with expansion:", + " 7 == 6", + "", + "ConditionTests.cpp:: FAILED:", + " CHECK( data.int_seven == 8 )", + "with expansion:", + " 7 == 8", + "", + "-------------------------------------------------------------------------------" + ] + newLines = [] + for line in lines: + result = obj.parseApprovedLine(line) + if isinstance(result, TestConditionData): + newLines += result.generateApprovedLines() + + newLines.append("-------------------------------------------------------------------------------") + #print lines + #print newLines + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testSuccessConditions(self): + obj = TestConditionApprovedParser() + lines = [ "ApproxTests.cpp::", + "PASSED:", + " REQUIRE( d == Approx( 1.23 ) )", + "with expansion:", + " 1.23 == Approx( 1.23 )", + "", + "-------------------------------------------------------------------------------" + ] + newLines = [] + for line in lines: + result = obj.parseApprovedLine(line) + if isinstance(result, TestConditionData): + newLines += result.generateApprovedLines() + + newLines.append("-------------------------------------------------------------------------------") + #print result + #print lines + #print newLines + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testConditionsWithoutExpansion(self): + obj = TestConditionApprovedParser() + lines = [ "ConditionTests.cpp:: FAILED:", + " CHECK( false != false )", + "", + "ConditionTests.cpp:: FAILED:", + " CHECK( true != true )", + "", + "ConditionTests.cpp:: FAILED:", + " CHECK( !true )", + "with expansion:", + " false", + "", + "-------------------------------------------------------------------------------" + ] + newLines = [] + for line in lines: + result = obj.parseApprovedLine(line) + if isinstance(result, TestConditionData): + newLines += result.generateApprovedLines() + + newLines.append("-------------------------------------------------------------------------------") + #print lines + #print newLines + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testExceptionsExplicit(self): + obj = TestConditionApprovedParser() + lines = [ "ExceptionTests.cpp:: FAILED:", + " CHECK_THROWS_AS( thisThrows() )", + "due to unexpected exception with message:", + " expected exception", + "", + "ExceptionTests.cpp:: FAILED:", + " CHECK_THROWS_AS( thisDoesntThrow() )", + "because no exception was thrown where one was expected:", + "", + "ExceptionTests.cpp:: FAILED:", + " CHECK_NOTHROW( thisThrows() )", + "due to unexpected exception with message:", + " expected exception", + "", + "-------------------------------------------------------------------------------" + ] + newLines = [] + for line in lines: + result = obj.parseApprovedLine(line) + if isinstance(result, TestConditionData): + newLines += result.generateApprovedLines() + + newLines.append("-------------------------------------------------------------------------------") + #print lines + #print newLines + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testExceptionsImplicit(self): + obj = TestConditionApprovedParser() + lines = [ "ExceptionTests.cpp:: FAILED:", + "due to unexpected exception with message:", + " unexpected exception", + "", + "ExceptionTests.cpp:: FAILED:", + " {Unknown expression after the reported line}", + "due to unexpected exception with message:", + " unexpected exception", + "", + "ExceptionTests.cpp:: FAILED:", + "due to unexpected exception with message:", + " 3.14", + "", + "-------------------------------------------------------------------------------" + ] + newLines = [] + for line in lines: + result = obj.parseApprovedLine(line) + if isinstance(result, TestConditionData): + #print result + newLines += result.generateApprovedLines() + + newLines.append("-------------------------------------------------------------------------------") + #print lines + #print newLines + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testWarning(self): + obj = TestConditionApprovedParser() + lines = [ "MessageTests.cpp::", + "warning:", + " this is a warning", + "", + "-------------------------------------------------------------------------------" + ] + newLines = [] + for line in lines: + result = obj.parseApprovedLine(line) + if isinstance(result, TestConditionData): + #print result + newLines += result.generateApprovedLines() + + newLines.append("-------------------------------------------------------------------------------") + #print lines + #print newLines + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testMessages1(self): + obj = TestConditionApprovedParser() + lines = [ "MessageTests.cpp:: FAILED:", + " REQUIRE( a == 1 )", + "with expansion:", + " 2 == 1", + "with messages:", + " this message should be logged", + " so should this", + "", + "-------------------------------------------------------------------------------" + ] + newLines = [] + for line in lines: + result = obj.parseApprovedLine(line) + if isinstance(result, TestConditionData): + #print result + newLines += result.generateApprovedLines() + + newLines.append("-------------------------------------------------------------------------------") + #print lines + #print newLines + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testMessagesExplicitFail(self): + obj = TestConditionApprovedParser() + lines = [ "MessageTests.cpp:: FAILED:", + "explicitly with message:", + " This is a failure", + "", + "-------------------------------------------------------------------------------" + ] + newLines = [] + for line in lines: + result = obj.parseApprovedLine(line) + if isinstance(result, TestConditionData): + #print result + newLines += result.generateApprovedLines() + + newLines.append("-------------------------------------------------------------------------------") + #print lines + #print newLines + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testMessagesOutput(self): + obj = TestConditionApprovedParser() + lines = [ "MessageTests.cpp:: FAILED:", + "explicitly with message:", + " Message from section two", + "", + "Message from section one", + "Message from section two", + "-------------------------------------------------------------------------------" + ] + newLines = [] + for line in lines: + result = obj.parseApprovedLine(line) + if isinstance(result, TestConditionData): + #print result + newLines += result.generateApprovedLines() + + newLines.append("-------------------------------------------------------------------------------") + #print lines + #print newLines + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testMultiMessages(self): + obj = TestConditionApprovedParser() + lines = [ "MessageTests.cpp:: FAILED:", + " REQUIRE( i < 10 )", + "with expansion:", + " 10 < 10", + "with messages:", + " current counter 10", + " i := 10", + "", + "-------------------------------------------------------------------------------" + ] + newLines = [] + for line in lines: + result = obj.parseApprovedLine(line) + if isinstance(result, TestConditionData): + #print result + newLines += result.generateApprovedLines() + + newLines.append("-------------------------------------------------------------------------------") + #print lines + #print newLines + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testMiscMessages(self): + obj = TestConditionApprovedParser() + lines = [ "MiscTests.cpp:: FAILED:", + " CHECK( ( fib[i] % 2 ) == 0 )", + "with expansion:", + " 1 == 0", + "with message:", + " Testing if fib[0] (1) is even", + "", + "MiscTests.cpp:: FAILED:", + " CHECK( ( fib[i] % 2 ) == 0 )", + "with expansion:", + " 1 == 0", + "with message:", + " Testing if fib[1] (1) is even", + "", + "MiscTests.cpp:: FAILED:", + " CHECK( ( fib[i] % 2 ) == 0 )", + "with expansion:", + " 1 == 0", + "with message:", + " Testing if fib[3] (3) is even", + "", + "MiscTests.cpp:: FAILED:", + " CHECK( ( fib[i] % 2 ) == 0 )", + "with expansion:", + " 1 == 0", + "with message:", + " Testing if fib[4] (5) is even", + "", + "MiscTests.cpp:: FAILED:", + " CHECK( ( fib[i] % 2 ) == 0 )", + "with expansion:", + " 1 == 0", + "with message:", + " Testing if fib[6] (13) is even", + "", + "MiscTests.cpp:: FAILED:", + " CHECK( ( fib[i] % 2 ) == 0 )", + "with expansion:", + " 1 == 0", + "with message:", + " Testing if fib[7] (21) is even", + "", + "Some information", + "An error", + "-------------------------------------------------------------------------------" + ] + newLines = [] + for line in lines: + result = obj.parseApprovedLine(line) + if isinstance(result, TestConditionData): + #print result + newLines += result.generateApprovedLines() + + newLines.append("-------------------------------------------------------------------------------") + #print lines + #print newLines + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testRandomOutput(self): + obj = TestConditionApprovedParser() + lines = [ "MiscTests.cpp:: FAILED:", + "explicitly with message:", + " to infinity and beyond", + "", + "Message from section one", + "Message from section two", + "Some information", + "An error", + "Message from section one", + "Message from section two", + "Some information", + "An error", + "hello", + "hello", + "-------------------------------------------------------------------------------" + ] + newLines = [] + for line in lines: + try: + result = obj.parseApprovedLine(line) + except RandomOutput as e: + randomOutput = e.output + if isinstance(result, TestConditionData): + #print result + newLines += result.generateApprovedLines() + + self.assertTrue( len(randomOutput) == 10) + newLines += randomOutput + newLines.append("-------------------------------------------------------------------------------") + #print lines + #print newLines + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testMultiLineWarning(self): + obj = TestConditionApprovedParser() + lines = [ "TrickyTests.cpp::", + "warning:", + " Uncomment the code in this test to check that it gives a sensible compiler", + " error", + "", + "-------------------------------------------------------------------------------" + ] + newLines = [] + for line in lines: + result = obj.parseApprovedLine(line) + if isinstance(result, TestConditionData): + #print result + newLines += result.generateApprovedLines() + + newLines.append("-------------------------------------------------------------------------------") + #print lines + #print newLines + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testMultiMessagesAfterCondition(self): + obj = TestConditionApprovedParser() + lines = [ "MiscTests.cpp:: FAILED:", + " REQUIRE( false )", + "with messages:", + " hi", + " i := 7", + "", + "-------------------------------------------------------------------------------" + ] + newLines = [] + randomOutput = [] + for line in lines: + result = obj.parseApprovedLine(line) + if isinstance(result, TestConditionData): + #print result + newLines += result.generateApprovedLines() + + newLines.append("-------------------------------------------------------------------------------") + #print lines + #print newLines + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testNoAssertions(self): + obj = TestConditionApprovedParser() + lines = [ "", + "No assertions in test case './succeeding/exceptions/implicit'", + "", + "-------------------------------------------------------------------------------" + ] + newLines = [] + randomOutput = [] + for line in lines: + result = obj.parseApprovedLine(line) + if isinstance(result, TestConditionData): + #print result + newLines += result.generateApprovedLines() + + newLines.append("-------------------------------------------------------------------------------") + #print lines + #print newLines + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testNoAssertionsWithOutput(self): + obj = TestConditionApprovedParser() + lines = [ "", + "No assertions in section 'one'", + "", + "Message from section two", + "-------------------------------------------------------------------------------" + ] + newLines = [] + randomOutput = [] + for line in lines: + result = obj.parseApprovedLine(line) + if isinstance(result, TestConditionData): + #print result + newLines += result.generateApprovedLines() + + newLines.append("-------------------------------------------------------------------------------") + #print lines + #print newLines + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testFailedButOk(self): + obj = TestConditionApprovedParser() + lines = [ "MessageTests.cpp::", + "FAILED - but was ok:", + " CHECK_NOFAIL( 1 == 2 )", + "", + "", + "No assertions in test case './succeeding/nofail'", + "", + "-------------------------------------------------------------------------------" + ] + newLines = [] + randomOutput = [] + for line in lines: + result = obj.parseApprovedLine(line) + if isinstance(result, TestConditionData): + #print result + newLines += result.generateApprovedLines() + + newLines.append("-------------------------------------------------------------------------------") + #print lines + #print newLines + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testMultiLineExpansion(self): + obj = TestConditionApprovedParser() + lines = [ "MiscTests.cpp::", + "PASSED:", + " CHECK_THAT( testStringForMatching() AllOf( Catch::Contains( \"string\" ), Catch::Contains( \"abc\" ) ) )", + "with expansion:", + " \"this string contains 'abc' as a substring\" ( contains: \"string\" and", + " contains: \"abc\" )", + "", + "-------------------------------------------------------------------------------" + ] + newLines = [] + randomOutput = [] + for line in lines: + result = obj.parseApprovedLine(line) + if isinstance(result, TestConditionData): + #print result + newLines += result.generateApprovedLines() + + newLines.append("-------------------------------------------------------------------------------") + #print lines + #print newLines + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testMultiLineExpansionWithWrap(self): + obj = TestConditionApprovedParser() + lines = [ "TestMain.cpp::", + "PASSED:", + " CHECK( text.toString() == \" one two\n three\n four\" )", + "with expansion:", + " \" one two", + " three", + " four\"", + " ==", + " \" one two", + " three", + " four\"", + "", + "-------------------------------------------------------------------------------" + ] + newLines = [] + randomOutput = [] + for line in lines: + result = obj.parseApprovedLine(line) + if isinstance(result, TestConditionData): + #print result + newLines += result.generateApprovedLines() + + newLines.append("-------------------------------------------------------------------------------") + #print lines + #print newLines + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testMultiLineExpansionWithTruncation(self): + obj = TestConditionApprovedParser() + lines = [ "TestMain.cpp::", + "PASSED:", + " CHECK_THAT( t.toString() EndsWith( \"... message truncated due to excessive size\" ) )", + "with expansion:", + " \"***************************************************************************-", + " ***-", + " ****************************************************************************-", + " **-", + "... message truncated due to excessive size", + "", + "-------------------------------------------------------------------------------" + ] + newLines = [] + for line in lines: + result = obj.parseApprovedLine(line) + if isinstance(result, TestConditionData): + #print result + newLines += result.generateApprovedLines() + + newLines.append("-------------------------------------------------------------------------------") + #print lines + #print newLines + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testBasicResultsParser(self): + obj = TestConditionResultParser() + lines = [ "..\..\..\SelfTest\ClassTests.cpp(28): FAILED:", + " REQUIRE( s == \"world\" )", + "with expansion:", + " \"hello\" == \"world\"", + "", + "-------------------------------------------------------------------------------" + ] + newLines = [] + for line in lines: + result = obj.parseResultLine(line) + if isinstance(result, TestConditionData): + #print result + newLines += result.generateResultLines() + + newLines.append("-------------------------------------------------------------------------------") + #print lines + #print newLines + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testBasicResultsWarning(self): + obj = TestConditionResultParser() + lines = [ "..\..\..\SelfTest\MessageTests.cpp(17): ", + "warning:", + " this is a warning", + "", + "-------------------------------------------------------------------------------" + ] + newLines = [] + for line in lines: + result = obj.parseResultLine(line) + if isinstance(result, TestConditionData): + #print result + newLines += result.generateResultLines() + + newLines.append("-------------------------------------------------------------------------------") + #print lines + #print newLines + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/scripts/test_test_case.py b/scripts/test_test_case.py new file mode 100644 index 00000000..6976453c --- /dev/null +++ b/scripts/test_test_case.py @@ -0,0 +1,341 @@ +import unittest + +from catch_test_case import TestCaseApprovedParser +from catch_test_case import TestCaseResultParser +from catch_test_case import TestCaseData +from catch_conditions import TestConditionData + +class TestCaseTest(unittest.TestCase): + + def testTestCaseEquality(self): + c1 = TestConditionData() + c2 = TestConditionData() + c1.filenamePrefix = "..\\..\\Test" + c2.filenamePrefix = "..\\..\\Junk" + t1 = TestCaseData() + t2 = TestCaseData() + t1.name = "Test 1" + t2.name = "Test 1" + t1.conditions.append(c1) + t1.conditions.append(c2) + t2.conditions.append(c1) + t2.conditions.append(c2) + self.assertTrue(t1 == t2) + + c3 = TestConditionData() + c3.filenamePrefix = "..\\..\\Fail" + t2.conditions.append(c3) + self.assertFalse(t1 == t2) + + t1.conditions.append(c3) + self.assertTrue(t1 == t2) + + t2.name = "Test 2" + self.assertFalse(t1 == t2) + + def testEndOfTestCaseIsFound(self): + obj = TestCaseApprovedParser() + line = "-------------------------------------------------------------------------------" + result = obj.parseApprovedLine(line) + self.assertTrue(result == None) + self.assertTrue(obj.current.empty()) + + def testTestCaseNameIsFound(self): + obj = TestCaseApprovedParser() + lines = [ "-------------------------------------------------------------------------------", + "./failing/TestClass/failingCase", + "-------------------------------------------------------------------------------" ] + for line in lines: + result = obj.parseApprovedLine(line) + self.assertTrue(result == None) + self.assertTrue(obj.current.name == "./failing/TestClass/failingCase") + self.assertTrue( not(obj.current.empty()) ) + + def testTestCaseClassIsFound(self): + obj = TestCaseApprovedParser() + lines = [ "-------------------------------------------------------------------------------", + "./failing/TestClass/failingCase", + "-------------------------------------------------------------------------------", + "ClassTests.cpp:", + "..............................................................................." ] + for line in lines: + result = obj.parseApprovedLine(line) + self.assertTrue(result == None) + self.assertTrue(obj.current.filename == "ClassTests.cpp") + self.assertTrue(obj.current.lineNumber == "line number") + + def testPartialConditionRequiresMoreData(self): + obj = TestCaseApprovedParser() + lines = [ "-------------------------------------------------------------------------------", + "./failing/TestClass/failingCase", + "-------------------------------------------------------------------------------", + "ClassTests.cpp:", + "...............................................................................", + "ClassTests.cpp:: FAILED:" ] + for line in lines: + result = obj.parseApprovedLine(line) + self.assertTrue(result == None) + self.assertTrue(len(obj.current.conditions) == 0) + + def testTestCaseConditionIsFound(self): + obj = TestCaseApprovedParser() + lines = [ "-------------------------------------------------------------------------------", + "./failing/TestClass/failingCase", + "-------------------------------------------------------------------------------", + "ClassTests.cpp:", + "...............................................................................", + "", + "ClassTests.cpp:: FAILED:", + " REQUIRE( s == \"world\" )", + "with expansion:", + " \"hello\" == \"world\""] + for line in lines: + result = obj.parseApprovedLine(line) + self.assertTrue(result == None) + self.assertTrue(not(obj.conditionParser.current.empty())) + self.assertTrue(obj.conditionParser.current.reason == "FAILED") + + def testTestCaseConditionIsInsertedIntoList(self): + obj = TestCaseApprovedParser() + lines = [ "-------------------------------------------------------------------------------", + "./failing/TestClass/failingCase", + "-------------------------------------------------------------------------------", + "ClassTests.cpp:", + "...............................................................................", + "", + "ClassTests.cpp:: FAILED:", + " REQUIRE( s == \"world\" )", + "with expansion:", + " \"hello\" == \"world\"", + "", + "-------------------------------------------------------------------------------"] + for line in lines: + result = obj.parseApprovedLine(line) + + self.assertTrue(isinstance(result, TestCaseData)) + self.assertTrue(len(result.conditions) > 0) + self.assertTrue(result.conditions[0].filename == "ClassTests.cpp" ) + self.assertTrue(result.conditions[0].lineNumber == "line number" ) + self.assertTrue(result.conditions[0].reason == "FAILED" ) + newLines = result.generateApprovedLines() + newLines.append("-------------------------------------------------------------------------------") + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testTwoTestCases(self): + obj = TestCaseApprovedParser() + lines = [ "-------------------------------------------------------------------------------", + "./failing/TestClass/failingCase", + "-------------------------------------------------------------------------------", + "ClassTests.cpp:", + "...............................................................................", + "", + "ClassTests.cpp:: FAILED:", + " REQUIRE( s == \"world\" )", + "with expansion:", + " \"hello\" == \"world\"", + "", + "-------------------------------------------------------------------------------", + "./failing/Fixture/failingCase", + "-------------------------------------------------------------------------------", + "ClassTests.cpp:", + "...............................................................................", + "", + "ClassTests.cpp:: FAILED:", + " REQUIRE( m_a == 2 )", + "with expansion:", + " 1 == 2", + "", + "===============================================================================" + ] + newLines = [] + for line in lines: + result = obj.parseApprovedLine(line) + if isinstance(result, TestCaseData): + newLines += result.generateApprovedLines() + + newLines.append("===============================================================================") + #for line in newLines: + # print line + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testTestCaseMultiConditionMatches(self): + obj = TestCaseApprovedParser() + lines = [ "-------------------------------------------------------------------------------", + "./failing/conditions/equality", + "-------------------------------------------------------------------------------", + "ConditionTests.cpp:", + "...............................................................................", + "", + "ConditionTests.cpp:: FAILED:", + " CHECK( data.int_seven == 6 )", + "with expansion:", + " 7 == 6", + "", + "ConditionTests.cpp:: FAILED:", + " CHECK( data.int_seven == 8 )", + "with expansion:", + " 7 == 8", + "", + "ConditionTests.cpp:: FAILED:", + " CHECK( data.int_seven == 0 )", + "with expansion:", + " 7 == 0", + "", + "ConditionTests.cpp:: FAILED:", + " CHECK( data.float_nine_point_one == Approx( 9.11f ) )", + "with expansion:", + " 9.1 == Approx( 9.11 )", + "", + "ConditionTests.cpp:: FAILED:", + " CHECK( data.float_nine_point_one == Approx( 9.0f ) )", + "with expansion:", + " 9.1 == Approx( 9 )", + "", + "ConditionTests.cpp:: FAILED:", + " CHECK( data.float_nine_point_one == Approx( 1 ) )", + "with expansion:", + " 9.1 == Approx( 1 )", + "", + "ConditionTests.cpp:: FAILED:", + " CHECK( data.float_nine_point_one == Approx( 0 ) )", + "with expansion:", + " 9.1 == Approx( 0 )", + "", + "ConditionTests.cpp:: FAILED:", + " CHECK( data.double_pi == Approx( 3.1415 ) )", + "with expansion:", + " 3.1415926535 == Approx( 3.1415 )", + "", + "ConditionTests.cpp:: FAILED:", + " CHECK( data.str_hello == \"goodbye\" )", + "with expansion:", + " \"hello\" == \"goodbye\"", + "", + "ConditionTests.cpp:: FAILED:", + " CHECK( data.str_hello == \"hell\" )", + "with expansion:", + " \"hello\" == \"hell\"", + "", + "ConditionTests.cpp:: FAILED:", + " CHECK( data.str_hello == \"hello1\" )", + "with expansion:", + " \"hello\" == \"hello1\"", + "", + "ConditionTests.cpp:: FAILED:", + " CHECK( data.str_hello.size() == 6 )", + "with expansion:", + " 5 == 6", + "", + "ConditionTests.cpp:: FAILED:", + " CHECK( x == Approx( 1.301 ) )", + "with expansion:", + " 1.3 == Approx( 1.301 )", + "", + "===============================================================================" + ] + + for line in lines: + result = obj.parseApprovedLine(line) + + self.assertTrue(isinstance(result, TestCaseData)) + newLines = result.generateApprovedLines() + newLines.append("===============================================================================") + #for line in newLines: + # print line + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testOneSection(self): + obj = TestCaseApprovedParser() + lines = [ "-------------------------------------------------------------------------------", + "./failing/message/sections", + " one", + "-------------------------------------------------------------------------------", + "MessageTests.cpp:", + "...............................................................................", + "", + "MessageTests.cpp:: FAILED:", + "explicitly with message:", + " Message from section one", + "", + "===============================================================================" + ] + newLines = [] + for line in lines: + result = obj.parseApprovedLine(line) + if isinstance(result, TestCaseData): + newLines += result.generateApprovedLines() + + newLines.append("===============================================================================") + #for line in newLines: + # print line + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testOneSection(self): + obj = TestCaseApprovedParser() + lines = [ "-------------------------------------------------------------------------------", + "Comparisons between unsigned ints and negative signed ints match c++ standard", + "behaviour", + "-------------------------------------------------------------------------------", + "ConditionTests.cpp:", + "...............................................................................", + "", + "ConditionTests.cpp::", + "PASSED:", + " CHECK( ( -1 > 2u ) )", + "with expansion:", + " true", + "", + "ConditionTests.cpp::", + "PASSED:", + " CHECK( -1 > 2u )", + "with expansion:", + " -1 > 2", + "", + "===============================================================================" + ] + newLines = [] + for line in lines: + result = obj.parseApprovedLine(line) + if isinstance(result, TestCaseData): + newLines += result.generateApprovedLines() + + newLines.append("===============================================================================") + #for line in newLines: + # print line + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testCaseBasicResults(self): + obj = TestCaseResultParser() + lines = [ "-------------------------------------------------------------------------------", + "./failing/TestClass/failingCase", + "-------------------------------------------------------------------------------", + "..\..\..\SelfTest\ClassTests.cpp(34)", + "...............................................................................", + "", + "..\..\..\SelfTest\ClassTests.cpp(28): FAILED:", + " REQUIRE( s == \"world\" )", + "with expansion:", + " \"hello\" == \"world\"", + "", + "===============================================================================" + ] + + newLines = [] + for line in lines: + result = obj.parseResultLine(line) + if isinstance(result, TestCaseData): + newLines += result.generateResultLines() + + newLines.append("===============================================================================") + #for line in newLines: + # print line + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/scripts/test_test_run.py b/scripts/test_test_run.py new file mode 100644 index 00000000..c75c35e4 --- /dev/null +++ b/scripts/test_test_run.py @@ -0,0 +1,334 @@ +import unittest +import catch_test_case + +from catch_test_run import TestRunData +from catch_test_run import TestRunApprovedParser +from catch_test_run import TestRunResultParser +from catch_test_case import TestCaseData +from catch_conditions import TestConditionData + +class TestCaseTest(unittest.TestCase): + + def testTestRunEquality(self): + c1 = TestConditionData() + c2 = TestConditionData() + c1.filenamePrefix = "..\\..\\Test" + c2.filenamePrefix = "..\\..\\Junk" + t1 = TestCaseData() + t2 = TestCaseData() + t1.name = "Test 1" + t2.name = "Test 1" + t1.conditions.append(c1) + t1.conditions.append(c2) + t2.conditions.append(c1) + t2.conditions.append(c2) + r1 = TestRunData() + r2 = TestRunData() + r1.appname = "One" + r2.appname = "One" + self.assertTrue(r1 == r2) + + r1.testcases.append(t1) + self.assertFalse(r1 == r2) + + r2.testcases.append(t2) + self.assertTrue(r1 == r2) + + c3 = TestConditionData() + c3.filenamePrefix = "..\\..\\Fail" + t2.conditions.append(c3) + self.assertFalse(r1 == r2) + + def testStartOfTestRunIsFound(self): + obj = TestRunApprovedParser() + line = "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + result = obj.parseApprovedLine(line) + self.assertTrue(result == None) + + def testTestRunVersionIsSet(self): + obj = TestRunApprovedParser() + lines = [ "", + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~", + "CatchSelfTest is a host application.", + "Run with -? for options" ] + for line in lines: + result = obj.parseApprovedLine(line) + #print obj + self.assertTrue(result == None) + self.assertTrue(obj.current.appname == "CatchSelfTest") + self.assertTrue(obj.current.version == "") + + def testTestRunParsesTestCase(self): + obj = TestRunApprovedParser() + lines = [ "", + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~", + "CatchSelfTest is a host application.", + "Run with -? for options", + "-------------------------------------------------------------------------------", + "./failing/TestClass/failingCase", + "-------------------------------------------------------------------------------", + "ClassTests.cpp:", + "...............................................................................", + "ClassTests.cpp:: FAILED:", + " REQUIRE( s == \"world\" )", + "with expansion:" + " \"hello\" == \"world\""] + for line in lines: + result = obj.parseApprovedLine(line) + #print obj + self.assertTrue(obj.testcaseParser.conditionParser.current.filename == "ClassTests.cpp" ) + self.assertTrue(obj.testcaseParser.conditionParser.current.lineNumber == "line number" ) + self.assertTrue(obj.testcaseParser.conditionParser.current.reason == "FAILED" ) + + def testTestRunAddsTestCase(self): + obj = TestRunApprovedParser() + lines = [ "", + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~", + "CatchSelfTest is a host application.", + "Run with -? for options", + "-------------------------------------------------------------------------------", + "./failing/TestClass/failingCase", + "-------------------------------------------------------------------------------", + "ClassTests.cpp:", + "...............................................................................", + "", + "ClassTests.cpp:: FAILED:", + " REQUIRE( s == \"world\" )", + "with expansion:" + " \"hello\" == \"world\"", + "", + "-------------------------------------------------------------------------------" + ] + for line in lines: + result = obj.parseApprovedLine(line) + #print obj + self.assertTrue( result == None ) + self.assertTrue( len(obj.current.testcases) == 1 ) + self.assertTrue(obj.current.testcases[0].filename == "ClassTests.cpp" ) + self.assertTrue(obj.current.testcases[0].lineNumber == "line number" ) + self.assertTrue( len(obj.current.testcases[0].conditions) == 1 ) + self.assertTrue(obj.current.testcases[0].conditions[0].filename == "ClassTests.cpp" ) + self.assertTrue(obj.current.testcases[0].conditions[0].lineNumber == "line number" ) + self.assertTrue(obj.current.testcases[0].conditions[0].reason == "FAILED" ) + + def testTestRunParsesTwoTestCases(self): + obj = TestRunApprovedParser() + lines = [ "", + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~", + "CatchSelfTest is a host application.", + "Run with -? for options", + "", + "-------------------------------------------------------------------------------", + "./failing/TestClass/failingCase", + "-------------------------------------------------------------------------------", + "ClassTests.cpp:", + "...............................................................................", + "", + "ClassTests.cpp:: FAILED:", + " REQUIRE( s == \"world\" )", + "with expansion:", + " \"hello\" == \"world\"", + "", + "-------------------------------------------------------------------------------", + "./failing/Fixture/failingCase", + "-------------------------------------------------------------------------------", + "ClassTests.cpp:", + "...............................................................................", + "", + "ClassTests.cpp:: FAILED:", + " REQUIRE( m_a == 2 )", + "with expansion:", + " 1 == 2", + "", + "===============================================================================", + "122 test cases - 35 failed (753 assertions - 90 failed)", + "" + ] + testRun = None + for line in lines: + result = obj.parseApprovedLine(line) + if isinstance(result, TestRunData): + testRun = result + + self.assertTrue( isinstance(testRun, TestRunData) ) + newLines = testRun.generateApprovedLines() + #for line in newLines: + # print "L:",line + #print len(lines),",",len(newLines) + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testTestRunWithRandomOutput(self): + obj = TestRunApprovedParser() + lines = [ "", + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~", + "CatchSelfTest is a host application.", + "Run with -? for options", + "", + "-------------------------------------------------------------------------------", + "./failing/TestClass/failingCase", + "-------------------------------------------------------------------------------", + "ClassTests.cpp:", + "...............................................................................", + "", + "MiscTests.cpp:: FAILED:", + "explicitly with message:", + " to infinity and beyond", + "", + "Message from section one", + "Message from section two", + "Some information", + "An error", + "Message from section one", + "Message from section two", + "Some information", + "An error", + "hello", + "hello", + "===============================================================================", + "122 test cases - 35 failed (753 assertions - 90 failed)", + "" + ] + testRun = None + for line in lines: + result = obj.parseApprovedLine(line) + if isinstance(result, TestRunData): + testRun = result + + self.assertTrue( isinstance(testRun, TestRunData) ) + #print "O:",result.outputLine + self.assertTrue( testRun.outputLine == 14 ) + newLines = testRun.generateApprovedLines() + #for line in newLines: + # print line + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testRunBasicResult(self): + obj = TestRunResultParser() + lines = [ "Message from section one", + "Message from section two", + "Some information", + "An error", + "Message from section one", + "Message from section two", + "Some information", + "An error", + "hello", + "hello", + "", + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~", + "TestCatch.exe is a Catch v1.0 b13 host application.", + "Run with -? for options", + "", + "-------------------------------------------------------------------------------", + "./failing/TestClass/failingCase", + "-------------------------------------------------------------------------------", + "..\..\..\SelfTest\ClassTests.cpp(34)", + "...............................................................................", + "", + "..\..\..\SelfTest\ClassTests.cpp(28): FAILED:", + " REQUIRE( s == \"world\" )", + "with expansion:", + " \"hello\" == \"world\"", + "", + "===============================================================================", + "122 test cases - 35 failed (753 assertions - 90 failed)", + "" + ] + + testRun = None + for line in lines: + result = obj.parseResultLine(line) + if isinstance(result, TestRunData): + testRun = result + + self.assertTrue( isinstance(testRun, TestRunData) ) + #print "O:",testRun.outputLine + self.assertTrue( testRun.outputLine == 0 ) + newLines = testRun.generateResultLines() + #for line in newLines: + # print line + self.assertTrue( len(lines) == len(newLines) ) + self.assertTrue( lines == newLines ) + + def testTestRunSorted(self): + obj = TestRunApprovedParser() + lines = [ "", + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~", + "CatchSelfTest is a host application.", + "Run with -? for options", + "", + "-------------------------------------------------------------------------------", + "./failing/TestClass/failingCase", + "-------------------------------------------------------------------------------", + "ClassTests.cpp:", + "...............................................................................", + "", + "ClassTests.cpp:: FAILED:", + " REQUIRE( s == \"world\" )", + "with expansion:", + " \"hello\" == \"world\"", + "", + "-------------------------------------------------------------------------------", + "./failing/Fixture/failingCase", + "-------------------------------------------------------------------------------", + "ClassTests.cpp:", + "...............................................................................", + "", + "ClassTests.cpp:: FAILED:", + " REQUIRE( m_a == 2 )", + "with expansion:", + " 1 == 2", + "", + "===============================================================================", + "122 test cases - 35 failed (753 assertions - 90 failed)", + "" + ] + testRun = None + for line in lines: + result = obj.parseApprovedLine(line) + if isinstance(result, TestRunData): + testRun = result + + self.assertTrue( isinstance(testRun, TestRunData) ) + newLines = testRun.generateSortedApprovedLines() + #for line in newLines: + # print "L:",line + #print len(lines),",",len(newLines) + expectedLines = [ "", + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~", + "CatchSelfTest is a host application.", + "Run with -? for options", + "", + "-------------------------------------------------------------------------------", + "./failing/Fixture/failingCase", + "-------------------------------------------------------------------------------", + "ClassTests.cpp:", + "...............................................................................", + "", + "ClassTests.cpp:: FAILED:", + " REQUIRE( m_a == 2 )", + "with expansion:", + " 1 == 2", + "", + "-------------------------------------------------------------------------------", + "./failing/TestClass/failingCase", + "-------------------------------------------------------------------------------", + "ClassTests.cpp:", + "...............................................................................", + "", + "ClassTests.cpp:: FAILED:", + " REQUIRE( s == \"world\" )", + "with expansion:", + " \"hello\" == \"world\"", + "", + "===============================================================================", + "122 test cases - 35 failed (753 assertions - 90 failed)", + "" + ] + self.assertTrue( len(expectedLines) == len(newLines) ) + self.assertTrue( expectedLines == newLines ) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file From 008ada6ead7e8958eaf958e49901044b3f5d6f46 Mon Sep 17 00:00:00 2001 From: Malcolm Noyes Date: Mon, 9 Dec 2013 16:20:45 +0000 Subject: [PATCH 6/6] Pre-merge --- projects/SelfTest/TestMain.cpp | 1015 ++++++++++++------------- projects/SelfTest/catch_self_test.cpp | 33 - projects/SelfTest/catch_self_test.hpp | 177 ----- 3 files changed, 478 insertions(+), 747 deletions(-) delete mode 100644 projects/SelfTest/catch_self_test.cpp delete mode 100644 projects/SelfTest/catch_self_test.hpp diff --git a/projects/SelfTest/TestMain.cpp b/projects/SelfTest/TestMain.cpp index f793ce6b..575a456a 100644 --- a/projects/SelfTest/TestMain.cpp +++ b/projects/SelfTest/TestMain.cpp @@ -5,596 +5,537 @@ * Distributed under the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ + +#define CATCH_CONFIG_MAIN +#include "catch.hpp" + #ifdef __clang__ #pragma clang diagnostic ignored "-Wpadded" -#endif - -#include "catch_self_test.hpp" -#include "internal/catch_text.h" -#include "internal/catch_console_colour.hpp" - -namespace TestMain { - - TEST_CASE( "selftest/main", "Runs all Catch self tests and checks their results" ) { - using namespace Catch; - - /////////////////////////////////////////////////////////////////////////// - SECTION( "selftest/expected result", - "Tests do what they claim" ) { - - SECTION( "selftest/expected result/failing tests", - "Tests in the 'failing' branch fail" ) { - MetaTestRunner::runMatching( "./failing/*", MetaTestRunner::Expected::ToFail, 0, 2 ); - } - - SECTION( "selftest/expected result/succeeding tests", - "Tests in the 'succeeding' branch succeed" ) { - MetaTestRunner::runMatching( "./succeeding/*", MetaTestRunner::Expected::ToSucceed, 1, 2 ); - } - } - - /////////////////////////////////////////////////////////////////////////// - SECTION( "selftest/test counts", - "Number of test cases that run is fixed" ) { - EmbeddedRunner runner; - - SECTION( "selftest/test counts/succeeding tests", - "Number of 'succeeding' tests is fixed" ) { - Totals totals = runner.runMatching( "./succeeding/*", 0, 2 ); - CHECK( totals.assertions.passed == 298 ); - CHECK( totals.assertions.failed == 0 ); - } - - SECTION( "selftest/test counts/failing tests", - "Number of 'failing' tests is fixed" ) { - Totals totals = runner.runMatching( "./failing/*", 1, 2 ); - CHECK( totals.assertions.passed == 2 ); - CHECK( totals.assertions.failed == 77 ); - } - } - } - - TEST_CASE( "meta/Misc/Sections", "looped tests" ) { - Catch::EmbeddedRunner runner; - - Catch::Totals totals = runner.runMatching( "./mixed/Misc/Sections/nested2", 0, 1 ); - CHECK( totals.assertions.passed == 2 ); - CHECK( totals.assertions.failed == 1 ); - } -} -#ifdef __clang__ #pragma clang diagnostic ignored "-Wweak-vtables" #endif -#include "../../include/internal/catch_commandline.hpp" -#include "../../include/internal/catch_test_spec.h" -#include "../../include/reporters/catch_reporter_xml.hpp" +template +void parseIntoConfig( const char * (&argv)[size], Catch::ConfigData& config ) { + Clara::CommandLine parser = Catch::makeCommandLineParser(); + parser.parseInto( size, argv, config ); +} -namespace TestMain { - - template - void parseIntoConfig( const char * (&argv)[size], Catch::ConfigData& config ) { - Clara::CommandLine parser = Catch::makeCommandLineParser(); - parser.parseInto( size, argv, config ); +template +std::string parseIntoConfigAndReturnError( const char * (&argv)[size], Catch::ConfigData& config ) { + try { + parseIntoConfig( argv, config ); + FAIL( "expected exception" ); } - - template - std::string parseIntoConfigAndReturnError( const char * (&argv)[size], Catch::ConfigData& config ) { - try { - parseIntoConfig( argv, config ); - FAIL( "expected exception" ); - } - catch( std::exception& ex ) { - return ex.what(); - } - return ""; + catch( std::exception& ex ) { + return ex.what(); } + return ""; +} - inline Catch::TestCase fakeTestCase( const char* name, const char* desc = "" ){ return Catch::makeTestCase( NULL, "", name, desc, CATCH_INTERNAL_LINEINFO ); } +inline Catch::TestCase fakeTestCase( const char* name, const char* desc = "" ){ return Catch::makeTestCase( NULL, "", name, desc, CATCH_INTERNAL_LINEINFO ); } - TEST_CASE( "Process can be configured on command line", "[config][command-line]" ) { +TEST_CASE( "Process can be configured on command line", "[config][command-line]" ) { - Catch::ConfigData config; + Catch::ConfigData config; - SECTION( "default - no arguments", "" ) { - const char* argv[] = { "test" }; + SECTION( "default - no arguments", "" ) { + const char* argv[] = { "test" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + + CHECK( config.shouldDebugBreak == false ); + CHECK( config.abortAfter == -1 ); + CHECK( config.noThrow == false ); + CHECK( config.reporterName.empty() ); + } + + SECTION( "test lists", "" ) { + SECTION( "1 test", "Specify one test case using" ) { + const char* argv[] = { "test", "test1" }; CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - - CHECK( config.shouldDebugBreak == false ); - CHECK( config.abortAfter == -1 ); - CHECK( config.noThrow == false ); - CHECK( config.reporterName.empty() ); + + Catch::Config cfg( config ); + REQUIRE( cfg.filters().size() == 1 ); + REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "notIncluded" ) ) == false ); + REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "test1" ) ) ); } - - SECTION( "test lists", "" ) { - SECTION( "1 test", "Specify one test case using" ) { - const char* argv[] = { "test", "test1" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + SECTION( "Specify one test case exclusion using exclude:", "" ) { + const char* argv[] = { "test", "exclude:test1" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - Catch::Config cfg( config ); - REQUIRE( cfg.filters().size() == 1 ); - REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "notIncluded" ) ) == false ); - REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "test1" ) ) ); - } - SECTION( "Specify one test case exclusion using exclude:", "" ) { - const char* argv[] = { "test", "exclude:test1" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - - Catch::Config cfg( config ); - REQUIRE( cfg.filters().size() == 1 ); - REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "test1" ) ) == false ); - REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "alwaysIncluded" ) ) ); - } - - SECTION( "Specify one test case exclusion using ~", "" ) { - const char* argv[] = { "test", "~test1" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - - Catch::Config cfg( config ); - REQUIRE( cfg.filters().size() == 1 ); - REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "test1" ) ) == false ); - REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "alwaysIncluded" ) ) ); - } - - SECTION( "Specify two test cases using -t", "" ) { - const char* argv[] = { "test", "-t", "test1", "test2" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - - Catch::Config cfg( config ); - REQUIRE( cfg.filters().size() == 1 ); - REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "notIncluded" ) ) == false ); - REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "test1" ) ) ); - REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "test2" ) ) ); - } + Catch::Config cfg( config ); + REQUIRE( cfg.filters().size() == 1 ); + REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "test1" ) ) == false ); + REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "alwaysIncluded" ) ) ); } + + SECTION( "Specify one test case exclusion using ~", "" ) { + const char* argv[] = { "test", "~test1" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + + Catch::Config cfg( config ); + REQUIRE( cfg.filters().size() == 1 ); + REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "test1" ) ) == false ); + REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "alwaysIncluded" ) ) ); + } + + SECTION( "Specify two test cases using -t", "" ) { + const char* argv[] = { "test", "-t", "test1", "test2" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + + Catch::Config cfg( config ); + REQUIRE( cfg.filters().size() == 1 ); + REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "notIncluded" ) ) == false ); + REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "test1" ) ) ); + REQUIRE( cfg.filters()[0].shouldInclude( fakeTestCase( "test2" ) ) ); + } + } - SECTION( "reporter", "" ) { - SECTION( "-r/console", "" ) { - const char* argv[] = { "test", "-r", "console" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + SECTION( "reporter", "" ) { + SECTION( "-r/console", "" ) { + const char* argv[] = { "test", "-r", "console" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - REQUIRE( config.reporterName == "console" ); - } - SECTION( "-r/xml", "" ) { - const char* argv[] = { "test", "-r", "xml" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + REQUIRE( config.reporterName == "console" ); + } + SECTION( "-r/xml", "" ) { + const char* argv[] = { "test", "-r", "xml" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - REQUIRE( config.reporterName == "xml" ); - } - SECTION( "--reporter/junit", "" ) { - const char* argv[] = { "test", "--reporter", "junit" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + REQUIRE( config.reporterName == "xml" ); + } + SECTION( "--reporter/junit", "" ) { + const char* argv[] = { "test", "--reporter", "junit" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - REQUIRE( config.reporterName == "junit" ); - } + REQUIRE( config.reporterName == "junit" ); } + } - SECTION( "debugger", "" ) { - SECTION( "-b", "" ) { - const char* argv[] = { "test", "-b" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + SECTION( "debugger", "" ) { + SECTION( "-b", "" ) { + const char* argv[] = { "test", "-b" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - REQUIRE( config.shouldDebugBreak == true ); - } - SECTION( "--break", "" ) { - const char* argv[] = { "test", "--break" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + REQUIRE( config.shouldDebugBreak == true ); + } + SECTION( "--break", "" ) { + const char* argv[] = { "test", "--break" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - REQUIRE( config.shouldDebugBreak ); - } + REQUIRE( config.shouldDebugBreak ); } + } - SECTION( "abort", "" ) { - SECTION( "-a aborts after first failure", "" ) { - const char* argv[] = { "test", "-a" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + SECTION( "abort", "" ) { + SECTION( "-a aborts after first failure", "" ) { + const char* argv[] = { "test", "-a" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - REQUIRE( config.abortAfter == 1 ); - } - SECTION( "-x 2 aborts after two failures", "" ) { - const char* argv[] = { "test", "-x", "2" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - - REQUIRE( config.abortAfter == 2 ); - } - SECTION( "-x must be greater than zero", "" ) { - const char* argv[] = { "test", "-x", "0" }; - REQUIRE_THAT( parseIntoConfigAndReturnError( argv, config ), Contains( "greater than zero" ) ); - } - SECTION( "-x must be numeric", "" ) { - const char* argv[] = { "test", "-x", "oops" }; - REQUIRE_THAT( parseIntoConfigAndReturnError( argv, config ), Contains( "-x" ) ); - } + REQUIRE( config.abortAfter == 1 ); } + SECTION( "-x 2 aborts after two failures", "" ) { + const char* argv[] = { "test", "-x", "2" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + + REQUIRE( config.abortAfter == 2 ); + } + SECTION( "-x must be greater than zero", "" ) { + const char* argv[] = { "test", "-x", "0" }; + REQUIRE_THAT( parseIntoConfigAndReturnError( argv, config ), Contains( "greater than zero" ) ); + } + SECTION( "-x must be numeric", "" ) { + const char* argv[] = { "test", "-x", "oops" }; + REQUIRE_THAT( parseIntoConfigAndReturnError( argv, config ), Contains( "-x" ) ); + } + } - SECTION( "nothrow", "" ) { - SECTION( "-e", "" ) { - const char* argv[] = { "test", "-e" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); + SECTION( "nothrow", "" ) { + SECTION( "-e", "" ) { + const char* argv[] = { "test", "-e" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - REQUIRE( config.noThrow == true ); - } - SECTION( "--nothrow", "" ) { - const char* argv[] = { "test", "--nothrow" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - - REQUIRE( config.noThrow == true ); - } + REQUIRE( config.noThrow == true ); } + SECTION( "--nothrow", "" ) { + const char* argv[] = { "test", "--nothrow" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - SECTION( "output filename", "" ) { - SECTION( "-o filename", "" ) { - const char* argv[] = { "test", "-o", "filename.ext" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - - REQUIRE( config.outputFilename == "filename.ext" ); - } - SECTION( "--out", "" ) { - const char* argv[] = { "test", "--out", "filename.ext" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - - REQUIRE( config.outputFilename == "filename.ext" ); - } - } - - SECTION( "combinations", "" ) { - SECTION( "Single character flags can be combined", "" ) { - const char* argv[] = { "test", "-abe" }; - CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - - CHECK( config.abortAfter == 1 ); - CHECK( config.shouldDebugBreak ); - CHECK( config.noThrow == true ); - } - } - } - - TEST_CASE( "selftest/test filter", "Individual filters" ) { - - Catch::TestCaseFilter matchAny( "*" ); - Catch::TestCaseFilter matchNone( "*", Catch::IfFilterMatches::ExcludeTests ); - CHECK( matchAny.shouldInclude( fakeTestCase( "any" ) )); - CHECK( matchNone.shouldInclude( fakeTestCase( "any" ) ) == false ); - - Catch::TestCaseFilter matchHidden( "./*" ); - Catch::TestCaseFilter matchNonHidden( "./*", Catch::IfFilterMatches::ExcludeTests ); - - CHECK( matchHidden.shouldInclude( fakeTestCase( "any" ) ) == false ); - CHECK( matchNonHidden.shouldInclude( fakeTestCase( "any" ) ) ); - - CHECK( matchHidden.shouldInclude( fakeTestCase( "./any" ) ) ); - CHECK( matchNonHidden.shouldInclude( fakeTestCase( "./any" ) ) == false ); - } - - TEST_CASE( "selftest/test filters", "Sets of filters" ) { - - Catch::TestCaseFilter matchHidden( "./*" ); - Catch::TestCaseFilter dontMatchA( "./a*", Catch::IfFilterMatches::ExcludeTests ); - Catch::TestCaseFilters filters( "" ); - filters.addFilter( matchHidden ); - filters.addFilter( dontMatchA ); - - CHECK( matchHidden.shouldInclude( fakeTestCase( "./something" ) ) ); - - CHECK( filters.shouldInclude( fakeTestCase( "any" ) ) == false ); - CHECK( filters.shouldInclude( fakeTestCase( "./something" ) ) ); - CHECK( filters.shouldInclude( fakeTestCase( "./anything" ) ) == false ); - } - - TEST_CASE( "selftest/filter/prefix wildcard", "Individual filters with wildcards at the start" ) { - Catch::TestCaseFilter matchBadgers( "*badger" ); - - CHECK( matchBadgers.shouldInclude( fakeTestCase( "big badger" ) )); - CHECK( matchBadgers.shouldInclude( fakeTestCase( "little badgers" ) ) == false ); - } - TEST_CASE( "selftest/filter/wildcard at both ends", "Individual filters with wildcards at both ends" ) { - Catch::TestCaseFilter matchBadgers( "*badger*" ); - - CHECK( matchBadgers.shouldInclude( fakeTestCase( "big badger" ) )); - CHECK( matchBadgers.shouldInclude( fakeTestCase( "little badgers" ) ) ); - CHECK( matchBadgers.shouldInclude( fakeTestCase( "badgers are big" ) ) ); - CHECK( matchBadgers.shouldInclude( fakeTestCase( "hedgehogs" ) ) == false ); - } - - - template - int getArgc( const char * (&)[size] ) { - return size; - } - - TEST_CASE( "selftest/tags", "[tags]" ) { - - std::string p1 = "[one]"; - std::string p2 = "[one],[two]"; - std::string p3 = "[one][two]"; - std::string p4 = "[one][two],[three]"; - std::string p5 = "[one][two]~[.],[three]"; - - SECTION( "single [one] tag", "" ) { - Catch::TestCase oneTag = makeTestCase( NULL, "", "test", "[one]", CATCH_INTERNAL_LINEINFO ); - - CHECK( oneTag.getTestCaseInfo().description == "" ); - CHECK( oneTag.hasTag( "one" ) ); - CHECK( oneTag.getTags().size() == 1 ); - - CHECK( oneTag.matchesTags( p1 ) == true ); - CHECK( oneTag.matchesTags( p2 ) == true ); - CHECK( oneTag.matchesTags( p3 ) == false ); - CHECK( oneTag.matchesTags( p4 ) == false ); - CHECK( oneTag.matchesTags( p5 ) == false ); - } - - SECTION( "single [two] tag", "" ) { - Catch::TestCase oneTag = makeTestCase( NULL, "", "test", "[two]", CATCH_INTERNAL_LINEINFO ); - - CHECK( oneTag.getTestCaseInfo().description == "" ); - CHECK( oneTag.hasTag( "two" ) ); - CHECK( oneTag.getTags().size() == 1 ); - - CHECK( oneTag.matchesTags( p1 ) == false ); - CHECK( oneTag.matchesTags( p2 ) == true ); - CHECK( oneTag.matchesTags( p3 ) == false ); - CHECK( oneTag.matchesTags( p4 ) == false ); - CHECK( oneTag.matchesTags( p5 ) == false ); - } - - SECTION( "two tags", "" ) { - Catch::TestCase twoTags= makeTestCase( NULL, "", "test", "[one][two]", CATCH_INTERNAL_LINEINFO ); - - CHECK( twoTags.getTestCaseInfo().description == "" ); - CHECK( twoTags.hasTag( "one" ) ); - CHECK( twoTags.hasTag( "two" ) ); - CHECK( twoTags.hasTag( "Two" ) ); - CHECK( twoTags.hasTag( "three" ) == false ); - CHECK( twoTags.getTags().size() == 2 ); - - CHECK( twoTags.matchesTags( p1 ) == true ); - CHECK( twoTags.matchesTags( p2 ) == true ); - CHECK( twoTags.matchesTags( p3 ) == true ); - CHECK( twoTags.matchesTags( p4 ) == true ); - CHECK( twoTags.matchesTags( p5 ) == true ); - } - SECTION( "complex", "" ) { - CHECK( fakeTestCase( "test", "[one][.]" ).matchesTags( p1 ) ); - CHECK_FALSE( fakeTestCase( "test", "[one][.]" ).matchesTags( p5 ) ); - CHECK( fakeTestCase( "test", "[three]" ).matchesTags( p4 ) ); - CHECK( fakeTestCase( "test", "[three]" ).matchesTags( p5 ) ); - CHECK( fakeTestCase( "test", "[three]" ).matchesTags( "[three]~[one]" ) ); - CHECK( fakeTestCase( "test", "[unit][not_apple]" ).matchesTags( "[unit]" ) ); - CHECK_FALSE( fakeTestCase( "test", "[unit][not_apple]" ).matchesTags( "[unit]~[not_apple]" ) ); - } - - SECTION( "one tag with characters either side", "" ) { - - Catch::TestCase oneTagWithExtras = makeTestCase( NULL, "", "test", "12[one]34", CATCH_INTERNAL_LINEINFO ); - CHECK( oneTagWithExtras.getTestCaseInfo().description == "1234" ); - CHECK( oneTagWithExtras.hasTag( "one" ) ); - CHECK( oneTagWithExtras.hasTag( "two" ) == false ); - CHECK( oneTagWithExtras.getTags().size() == 1 ); - } - - SECTION( "start of a tag, but not closed", "" ) { - - Catch::TestCase oneTagOpen = makeTestCase( NULL, "", "test", "[one", CATCH_INTERNAL_LINEINFO ); - - CHECK( oneTagOpen.getTestCaseInfo().description == "[one" ); - CHECK( oneTagOpen.hasTag( "one" ) == false ); - CHECK( oneTagOpen.getTags().size() == 0 ); - } - - SECTION( "hidden", "" ) { - Catch::TestCase oneTag = makeTestCase( NULL, "", "test", "[.]", CATCH_INTERNAL_LINEINFO ); - - CHECK( oneTag.getTestCaseInfo().description == "" ); - CHECK( oneTag.hasTag( "." ) ); - CHECK( oneTag.isHidden() ); - - CHECK( oneTag.matchesTags( "~[.]" ) == false ); - + REQUIRE( config.noThrow == true ); } } - TEST_CASE( "Long strings can be wrapped", "[wrap]" ) { + SECTION( "output filename", "" ) { + SECTION( "-o filename", "" ) { + const char* argv[] = { "test", "-o", "filename.ext" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - using namespace Catch; - SECTION( "plain string", "" ) { - // guide: 123456789012345678 - std::string testString = "one two three four"; - - SECTION( "No wrapping", "" ) { - CHECK( Catch::Text( testString, TextAttributes().setWidth( 80 ) ).toString() == testString ); - CHECK( Catch::Text( testString, TextAttributes().setWidth( 18 ) ).toString() == testString ); - } - SECTION( "Wrapped once", "" ) { - CHECK( Catch::Text( testString, TextAttributes().setWidth( 17 ) ).toString() == "one two three\nfour" ); - CHECK( Catch::Text( testString, TextAttributes().setWidth( 16 ) ).toString() == "one two three\nfour" ); - CHECK( Catch::Text( testString, TextAttributes().setWidth( 14 ) ).toString() == "one two three\nfour" ); - CHECK( Catch::Text( testString, TextAttributes().setWidth( 13 ) ).toString() == "one two three\nfour" ); - CHECK( Catch::Text( testString, TextAttributes().setWidth( 12 ) ).toString() == "one two\nthree four" ); - } - SECTION( "Wrapped twice", "" ) { - CHECK( Catch::Text( testString, TextAttributes().setWidth( 9 ) ).toString() == "one two\nthree\nfour" ); - CHECK( Catch::Text( testString, TextAttributes().setWidth( 8 ) ).toString() == "one two\nthree\nfour" ); - CHECK( Catch::Text( testString, TextAttributes().setWidth( 7 ) ).toString() == "one two\nthree\nfour" ); - } - SECTION( "Wrapped three times", "" ) { - CHECK( Catch::Text( testString, TextAttributes().setWidth( 6 ) ).toString() == "one\ntwo\nthree\nfour" ); - CHECK( Catch::Text( testString, TextAttributes().setWidth( 5 ) ).toString() == "one\ntwo\nthree\nfour" ); - } - SECTION( "Short wrap", "" ) { - CHECK( Catch::Text( "abcdef", TextAttributes().setWidth( 4 ) ).toString() == "abc-\ndef" ); - CHECK( Catch::Text( "abcdefg", TextAttributes().setWidth( 4 ) ).toString() == "abc-\ndefg" ); - CHECK( Catch::Text( "abcdefgh", TextAttributes().setWidth( 4 ) ).toString() == "abc-\ndef-\ngh" ); + REQUIRE( config.outputFilename == "filename.ext" ); + } + SECTION( "--out", "" ) { + const char* argv[] = { "test", "--out", "filename.ext" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - CHECK( Catch::Text( testString, TextAttributes().setWidth( 4 ) ).toString() == "one\ntwo\nthr-\nee\nfour" ); - CHECK( Catch::Text( testString, TextAttributes().setWidth( 3 ) ).toString() == "one\ntwo\nth-\nree\nfo-\nur" ); - } - SECTION( "As container", "" ) { - Catch::Text text( testString, TextAttributes().setWidth( 6 ) ); - REQUIRE( text.size() == 4 ); - CHECK( text[0] == "one" ); - CHECK( text[1] == "two" ); - CHECK( text[2] == "three" ); - CHECK( text[3] == "four" ); - } - SECTION( "Indent first line differently", "" ) { - Catch::Text text( testString, TextAttributes() - .setWidth( 10 ) - .setIndent( 4 ) - .setInitialIndent( 1 ) ); - CHECK( text.toString() == " one two\n three\n four" ); - } - + REQUIRE( config.outputFilename == "filename.ext" ); } - - SECTION( "With newlines", "" ) { - - // guide: 1234567890123456789 - std::string testString = "one two\nthree four"; - - SECTION( "No wrapping" , "" ) { - CHECK( Catch::Text( testString, TextAttributes().setWidth( 80 ) ).toString() == testString ); - CHECK( Catch::Text( testString, TextAttributes().setWidth( 18 ) ).toString() == testString ); - CHECK( Catch::Text( testString, TextAttributes().setWidth( 10 ) ).toString() == testString ); - } - SECTION( "Trailing newline" , "" ) { - CHECK( Catch::Text( "abcdef\n", TextAttributes().setWidth( 10 ) ).toString() == "abcdef\n" ); - CHECK( Catch::Text( "abcdef", TextAttributes().setWidth( 6 ) ).toString() == "abcdef" ); - CHECK( Catch::Text( "abcdef\n", TextAttributes().setWidth( 6 ) ).toString() == "abcdef\n" ); - } - SECTION( "Wrapped once", "" ) { - CHECK( Catch::Text( testString, TextAttributes().setWidth( 9 ) ).toString() == "one two\nthree\nfour" ); - CHECK( Catch::Text( testString, TextAttributes().setWidth( 8 ) ).toString() == "one two\nthree\nfour" ); - CHECK( Catch::Text( testString, TextAttributes().setWidth( 7 ) ).toString() == "one two\nthree\nfour" ); - } - SECTION( "Wrapped twice", "" ) { - CHECK( Catch::Text( testString, TextAttributes().setWidth( 6 ) ).toString() == "one\ntwo\nthree\nfour" ); - } - } - - SECTION( "With tabs", "" ) { - - // guide: 1234567890123456789 - std::string testString = "one two \tthree four five six"; - - CHECK( Catch::Text( testString, TextAttributes().setWidth( 15 ) ).toString() - == "one two three\n four\n five\n six" ); - } - - } - using namespace Catch; + SECTION( "combinations", "" ) { + SECTION( "Single character flags can be combined", "" ) { + const char* argv[] = { "test", "-abe" }; + CHECK_NOTHROW( parseIntoConfig( argv, config ) ); - class ColourString { - public: + CHECK( config.abortAfter == 1 ); + CHECK( config.shouldDebugBreak ); + CHECK( config.noThrow == true ); + } + } +} - struct ColourIndex { - ColourIndex( Colour::Code _colour, std::size_t _fromIndex, std::size_t _toIndex ) - : colour( _colour ), - fromIndex( _fromIndex ), - toIndex( _toIndex ) - {} +TEST_CASE( "selftest/test filter", "Individual filters" ) { - Colour::Code colour; - std::size_t fromIndex; - std::size_t toIndex; - }; + Catch::TestCaseFilter matchAny( "*" ); + Catch::TestCaseFilter matchNone( "*", Catch::IfFilterMatches::ExcludeTests ); + CHECK( matchAny.shouldInclude( fakeTestCase( "any" ) )); + CHECK( matchNone.shouldInclude( fakeTestCase( "any" ) ) == false ); - ColourString( std::string const& _string ) - : string( _string ) - {} - ColourString( std::string const& _string, std::vector const& _colours ) - : string( _string ), colours( _colours ) - {} + Catch::TestCaseFilter matchHidden( "./*" ); + Catch::TestCaseFilter matchNonHidden( "./*", Catch::IfFilterMatches::ExcludeTests ); + + CHECK( matchHidden.shouldInclude( fakeTestCase( "any" ) ) == false ); + CHECK( matchNonHidden.shouldInclude( fakeTestCase( "any" ) ) ); + + CHECK( matchHidden.shouldInclude( fakeTestCase( "./any" ) ) ); + CHECK( matchNonHidden.shouldInclude( fakeTestCase( "./any" ) ) == false ); +} + +TEST_CASE( "selftest/test filters", "Sets of filters" ) { + + Catch::TestCaseFilter matchHidden( "./*" ); + Catch::TestCaseFilter dontMatchA( "./a*", Catch::IfFilterMatches::ExcludeTests ); + Catch::TestCaseFilters filters( "" ); + filters.addFilter( matchHidden ); + filters.addFilter( dontMatchA ); + + CHECK( matchHidden.shouldInclude( fakeTestCase( "./something" ) ) ); + + CHECK( filters.shouldInclude( fakeTestCase( "any" ) ) == false ); + CHECK( filters.shouldInclude( fakeTestCase( "./something" ) ) ); + CHECK( filters.shouldInclude( fakeTestCase( "./anything" ) ) == false ); +} + +TEST_CASE( "selftest/filter/prefix wildcard", "Individual filters with wildcards at the start" ) { + Catch::TestCaseFilter matchBadgers( "*badger" ); + + CHECK( matchBadgers.shouldInclude( fakeTestCase( "big badger" ) )); + CHECK( matchBadgers.shouldInclude( fakeTestCase( "little badgers" ) ) == false ); +} +TEST_CASE( "selftest/filter/wildcard at both ends", "Individual filters with wildcards at both ends" ) { + Catch::TestCaseFilter matchBadgers( "*badger*" ); + + CHECK( matchBadgers.shouldInclude( fakeTestCase( "big badger" ) )); + CHECK( matchBadgers.shouldInclude( fakeTestCase( "little badgers" ) ) ); + CHECK( matchBadgers.shouldInclude( fakeTestCase( "badgers are big" ) ) ); + CHECK( matchBadgers.shouldInclude( fakeTestCase( "hedgehogs" ) ) == false ); +} + + +template +int getArgc( const char * (&)[size] ) { + return size; +} + +TEST_CASE( "selftest/tags", "[tags]" ) { + + std::string p1 = "[one]"; + std::string p2 = "[one],[two]"; + std::string p3 = "[one][two]"; + std::string p4 = "[one][two],[three]"; + std::string p5 = "[one][two]~[.],[three]"; - ColourString& addColour( Colour::Code colour, int _index ) { - colours.push_back( ColourIndex( colour, - resolveRelativeIndex( _index ), - resolveRelativeIndex( _index )+1 ) ); - return *this; - } - ColourString& addColour( Colour::Code colour, int _fromIndex, int _toIndex ) { - colours.push_back( ColourIndex( colour, - resolveRelativeIndex(_fromIndex), - resolveLastRelativeIndex( _toIndex ) ) ); - return *this; - } - - void writeToStream( std::ostream& _stream ) const { - std::size_t last = 0; - for( std::size_t i = 0; i < colours.size(); ++i ) { - ColourIndex const& index = colours[i]; - if( index.fromIndex > last ) - _stream << string.substr( last, index.fromIndex-last ); - { - Colour colourGuard( index.colour ); - _stream << string.substr( index.fromIndex, index.toIndex-index.fromIndex ); - } - last = index.toIndex; - } - if( last < string.size() ) - _stream << string.substr( last ); - } - friend std::ostream& operator << ( std::ostream& _stream, ColourString const& _colourString ) { - _colourString.writeToStream( _stream ); - return _stream; - } + SECTION( "single [one] tag", "" ) { + Catch::TestCase oneTag = makeTestCase( NULL, "", "test", "[one]", CATCH_INTERNAL_LINEINFO ); - private: + CHECK( oneTag.getTestCaseInfo().description == "" ); + CHECK( oneTag.hasTag( "one" ) ); + CHECK( oneTag.getTags().size() == 1 ); - std::size_t resolveLastRelativeIndex( int _index ) { - std::size_t index = resolveRelativeIndex( _index ); - return index == 0 ? string.size() : index; - } - std::size_t resolveRelativeIndex( int _index ) { - return static_cast( _index >= 0 - ? _index - : static_cast( string.size() )+_index ); - } - std::string string; - std::vector colours; - }; - - // !TBD: This will be folded into Text class - TEST_CASE( "Strings can be rendered with colour", "[colour]" ) { - - { - ColourString cs( "hello" ); - cs .addColour( Colour::Red, 0 ) - .addColour( Colour::Green, -1 ); - - std::cout << cs << std::endl; - } - - { - ColourString cs( "hello" ); - cs .addColour( Colour::Blue, 1, -2 ); - - std::cout << cs << std::endl; - } - + CHECK( oneTag.matchesTags( p1 ) == true ); + CHECK( oneTag.matchesTags( p2 ) == true ); + CHECK( oneTag.matchesTags( p3 ) == false ); + CHECK( oneTag.matchesTags( p4 ) == false ); + CHECK( oneTag.matchesTags( p5 ) == false ); } - TEST_CASE( "Text can be formatted using the Text class", "" ) { - - CHECK( Catch::Text( "hi there" ).toString() == "hi there" ); - - TextAttributes narrow; - narrow.setWidth( 6 ); - - CHECK( Catch::Text( "hi there", narrow ).toString() == "hi\nthere" ); + SECTION( "single [two] tag", "" ) { + Catch::TestCase oneTag = makeTestCase( NULL, "", "test", "[two]", CATCH_INTERNAL_LINEINFO ); + + CHECK( oneTag.getTestCaseInfo().description == "" ); + CHECK( oneTag.hasTag( "two" ) ); + CHECK( oneTag.getTags().size() == 1 ); + + CHECK( oneTag.matchesTags( p1 ) == false ); + CHECK( oneTag.matchesTags( p2 ) == true ); + CHECK( oneTag.matchesTags( p3 ) == false ); + CHECK( oneTag.matchesTags( p4 ) == false ); + CHECK( oneTag.matchesTags( p5 ) == false ); } - TEST_CASE( "Long text is truncted", "[Text][Truncated]" ) { + SECTION( "two tags", "" ) { + Catch::TestCase twoTags= makeTestCase( NULL, "", "test", "[one][two]", CATCH_INTERNAL_LINEINFO ); - std::string longLine( 90, '*' ); + CHECK( twoTags.getTestCaseInfo().description == "" ); + CHECK( twoTags.hasTag( "one" ) ); + CHECK( twoTags.hasTag( "two" ) ); + CHECK( twoTags.hasTag( "Two" ) ); + CHECK( twoTags.hasTag( "three" ) == false ); + CHECK( twoTags.getTags().size() == 2 ); - std::ostringstream oss; - for(int i = 0; i < 600; ++i ) - oss << longLine << longLine << "\n"; - Catch::Text t( oss.str() ); - CHECK_THAT( t.toString(), EndsWith( "... message truncated due to excessive size" ) ); + CHECK( twoTags.matchesTags( p1 ) == true ); + CHECK( twoTags.matchesTags( p2 ) == true ); + CHECK( twoTags.matchesTags( p3 ) == true ); + CHECK( twoTags.matchesTags( p4 ) == true ); + CHECK( twoTags.matchesTags( p5 ) == true ); + } + SECTION( "complex", "" ) { + CHECK( fakeTestCase( "test", "[one][.]" ).matchesTags( p1 ) ); + CHECK_FALSE( fakeTestCase( "test", "[one][.]" ).matchesTags( p5 ) ); + CHECK( fakeTestCase( "test", "[three]" ).matchesTags( p4 ) ); + CHECK( fakeTestCase( "test", "[three]" ).matchesTags( p5 ) ); + CHECK( fakeTestCase( "test", "[three]" ).matchesTags( "[three]~[one]" ) ); + CHECK( fakeTestCase( "test", "[unit][not_apple]" ).matchesTags( "[unit]" ) ); + CHECK_FALSE( fakeTestCase( "test", "[unit][not_apple]" ).matchesTags( "[unit]~[not_apple]" ) ); + } + + SECTION( "one tag with characters either side", "" ) { + + Catch::TestCase oneTagWithExtras = makeTestCase( NULL, "", "test", "12[one]34", CATCH_INTERNAL_LINEINFO ); + CHECK( oneTagWithExtras.getTestCaseInfo().description == "1234" ); + CHECK( oneTagWithExtras.hasTag( "one" ) ); + CHECK( oneTagWithExtras.hasTag( "two" ) == false ); + CHECK( oneTagWithExtras.getTags().size() == 1 ); + } + SECTION( "start of a tag, but not closed", "" ) { + + Catch::TestCase oneTagOpen = makeTestCase( NULL, "", "test", "[one", CATCH_INTERNAL_LINEINFO ); + + CHECK( oneTagOpen.getTestCaseInfo().description == "[one" ); + CHECK( oneTagOpen.hasTag( "one" ) == false ); + CHECK( oneTagOpen.getTags().size() == 0 ); + } + + SECTION( "hidden", "" ) { + Catch::TestCase oneTag = makeTestCase( NULL, "", "test", "[.]", CATCH_INTERNAL_LINEINFO ); + + CHECK( oneTag.getTestCaseInfo().description == "" ); + CHECK( oneTag.hasTag( "." ) ); + CHECK( oneTag.isHidden() ); + + CHECK( oneTag.matchesTags( "~[.]" ) == false ); + } } + +TEST_CASE( "Long strings can be wrapped", "[wrap]" ) { + + using namespace Catch; + SECTION( "plain string", "" ) { + // guide: 123456789012345678 + std::string testString = "one two three four"; + + SECTION( "No wrapping", "" ) { + CHECK( Text( testString, TextAttributes().setWidth( 80 ) ).toString() == testString ); + CHECK( Text( testString, TextAttributes().setWidth( 18 ) ).toString() == testString ); + } + SECTION( "Wrapped once", "" ) { + CHECK( Text( testString, TextAttributes().setWidth( 17 ) ).toString() == "one two three\nfour" ); + CHECK( Text( testString, TextAttributes().setWidth( 16 ) ).toString() == "one two three\nfour" ); + CHECK( Text( testString, TextAttributes().setWidth( 14 ) ).toString() == "one two three\nfour" ); + CHECK( Text( testString, TextAttributes().setWidth( 13 ) ).toString() == "one two three\nfour" ); + CHECK( Text( testString, TextAttributes().setWidth( 12 ) ).toString() == "one two\nthree four" ); + } + SECTION( "Wrapped twice", "" ) { + CHECK( Text( testString, TextAttributes().setWidth( 9 ) ).toString() == "one two\nthree\nfour" ); + CHECK( Text( testString, TextAttributes().setWidth( 8 ) ).toString() == "one two\nthree\nfour" ); + CHECK( Text( testString, TextAttributes().setWidth( 7 ) ).toString() == "one two\nthree\nfour" ); + } + SECTION( "Wrapped three times", "" ) { + CHECK( Text( testString, TextAttributes().setWidth( 6 ) ).toString() == "one\ntwo\nthree\nfour" ); + CHECK( Text( testString, TextAttributes().setWidth( 5 ) ).toString() == "one\ntwo\nthree\nfour" ); + } + SECTION( "Short wrap", "" ) { + CHECK( Text( "abcdef", TextAttributes().setWidth( 4 ) ).toString() == "abc-\ndef" ); + CHECK( Text( "abcdefg", TextAttributes().setWidth( 4 ) ).toString() == "abc-\ndefg" ); + CHECK( Text( "abcdefgh", TextAttributes().setWidth( 4 ) ).toString() == "abc-\ndef-\ngh" ); + + CHECK( Text( testString, TextAttributes().setWidth( 4 ) ).toString() == "one\ntwo\nthr-\nee\nfour" ); + CHECK( Text( testString, TextAttributes().setWidth( 3 ) ).toString() == "one\ntwo\nth-\nree\nfo-\nur" ); + } + SECTION( "As container", "" ) { + Text text( testString, TextAttributes().setWidth( 6 ) ); + REQUIRE( text.size() == 4 ); + CHECK( text[0] == "one" ); + CHECK( text[1] == "two" ); + CHECK( text[2] == "three" ); + CHECK( text[3] == "four" ); + } + SECTION( "Indent first line differently", "" ) { + Text text( testString, TextAttributes() + .setWidth( 10 ) + .setIndent( 4 ) + .setInitialIndent( 1 ) ); + CHECK( text.toString() == " one two\n three\n four" ); + } + + } + + SECTION( "With newlines", "" ) { + + // guide: 1234567890123456789 + std::string testString = "one two\nthree four"; + + SECTION( "No wrapping" , "" ) { + CHECK( Text( testString, TextAttributes().setWidth( 80 ) ).toString() == testString ); + CHECK( Text( testString, TextAttributes().setWidth( 18 ) ).toString() == testString ); + CHECK( Text( testString, TextAttributes().setWidth( 10 ) ).toString() == testString ); + } + SECTION( "Trailing newline" , "" ) { + CHECK( Text( "abcdef\n", TextAttributes().setWidth( 10 ) ).toString() == "abcdef\n" ); + CHECK( Text( "abcdef", TextAttributes().setWidth( 6 ) ).toString() == "abcdef" ); + CHECK( Text( "abcdef\n", TextAttributes().setWidth( 6 ) ).toString() == "abcdef\n" ); + } + SECTION( "Wrapped once", "" ) { + CHECK( Text( testString, TextAttributes().setWidth( 9 ) ).toString() == "one two\nthree\nfour" ); + CHECK( Text( testString, TextAttributes().setWidth( 8 ) ).toString() == "one two\nthree\nfour" ); + CHECK( Text( testString, TextAttributes().setWidth( 7 ) ).toString() == "one two\nthree\nfour" ); + } + SECTION( "Wrapped twice", "" ) { + CHECK( Text( testString, TextAttributes().setWidth( 6 ) ).toString() == "one\ntwo\nthree\nfour" ); + } + } + + SECTION( "With tabs", "" ) { + + // guide: 1234567890123456789 + std::string testString = "one two \tthree four five six"; + + CHECK( Text( testString, TextAttributes().setWidth( 15 ) ).toString() + == "one two three\n four\n five\n six" ); + } + + +} + +using namespace Catch; + +class ColourString { +public: + + struct ColourIndex { + ColourIndex( Colour::Code _colour, std::size_t _fromIndex, std::size_t _toIndex ) + : colour( _colour ), + fromIndex( _fromIndex ), + toIndex( _toIndex ) + {} + + Colour::Code colour; + std::size_t fromIndex; + std::size_t toIndex; + }; + + ColourString( std::string const& _string ) + : string( _string ) + {} + ColourString( std::string const& _string, std::vector const& _colours ) + : string( _string ), colours( _colours ) + {} + + ColourString& addColour( Colour::Code colour, int _index ) { + colours.push_back( ColourIndex( colour, + resolveRelativeIndex( _index ), + resolveRelativeIndex( _index )+1 ) ); + return *this; + } + ColourString& addColour( Colour::Code colour, int _fromIndex, int _toIndex ) { + colours.push_back( ColourIndex( colour, + resolveRelativeIndex(_fromIndex), + resolveLastRelativeIndex( _toIndex ) ) ); + return *this; + } + + void writeToStream( std::ostream& _stream ) const { + std::size_t last = 0; + for( std::size_t i = 0; i < colours.size(); ++i ) { + ColourIndex const& index = colours[i]; + if( index.fromIndex > last ) + _stream << string.substr( last, index.fromIndex-last ); + { + Colour colourGuard( index.colour ); + _stream << string.substr( index.fromIndex, index.toIndex-index.fromIndex ); + } + last = index.toIndex; + } + if( last < string.size() ) + _stream << string.substr( last ); + } + friend std::ostream& operator << ( std::ostream& _stream, ColourString const& _colourString ) { + _colourString.writeToStream( _stream ); + return _stream; + } + +private: + + std::size_t resolveLastRelativeIndex( int _index ) { + std::size_t index = resolveRelativeIndex( _index ); + return index == 0 ? string.size() : index; + } + std::size_t resolveRelativeIndex( int _index ) { + return static_cast( _index >= 0 + ? _index + : static_cast( string.size() )+_index ); + } + std::string string; + std::vector colours; +}; + +// !TBD: This will be folded into Text class +TEST_CASE( "Strings can be rendered with colour", "[colour]" ) { + + { + ColourString cs( "hello" ); + cs .addColour( Colour::Red, 0 ) + .addColour( Colour::Green, -1 ); + + std::cout << cs << std::endl; + } + + { + ColourString cs( "hello" ); + cs .addColour( Colour::Blue, 1, -2 ); + + std::cout << cs << std::endl; + } + +} + +TEST_CASE( "Text can be formatted using the Text class", "" ) { + + CHECK( Text( "hi there" ).toString() == "hi there" ); + + TextAttributes narrow; + narrow.setWidth( 6 ); + + CHECK( Text( "hi there", narrow ).toString() == "hi\nthere" ); +} + +TEST_CASE( "Long text is truncted", "[Text][Truncated]" ) { + + std::string longLine( 90, '*' ); + + std::ostringstream oss; + for(int i = 0; i < 600; ++i ) + oss << longLine << longLine << "\n"; + Text t( oss.str() ); + CHECK_THAT( t.toString(), EndsWith( "... message truncated due to excessive size" ) ); + +} diff --git a/projects/SelfTest/catch_self_test.cpp b/projects/SelfTest/catch_self_test.cpp deleted file mode 100644 index 4dce6fda..00000000 --- a/projects/SelfTest/catch_self_test.cpp +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Created by Phil on 14/02/2012. - * Copyright 2012 Two Blue Cubes Ltd. All rights reserved. - * - * Distributed under the Boost Software License, Version 1.0. (See accompanying - * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) - */ - -#if !defined(_WINDLL) -#define CATCH_CONFIG_MAIN -#endif -#include "catch_self_test.hpp" - -namespace Catch{ - - NullStreamingReporter::~NullStreamingReporter() {} - - Totals EmbeddedRunner::runMatching( const std::string& rawTestSpec, std::size_t groupIndex, std::size_t groupsCount, const std::string& ) { - std::ostringstream oss; - Ptr config = new Config(); - config->setStreamBuf( oss.rdbuf() ); - - Totals totals; - - // Scoped because RunContext doesn't report EndTesting until its destructor - { - RunContext runner( config.get(), m_reporter.get() ); - totals = runner.runMatching( rawTestSpec, groupIndex, groupsCount ); - } - return totals; - } - -} diff --git a/projects/SelfTest/catch_self_test.hpp b/projects/SelfTest/catch_self_test.hpp deleted file mode 100644 index 1629644d..00000000 --- a/projects/SelfTest/catch_self_test.hpp +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Created by Phil on 14/01/2011. - * Copyright 2011 Two Blue Cubes Ltd. All rights reserved. - * - * Distributed under the Boost Software License, Version 1.0. (See accompanying - * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) - */ -#ifndef TWOBLUECUBES_CATCH_SELF_TEST_HPP_INCLUDED -#define TWOBLUECUBES_CATCH_SELF_TEST_HPP_INCLUDED - -#include "catch.hpp" - -// Use this external guard here as if we're using the single header version -// this will already be defined -#ifndef TWOBLUECUBES_CATCH_INTERFACES_REGISTRY_HUB_H_INCLUDED -#include "catch_interfaces_registry_hub.h" -#endif - -#include "set" - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpadded" -#endif - -namespace Catch { - - class NullStreamingReporter : public SharedImpl { - public: - - virtual ~NullStreamingReporter(); - - static std::string getDescription() { - return "null reporter"; - } - - private: // IStreamingReporter - - virtual ReporterPreferences getPreferences() const { - return ReporterPreferences(); - } - - virtual void noMatchingTestCases( std::string const& ) {} - virtual void testRunStarting( TestRunInfo const& ) {} - virtual void testGroupStarting( GroupInfo const& ) {} - virtual void testCaseStarting( TestCaseInfo const& ) {} - virtual void sectionStarting( SectionInfo const& ) {} - virtual void assertionStarting( AssertionInfo const& ) {} - virtual bool assertionEnded( AssertionStats const& ) { return false; } - virtual void sectionEnded( SectionStats const& ) {} - virtual void testCaseEnded( TestCaseStats const& ) {} - virtual void testGroupEnded( TestGroupStats const& ) {} - virtual void testRunEnded( TestRunStats const& ) {} - }; - - class EmbeddedRunner { - - public: - EmbeddedRunner() : m_reporter( new NullStreamingReporter() ) {} - - Totals runMatching( const std::string& rawTestSpec, - std::size_t groupIndex, - std::size_t groupsCount, - const std::string& reporter = "console" ); - - private: - Ptr m_reporter; - }; - - class MetaTestRunner { - - public: - struct Expected { enum Result { - ToSucceed, - ToFail - }; }; - - MetaTestRunner( Expected::Result expectedResult, std::size_t groupIndex, std::size_t groupsCount ) - : m_expectedResult( expectedResult ), - m_groupIndex( groupIndex ), - m_groupsCount( groupsCount ) - {} - - static void runMatching( const std::string& testSpec, - Expected::Result expectedResult, - std::size_t groupIndex, - std::size_t groupsCount ) { - forEach( getRegistryHub().getTestCaseRegistry().getMatchingTestCases( testSpec ), - MetaTestRunner( expectedResult, groupIndex, groupsCount ) ); - } - - void operator()( const TestCase& testCase ) { - std::string name; - Totals totals; - { - EmbeddedRunner runner; - name = testCase.getTestCaseInfo().name; - totals = runner.runMatching( name, m_groupIndex, m_groupsCount ); - } - switch( m_expectedResult ) { - case Expected::ToSucceed: - if( totals.assertions.failed > 0 ) { - FAIL( "Expected test case '" - << name - << "' to succeed but there was/ were " - << totals.assertions.failed << " failure(s)" ); - } - else { - SUCCEED( "Tests passed, as expected" ); - } - break; - case Expected::ToFail: - if( totals.assertions.failed == 0 ) { - FAIL( "Expected test case '" - << name - << "' to fail but there was/ were " - << totals.assertions.passed << " success(es)" ); - } - else { - SUCCEED( "Tests failed, as expected" ); - } - break; - } - } - - private: - Expected::Result m_expectedResult; - std::size_t m_groupIndex; - std::size_t m_groupsCount; - }; - - - struct LineInfoRegistry { - - static LineInfoRegistry& get() { - static LineInfoRegistry s_instance; - return s_instance; - } - - void registerLineInfo( const std::string& name, - const SourceLineInfo& info ) { - m_registry.insert( std::make_pair( name, info ) ); - } - - const SourceLineInfo* find( const std::string& name ) const { - std::map::const_iterator it = m_registry.find( name ); - return it == m_registry.end() ? NULL : &(it->second); - } - - const std::string infoForName( const std::string& name ) const { - std::map::const_iterator it = m_registry.find( name ); - if( it == m_registry.end() ) - return ""; - std::ostringstream oss; - oss << it->second; - return oss.str(); - } - - std::map m_registry; - }; - - struct LineInfoRegistrar { - LineInfoRegistrar( const char* name, const SourceLineInfo& lineInfo ) { - LineInfoRegistry::get().registerLineInfo( name, lineInfo ); - } - }; - -} - -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -#define CATCH_REGISTER_LINE_INFO( name ) ::Catch::LineInfoRegistrar INTERNAL_CATCH_UNIQUE_NAME( lineRegistrar )( name, ::Catch::SourceLineInfo( __FILE__, __LINE__ ) ); -#define CATCH_GET_LINE_INFO( name ) ::Catch::LineInfoRegistry::get().infoForName( name ) - -#endif // TWOBLUECUBES_CATCH_SELF_TEST_HPP_INCLUDED