aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/testing/gtest_protobuf/matcher.h
blob: 006e56ee3da3f78404ada345d32145aac5c128e8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
#pragma once

#include <google/protobuf/util/message_differencer.h>
#include <util/generic/string.h>

#include <gmock/gmock.h>

#include <ostream>

namespace NGTest {
    // Protobuf message compare options
    struct TProtoCompareOptions {
        using EScope = google::protobuf::util::MessageDifferencer::Scope;
        using EMessageFieldComparison = google::protobuf::util::MessageDifferencer::MessageFieldComparison;
        using ERepeatedFieldComparison = google::protobuf::util::MessageDifferencer::RepeatedFieldComparison;
        using EFloatComparison = google::protobuf::util::DefaultFieldComparator::FloatComparison;

        // Sets the scope of the comparison
        EScope Scope = EScope::FULL;
        // Unset fields comparison method
        EMessageFieldComparison MessageFieldComparison = EMessageFieldComparison::EQUIVALENT;
        // Sets the type of comparison for repeated field
        ERepeatedFieldComparison RepeatedFieldComparison = ERepeatedFieldComparison::AS_LIST;
        // Floats and doubles are comparison method
        EFloatComparison FloatComparison = EFloatComparison::EXACT;
        // if true comparator will treat to float or double fields are equal when both are NaN
        bool TreatNanAsEqual = true;
    };

    // Implements the protobuf message equality matcher, which matches two messages using MessageDifferencer.
    template <typename T>
    class TEqualsProtoMatcher {
    public:
        TEqualsProtoMatcher(T expected, const TProtoCompareOptions& options)
            : Expected_(expected)
            , Options_(options)
        {
        }
        TEqualsProtoMatcher(const TEqualsProtoMatcher& other) = default;
        TEqualsProtoMatcher(TEqualsProtoMatcher&& other) = default;

        TEqualsProtoMatcher& operator=(const TEqualsProtoMatcher& other) = delete;
        TEqualsProtoMatcher& operator=(TEqualsProtoMatcher&& other) = delete;

        template <class X>
        operator ::testing::Matcher<X>() const {
            return ::testing::MakeMatcher(new TImpl<X>(Expected_, Options_));
        }

    private:
        // Implements the protobuf message equality matcher as a Matcher<X>.
        template <class X>
        class TImpl : public ::testing::MatcherInterface<X> {
        public:
            TImpl(T expected, const TProtoCompareOptions& options)
                : Expected_(expected)
                , Options_(options)
            {
            }

            bool MatchAndExplain(X actual, ::testing::MatchResultListener* listener) const override {
                google::protobuf::util::DefaultFieldComparator cmp;
                cmp.set_float_comparison(Options_.FloatComparison);
                cmp.set_treat_nan_as_equal(Options_.TreatNanAsEqual);

                google::protobuf::util::MessageDifferencer md;
                md.set_scope(Options_.Scope);
                md.set_message_field_comparison(Options_.MessageFieldComparison);
                md.set_repeated_field_comparison(Options_.RepeatedFieldComparison);
                md.set_field_comparator(&cmp);

                TString diff;
                md.ReportDifferencesToString(&diff);

                if (!md.Compare(actual, ::testing::internal::Unwrap(Expected_))) {
                    if (listener->IsInterested()) {
                        *listener << diff.c_str();
                    }
                    return false;
                } else {
                    if (listener->IsInterested()) {
                        *listener << "is equal.";
                    }
                    return true;
                }
            }

            void DescribeTo(::std::ostream* os) const override {
                *os << "message is equal to ";
                ::testing::internal::UniversalPrint(::testing::internal::Unwrap(Expected_), os);
            }

            void DescribeNegationTo(::std::ostream* os) const override {
                *os << "message isn't equal to ";
                ::testing::internal::UniversalPrint(::testing::internal::Unwrap(Expected_), os);
            }

        private:
            const T Expected_;
            const TProtoCompareOptions Options_;
        };

    private:
        const T Expected_;
        const TProtoCompareOptions Options_;
    };

    /**
     * Matcher that checks that protobuf messages are equal.
     *
     * Example:
     *
     * ```c++
     * EXPECT_THAT(actualMessage, EqualsProto(std::ref(expectedMessage));
     * ```
     * NOTE: according to the documentation there is a similar matcher in the internal gtest implementation of Google
     * and it may appear in the opensource in course of this `https://github.com/google/googletest/issues/1761`
     */

    template <class T>
    TEqualsProtoMatcher<T> EqualsProto(T expected, const TProtoCompareOptions& options = {}) {
        return {expected, options};
    }

    template <typename T>
    TEqualsProtoMatcher<T> EqualsProtoStrict(T expected) {
        return EqualsProto(expected, TProtoCompareOptions{
            // Either both fields must be present, or none of them (unset != default value)
            .MessageFieldComparison = TProtoCompareOptions::EMessageFieldComparison::EQUAL,
        });
    }
}