diff options
| author | Gennadiy Civil <gennadiycivil@users.noreply.github.com> | 2018-03-01 11:18:17 -0500 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-03-01 11:18:17 -0500 | 
| commit | f8fbe1c82175b493acd010a6891119dbe5712b5d (patch) | |
| tree | fa8e9f14e2b4efb66787742ac7bdd7ac4f262700 | |
| parent | 837c24637040566a223d6330de3ae6182caa73ee (diff) | |
| parent | 447d58b4ee8ea96b4757a5bb5f0b3be75af6c2a1 (diff) | |
| download | googletest-f8fbe1c82175b493acd010a6891119dbe5712b5d.tar.gz googletest-f8fbe1c82175b493acd010a6891119dbe5712b5d.tar.bz2 googletest-f8fbe1c82175b493acd010a6891119dbe5712b5d.zip | |
Merge branch 'master' into unsigned-wchar
| -rw-r--r-- | appveyor.yml | 3 | ||||
| -rw-r--r-- | googlemock/docs/CheatSheet.md | 2 | ||||
| -rw-r--r-- | googlemock/include/gmock/gmock-matchers.h | 302 | ||||
| -rw-r--r-- | googlemock/src/gmock-matchers.cc | 196 | ||||
| -rw-r--r-- | googlemock/test/gmock-matchers_test.cc | 64 | ||||
| -rw-r--r-- | googlemock/test/gmock_link_test.h | 24 | ||||
| -rw-r--r-- | googletest/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | googletest/cmake/internal_utils.cmake | 6 | ||||
| -rw-r--r-- | googletest/docs/AdvancedGuide.md | 201 | ||||
| -rw-r--r-- | googletest/include/gtest/internal/gtest-port.h | 7 | ||||
| -rw-r--r-- | googletest/src/gtest.cc | 375 | ||||
| -rw-r--r-- | googletest/test/gtest_json_outfiles_test.py | 163 | ||||
| -rw-r--r-- | googletest/test/gtest_json_output_unittest.py | 612 | ||||
| -rw-r--r-- | googletest/test/gtest_json_test_utils.py | 60 | 
14 files changed, 1880 insertions, 139 deletions
| diff --git a/appveyor.yml b/appveyor.yml index 8d9cc64f..84d9fbce 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -72,7 +72,8 @@ build_script:      if ($LastExitCode -ne 0) {          throw "Exec: $ErrorMessage"      } -    & cmake --build . --config $env:configuration +    $cmake_parallel = if ($env:generator -eq "MinGW Makefiles") {"-j2"} else  {"/m"} +    & cmake --build . --config $env:configuration -- $cmake_parallel      if ($LastExitCode -ne 0) {          throw "Exec: $ErrorMessage"      } diff --git a/googlemock/docs/CheatSheet.md b/googlemock/docs/CheatSheet.md index c6367fdd..f8bbbfe6 100644 --- a/googlemock/docs/CheatSheet.md +++ b/googlemock/docs/CheatSheet.md @@ -178,6 +178,8 @@ divided into several categories:  |`Ne(value)`           |`argument != value`|  |`IsNull()`            |`argument` is a `NULL` pointer (raw or smart).|  |`NotNull()`           |`argument` is a non-null pointer (raw or smart).| +|`VariantWith<T>(m)`   |`argument` is `variant<>` that holds the alternative of +type T with a value matching `m`.|  |`Ref(variable)`       |`argument` is a reference to `variable`.|  |`TypedEq<type>(value)`|`argument` has type `type` and is equal to `value`. You may need to use this instead of `Eq(value)` when the mock function is overloaded.| diff --git a/googlemock/include/gmock/gmock-matchers.h b/googlemock/include/gmock/gmock-matchers.h index fc3fe3aa..9522c850 100644 --- a/googlemock/include/gmock/gmock-matchers.h +++ b/googlemock/include/gmock/gmock-matchers.h @@ -514,7 +514,7 @@ template <typename T, typename M>  class MatcherCastImpl {   public:    static Matcher<T> Cast(const M& polymorphic_matcher_or_value) { -    // M can be a polymorhic matcher, in which case we want to use +    // M can be a polymorphic matcher, in which case we want to use      // its conversion operator to create Matcher<T>.  Or it can be a value      // that should be passed to the Matcher<T>'s constructor.      // @@ -3303,14 +3303,23 @@ typedef ::std::vector<ElementMatcherPair> ElementMatcherPairs;  GTEST_API_ ElementMatcherPairs  FindMaxBipartiteMatching(const MatchMatrix& g); -GTEST_API_ bool FindPairing(const MatchMatrix& matrix, -                            MatchResultListener* listener); +struct UnorderedMatcherRequire { +  enum Flags { +    Superset = 1 << 0, +    Subset = 1 << 1, +    ExactMatch = Superset | Subset, +  }; +};  // Untyped base class for implementing UnorderedElementsAre.  By  // putting logic that's not specific to the element type here, we  // reduce binary bloat and increase compilation speed.  class GTEST_API_ UnorderedElementsAreMatcherImplBase {   protected: +  explicit UnorderedElementsAreMatcherImplBase( +      UnorderedMatcherRequire::Flags matcher_flags) +      : match_flags_(matcher_flags) {} +    // A vector of matcher describers, one for each element matcher.    // Does not own the describers (and thus can be used only when the    // element matchers are alive). @@ -3322,9 +3331,12 @@ class GTEST_API_ UnorderedElementsAreMatcherImplBase {    // Describes the negation of this UnorderedElementsAre matcher.    void DescribeNegationToImpl(::std::ostream* os) const; -  bool VerifyAllElementsAndMatchersAreMatched( -      const ::std::vector<std::string>& element_printouts, -      const MatchMatrix& matrix, MatchResultListener* listener) const; +  bool VerifyMatchMatrix(const ::std::vector<std::string>& element_printouts, +                         const MatchMatrix& matrix, +                         MatchResultListener* listener) const; + +  bool FindPairing(const MatchMatrix& matrix, +                   MatchResultListener* listener) const;    MatcherDescriberVec& matcher_describers() {      return matcher_describers_; @@ -3334,13 +3346,17 @@ class GTEST_API_ UnorderedElementsAreMatcherImplBase {      return Message() << n << " element" << (n == 1 ? "" : "s");    } +  UnorderedMatcherRequire::Flags match_flags() const { return match_flags_; } +   private: +  UnorderedMatcherRequire::Flags match_flags_;    MatcherDescriberVec matcher_describers_;    GTEST_DISALLOW_ASSIGN_(UnorderedElementsAreMatcherImplBase);  }; -// Implements unordered ElementsAre and unordered ElementsAreArray. +// Implements UnorderedElementsAre, UnorderedElementsAreArray, IsSubsetOf, and +// IsSupersetOf.  template <typename Container>  class UnorderedElementsAreMatcherImpl      : public MatcherInterface<Container>, @@ -3353,10 +3369,10 @@ class UnorderedElementsAreMatcherImpl    typedef typename StlContainer::const_iterator StlContainerConstIterator;    typedef typename StlContainer::value_type Element; -  // Constructs the matcher from a sequence of element values or -  // element matchers.    template <typename InputIter> -  UnorderedElementsAreMatcherImpl(InputIter first, InputIter last) { +  UnorderedElementsAreMatcherImpl(UnorderedMatcherRequire::Flags matcher_flags, +                                  InputIter first, InputIter last) +      : UnorderedElementsAreMatcherImplBase(matcher_flags) {      for (; first != last; ++first) {        matchers_.push_back(MatcherCast<const Element&>(*first));        matcher_describers().push_back(matchers_.back().GetDescriber()); @@ -3377,34 +3393,32 @@ class UnorderedElementsAreMatcherImpl                                 MatchResultListener* listener) const {      StlContainerReference stl_container = View::ConstReference(container);      ::std::vector<std::string> element_printouts; -    MatchMatrix matrix = AnalyzeElements(stl_container.begin(), -                                         stl_container.end(), -                                         &element_printouts, -                                         listener); +    MatchMatrix matrix = +        AnalyzeElements(stl_container.begin(), stl_container.end(), +                        &element_printouts, listener); -    const size_t actual_count = matrix.LhsSize(); -    if (actual_count == 0 && matchers_.empty()) { +    if (matrix.LhsSize() == 0 && matrix.RhsSize() == 0) {        return true;      } -    if (actual_count != matchers_.size()) { -      // The element count doesn't match.  If the container is empty, -      // there's no need to explain anything as Google Mock already -      // prints the empty container. Otherwise we just need to show -      // how many elements there actually are. -      if (actual_count != 0 && listener->IsInterested()) { -        *listener << "which has " << Elements(actual_count); + +    if (match_flags() == UnorderedMatcherRequire::ExactMatch) { +      if (matrix.LhsSize() != matrix.RhsSize()) { +        // The element count doesn't match.  If the container is empty, +        // there's no need to explain anything as Google Mock already +        // prints the empty container. Otherwise we just need to show +        // how many elements there actually are. +        if (matrix.LhsSize() != 0 && listener->IsInterested()) { +          *listener << "which has " << Elements(matrix.LhsSize()); +        } +        return false;        } -      return false;      } -    return VerifyAllElementsAndMatchersAreMatched(element_printouts, -                                                  matrix, listener) && +    return VerifyMatchMatrix(element_printouts, matrix, listener) &&             FindPairing(matrix, listener);    }   private: -  typedef ::std::vector<Matcher<const Element&> > MatcherVec; -    template <typename ElementIter>    MatchMatrix AnalyzeElements(ElementIter elem_first, ElementIter elem_last,                                ::std::vector<std::string>* element_printouts, @@ -3431,7 +3445,7 @@ class UnorderedElementsAreMatcherImpl      return matrix;    } -  MatcherVec matchers_; +  ::std::vector<Matcher<const Element&> > matchers_;    GTEST_DISALLOW_ASSIGN_(UnorderedElementsAreMatcherImpl);  }; @@ -3464,7 +3478,7 @@ class UnorderedElementsAreMatcher {      TransformTupleValues(CastAndAppendTransform<const Element&>(), matchers_,                           ::std::back_inserter(matchers));      return MakeMatcher(new UnorderedElementsAreMatcherImpl<Container>( -                           matchers.begin(), matchers.end())); +        UnorderedMatcherRequire::ExactMatch, matchers.begin(), matchers.end()));    }   private: @@ -3497,24 +3511,23 @@ class ElementsAreMatcher {    GTEST_DISALLOW_ASSIGN_(ElementsAreMatcher);  }; -// Implements UnorderedElementsAreArray(). +// Implements UnorderedElementsAreArray(), IsSubsetOf(), and IsSupersetOf().  template <typename T>  class UnorderedElementsAreArrayMatcher {   public: -  UnorderedElementsAreArrayMatcher() {} -    template <typename Iter> -  UnorderedElementsAreArrayMatcher(Iter first, Iter last) -      : matchers_(first, last) {} +  UnorderedElementsAreArrayMatcher(UnorderedMatcherRequire::Flags match_flags, +                                   Iter first, Iter last) +      : match_flags_(match_flags), matchers_(first, last) {}    template <typename Container>    operator Matcher<Container>() const { -    return MakeMatcher( -        new UnorderedElementsAreMatcherImpl<Container>(matchers_.begin(), -                                                       matchers_.end())); +    return MakeMatcher(new UnorderedElementsAreMatcherImpl<Container>( +        match_flags_, matchers_.begin(), matchers_.end()));    }   private: +  UnorderedMatcherRequire::Flags match_flags_;    ::std::vector<T> matchers_;    GTEST_DISALLOW_ASSIGN_(UnorderedElementsAreArrayMatcher); @@ -3623,9 +3636,69 @@ GTEST_API_ std::string FormatMatcherDescription(bool negation,                                                  const char* matcher_name,                                                  const Strings& param_values); +namespace variant_matcher { +// Overloads to allow VariantMatcher to do proper ADL lookup. +template <typename T> +void holds_alternative() {} +template <typename T> +void get() {} + +// Implements a matcher that checks the value of a variant<> type variable. +template <typename T> +class VariantMatcher { + public: +  explicit VariantMatcher(::testing::Matcher<const T&> matcher) +      : matcher_(internal::move(matcher)) {} + +  template <typename Variant> +  bool MatchAndExplain(const Variant& value, +                       ::testing::MatchResultListener* listener) const { +    if (!listener->IsInterested()) { +      return holds_alternative<T>(value) && matcher_.Matches(get<T>(value)); +    } + +    if (!holds_alternative<T>(value)) { +      *listener << "whose value is not of type '" << GetTypeName() << "'"; +      return false; +    } + +    const T& elem = get<T>(value); +    StringMatchResultListener elem_listener; +    const bool match = matcher_.MatchAndExplain(elem, &elem_listener); +    *listener << "whose value " << PrintToString(elem) +              << (match ? " matches" : " doesn't match"); +    PrintIfNotEmpty(elem_listener.str(), listener->stream()); +    return match; +  } + +  void DescribeTo(std::ostream* os) const { +    *os << "is a variant<> with value of type '" << GetTypeName() +        << "' and the value "; +    matcher_.DescribeTo(os); +  } + +  void DescribeNegationTo(std::ostream* os) const { +    *os << "is a variant<> with value of type other than '" << GetTypeName() +        << "' or the value "; +    matcher_.DescribeNegationTo(os); +  } + + private: +  static string GetTypeName() { +#if GTEST_HAS_RTTI +    return internal::GetTypeName<T>(); +#endif +    return "the element type"; +  } + +  const ::testing::Matcher<const T&> matcher_; +}; + +}  // namespace variant_matcher +  }  // namespace internal -// ElementsAreArray(first, last) +// ElementsAreArray(iterator_first, iterator_last)  // ElementsAreArray(pointer, count)  // ElementsAreArray(array)  // ElementsAreArray(container) @@ -3674,20 +3747,26 @@ ElementsAreArray(::std::initializer_list<T> xs) {  }  #endif -// UnorderedElementsAreArray(first, last) +// UnorderedElementsAreArray(iterator_first, iterator_last)  // UnorderedElementsAreArray(pointer, count)  // UnorderedElementsAreArray(array)  // UnorderedElementsAreArray(container)  // UnorderedElementsAreArray({ e1, e2, ..., en })  // -// The UnorderedElementsAreArray() functions are like -// ElementsAreArray(...), but allow matching the elements in any order. +// UnorderedElementsAreArray() verifies that a bijective mapping onto a +// collection of matchers exists. +// +// The matchers can be specified as an array, a pointer and count, a container, +// an initializer list, or an STL iterator range. In each of these cases, the +// underlying matchers can be either values or matchers. +  template <typename Iter>  inline internal::UnorderedElementsAreArrayMatcher<      typename ::std::iterator_traits<Iter>::value_type>  UnorderedElementsAreArray(Iter first, Iter last) {    typedef typename ::std::iterator_traits<Iter>::value_type T; -  return internal::UnorderedElementsAreArrayMatcher<T>(first, last); +  return internal::UnorderedElementsAreArrayMatcher<T>( +      internal::UnorderedMatcherRequire::ExactMatch, first, last);  }  template <typename T> @@ -3729,7 +3808,9 @@ UnorderedElementsAreArray(::std::initializer_list<T> xs) {  const internal::AnythingMatcher _ = {};  // Creates a matcher that matches any value of the given type T.  template <typename T> -inline Matcher<T> A() { return MakeMatcher(new internal::AnyMatcherImpl<T>()); } +inline Matcher<T> A() { +  return Matcher<T>(new internal::AnyMatcherImpl<T>()); +}  // Creates a matcher that matches any value of the given type T.  template <typename T> @@ -4299,6 +4380,128 @@ inline internal::ContainsMatcher<M> Contains(M matcher) {    return internal::ContainsMatcher<M>(matcher);  } +// IsSupersetOf(iterator_first, iterator_last) +// IsSupersetOf(pointer, count) +// IsSupersetOf(array) +// IsSupersetOf(container) +// IsSupersetOf({e1, e2, ..., en}) +// +// IsSupersetOf() verifies that a surjective partial mapping onto a collection +// of matchers exists. In other words, a container matches +// IsSupersetOf({e1, ..., en}) if and only if there is a permutation +// {y1, ..., yn} of some of the container's elements where y1 matches e1, +// ..., and yn matches en. Obviously, the size of the container must be >= n +// in order to have a match. Examples: +// +// - {1, 2, 3} matches IsSupersetOf({Ge(3), Ne(0)}), as 3 matches Ge(3) and +//   1 matches Ne(0). +// - {1, 2} doesn't match IsSupersetOf({Eq(1), Lt(2)}), even though 1 matches +//   both Eq(1) and Lt(2). The reason is that different matchers must be used +//   for elements in different slots of the container. +// - {1, 1, 2} matches IsSupersetOf({Eq(1), Lt(2)}), as (the first) 1 matches +//   Eq(1) and (the second) 1 matches Lt(2). +// - {1, 2, 3} matches IsSupersetOf(Gt(1), Gt(1)), as 2 matches (the first) +//   Gt(1) and 3 matches (the second) Gt(1). +// +// The matchers can be specified as an array, a pointer and count, a container, +// an initializer list, or an STL iterator range. In each of these cases, the +// underlying matchers can be either values or matchers. + +template <typename Iter> +inline internal::UnorderedElementsAreArrayMatcher< +    typename ::std::iterator_traits<Iter>::value_type> +IsSupersetOf(Iter first, Iter last) { +  typedef typename ::std::iterator_traits<Iter>::value_type T; +  return internal::UnorderedElementsAreArrayMatcher<T>( +      internal::UnorderedMatcherRequire::Superset, first, last); +} + +template <typename T> +inline internal::UnorderedElementsAreArrayMatcher<T> IsSupersetOf( +    const T* pointer, size_t count) { +  return IsSupersetOf(pointer, pointer + count); +} + +template <typename T, size_t N> +inline internal::UnorderedElementsAreArrayMatcher<T> IsSupersetOf( +    const T (&array)[N]) { +  return IsSupersetOf(array, N); +} + +template <typename Container> +inline internal::UnorderedElementsAreArrayMatcher< +    typename Container::value_type> +IsSupersetOf(const Container& container) { +  return IsSupersetOf(container.begin(), container.end()); +} + +#if GTEST_HAS_STD_INITIALIZER_LIST_ +template <typename T> +inline internal::UnorderedElementsAreArrayMatcher<T> IsSupersetOf( +    ::std::initializer_list<T> xs) { +  return IsSupersetOf(xs.begin(), xs.end()); +} +#endif + +// IsSubsetOf(iterator_first, iterator_last) +// IsSubsetOf(pointer, count) +// IsSubsetOf(array) +// IsSubsetOf(container) +// IsSubsetOf({e1, e2, ..., en}) +// +// IsSubsetOf() verifies that an injective mapping onto a collection of matchers +// exists.  In other words, a container matches IsSubsetOf({e1, ..., en}) if and +// only if there is a subset of matchers {m1, ..., mk} which would match the +// container using UnorderedElementsAre.  Obviously, the size of the container +// must be <= n in order to have a match. Examples: +// +// - {1} matches IsSubsetOf({Gt(0), Lt(0)}), as 1 matches Gt(0). +// - {1, -1} matches IsSubsetOf({Lt(0), Gt(0)}), as 1 matches Gt(0) and -1 +//   matches Lt(0). +// - {1, 2} doesn't matches IsSubsetOf({Gt(0), Lt(0)}), even though 1 and 2 both +//   match Gt(0). The reason is that different matchers must be used for +//   elements in different slots of the container. +// +// The matchers can be specified as an array, a pointer and count, a container, +// an initializer list, or an STL iterator range. In each of these cases, the +// underlying matchers can be either values or matchers. + +template <typename Iter> +inline internal::UnorderedElementsAreArrayMatcher< +    typename ::std::iterator_traits<Iter>::value_type> +IsSubsetOf(Iter first, Iter last) { +  typedef typename ::std::iterator_traits<Iter>::value_type T; +  return internal::UnorderedElementsAreArrayMatcher<T>( +      internal::UnorderedMatcherRequire::Subset, first, last); +} + +template <typename T> +inline internal::UnorderedElementsAreArrayMatcher<T> IsSubsetOf( +    const T* pointer, size_t count) { +  return IsSubsetOf(pointer, pointer + count); +} + +template <typename T, size_t N> +inline internal::UnorderedElementsAreArrayMatcher<T> IsSubsetOf( +    const T (&array)[N]) { +  return IsSubsetOf(array, N); +} + +template <typename Container> +inline internal::UnorderedElementsAreArrayMatcher< +    typename Container::value_type> +IsSubsetOf(const Container& container) { +  return IsSubsetOf(container.begin(), container.end()); +} + +#if GTEST_HAS_STD_INITIALIZER_LIST_ +template <typename T> +inline internal::UnorderedElementsAreArrayMatcher<T> IsSubsetOf( +    ::std::initializer_list<T> xs) { +  return IsSubsetOf(xs.begin(), xs.end()); +} +#endif +  // Matches an STL-style container or a native array that contains only  // elements matching the given value or matcher.  // @@ -4397,6 +4600,17 @@ inline internal::AnyOfMatcher<Args...> AnyOf(const Args&... matchers) {  template <typename InnerMatcher>  inline InnerMatcher AllArgs(const InnerMatcher& matcher) { return matcher; } +// Returns a matcher that matches the value of a variant<> type variable. +// The matcher implementation uses ADL to find the holds_alternative and get +// functions. +// It is compatible with std::variant. +template <typename T> +PolymorphicMatcher<internal::variant_matcher::VariantMatcher<T> > VariantWith( +    const Matcher<const T&>& matcher) { +  return MakePolymorphicMatcher( +      internal::variant_matcher::VariantMatcher<T>(matcher)); +} +  // These macros allow using matchers to check values in Google Test  // tests.  ASSERT_THAT(value, matcher) and EXPECT_THAT(value, matcher)  // succeed iff the value matches the matcher.  If the assertion fails, diff --git a/googlemock/src/gmock-matchers.cc b/googlemock/src/gmock-matchers.cc index f37d5c2d..88e40088 100644 --- a/googlemock/src/gmock-matchers.cc +++ b/googlemock/src/gmock-matchers.cc @@ -38,6 +38,7 @@  #include "gmock/gmock-generated-matchers.h"  #include <string.h> +#include <iostream>  #include <sstream>  #include <string> @@ -181,8 +182,7 @@ class MaxBipartiteMatchState {    explicit MaxBipartiteMatchState(const MatchMatrix& graph)        : graph_(&graph),          left_(graph_->LhsSize(), kUnused), -        right_(graph_->RhsSize(), kUnused) { -  } +        right_(graph_->RhsSize(), kUnused) {}    // Returns the edges of a maximal match, each in the form {left, right}.    ElementMatcherPairs Compute() { @@ -239,10 +239,8 @@ class MaxBipartiteMatchState {    //    bool TryAugment(size_t ilhs, ::std::vector<char>* seen) {      for (size_t irhs = 0; irhs < graph_->RhsSize(); ++irhs) { -      if ((*seen)[irhs]) -        continue; -      if (!graph_->HasEdge(ilhs, irhs)) -        continue; +      if ((*seen)[irhs]) continue; +      if (!graph_->HasEdge(ilhs, irhs)) continue;        // There's an available edge from ilhs to irhs.        (*seen)[irhs] = 1;        // Next a search is performed to determine whether @@ -285,8 +283,7 @@ class MaxBipartiteMatchState {  const size_t MaxBipartiteMatchState::kUnused; -GTEST_API_ ElementMatcherPairs -FindMaxBipartiteMatching(const MatchMatrix& g) { +GTEST_API_ ElementMatcherPairs FindMaxBipartiteMatching(const MatchMatrix& g) {    return MaxBipartiteMatchState(g).Compute();  } @@ -295,7 +292,7 @@ static void LogElementMatcherPairVec(const ElementMatcherPairs& pairs,    typedef ElementMatcherPairs::const_iterator Iter;    ::std::ostream& os = *stream;    os << "{"; -  const char *sep = ""; +  const char* sep = "";    for (Iter it = pairs.begin(); it != pairs.end(); ++it) {      os << sep << "\n  ("         << "element #" << it->first << ", " @@ -305,38 +302,6 @@ static void LogElementMatcherPairVec(const ElementMatcherPairs& pairs,    os << "\n}";  } -// Tries to find a pairing, and explains the result. -GTEST_API_ bool FindPairing(const MatchMatrix& matrix, -                            MatchResultListener* listener) { -  ElementMatcherPairs matches = FindMaxBipartiteMatching(matrix); - -  size_t max_flow = matches.size(); -  bool result = (max_flow == matrix.RhsSize()); - -  if (!result) { -    if (listener->IsInterested()) { -      *listener << "where no permutation of the elements can " -                   "satisfy all matchers, and the closest match is " -                << max_flow << " of " << matrix.RhsSize() -                << " matchers with the pairings:\n"; -      LogElementMatcherPairVec(matches, listener->stream()); -    } -    return false; -  } - -  if (matches.size() > 1) { -    if (listener->IsInterested()) { -      const char *sep = "where:\n"; -      for (size_t mi = 0; mi < matches.size(); ++mi) { -        *listener << sep << " - element #" << matches[mi].first -                  << " is matched by matcher #" << matches[mi].second; -        sep = ",\n"; -      } -    } -  } -  return true; -} -  bool MatchMatrix::NextGraph() {    for (size_t ilhs = 0; ilhs < LhsSize(); ++ilhs) {      for (size_t irhs = 0; irhs < RhsSize(); ++irhs) { @@ -362,7 +327,7 @@ void MatchMatrix::Randomize() {  std::string MatchMatrix::DebugString() const {    ::std::stringstream ss; -  const char *sep = ""; +  const char* sep = "";    for (size_t i = 0; i < LhsSize(); ++i) {      ss << sep;      for (size_t j = 0; j < RhsSize(); ++j) { @@ -375,44 +340,83 @@ std::string MatchMatrix::DebugString() const {  void UnorderedElementsAreMatcherImplBase::DescribeToImpl(      ::std::ostream* os) const { -  if (matcher_describers_.empty()) { -    *os << "is empty"; -    return; -  } -  if (matcher_describers_.size() == 1) { -    *os << "has " << Elements(1) << " and that element "; -    matcher_describers_[0]->DescribeTo(os); -    return; +  switch (match_flags()) { +    case UnorderedMatcherRequire::ExactMatch: +      if (matcher_describers_.empty()) { +        *os << "is empty"; +        return; +      } +      if (matcher_describers_.size() == 1) { +        *os << "has " << Elements(1) << " and that element "; +        matcher_describers_[0]->DescribeTo(os); +        return; +      } +      *os << "has " << Elements(matcher_describers_.size()) +          << " and there exists some permutation of elements such that:\n"; +      break; +    case UnorderedMatcherRequire::Superset: +      *os << "a surjection from elements to requirements exists such that:\n"; +      break; +    case UnorderedMatcherRequire::Subset: +      *os << "an injection from elements to requirements exists such that:\n"; +      break;    } -  *os << "has " << Elements(matcher_describers_.size()) -      << " and there exists some permutation of elements such that:\n"; +    const char* sep = "";    for (size_t i = 0; i != matcher_describers_.size(); ++i) { -    *os << sep << " - element #" << i << " "; +    *os << sep; +    if (match_flags() == UnorderedMatcherRequire::ExactMatch) { +      *os << " - element #" << i << " "; +    } else { +      *os << " - an element "; +    }      matcher_describers_[i]->DescribeTo(os); -    sep = ", and\n"; +    if (match_flags() == UnorderedMatcherRequire::ExactMatch) { +      sep = ", and\n"; +    } else { +      sep = "\n"; +    }    }  }  void UnorderedElementsAreMatcherImplBase::DescribeNegationToImpl(      ::std::ostream* os) const { -  if (matcher_describers_.empty()) { -    *os << "isn't empty"; -    return; -  } -  if (matcher_describers_.size() == 1) { -    *os << "doesn't have " << Elements(1) -        << ", or has " << Elements(1) << " that "; -    matcher_describers_[0]->DescribeNegationTo(os); -    return; +  switch (match_flags()) { +    case UnorderedMatcherRequire::ExactMatch: +      if (matcher_describers_.empty()) { +        *os << "isn't empty"; +        return; +      } +      if (matcher_describers_.size() == 1) { +        *os << "doesn't have " << Elements(1) << ", or has " << Elements(1) +            << " that "; +        matcher_describers_[0]->DescribeNegationTo(os); +        return; +      } +      *os << "doesn't have " << Elements(matcher_describers_.size()) +          << ", or there exists no permutation of elements such that:\n"; +      break; +    case UnorderedMatcherRequire::Superset: +      *os << "no surjection from elements to requirements exists such that:\n"; +      break; +    case UnorderedMatcherRequire::Subset: +      *os << "no injection from elements to requirements exists such that:\n"; +      break;    } -  *os << "doesn't have " << Elements(matcher_describers_.size()) -      << ", or there exists no permutation of elements such that:\n";    const char* sep = "";    for (size_t i = 0; i != matcher_describers_.size(); ++i) { -    *os << sep << " - element #" << i << " "; +    *os << sep; +    if (match_flags() == UnorderedMatcherRequire::ExactMatch) { +      *os << " - element #" << i << " "; +    } else { +      *os << " - an element "; +    }      matcher_describers_[i]->DescribeTo(os); -    sep = ", and\n"; +    if (match_flags() == UnorderedMatcherRequire::ExactMatch) { +      sep = ", and\n"; +    } else { +      sep = "\n"; +    }    }  } @@ -421,10 +425,9 @@ void UnorderedElementsAreMatcherImplBase::DescribeNegationToImpl(  // and better error reporting.  // Returns false, writing an explanation to 'listener', if and only  // if the success criteria are not met. -bool UnorderedElementsAreMatcherImplBase:: -    VerifyAllElementsAndMatchersAreMatched( -        const ::std::vector<std::string>& element_printouts, -        const MatchMatrix& matrix, MatchResultListener* listener) const { +bool UnorderedElementsAreMatcherImplBase::VerifyMatchMatrix( +    const ::std::vector<std::string>& element_printouts, +    const MatchMatrix& matrix, MatchResultListener* listener) const {    bool result = true;    ::std::vector<char> element_matched(matrix.LhsSize(), 0);    ::std::vector<char> matcher_matched(matrix.RhsSize(), 0); @@ -437,12 +440,11 @@ bool UnorderedElementsAreMatcherImplBase::      }    } -  { +  if (match_flags() & UnorderedMatcherRequire::Superset) {      const char* sep =          "where the following matchers don't match any elements:\n";      for (size_t mi = 0; mi < matcher_matched.size(); ++mi) { -      if (matcher_matched[mi]) -        continue; +      if (matcher_matched[mi]) continue;        result = false;        if (listener->IsInterested()) {          *listener << sep << "matcher #" << mi << ": "; @@ -452,7 +454,7 @@ bool UnorderedElementsAreMatcherImplBase::      }    } -  { +  if (match_flags() & UnorderedMatcherRequire::Subset) {      const char* sep =          "where the following elements don't match any matchers:\n";      const char* outer_sep = ""; @@ -460,8 +462,7 @@ bool UnorderedElementsAreMatcherImplBase::        outer_sep = "\nand ";      }      for (size_t ei = 0; ei < element_matched.size(); ++ei) { -      if (element_matched[ei]) -        continue; +      if (element_matched[ei]) continue;        result = false;        if (listener->IsInterested()) {          *listener << outer_sep << sep << "element #" << ei << ": " @@ -474,5 +475,46 @@ bool UnorderedElementsAreMatcherImplBase::    return result;  } +bool UnorderedElementsAreMatcherImplBase::FindPairing( +    const MatchMatrix& matrix, MatchResultListener* listener) const { +  ElementMatcherPairs matches = FindMaxBipartiteMatching(matrix); + +  size_t max_flow = matches.size(); +  if ((match_flags() & UnorderedMatcherRequire::Superset) && +      max_flow < matrix.RhsSize()) { +    if (listener->IsInterested()) { +      *listener << "where no permutation of the elements can satisfy all " +                   "matchers, and the closest match is " +                << max_flow << " of " << matrix.RhsSize() +                << " matchers with the pairings:\n"; +      LogElementMatcherPairVec(matches, listener->stream()); +    } +    return false; +  } +  if ((match_flags() & UnorderedMatcherRequire::Subset) && +      max_flow < matrix.LhsSize()) { +    if (listener->IsInterested()) { +      *listener +          << "where not all elements can be matched, and the closest match is " +          << max_flow << " of " << matrix.RhsSize() +          << " matchers with the pairings:\n"; +      LogElementMatcherPairVec(matches, listener->stream()); +    } +    return false; +  } + +  if (matches.size() > 1) { +    if (listener->IsInterested()) { +      const char* sep = "where:\n"; +      for (size_t mi = 0; mi < matches.size(); ++mi) { +        *listener << sep << " - element #" << matches[mi].first +                  << " is matched by matcher #" << matches[mi].second; +        sep = ",\n"; +      } +    } +  } +  return true; +} +  }  // namespace internal  }  // namespace testing diff --git a/googlemock/test/gmock-matchers_test.cc b/googlemock/test/gmock-matchers_test.cc index 761c0c22..829935ef 100644 --- a/googlemock/test/gmock-matchers_test.cc +++ b/googlemock/test/gmock-matchers_test.cc @@ -5655,5 +5655,69 @@ TEST(UnorderedPointwiseTest, AllowsMonomorphicInnerMatcher) {    EXPECT_THAT(lhs, UnorderedPointwise(m2, rhs));  } +class SampleVariantIntString { + public: +  SampleVariantIntString(int i) : i_(i), has_int_(true) {} +  SampleVariantIntString(const std::string& s) : s_(s), has_int_(false) {} + +  template <typename T> +  friend bool holds_alternative(const SampleVariantIntString& value) { +    return value.has_int_ == internal::IsSame<T, int>::value; +  } + +  template <typename T> +  friend const T& get(const SampleVariantIntString& value) { +    return value.get_impl(static_cast<T*>(NULL)); +  } + + private: +  const int& get_impl(int*) const { return i_; } +  const std::string& get_impl(std::string*) const { return s_; } + +  int i_; +  std::string s_; +  bool has_int_; +}; + +TEST(VariantTest, DescribesSelf) { +  const Matcher<SampleVariantIntString> m = VariantWith<int>(Eq(1)); +  EXPECT_THAT(Describe(m), ContainsRegex("is a variant<> with value of type " +                                         "'.*' and the value is equal to 1")); +} + +TEST(VariantTest, ExplainsSelf) { +  const Matcher<SampleVariantIntString> m = VariantWith<int>(Eq(1)); +  EXPECT_THAT(Explain(m, SampleVariantIntString(1)), +              ContainsRegex("whose value 1")); +  EXPECT_THAT(Explain(m, SampleVariantIntString("A")), +              HasSubstr("whose value is not of type '")); +  EXPECT_THAT(Explain(m, SampleVariantIntString(2)), +              "whose value 2 doesn't match"); +} + +TEST(VariantTest, FullMatch) { +  Matcher<SampleVariantIntString> m = VariantWith<int>(Eq(1)); +  EXPECT_TRUE(m.Matches(SampleVariantIntString(1))); + +  m = VariantWith<std::string>(Eq("1")); +  EXPECT_TRUE(m.Matches(SampleVariantIntString("1"))); +} + +TEST(VariantTest, TypeDoesNotMatch) { +  Matcher<SampleVariantIntString> m = VariantWith<int>(Eq(1)); +  EXPECT_FALSE(m.Matches(SampleVariantIntString("1"))); + +  m = VariantWith<std::string>(Eq("1")); +  EXPECT_FALSE(m.Matches(SampleVariantIntString(1))); +} + +TEST(VariantTest, InnerDoesNotMatch) { +  Matcher<SampleVariantIntString> m = VariantWith<int>(Eq(1)); +  EXPECT_FALSE(m.Matches(SampleVariantIntString(2))); + +  m = VariantWith<std::string>(Eq("1")); +  EXPECT_FALSE(m.Matches(SampleVariantIntString("2"))); +} +  }  // namespace gmock_matchers_test  }  // namespace testing diff --git a/googlemock/test/gmock_link_test.h b/googlemock/test/gmock_link_test.h index 1f55f5bd..5f855d19 100644 --- a/googlemock/test/gmock_link_test.h +++ b/googlemock/test/gmock_link_test.h @@ -120,13 +120,15 @@  # include <errno.h>  #endif -#include "gmock/internal/gmock-port.h" -#include "gtest/gtest.h"  #include <iostream>  #include <vector> +#include "gtest/gtest.h" +#include "gtest/internal/gtest-port.h" +  using testing::_;  using testing::A; +using testing::Action;  using testing::AllOf;  using testing::AnyOf;  using testing::Assign; @@ -148,6 +150,8 @@ using testing::Invoke;  using testing::InvokeArgument;  using testing::InvokeWithoutArgs;  using testing::IsNull; +using testing::IsSubsetOf; +using testing::IsSupersetOf;  using testing::Le;  using testing::Lt;  using testing::Matcher; @@ -592,6 +596,22 @@ TEST(LinkTest, TestMatcherElementsAreArray) {    ON_CALL(mock, VoidFromVector(ElementsAreArray(arr))).WillByDefault(Return());  } +// Tests the linkage of the IsSubsetOf matcher. +TEST(LinkTest, TestMatcherIsSubsetOf) { +  Mock mock; +  char arr[] = {'a', 'b'}; + +  ON_CALL(mock, VoidFromVector(IsSubsetOf(arr))).WillByDefault(Return()); +} + +// Tests the linkage of the IsSupersetOf matcher. +TEST(LinkTest, TestMatcherIsSupersetOf) { +  Mock mock; +  char arr[] = {'a', 'b'}; + +  ON_CALL(mock, VoidFromVector(IsSupersetOf(arr))).WillByDefault(Return()); +} +  // Tests the linkage of the ContainerEq matcher.  TEST(LinkTest, TestMatcherContainerEq) {    Mock mock; diff --git a/googletest/CMakeLists.txt b/googletest/CMakeLists.txt index 77b55cae..b09c46ed 100644 --- a/googletest/CMakeLists.txt +++ b/googletest/CMakeLists.txt @@ -27,8 +27,6 @@ option(    "Build gtest with internal symbols hidden in shared libraries."    OFF) -set(CMAKE_DEBUG_POSTFIX "d" CACHE STRING "Generate debug library name with a postfix.") -  # Defines pre_project_set_up_hermetic_build() and set_up_hermetic_build().  include(cmake/hermetic_build.cmake OPTIONAL) @@ -306,7 +304,9 @@ if (gtest_build_tests)    cxx_executable(gtest_xml_outfile1_test_ test gtest_main)    cxx_executable(gtest_xml_outfile2_test_ test gtest_main)    py_test(gtest_xml_outfiles_test) +  py_test(gtest_json_outfiles_test)    cxx_executable(gtest_xml_output_unittest_ test gtest)    py_test(gtest_xml_output_unittest) +  py_test(gtest_json_output_unittest)  endif() diff --git a/googletest/cmake/internal_utils.cmake b/googletest/cmake/internal_utils.cmake index 2c978332..6448918f 100644 --- a/googletest/cmake/internal_utils.cmake +++ b/googletest/cmake/internal_utils.cmake @@ -91,7 +91,7 @@ macro(config_compiler_and_linker)      set(cxx_base_flags "${cxx_base_flags} -D_UNICODE -DUNICODE -DWIN32 -D_WIN32")      set(cxx_base_flags "${cxx_base_flags} -DSTRICT -DWIN32_LEAN_AND_MEAN")      set(cxx_exception_flags "-EHsc -D_HAS_EXCEPTIONS=1") -    set(cxx_no_exception_flags "-D_HAS_EXCEPTIONS=0") +    set(cxx_no_exception_flags "-EHs-c- -D_HAS_EXCEPTIONS=0")      set(cxx_no_rtti_flags "-GR-")    elseif (CMAKE_COMPILER_IS_GNUCXX)      set(cxx_base_flags "-Wall -Wshadow -Werror") @@ -158,6 +158,10 @@ function(cxx_library_with_type name type cxx_flags)    set_target_properties(${name}      PROPERTIES      COMPILE_FLAGS "${cxx_flags}") +  # Generate debug library name with a postfix. +  set_target_properties(${name} +    PROPERTIES +    DEBUG_POSTFIX "d")    if (BUILD_SHARED_LIBS OR type STREQUAL "SHARED")      set_target_properties(${name}        PROPERTIES diff --git a/googletest/docs/AdvancedGuide.md b/googletest/docs/AdvancedGuide.md index 6605f448..c1a1a4ab 100644 --- a/googletest/docs/AdvancedGuide.md +++ b/googletest/docs/AdvancedGuide.md @@ -2060,6 +2060,207 @@ Things to note:  _Availability:_ Linux, Windows, Mac. +#### Generating an JSON Report {#JsonReport} + +gUnit can also emit a JSON report as an alternative format to XML. To generate +the JSON report, set the `GUNIT_OUTPUT` environment variable or the +`--gunit_output` flag to the string `"json:path_to_output_file"`, which will +create the file at the given location. You can also just use the string +`"json"`, in which case the output can be found in the `test_detail.json` file +in the current directory. + +The report format conforms to the following JSON Schema: + +```json +{ +  "$schema": "http://json-schema.org/schema#", +  "type": "object", +  "definitions": { +    "TestCase": { +      "type": "object", +      "properties": { +        "name": { "type": "string" }, +        "tests": { "type": "integer" }, +        "failures": { "type": "integer" }, +        "disabled": { "type": "integer" }, +        "time": { "type": "string" }, +        "testsuite": { +          "type": "array", +          "items": { +            "$ref": "#/definitions/TestInfo" +          } +        } +      } +    }, +    "TestInfo": { +      "type": "object", +      "properties": { +        "name": { "type": "string" }, +        "status": { +          "type": "string", +          "enum": ["RUN", "NOTRUN"] +        }, +        "time": { "type": "string" }, +        "classname": { "type": "string" }, +        "failures": { +          "type": "array", +          "items": { +            "$ref": "#/definitions/Failure" +          } +        } +      } +    }, +    "Failure": { +      "type": "object", +      "properties": { +        "failures": { "type": "string" }, +        "type": { "type": "string" } +      } +    } +  }, +  "properties": { +    "tests": { "type": "integer" }, +    "failures": { "type": "integer" }, +    "disabled": { "type": "integer" }, +    "errors": { "type": "integer" }, +    "timestamp": { +      "type": "string", +      "format": "date-time" +    }, +    "time": { "type": "string" }, +    "name": { "type": "string" }, +    "testsuites": { +      "type": "array", +      "items": { +        "$ref": "#/definitions/TestCase" +      } +    } +  } +} +``` + +The report uses the format that conforms to the following Proto3 using the +[JSON encoding](https://developers.google.com/protocol-buffers/docs/proto3#json): + +```proto +syntax = "proto3"; + +package googletest; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/duration.proto"; + +message UnitTest { +  int32 tests = 1; +  int32 failures = 2; +  int32 disabled = 3; +  int32 errors = 4; +  google.protobuf.Timestamp timestamp = 5; +  google.protobuf.Duration time = 6; +  string name = 7; +  repeated TestCase testsuites = 8; +} + +message TestCase { +  string name = 1; +  int32 tests = 2; +  int32 failures = 3; +  int32 disabled = 4; +  int32 errors = 5; +  google.protobuf.Duration time = 6; +  repeated TestInfo testsuite = 7; +} + +message TestInfo { +  string name = 1; +  enum Status { +    RUN = 0; +    NOTRUN = 1; +  } +  Status status = 2; +  google.protobuf.Duration time = 3; +  string classname = 4; +  message Failure { +    string failures = 1; +    string type = 2; +  } +  repeated Failure failures = 5; +} +``` + +For instance, the following program + +```c++ +TEST(MathTest, Addition) { ... } +TEST(MathTest, Subtraction) { ... } +TEST(LogicTest, NonContradiction) { ... } +``` + +could generate this report: + +```json +{ +  "tests": 3, +  "failures": 1, +  "errors": 0, +  "time": "0.035s", +  "timestamp": "2011-10-31T18:52:42Z" +  "name": "AllTests", +  "testsuites": [ +    { +      "name": "MathTest", +      "tests": 2, +      "failures": 1, +      "errors": 0, +      "time": "0.015s", +      "testsuite": [ +        { +          "name": "Addition", +          "status": "RUN", +          "time": "0.007s", +          "classname": "", +          "failures": [ +            { +              "message": "Value of: add(1, 1)\x0A  Actual: 3\x0AExpected: 2", +              "type": "" +            }, +            { +              "message": "Value of: add(1, -1)\x0A  Actual: 1\x0AExpected: 0", +              "type": "" +            } +          ] +        }, +        { +          "name": "Subtraction", +          "status": "RUN", +          "time": "0.005s", +          "classname": "" +        } +      ] +    } +    { +      "name": "LogicTest", +      "tests": 1, +      "failures": 0, +      "errors": 0, +      "time": "0.005s", +      "testsuite": [ +        { +          "name": "NonContradiction", +          "status": "RUN", +          "time": "0.005s", +          "classname": "" +        } +      ] +    } +  ] +} +``` + +IMPORTANT: The exact format of the JSON document is subject to change. + +**Availability**: Linux, Windows, Mac. +  ## Controlling How Failures Are Reported ##  ### Turning Assertion Failures into Break-Points ### diff --git a/googletest/include/gtest/internal/gtest-port.h b/googletest/include/gtest/internal/gtest-port.h index 2c819c9f..4d5aa043 100644 --- a/googletest/include/gtest/internal/gtest-port.h +++ b/googletest/include/gtest/internal/gtest-port.h @@ -471,8 +471,11 @@ typedef struct _RTL_CRITICAL_SECTION GTEST_CRITICAL_SECTION;  #ifndef GTEST_HAS_EXCEPTIONS  // The user didn't tell us whether exceptions are enabled, so we need  // to figure it out. -# if defined(_MSC_VER) || defined(__BORLANDC__) -// MSVC's and C++Builder's implementations of the STL use the _HAS_EXCEPTIONS +# if defined(_MSC_VER) && defined(_CPPUNWIND) +// MSVC defines _CPPUNWIND to 1 iff exceptions are enabled. +#  define GTEST_HAS_EXCEPTIONS 1 +# elif defined(__BORLANDC__) +// C++Builder's implementation of the STL uses the _HAS_EXCEPTIONS  // macro to enable exceptions, so we'll do the same.  // Assumes that exceptions are enabled by default.  #  ifndef _HAS_EXCEPTIONS diff --git a/googletest/src/gtest.cc b/googletest/src/gtest.cc index 7afa5a95..15cc9073 100644 --- a/googletest/src/gtest.cc +++ b/googletest/src/gtest.cc @@ -160,8 +160,10 @@ static const char kDeathTestCaseFilter[] = "*DeathTest:*DeathTest/*";  // A test filter that matches everything.  static const char kUniversalFilter[] = "*"; -// The default output file for XML output. -static const char kDefaultOutputFile[] = "test_detail.xml"; +// The default output format. +static const char kDefaultOutputFormat[] = "xml"; +// The default output file. +static const char kDefaultOutputFile[] = "test_detail";  // The environment variable name for the test shard index.  static const char kTestShardIndex[] = "GTEST_SHARD_INDEX"; @@ -231,9 +233,9 @@ GTEST_DEFINE_bool_(list_tests, false,  GTEST_DEFINE_string_(      output,      internal::StringFromGTestEnv("output", ""), -    "A format (currently must be \"xml\"), optionally followed " -    "by a colon and an output file name or directory. A directory " -    "is indicated by a trailing pathname separator. " +    "A format (defaults to \"xml\" but can be specified to be \"json\"), " +    "optionally followed by a colon and an output file name or directory. " +    "A directory is indicated by a trailing pathname separator. "      "Examples: \"xml:filename.xml\", \"xml::directoryname/\". "      "If a directory is specified, output files will be created "      "within that directory, with file-names based on the test " @@ -428,12 +430,17 @@ std::string UnitTestOptions::GetAbsolutePathToOutputFile() {    if (gtest_output_flag == NULL)      return ""; +  std::string format = GetOutputFormat(); +  if (format.empty()) +    format = std::string(kDefaultOutputFormat); +    const char* const colon = strchr(gtest_output_flag, ':');    if (colon == NULL) -    return internal::FilePath::ConcatPaths( +    return internal::FilePath::MakeFileName(          internal::FilePath(              UnitTest::GetInstance()->original_working_dir()), -        internal::FilePath(kDefaultOutputFile)).string(); +        internal::FilePath(kDefaultOutputFile), 0, +        format.c_str()).string();    internal::FilePath output_name(colon + 1);    if (!output_name.IsAbsolutePath()) @@ -3771,6 +3778,351 @@ std::string XmlUnitTestResultPrinter::TestPropertiesAsXmlAttributes(  // End XmlUnitTestResultPrinter +// This class generates an JSON output file. +class JsonUnitTestResultPrinter : public EmptyTestEventListener { + public: +  explicit JsonUnitTestResultPrinter(const char* output_file); + +  virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration); + + private: +  // Returns an JSON-escaped copy of the input string str. +  static std::string EscapeJson(const std::string& str); + +  //// Verifies that the given attribute belongs to the given element and +  //// streams the attribute as JSON. +  static void OutputJsonKey(std::ostream* stream, +                            const std::string& element_name, +                            const std::string& name, +                            const std::string& value, +                            const std::string& indent, +                            bool comma = true); +  static void OutputJsonKey(std::ostream* stream, +                            const std::string& element_name, +                            const std::string& name, +                            int value, +                            const std::string& indent, +                            bool comma = true); + +  // Streams a JSON representation of a TestInfo object. +  static void OutputJsonTestInfo(::std::ostream* stream, +                                 const char* test_case_name, +                                 const TestInfo& test_info); + +  // Prints a JSON representation of a TestCase object +  static void PrintJsonTestCase(::std::ostream* stream, +                                const TestCase& test_case); + +  // Prints a JSON summary of unit_test to output stream out. +  static void PrintJsonUnitTest(::std::ostream* stream, +                                const UnitTest& unit_test); + +  // Produces a string representing the test properties in a result as +  // a JSON dictionary. +  static std::string TestPropertiesAsJson(const TestResult& result, +                                          const std::string& indent); + +  // The output file. +  const std::string output_file_; + +  GTEST_DISALLOW_COPY_AND_ASSIGN_(JsonUnitTestResultPrinter); +}; + +// Creates a new JsonUnitTestResultPrinter. +JsonUnitTestResultPrinter::JsonUnitTestResultPrinter(const char* output_file) +    : output_file_(output_file) { +  if (output_file_.empty()) { +    GTEST_LOG_(FATAL) << "JSON output file may not be null"; +  } +} + +void JsonUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test, +                                                  int /*iteration*/) { +  FILE* jsonout = NULL; +  FilePath output_file(output_file_); +  FilePath output_dir(output_file.RemoveFileName()); + +  if (output_dir.CreateDirectoriesRecursively()) { +    jsonout = posix::FOpen(output_file_.c_str(), "w"); +  } +  if (jsonout == NULL) { +    // TODO(phosek): report the reason of the failure. +    // +    // We don't do it for now as: +    // +    //   1. There is no urgent need for it. +    //   2. It's a bit involved to make the errno variable thread-safe on +    //      all three operating systems (Linux, Windows, and Mac OS). +    //   3. To interpret the meaning of errno in a thread-safe way, +    //      we need the strerror_r() function, which is not available on +    //      Windows. +    GTEST_LOG_(FATAL) << "Unable to open file \"" +                      << output_file_ << "\""; +  } +  std::stringstream stream; +  PrintJsonUnitTest(&stream, unit_test); +  fprintf(jsonout, "%s", StringStreamToString(&stream).c_str()); +  fclose(jsonout); +} + +// Returns an JSON-escaped copy of the input string str. +std::string JsonUnitTestResultPrinter::EscapeJson(const std::string& str) { +  Message m; + +  for (size_t i = 0; i < str.size(); ++i) { +    const char ch = str[i]; +    switch (ch) { +      case '\\': +      case '"': +      case '/': +        m << '\\' << ch; +        break; +      case '\b': +        m << "\\b"; +        break; +      case '\t': +        m << "\\t"; +        break; +      case '\n': +        m << "\\n"; +        break; +      case '\f': +        m << "\\f"; +        break; +      case '\r': +        m << "\\r"; +        break; +      default: +        if (ch < ' ') { +          m << "\\u00" << String::FormatByte(static_cast<unsigned char>(ch)); +        } else { +          m << ch; +        } +        break; +    } +  } + +  return m.GetString(); +} + +// The following routines generate an JSON representation of a UnitTest +// object. + +// Formats the given time in milliseconds as seconds. +static std::string FormatTimeInMillisAsDuration(TimeInMillis ms) { +  ::std::stringstream ss; +  ss << (static_cast<double>(ms) * 1e-3) << "s"; +  return ss.str(); +} + +// Converts the given epoch time in milliseconds to a date string in the +// RFC3339 format, without the timezone information. +static std::string FormatEpochTimeInMillisAsRFC3339(TimeInMillis ms) { +  struct tm time_struct; +  if (!PortableLocaltime(static_cast<time_t>(ms / 1000), &time_struct)) +    return ""; +  // YYYY-MM-DDThh:mm:ss +  return StreamableToString(time_struct.tm_year + 1900) + "-" + +      String::FormatIntWidth2(time_struct.tm_mon + 1) + "-" + +      String::FormatIntWidth2(time_struct.tm_mday) + "T" + +      String::FormatIntWidth2(time_struct.tm_hour) + ":" + +      String::FormatIntWidth2(time_struct.tm_min) + ":" + +      String::FormatIntWidth2(time_struct.tm_sec) + "Z"; +} + +static inline std::string Indent(int width) { +  return std::string(width, ' '); +} + +void JsonUnitTestResultPrinter::OutputJsonKey( +    std::ostream* stream, +    const std::string& element_name, +    const std::string& name, +    const std::string& value, +    const std::string& indent, +    bool comma) { +  const std::vector<std::string>& allowed_names = +      GetReservedAttributesForElement(element_name); + +  GTEST_CHECK_(std::find(allowed_names.begin(), allowed_names.end(), name) != +                   allowed_names.end()) +      << "Key \"" << name << "\" is not allowed for value \"" << element_name +      << "\"."; + +  *stream << indent << "\"" << name << "\": \"" << EscapeJson(value) << "\""; +  if (comma) +    *stream << ",\n"; +} + +void JsonUnitTestResultPrinter::OutputJsonKey( +    std::ostream* stream, +    const std::string& element_name, +    const std::string& name, +    int value, +    const std::string& indent, +    bool comma) { +  const std::vector<std::string>& allowed_names = +      GetReservedAttributesForElement(element_name); + +  GTEST_CHECK_(std::find(allowed_names.begin(), allowed_names.end(), name) != +                   allowed_names.end()) +      << "Key \"" << name << "\" is not allowed for value \"" << element_name +      << "\"."; + +  *stream << indent << "\"" << name << "\": " << StreamableToString(value); +  if (comma) +    *stream << ",\n"; +} + +// Prints a JSON representation of a TestInfo object. +void JsonUnitTestResultPrinter::OutputJsonTestInfo(::std::ostream* stream, +                                                   const char* test_case_name, +                                                   const TestInfo& test_info) { +  const TestResult& result = *test_info.result(); +  const std::string kTestcase = "testcase"; +  const std::string kIndent = Indent(10); + +  *stream << Indent(8) << "{\n"; +  OutputJsonKey(stream, kTestcase, "name", test_info.name(), kIndent); + +  if (test_info.value_param() != NULL) { +    OutputJsonKey(stream, kTestcase, "value_param", +                  test_info.value_param(), kIndent); +  } +  if (test_info.type_param() != NULL) { +    OutputJsonKey(stream, kTestcase, "type_param", test_info.type_param(), +                  kIndent); +  } + +  OutputJsonKey(stream, kTestcase, "status", +                test_info.should_run() ? "RUN" : "NOTRUN", kIndent); +  OutputJsonKey(stream, kTestcase, "time", +                FormatTimeInMillisAsDuration(result.elapsed_time()), kIndent); +  OutputJsonKey(stream, kTestcase, "classname", test_case_name, kIndent, false); +  *stream << TestPropertiesAsJson(result, kIndent); + +  int failures = 0; +  for (int i = 0; i < result.total_part_count(); ++i) { +    const TestPartResult& part = result.GetTestPartResult(i); +    if (part.failed()) { +      *stream << ",\n"; +      if (++failures == 1) { +        *stream << kIndent << "\"" << "failures" << "\": [\n"; +      } +      const std::string location = +          internal::FormatCompilerIndependentFileLocation(part.file_name(), +                                                          part.line_number()); +      const std::string summary = EscapeJson(location + "\n" + part.summary()); +      *stream << kIndent << "  {\n" +              << kIndent << "    \"failure\": \"" << summary << "\",\n" +              << kIndent << "    \"type\": \"\"\n" +              << kIndent << "  }"; +    } +  } + +  if (failures > 0) +    *stream << "\n" << kIndent << "]"; +  *stream << "\n" << Indent(8) << "}"; +} + +// Prints an JSON representation of a TestCase object +void JsonUnitTestResultPrinter::PrintJsonTestCase(std::ostream* stream, +                                                  const TestCase& test_case) { +  const std::string kTestsuite = "testsuite"; +  const std::string kIndent = Indent(6); + +  *stream << Indent(4) << "{\n"; +  OutputJsonKey(stream, kTestsuite, "name", test_case.name(), kIndent); +  OutputJsonKey(stream, kTestsuite, "tests", test_case.reportable_test_count(), +                kIndent); +  OutputJsonKey(stream, kTestsuite, "failures", test_case.failed_test_count(), +                kIndent); +  OutputJsonKey(stream, kTestsuite, "disabled", +                test_case.reportable_disabled_test_count(), kIndent); +  OutputJsonKey(stream, kTestsuite, "errors", 0, kIndent); +  OutputJsonKey(stream, kTestsuite, "time", +                FormatTimeInMillisAsDuration(test_case.elapsed_time()), kIndent, +                false); +  *stream << TestPropertiesAsJson(test_case.ad_hoc_test_result(), kIndent) +          << ",\n"; + +  *stream << kIndent << "\"" << kTestsuite << "\": [\n"; + +  bool comma = false; +  for (int i = 0; i < test_case.total_test_count(); ++i) { +    if (test_case.GetTestInfo(i)->is_reportable()) { +      if (comma) { +        *stream << ",\n"; +      } else { +        comma = true; +      } +      OutputJsonTestInfo(stream, test_case.name(), *test_case.GetTestInfo(i)); +    } +  } +  *stream << "\n" << kIndent << "]\n" << Indent(4) << "}"; +} + +// Prints a JSON summary of unit_test to output stream out. +void JsonUnitTestResultPrinter::PrintJsonUnitTest(std::ostream* stream, +                                                  const UnitTest& unit_test) { +  const std::string kTestsuites = "testsuites"; +  const std::string kIndent = Indent(2); +  *stream << "{\n"; + +  OutputJsonKey(stream, kTestsuites, "tests", unit_test.reportable_test_count(), +                kIndent); +  OutputJsonKey(stream, kTestsuites, "failures", unit_test.failed_test_count(), +                kIndent); +  OutputJsonKey(stream, kTestsuites, "disabled", +                unit_test.reportable_disabled_test_count(), kIndent); +  OutputJsonKey(stream, kTestsuites, "errors", 0, kIndent); +  if (GTEST_FLAG(shuffle)) { +    OutputJsonKey(stream, kTestsuites, "random_seed", unit_test.random_seed(), +                  kIndent); +  } +  OutputJsonKey(stream, kTestsuites, "timestamp", +                FormatEpochTimeInMillisAsRFC3339(unit_test.start_timestamp()), +                kIndent); +  OutputJsonKey(stream, kTestsuites, "time", +                FormatTimeInMillisAsDuration(unit_test.elapsed_time()), kIndent, +                false); + +  *stream << TestPropertiesAsJson(unit_test.ad_hoc_test_result(), kIndent) +          << ",\n"; + +  OutputJsonKey(stream, kTestsuites, "name", "AllTests", kIndent); +  *stream << kIndent << "\"" << kTestsuites << "\": [\n"; + +  bool comma = false; +  for (int i = 0; i < unit_test.total_test_case_count(); ++i) { +    if (unit_test.GetTestCase(i)->reportable_test_count() > 0) { +      if (comma) { +        *stream << ",\n"; +      } else { +        comma = true; +      } +      PrintJsonTestCase(stream, *unit_test.GetTestCase(i)); +    } +  } + +  *stream << "\n" << kIndent << "]\n" << "}\n"; +} + +// Produces a string representing the test properties in a result as +// a JSON dictionary. +std::string JsonUnitTestResultPrinter::TestPropertiesAsJson( +    const TestResult& result, const std::string& indent) { +  Message attributes; +  for (int i = 0; i < result.test_property_count(); ++i) { +    const TestProperty& property = result.GetTestProperty(i); +    attributes << ",\n" << indent << "\"" << property.key() << "\": " +               << "\"" << EscapeJson(property.value()) << "\""; +  } +  return attributes.GetString(); +} + +// End JsonUnitTestResultPrinter +  #if GTEST_CAN_STREAM_RESULTS_  // Checks if str contains '=', '&', '%' or '\n' characters. If yes, @@ -4397,6 +4749,9 @@ void UnitTestImpl::ConfigureXmlOutput() {    if (output_format == "xml") {      listeners()->SetDefaultXmlGenerator(new XmlUnitTestResultPrinter(          UnitTestOptions::GetAbsolutePathToOutputFile().c_str())); +  } else if (output_format == "json") { +    listeners()->SetDefaultXmlGenerator(new JsonUnitTestResultPrinter( +        UnitTestOptions::GetAbsolutePathToOutputFile().c_str()));    } else if (output_format != "") {      GTEST_LOG_(WARNING) << "WARNING: unrecognized output format \""                          << output_format << "\" ignored."; @@ -5182,10 +5537,10 @@ static const char kColorEncodedHelpMessage[] =  "      Enable/disable colored output. The default is @Gauto@D.\n"  "  @G--" GTEST_FLAG_PREFIX_ "print_time=0@D\n"  "      Don't print the elapsed time of each test.\n" -"  @G--" GTEST_FLAG_PREFIX_ "output=xml@Y[@G:@YDIRECTORY_PATH@G" +"  @G--" GTEST_FLAG_PREFIX_ "output=@Y(@Gjson@Y|@Gxml@Y)[@G:@YDIRECTORY_PATH@G"      GTEST_PATH_SEP_ "@Y|@G:@YFILE_PATH]@D\n" -"      Generate an XML report in the given directory or with the given file\n" -"      name. @YFILE_PATH@D defaults to @Gtest_detail.xml@D.\n" +"      Generate a JSON or XML report in the given directory or with the given\n" +"      file name. @YFILE_PATH@D defaults to @Gtest_details.xml@D.\n"  #if GTEST_CAN_STREAM_RESULTS_  "  @G--" GTEST_FLAG_PREFIX_ "stream_result_to=@YHOST@G:@YPORT@D\n"  "      Stream test results to the given server.\n" diff --git a/googletest/test/gtest_json_outfiles_test.py b/googletest/test/gtest_json_outfiles_test.py new file mode 100644 index 00000000..62ad18e7 --- /dev/null +++ b/googletest/test/gtest_json_outfiles_test.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python +# Copyright 2018, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +#     * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +#     * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +#     * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Unit test for the gtest_json_output module.""" + +import json +import os +import gtest_test_utils +import gtest_json_test_utils + + +GTEST_OUTPUT_SUBDIR = 'json_outfiles' +GTEST_OUTPUT_1_TEST = 'gtest_xml_outfile1_test_' +GTEST_OUTPUT_2_TEST = 'gtest_xml_outfile2_test_' + +EXPECTED_1 = { +    u'tests': 1, +    u'failures': 0, +    u'disabled': 0, +    u'errors': 0, +    u'time': u'*', +    u'timestamp': u'*', +    u'name': u'AllTests', +    u'testsuites': [{ +        u'name': u'PropertyOne', +        u'tests': 1, +        u'failures': 0, +        u'disabled': 0, +        u'errors': 0, +        u'time': u'*', +        u'testsuite': [{ +            u'name': u'TestSomeProperties', +            u'status': u'RUN', +            u'time': u'*', +            u'classname': u'PropertyOne', +            u'SetUpProp': u'1', +            u'TestSomeProperty': u'1', +            u'TearDownProp': u'1', +        }], +    }], +} + +EXPECTED_2 = { +    u'tests': 1, +    u'failures': 0, +    u'disabled': 0, +    u'errors': 0, +    u'time': u'*', +    u'timestamp': u'*', +    u'name': u'AllTests', +    u'testsuites': [{ +        u'name': u'PropertyTwo', +        u'tests': 1, +        u'failures': 0, +        u'disabled': 0, +        u'errors': 0, +        u'time': u'*', +        u'testsuite': [{ +            u'name': u'TestSomeProperties', +            u'status': u'RUN', +            u'time': u'*', +            u'classname': u'PropertyTwo', +            u'SetUpProp': u'2', +            u'TestSomeProperty': u'2', +            u'TearDownProp': u'2', +        }], +    }], +} + + +class GTestJsonOutFilesTest(gtest_test_utils.TestCase): +  """Unit test for Google Test's JSON output functionality.""" + +  def setUp(self): +    # We want the trailing '/' that the last "" provides in os.path.join, for +    # telling Google Test to create an output directory instead of a single file +    # for xml output. +    self.output_dir_ = os.path.join(gtest_test_utils.GetTempDir(), +                                    GTEST_OUTPUT_SUBDIR, '') +    self.DeleteFilesAndDir() + +  def tearDown(self): +    self.DeleteFilesAndDir() + +  def DeleteFilesAndDir(self): +    try: +      os.remove(os.path.join(self.output_dir_, GTEST_OUTPUT_1_TEST + '.json')) +    except os.error: +      pass +    try: +      os.remove(os.path.join(self.output_dir_, GTEST_OUTPUT_2_TEST + '.json')) +    except os.error: +      pass +    try: +      os.rmdir(self.output_dir_) +    except os.error: +      pass + +  def testOutfile1(self): +    self._TestOutFile(GTEST_OUTPUT_1_TEST, EXPECTED_1) + +  def testOutfile2(self): +    self._TestOutFile(GTEST_OUTPUT_2_TEST, EXPECTED_2) + +  def _TestOutFile(self, test_name, expected): +    gtest_prog_path = gtest_test_utils.GetTestExecutablePath(test_name) +    command = [gtest_prog_path, '--gtest_output=json:%s' % self.output_dir_] +    p = gtest_test_utils.Subprocess(command, +                                    working_dir=gtest_test_utils.GetTempDir()) +    self.assert_(p.exited) +    self.assertEquals(0, p.exit_code) + +    # TODO(wan@google.com): libtool causes the built test binary to be +    #   named lt-gtest_xml_outfiles_test_ instead of +    #   gtest_xml_outfiles_test_.  To account for this possibility, we +    #   allow both names in the following code.  We should remove this +    #   hack when Chandler Carruth's libtool replacement tool is ready. +    output_file_name1 = test_name + '.json' +    output_file1 = os.path.join(self.output_dir_, output_file_name1) +    output_file_name2 = 'lt-' + output_file_name1 +    output_file2 = os.path.join(self.output_dir_, output_file_name2) +    self.assert_(os.path.isfile(output_file1) or os.path.isfile(output_file2), +                 output_file1) + +    if os.path.isfile(output_file1): +      with open(output_file1) as f: +        actual = json.load(f) +    else: +      with open(output_file2) as f: +        actual = json.load(f) +    self.assertEqual(expected, gtest_json_test_utils.normalize(actual)) + + +if __name__ == '__main__': +  os.environ['GTEST_STACK_TRACE_DEPTH'] = '0' +  gtest_test_utils.Main() diff --git a/googletest/test/gtest_json_output_unittest.py b/googletest/test/gtest_json_output_unittest.py new file mode 100644 index 00000000..4d23c3ab --- /dev/null +++ b/googletest/test/gtest_json_output_unittest.py @@ -0,0 +1,612 @@ +#!/usr/bin/env python +# Copyright 2018, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +#     * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +#     * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +#     * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Unit test for the gtest_json_output module.""" + +import datetime +import errno +import json +import os +import re +import sys + +import gtest_test_utils +import gtest_json_test_utils + + +GTEST_FILTER_FLAG = '--gtest_filter' +GTEST_LIST_TESTS_FLAG = '--gtest_list_tests' +GTEST_OUTPUT_FLAG = '--gtest_output' +GTEST_DEFAULT_OUTPUT_FILE = 'test_detail.json' +GTEST_PROGRAM_NAME = 'gtest_xml_output_unittest_' + +SUPPORTS_STACK_TRACES = False + +if SUPPORTS_STACK_TRACES: +  STACK_TRACE_TEMPLATE = '\nStack trace:\n*' +else: +  STACK_TRACE_TEMPLATE = '' + +EXPECTED_NON_EMPTY = { +    u'tests': 23, +    u'failures': 4, +    u'disabled': 2, +    u'errors': 0, +    u'timestamp': u'*', +    u'time': u'*', +    u'ad_hoc_property': u'42', +    u'name': u'AllTests', +    u'testsuites': [ +        { +            u'name': u'SuccessfulTest', +            u'tests': 1, +            u'failures': 0, +            u'disabled': 0, +            u'errors': 0, +            u'time': u'*', +            u'testsuite': [ +                { +                    u'name': u'Succeeds', +                    u'status': u'RUN', +                    u'time': u'*', +                    u'classname': u'SuccessfulTest' +                } +            ] +        }, +        { +            u'name': u'FailedTest', +            u'tests': 1, +            u'failures': 1, +            u'disabled': 0, +            u'errors': 0, +            u'time': u'*', +            u'testsuite': [ +                { +                    u'name': u'Fails', +                    u'status': u'RUN', +                    u'time': u'*', +                    u'classname': u'FailedTest', +                    u'failures': [ +                        { +                            u'failure': +                                u'gtest_xml_output_unittest_.cc:*\n' +                                u'Expected equality of these values:\n' +                                u'  1\n  2' + STACK_TRACE_TEMPLATE, +                            u'type': u'' +                        } +                    ] +                } +            ] +        }, +        { +            u'name': u'DisabledTest', +            u'tests': 1, +            u'failures': 0, +            u'disabled': 1, +            u'errors': 0, +            u'time': u'*', +            u'testsuite': [ +                { +                    u'name': u'DISABLED_test_not_run', +                    u'status': u'NOTRUN', +                    u'time': u'*', +                    u'classname': u'DisabledTest' +                } +            ] +        }, +        { +            u'name': u'MixedResultTest', +            u'tests': 3, +            u'failures': 1, +            u'disabled': 1, +            u'errors': 0, +            u'time': u'*', +            u'testsuite': [ +                { +                    u'name': u'Succeeds', +                    u'status': u'RUN', +                    u'time': u'*', +                    u'classname': u'MixedResultTest' +                }, +                { +                    u'name': u'Fails', +                    u'status': u'RUN', +                    u'time': u'*', +                    u'classname': u'MixedResultTest', +                    u'failures': [ +                        { +                            u'failure': +                                u'gtest_xml_output_unittest_.cc:*\n' +                                u'Expected equality of these values:\n' +                                u'  1\n  2' + STACK_TRACE_TEMPLATE, +                            u'type': u'' +                        }, +                        { +                            u'failure': +                                u'gtest_xml_output_unittest_.cc:*\n' +                                u'Expected equality of these values:\n' +                                u'  2\n  3' + STACK_TRACE_TEMPLATE, +                            u'type': u'' +                        } +                    ] +                }, +                { +                    u'name': u'DISABLED_test', +                    u'status': u'NOTRUN', +                    u'time': u'*', +                    u'classname': u'MixedResultTest' +                } +            ] +        }, +        { +            u'name': u'XmlQuotingTest', +            u'tests': 1, +            u'failures': 1, +            u'disabled': 0, +            u'errors': 0, +            u'time': u'*', +            u'testsuite': [ +                { +                    u'name': u'OutputsCData', +                    u'status': u'RUN', +                    u'time': u'*', +                    u'classname': u'XmlQuotingTest', +                    u'failures': [ +                        { +                            u'failure': +                                u'gtest_xml_output_unittest_.cc:*\n' +                                u'Failed\nXML output: <?xml encoding="utf-8">' +                                u'<top><![CDATA[cdata text]]></top>' + +                                STACK_TRACE_TEMPLATE, +                            u'type': u'' +                        } +                    ] +                } +            ] +        }, +        { +            u'name': u'InvalidCharactersTest', +            u'tests': 1, +            u'failures': 1, +            u'disabled': 0, +            u'errors': 0, +            u'time': u'*', +            u'testsuite': [ +                { +                    u'name': u'InvalidCharactersInMessage', +                    u'status': u'RUN', +                    u'time': u'*', +                    u'classname': u'InvalidCharactersTest', +                    u'failures': [ +                        { +                            u'failure': +                                u'gtest_xml_output_unittest_.cc:*\n' +                                u'Failed\nInvalid characters in brackets' +                                u' [\x01\x02]' + STACK_TRACE_TEMPLATE, +                            u'type': u'' +                        } +                    ] +                } +            ] +        }, +        { +            u'name': u'PropertyRecordingTest', +            u'tests': 4, +            u'failures': 0, +            u'disabled': 0, +            u'errors': 0, +            u'time': u'*', +            u'SetUpTestCase': u'yes', +            u'TearDownTestCase': u'aye', +            u'testsuite': [ +                { +                    u'name': u'OneProperty', +                    u'status': u'RUN', +                    u'time': u'*', +                    u'classname': u'PropertyRecordingTest', +                    u'key_1': u'1' +                }, +                { +                    u'name': u'IntValuedProperty', +                    u'status': u'RUN', +                    u'time': u'*', +                    u'classname': u'PropertyRecordingTest', +                    u'key_int': u'1' +                }, +                { +                    u'name': u'ThreeProperties', +                    u'status': u'RUN', +                    u'time': u'*', +                    u'classname': u'PropertyRecordingTest', +                    u'key_1': u'1', +                    u'key_2': u'2', +                    u'key_3': u'3' +                }, +                { +                    u'name': u'TwoValuesForOneKeyUsesLastValue', +                    u'status': u'RUN', +                    u'time': u'*', +                    u'classname': u'PropertyRecordingTest', +                    u'key_1': u'2' +                } +            ] +        }, +        { +            u'name': u'NoFixtureTest', +            u'tests': 3, +            u'failures': 0, +            u'disabled': 0, +            u'errors': 0, +            u'time': u'*', +            u'testsuite': [ +                { +                    u'name': u'RecordProperty', +                    u'status': u'RUN', +                    u'time': u'*', +                    u'classname': u'NoFixtureTest', +                    u'key': u'1' +                }, +                { +                    u'name': u'ExternalUtilityThatCallsRecordIntValuedProperty', +                    u'status': u'RUN', +                    u'time': u'*', +                    u'classname': u'NoFixtureTest', +                    u'key_for_utility_int': u'1' +                }, +                { +                    u'name': +                        u'ExternalUtilityThatCallsRecordStringValuedProperty', +                    u'status': u'RUN', +                    u'time': u'*', +                    u'classname': u'NoFixtureTest', +                    u'key_for_utility_string': u'1' +                } +            ] +        }, +        { +            u'name': u'TypedTest/0', +            u'tests': 1, +            u'failures': 0, +            u'disabled': 0, +            u'errors': 0, +            u'time': u'*', +            u'testsuite': [ +                { +                    u'name': u'HasTypeParamAttribute', +                    u'type_param': u'int', +                    u'status': u'RUN', +                    u'time': u'*', +                    u'classname': u'TypedTest/0' +                } +            ] +        }, +        { +            u'name': u'TypedTest/1', +            u'tests': 1, +            u'failures': 0, +            u'disabled': 0, +            u'errors': 0, +            u'time': u'*', +            u'testsuite': [ +                { +                    u'name': u'HasTypeParamAttribute', +                    u'type_param': u'long', +                    u'status': u'RUN', +                    u'time': u'*', +                    u'classname': u'TypedTest/1' +                } +            ] +        }, +        { +            u'name': u'Single/TypeParameterizedTestCase/0', +            u'tests': 1, +            u'failures': 0, +            u'disabled': 0, +            u'errors': 0, +            u'time': u'*', +            u'testsuite': [ +                { +                    u'name': u'HasTypeParamAttribute', +                    u'type_param': u'int', +                    u'status': u'RUN', +                    u'time': u'*', +                    u'classname': u'Single/TypeParameterizedTestCase/0' +                } +            ] +        }, +        { +            u'name': u'Single/TypeParameterizedTestCase/1', +            u'tests': 1, +            u'failures': 0, +            u'disabled': 0, +            u'errors': 0, +            u'time': u'*', +            u'testsuite': [ +                { +                    u'name': u'HasTypeParamAttribute', +                    u'type_param': u'long', +                    u'status': u'RUN', +                    u'time': u'*', +                    u'classname': u'Single/TypeParameterizedTestCase/1' +                } +            ] +        }, +        { +            u'name': u'Single/ValueParamTest', +            u'tests': 4, +            u'failures': 0, +            u'disabled': 0, +            u'errors': 0, +            u'time': u'*', +            u'testsuite': [ +                { +                    u'name': u'HasValueParamAttribute/0', +                    u'value_param': u'33', +                    u'status': u'RUN', +                    u'time': u'*', +                    u'classname': u'Single/ValueParamTest' +                }, +                { +                    u'name': u'HasValueParamAttribute/1', +                    u'value_param': u'42', +                    u'status': u'RUN', +                    u'time': u'*', +                    u'classname': u'Single/ValueParamTest' +                }, +                { +                    u'name': u'AnotherTestThatHasValueParamAttribute/0', +                    u'value_param': u'33', +                    u'status': u'RUN', +                    u'time': u'*', +                    u'classname': u'Single/ValueParamTest' +                }, +                { +                    u'name': u'AnotherTestThatHasValueParamAttribute/1', +                    u'value_param': u'42', +                    u'status': u'RUN', +                    u'time': u'*', +                    u'classname': u'Single/ValueParamTest' +                } +            ] +        } +    ] +} + +EXPECTED_FILTERED = { +    u'tests': 1, +    u'failures': 0, +    u'disabled': 0, +    u'errors': 0, +    u'time': u'*', +    u'timestamp': u'*', +    u'name': u'AllTests', +    u'ad_hoc_property': u'42', +    u'testsuites': [{ +        u'name': u'SuccessfulTest', +        u'tests': 1, +        u'failures': 0, +        u'disabled': 0, +        u'errors': 0, +        u'time': u'*', +        u'testsuite': [{ +            u'name': u'Succeeds', +            u'status': u'RUN', +            u'time': u'*', +            u'classname': u'SuccessfulTest', +        }] +    }], +} + +EXPECTED_EMPTY = { +    u'tests': 0, +    u'failures': 0, +    u'disabled': 0, +    u'errors': 0, +    u'time': u'*', +    u'timestamp': u'*', +    u'name': u'AllTests', +    u'testsuites': [], +} + +GTEST_PROGRAM_PATH = gtest_test_utils.GetTestExecutablePath(GTEST_PROGRAM_NAME) + +SUPPORTS_TYPED_TESTS = 'TypedTest' in gtest_test_utils.Subprocess( +    [GTEST_PROGRAM_PATH, GTEST_LIST_TESTS_FLAG], capture_stderr=False).output + + +class GTestJsonOutputUnitTest(gtest_test_utils.TestCase): +  """Unit test for Google Test's JSON output functionality. +  """ + +  # This test currently breaks on platforms that do not support typed and +  # type-parameterized tests, so we don't run it under them. +  if SUPPORTS_TYPED_TESTS: + +    def testNonEmptyJsonOutput(self): +      """Verifies JSON output for a Google Test binary with non-empty output. + +      Runs a test program that generates a non-empty JSON output, and +      tests that the JSON output is expected. +      """ +      self._TestJsonOutput(GTEST_PROGRAM_NAME, EXPECTED_NON_EMPTY, 1) + +  def testEmptyJsonOutput(self): +    """Verifies JSON output for a Google Test binary without actual tests. + +    Runs a test program that generates an empty JSON output, and +    tests that the JSON output is expected. +    """ + +    self._TestJsonOutput('gtest_no_test_unittest', EXPECTED_EMPTY, 0) + +  def testTimestampValue(self): +    """Checks whether the timestamp attribute in the JSON output is valid. + +    Runs a test program that generates an empty JSON output, and checks if +    the timestamp attribute in the testsuites tag is valid. +    """ +    actual = self._GetJsonOutput('gtest_no_test_unittest', [], 0) +    date_time_str = actual['timestamp'] +    # datetime.strptime() is only available in Python 2.5+ so we have to +    # parse the expected datetime manually. +    match = re.match(r'(\d+)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)', date_time_str) +    self.assertTrue( +        re.match, +        'JSON datettime string %s has incorrect format' % date_time_str) +    date_time_from_json = datetime.datetime( +        year=int(match.group(1)), month=int(match.group(2)), +        day=int(match.group(3)), hour=int(match.group(4)), +        minute=int(match.group(5)), second=int(match.group(6))) + +    time_delta = abs(datetime.datetime.now() - date_time_from_json) +    # timestamp value should be near the current local time +    self.assertTrue(time_delta < datetime.timedelta(seconds=600), +                    'time_delta is %s' % time_delta) + +  def testDefaultOutputFile(self): +    """Verifies the default output file name. + +    Confirms that Google Test produces an JSON output file with the expected +    default name if no name is explicitly specified. +    """ +    output_file = os.path.join(gtest_test_utils.GetTempDir(), +                               GTEST_DEFAULT_OUTPUT_FILE) +    gtest_prog_path = gtest_test_utils.GetTestExecutablePath( +        'gtest_no_test_unittest') +    try: +      os.remove(output_file) +    except OSError: +      e = sys.exc_info()[1] +      if e.errno != errno.ENOENT: +        raise + +    p = gtest_test_utils.Subprocess( +        [gtest_prog_path, '%s=json' % GTEST_OUTPUT_FLAG], +        working_dir=gtest_test_utils.GetTempDir()) +    self.assert_(p.exited) +    self.assertEquals(0, p.exit_code) +    self.assert_(os.path.isfile(output_file)) + +  def testSuppressedJsonOutput(self): +    """Verifies that no JSON output is generated. + +    Tests that no JSON file is generated if the default JSON listener is +    shut down before RUN_ALL_TESTS is invoked. +    """ + +    json_path = os.path.join(gtest_test_utils.GetTempDir(), +                             GTEST_PROGRAM_NAME + 'out.json') +    if os.path.isfile(json_path): +      os.remove(json_path) + +    command = [GTEST_PROGRAM_PATH, +               '%s=json:%s' % (GTEST_OUTPUT_FLAG, json_path), +               '--shut_down_xml'] +    p = gtest_test_utils.Subprocess(command) +    if p.terminated_by_signal: +      # p.signal is available only if p.terminated_by_signal is True. +      self.assertFalse( +          p.terminated_by_signal, +          '%s was killed by signal %d' % (GTEST_PROGRAM_NAME, p.signal)) +    else: +      self.assert_(p.exited) +      self.assertEquals(1, p.exit_code, +                        "'%s' exited with code %s, which doesn't match " +                        'the expected exit code %s.' +                        % (command, p.exit_code, 1)) + +    self.assert_(not os.path.isfile(json_path)) + +  def testFilteredTestJsonOutput(self): +    """Verifies JSON output when a filter is applied. + +    Runs a test program that executes only some tests and verifies that +    non-selected tests do not show up in the JSON output. +    """ + +    self._TestJsonOutput(GTEST_PROGRAM_NAME, EXPECTED_FILTERED, 0, +                         extra_args=['%s=SuccessfulTest.*' % GTEST_FILTER_FLAG]) + +  def _GetJsonOutput(self, gtest_prog_name, extra_args, expected_exit_code): +    """Returns the JSON output generated by running the program gtest_prog_name. + +    Furthermore, the program's exit code must be expected_exit_code. + +    Args: +      gtest_prog_name: Google Test binary name. +      extra_args: extra arguments to binary invocation. +      expected_exit_code: program's exit code. +    """ +    json_path = os.path.join(gtest_test_utils.GetTempDir(), +                             gtest_prog_name + 'out.json') +    gtest_prog_path = gtest_test_utils.GetTestExecutablePath(gtest_prog_name) + +    command = ( +        [gtest_prog_path, '%s=json:%s' % (GTEST_OUTPUT_FLAG, json_path)] + +        extra_args +    ) +    p = gtest_test_utils.Subprocess(command) +    if p.terminated_by_signal: +      self.assert_(False, +                   '%s was killed by signal %d' % (gtest_prog_name, p.signal)) +    else: +      self.assert_(p.exited) +      self.assertEquals(expected_exit_code, p.exit_code, +                        "'%s' exited with code %s, which doesn't match " +                        'the expected exit code %s.' +                        % (command, p.exit_code, expected_exit_code)) +    with open(json_path) as f: +      actual = json.load(f) +    return actual + +  def _TestJsonOutput(self, gtest_prog_name, expected, +                      expected_exit_code, extra_args=None): +    """Checks the JSON output generated by the Google Test binary. + +    Asserts that the JSON document generated by running the program +    gtest_prog_name matches expected_json, a string containing another +    JSON document.  Furthermore, the program's exit code must be +    expected_exit_code. + +    Args: +      gtest_prog_name: Google Test binary name. +      expected: expected output. +      expected_exit_code: program's exit code. +      extra_args: extra arguments to binary invocation. +    """ + +    actual = self._GetJsonOutput(gtest_prog_name, extra_args or [], +                                 expected_exit_code) +    self.assertEqual(expected, gtest_json_test_utils.normalize(actual)) + + +if __name__ == '__main__': +  os.environ['GTEST_STACK_TRACE_DEPTH'] = '1' +  gtest_test_utils.Main() diff --git a/googletest/test/gtest_json_test_utils.py b/googletest/test/gtest_json_test_utils.py new file mode 100644 index 00000000..4ef5f6fc --- /dev/null +++ b/googletest/test/gtest_json_test_utils.py @@ -0,0 +1,60 @@ +# Copyright 2018, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +#     * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +#     * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +#     * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Unit test utilities for gtest_json_output.""" + +import re + + +def normalize(obj): +  """Normalize output object. + +  Args: +     obj: Google Test's JSON output object to normalize. + +  Returns: +     Normalized output without any references to transient information that may +     change from run to run. +  """ +  def _normalize(key, value): +    if key == 'time': +      return re.sub(r'^\d+(\.\d+)?s$', u'*', value) +    elif key == 'timestamp': +      return re.sub(r'^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\dZ$', '*', value) +    elif key == 'failure': +      value = re.sub(r'^.*[/\\](.*:)\d+\n', '\\1*\n', value) +      return re.sub(r'Stack trace:\n(.|\n)*', 'Stack trace:\n*', value) +    else: +      return normalize(value) +  if isinstance(obj, dict): +    return {k: _normalize(k, v) for k, v in obj.items()} +  if isinstance(obj, list): +    return [normalize(x) for x in obj] +  else: +    return obj | 
