1 /* 2 * Created by Phil on 14/08/2012. 3 * Copyright 2012 Two Blue Cubes Ltd. All rights reserved. 4 * 5 * Distributed under the Boost Software License, Version 1.0. (See accompanying 6 * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 7 */ 8 9 #include "catch_test_case_info.h" 10 #include "catch_enforce.h" 11 #include "catch_test_spec.h" 12 #include "catch_interfaces_testcase.h" 13 #include "catch_string_manip.h" 14 15 #include <cctype> 16 #include <exception> 17 #include <algorithm> 18 #include <sstream> 19 20 namespace Catch { 21 22 namespace { parseSpecialTag(std::string const & tag)23 TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { 24 if( startsWith( tag, '.' ) || 25 tag == "!hide" ) 26 return TestCaseInfo::IsHidden; 27 else if( tag == "!throws" ) 28 return TestCaseInfo::Throws; 29 else if( tag == "!shouldfail" ) 30 return TestCaseInfo::ShouldFail; 31 else if( tag == "!mayfail" ) 32 return TestCaseInfo::MayFail; 33 else if( tag == "!nonportable" ) 34 return TestCaseInfo::NonPortable; 35 else if( tag == "!benchmark" ) 36 return static_cast<TestCaseInfo::SpecialProperties>( TestCaseInfo::Benchmark | TestCaseInfo::IsHidden ); 37 else 38 return TestCaseInfo::None; 39 } isReservedTag(std::string const & tag)40 bool isReservedTag( std::string const& tag ) { 41 return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( static_cast<unsigned char>(tag[0]) ); 42 } enforceNotReservedTag(std::string const & tag,SourceLineInfo const & _lineInfo)43 void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { 44 CATCH_ENFORCE( !isReservedTag(tag), 45 "Tag name: [" << tag << "] is not allowed.\n" 46 << "Tag names starting with non alphanumeric characters are reserved\n" 47 << _lineInfo ); 48 } 49 } 50 makeTestCase(ITestInvoker * _testCase,std::string const & _className,NameAndTags const & nameAndTags,SourceLineInfo const & _lineInfo)51 TestCase makeTestCase( ITestInvoker* _testCase, 52 std::string const& _className, 53 NameAndTags const& nameAndTags, 54 SourceLineInfo const& _lineInfo ) 55 { 56 bool isHidden = false; 57 58 // Parse out tags 59 std::vector<std::string> tags; 60 std::string desc, tag; 61 bool inTag = false; 62 for (char c : nameAndTags.tags) { 63 if( !inTag ) { 64 if( c == '[' ) 65 inTag = true; 66 else 67 desc += c; 68 } 69 else { 70 if( c == ']' ) { 71 TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); 72 if( ( prop & TestCaseInfo::IsHidden ) != 0 ) 73 isHidden = true; 74 else if( prop == TestCaseInfo::None ) 75 enforceNotReservedTag( tag, _lineInfo ); 76 77 // Merged hide tags like `[.approvals]` should be added as 78 // `[.][approvals]`. The `[.]` is added at later point, so 79 // we only strip the prefix 80 if (startsWith(tag, '.') && tag.size() > 1) { 81 tag.erase(0, 1); 82 } 83 tags.push_back( tag ); 84 tag.clear(); 85 inTag = false; 86 } 87 else 88 tag += c; 89 } 90 } 91 if( isHidden ) { 92 // Add all "hidden" tags to make them behave identically 93 tags.insert( tags.end(), { ".", "!hide" } ); 94 } 95 96 TestCaseInfo info( static_cast<std::string>(nameAndTags.name), _className, desc, tags, _lineInfo ); 97 return TestCase( _testCase, std::move(info) ); 98 } 99 setTags(TestCaseInfo & testCaseInfo,std::vector<std::string> tags)100 void setTags( TestCaseInfo& testCaseInfo, std::vector<std::string> tags ) { 101 std::sort(begin(tags), end(tags)); 102 tags.erase(std::unique(begin(tags), end(tags)), end(tags)); 103 testCaseInfo.lcaseTags.clear(); 104 105 for( auto const& tag : tags ) { 106 std::string lcaseTag = toLower( tag ); 107 testCaseInfo.properties = static_cast<TestCaseInfo::SpecialProperties>( testCaseInfo.properties | parseSpecialTag( lcaseTag ) ); 108 testCaseInfo.lcaseTags.push_back( lcaseTag ); 109 } 110 testCaseInfo.tags = std::move(tags); 111 } 112 TestCaseInfo(std::string const & _name,std::string const & _className,std::string const & _description,std::vector<std::string> const & _tags,SourceLineInfo const & _lineInfo)113 TestCaseInfo::TestCaseInfo( std::string const& _name, 114 std::string const& _className, 115 std::string const& _description, 116 std::vector<std::string> const& _tags, 117 SourceLineInfo const& _lineInfo ) 118 : name( _name ), 119 className( _className ), 120 description( _description ), 121 lineInfo( _lineInfo ), 122 properties( None ) 123 { 124 setTags( *this, _tags ); 125 } 126 isHidden() const127 bool TestCaseInfo::isHidden() const { 128 return ( properties & IsHidden ) != 0; 129 } throws() const130 bool TestCaseInfo::throws() const { 131 return ( properties & Throws ) != 0; 132 } okToFail() const133 bool TestCaseInfo::okToFail() const { 134 return ( properties & (ShouldFail | MayFail ) ) != 0; 135 } expectedToFail() const136 bool TestCaseInfo::expectedToFail() const { 137 return ( properties & (ShouldFail ) ) != 0; 138 } 139 tagsAsString() const140 std::string TestCaseInfo::tagsAsString() const { 141 std::string ret; 142 // '[' and ']' per tag 143 std::size_t full_size = 2 * tags.size(); 144 for (const auto& tag : tags) { 145 full_size += tag.size(); 146 } 147 ret.reserve(full_size); 148 for (const auto& tag : tags) { 149 ret.push_back('['); 150 ret.append(tag); 151 ret.push_back(']'); 152 } 153 154 return ret; 155 } 156 157 TestCase(ITestInvoker * testCase,TestCaseInfo && info)158 TestCase::TestCase( ITestInvoker* testCase, TestCaseInfo&& info ) : TestCaseInfo( std::move(info) ), test( testCase ) {} 159 160 withName(std::string const & _newName) const161 TestCase TestCase::withName( std::string const& _newName ) const { 162 TestCase other( *this ); 163 other.name = _newName; 164 return other; 165 } 166 invoke() const167 void TestCase::invoke() const { 168 test->invoke(); 169 } 170 operator ==(TestCase const & other) const171 bool TestCase::operator == ( TestCase const& other ) const { 172 return test.get() == other.test.get() && 173 name == other.name && 174 className == other.className; 175 } 176 operator <(TestCase const & other) const177 bool TestCase::operator < ( TestCase const& other ) const { 178 return name < other.name; 179 } 180 getTestCaseInfo() const181 TestCaseInfo const& TestCase::getTestCaseInfo() const 182 { 183 return *this; 184 } 185 186 } // end namespace Catch 187