/ externals / catch / src / catch2 / catch_test_case_info.cpp
catch_test_case_info.cpp
  1  
  2  //              Copyright Catch2 Authors
  3  // Distributed under the Boost Software License, Version 1.0.
  4  //   (See accompanying file LICENSE.txt or copy at
  5  //        https://www.boost.org/LICENSE_1_0.txt)
  6  
  7  // SPDX-License-Identifier: BSL-1.0
  8  #include <catch2/catch_test_case_info.hpp>
  9  #include <catch2/internal/catch_enforce.hpp>
 10  #include <catch2/internal/catch_string_manip.hpp>
 11  #include <catch2/internal/catch_case_insensitive_comparisons.hpp>
 12  
 13  #include <cassert>
 14  #include <cctype>
 15  #include <algorithm>
 16  
 17  namespace Catch {
 18  
 19      namespace {
 20          using TCP_underlying_type = uint8_t;
 21          static_assert(sizeof(TestCaseProperties) == sizeof(TCP_underlying_type),
 22                        "The size of the TestCaseProperties is different from the assumed size");
 23  
 24          TestCaseProperties operator|(TestCaseProperties lhs, TestCaseProperties rhs) {
 25              return static_cast<TestCaseProperties>(
 26                  static_cast<TCP_underlying_type>(lhs) | static_cast<TCP_underlying_type>(rhs)
 27              );
 28          }
 29  
 30          TestCaseProperties& operator|=(TestCaseProperties& lhs, TestCaseProperties rhs) {
 31              lhs = static_cast<TestCaseProperties>(
 32                  static_cast<TCP_underlying_type>(lhs) | static_cast<TCP_underlying_type>(rhs)
 33              );
 34              return lhs;
 35          }
 36  
 37          TestCaseProperties operator&(TestCaseProperties lhs, TestCaseProperties rhs) {
 38              return static_cast<TestCaseProperties>(
 39                  static_cast<TCP_underlying_type>(lhs) & static_cast<TCP_underlying_type>(rhs)
 40              );
 41          }
 42  
 43          bool applies(TestCaseProperties tcp) {
 44              static_assert(static_cast<TCP_underlying_type>(TestCaseProperties::None) == 0,
 45                            "TestCaseProperties::None must be equal to 0");
 46              return tcp != TestCaseProperties::None;
 47          }
 48  
 49          TestCaseProperties parseSpecialTag( StringRef tag ) {
 50              if( !tag.empty() && tag[0] == '.' )
 51                  return TestCaseProperties::IsHidden;
 52              else if( tag == "!throws"_sr )
 53                  return TestCaseProperties::Throws;
 54              else if( tag == "!shouldfail"_sr )
 55                  return TestCaseProperties::ShouldFail;
 56              else if( tag == "!mayfail"_sr )
 57                  return TestCaseProperties::MayFail;
 58              else if( tag == "!nonportable"_sr )
 59                  return TestCaseProperties::NonPortable;
 60              else if( tag == "!benchmark"_sr )
 61                  return TestCaseProperties::Benchmark | TestCaseProperties::IsHidden;
 62              else
 63                  return TestCaseProperties::None;
 64          }
 65          bool isReservedTag( StringRef tag ) {
 66              return parseSpecialTag( tag ) == TestCaseProperties::None
 67                  && tag.size() > 0
 68                  && !std::isalnum( static_cast<unsigned char>(tag[0]) );
 69          }
 70          void enforceNotReservedTag( StringRef tag, SourceLineInfo const& _lineInfo ) {
 71              CATCH_ENFORCE( !isReservedTag(tag),
 72                            "Tag name: [" << tag << "] is not allowed.\n"
 73                            << "Tag names starting with non alphanumeric characters are reserved\n"
 74                            << _lineInfo );
 75          }
 76  
 77          std::string makeDefaultName() {
 78              static size_t counter = 0;
 79              return "Anonymous test case " + std::to_string(++counter);
 80          }
 81  
 82          StringRef extractFilenamePart(StringRef filename) {
 83              size_t lastDot = filename.size();
 84              while (lastDot > 0 && filename[lastDot - 1] != '.') {
 85                  --lastDot;
 86              }
 87              --lastDot;
 88  
 89              size_t nameStart = lastDot;
 90              while (nameStart > 0 && filename[nameStart - 1] != '/' && filename[nameStart - 1] != '\\') {
 91                  --nameStart;
 92              }
 93  
 94              return filename.substr(nameStart, lastDot - nameStart);
 95          }
 96  
 97          // Returns the upper bound on size of extra tags ([#file]+[.])
 98          size_t sizeOfExtraTags(StringRef filepath) {
 99              // [.] is 3, [#] is another 3
100              const size_t extras = 3 + 3;
101              return extractFilenamePart(filepath).size() + extras;
102          }
103      } // end unnamed namespace
104  
105      bool operator<(  Tag const& lhs, Tag const& rhs ) {
106          Detail::CaseInsensitiveLess cmp;
107          return cmp( lhs.original, rhs.original );
108      }
109      bool operator==( Tag const& lhs, Tag const& rhs ) {
110          Detail::CaseInsensitiveEqualTo cmp;
111          return cmp( lhs.original, rhs.original );
112      }
113  
114      Detail::unique_ptr<TestCaseInfo>
115          makeTestCaseInfo(StringRef _className,
116                           NameAndTags const& nameAndTags,
117                           SourceLineInfo const& _lineInfo ) {
118          return Detail::make_unique<TestCaseInfo>(_className, nameAndTags, _lineInfo);
119      }
120  
121      TestCaseInfo::TestCaseInfo(StringRef _className,
122                                 NameAndTags const& _nameAndTags,
123                                 SourceLineInfo const& _lineInfo):
124          name( _nameAndTags.name.empty() ? makeDefaultName() : _nameAndTags.name ),
125          className( _className ),
126          lineInfo( _lineInfo )
127      {
128          StringRef originalTags = _nameAndTags.tags;
129          // We need to reserve enough space to store all of the tags
130          // (including optional hidden tag and filename tag)
131          auto requiredSize = originalTags.size() + sizeOfExtraTags(_lineInfo.file);
132          backingTags.reserve(requiredSize);
133  
134          // We cannot copy the tags directly, as we need to normalize
135          // some tags, so that [.foo] is copied as [.][foo].
136          size_t tagStart = 0;
137          size_t tagEnd = 0;
138          bool inTag = false;
139          for (size_t idx = 0; idx < originalTags.size(); ++idx) {
140              auto c = originalTags[idx];
141              if (c == '[') {
142                  CATCH_ENFORCE(
143                      !inTag,
144                      "Found '[' inside a tag while registering test case '"
145                          << _nameAndTags.name << "' at " << _lineInfo );
146  
147                  inTag = true;
148                  tagStart = idx;
149              }
150              if (c == ']') {
151                  CATCH_ENFORCE(
152                      inTag,
153                      "Found unmatched ']' while registering test case '"
154                          << _nameAndTags.name << "' at " << _lineInfo );
155  
156                  inTag = false;
157                  tagEnd = idx;
158                  assert(tagStart < tagEnd);
159  
160                  // We need to check the tag for special meanings, copy
161                  // it over to backing storage and actually reference the
162                  // backing storage in the saved tags
163                  StringRef tagStr = originalTags.substr(tagStart+1, tagEnd - tagStart - 1);
164                  CATCH_ENFORCE( !tagStr.empty(),
165                                 "Found an empty tag while registering test case '"
166                                     << _nameAndTags.name << "' at "
167                                     << _lineInfo );
168  
169                  enforceNotReservedTag(tagStr, lineInfo);
170                  properties |= parseSpecialTag(tagStr);
171                  // When copying a tag to the backing storage, we need to
172                  // check if it is a merged hide tag, such as [.foo], and
173                  // if it is, we need to handle it as if it was [foo].
174                  if (tagStr.size() > 1 && tagStr[0] == '.') {
175                      tagStr = tagStr.substr(1, tagStr.size() - 1);
176                  }
177                  // We skip over dealing with the [.] tag, as we will add
178                  // it later unconditionally and then sort and unique all
179                  // the tags.
180                  internalAppendTag(tagStr);
181              }
182          }
183          CATCH_ENFORCE( !inTag,
184                         "Found an unclosed tag while registering test case '"
185                             << _nameAndTags.name << "' at " << _lineInfo );
186  
187  
188          // Add [.] if relevant
189          if (isHidden()) {
190              internalAppendTag("."_sr);
191          }
192  
193          // Sort and prepare tags
194          std::sort(begin(tags), end(tags));
195          tags.erase(std::unique(begin(tags), end(tags)),
196                     end(tags));
197      }
198  
199      bool TestCaseInfo::isHidden() const {
200          return applies( properties & TestCaseProperties::IsHidden );
201      }
202      bool TestCaseInfo::throws() const {
203          return applies( properties & TestCaseProperties::Throws );
204      }
205      bool TestCaseInfo::okToFail() const {
206          return applies( properties & (TestCaseProperties::ShouldFail | TestCaseProperties::MayFail ) );
207      }
208      bool TestCaseInfo::expectedToFail() const {
209          return applies( properties & (TestCaseProperties::ShouldFail) );
210      }
211  
212      void TestCaseInfo::addFilenameTag() {
213          std::string combined("#");
214          combined += extractFilenamePart(lineInfo.file);
215          internalAppendTag(combined);
216      }
217  
218      std::string TestCaseInfo::tagsAsString() const {
219          std::string ret;
220          // '[' and ']' per tag
221          std::size_t full_size = 2 * tags.size();
222          for (const auto& tag : tags) {
223              full_size += tag.original.size();
224          }
225          ret.reserve(full_size);
226          for (const auto& tag : tags) {
227              ret.push_back('[');
228              ret += tag.original;
229              ret.push_back(']');
230          }
231  
232          return ret;
233      }
234  
235      void TestCaseInfo::internalAppendTag(StringRef tagStr) {
236          backingTags += '[';
237          const auto backingStart = backingTags.size();
238          backingTags += tagStr;
239          const auto backingEnd = backingTags.size();
240          backingTags += ']';
241          tags.emplace_back(StringRef(backingTags.c_str() + backingStart, backingEnd - backingStart));
242      }
243  
244      bool operator<( TestCaseInfo const& lhs, TestCaseInfo const& rhs ) {
245          // We want to avoid redoing the string comparisons multiple times,
246          // so we store the result of a three-way comparison before using
247          // it in the actual comparison logic.
248          const auto cmpName = lhs.name.compare( rhs.name );
249          if ( cmpName != 0 ) {
250              return cmpName < 0;
251          }
252          const auto cmpClassName = lhs.className.compare( rhs.className );
253          if ( cmpClassName != 0 ) {
254              return cmpClassName < 0;
255          }
256          return lhs.tags < rhs.tags;
257      }
258  
259      TestCaseInfo const& TestCaseHandle::getTestCaseInfo() const {
260          return *m_info;
261      }
262  
263  } // end namespace Catch