1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2017 Couchbase, Inc.
4  *
5  *   Licensed under the Apache License, Version 2.0 (the "License");
6  *   you may not use this file except in compliance with the License.
7  *   You may obtain a copy of the License at
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *   Unless required by applicable law or agreed to in writing, software
12  *   distributed under the License is distributed on an "AS IS" BASIS,
13  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *   See the License for the specific language governing permissions and
15  *   limitations under the License.
16  */
17 #pragma once
18 
19 #include "config.h"
20 #include "testapp.h"
21 #include <sstream>
22 #include <type_traits>
23 
24 #include <utilities/protocol2text.h>
25 #include <include/memcached/util.h>
26 
27 /**
28  * Unfortunately I'm largely recreating some of GTest's internals for this
29  * function. GTest provides similar helpers like these, but they are not
30  * intended for public consumption. Ultimately, many tests call this, so
31  * it's best to favor clear messages over small code.
32  */
33 class AssertHelper {
34 public:
AssertHelper()35     AssertHelper() : cur_failure(::testing::AssertionFailure()) {
36     }
37 
AssertHelper(::testing::AssertionResult & existing)38     AssertHelper(::testing::AssertionResult& existing)
39         : cur_failure(existing), has_failure(!cur_failure) {
40     }
41 
combine(::testing::AssertionResult & existing)42     void combine(::testing::AssertionResult& existing) {
43         if (!existing) {
44             has_failure = true;
45             cur_failure << existing;
46         }
47     }
48 
49     template <typename T1, typename T2>
eq(const char * astr,const char * bstr,const T1 & a,const T2 & b)50     ::testing::AssertionResult& eq(const char* astr, const char* bstr,
51                                    const T1& a, const T2& b) {
52         if (a != b) {
53             return assertOp(astr, bstr, a, b, "==");
54         } else {
55             return returnDummy();
56         }
57     }
58 
59     template <typename T1, typename T2>
ne(const char * astr,const char * bstr,const T1 & a,const T2 & b)60     ::testing::AssertionResult& ne(const char* astr, const char* bstr,
61                                    const T1& a, const T2& b) {
62         if (a == b) {
63             return assertOp(astr, bstr, a, b, "!=");
64         } else {
65             return returnDummy();
66         }
67     }
68 
69     template <typename T1, typename T2>
gt(const char * astr,const char * bstr,const T1 & a,const T2 & b)70     ::testing::AssertionResult gt(const char* astr, const char* bstr,
71                                   const T1& a, const T2& b) {
72         if (!(a > b)) {
73             return assertOp(astr, bstr, a, b, ">");
74         } else {
75             return returnDummy();
76         }
77     }
78 
79     template <typename T1, typename T2>
ge(const char * astr,const char * bstr,const T1 & a,const T2 & b)80     ::testing::AssertionResult ge(const char* astr, const char* bstr,
81                                    const T1& a, const T2& b) {
82         if (!(a >= b)) {
83             return assertOp(astr, bstr, a, b, ">=");
84         } else {
85             return returnDummy();
86         }
87     }
88 
89     template <typename T>
empty(const char * s,const T & a)90     ::testing::AssertionResult empty(const char* s, const T& a) {
91         if (!a.empty()) {
92             return assertOp("empty()", std::to_string(a.size()), "<EMPTY>");
93         } else {
94             return returnDummy();
95         }
96     }
97 
98     ::testing::AssertionResult& fail(const char* msg = nullptr) {
99         if (msg != nullptr) {
100             cur_failure << msg << std::endl;
101         }
102         has_failure = true;
103         return cur_failure;
104     }
105 
result()106     ::testing::AssertionResult result() {
107         if (has_failure) {
108             return cur_failure;
109         } else {
110             return ::testing::AssertionSuccess();
111         }
112     }
113 
hasFailure()114     bool hasFailure() const {
115         return has_failure;
116     }
117 
118 private:
119     template <typename T>
formatArg(const T & arg)120     std::string formatArg(const T& arg) const {
121         return std::to_string(arg);
122     }
123 
124     // Don't call this variant iff the first argument is an enum and the second
125     // argument isn't. We want to convert it in the other variant
126     template <typename T1, typename T2,
127               typename std::enable_if<
128                   !std::is_enum<T1>::value || std::is_same<T1,T2>::value,
129                   bool>::type = true
130              >
assertOp(const char * expstr,const char * gotstr,const T1 & expected,const T2 & got,const char * opstr)131     ::testing::AssertionResult& assertOp(const char* expstr, const char* gotstr,
132                                          const T1& expected, const T2& got,
133                                          const char* opstr) {
134         return fail()
135                 << "Expected: " << std::endl
136                 << "  " << gotstr << " " << opstr << " " << expstr
137                 << " (" << formatArg(expected) << ")" << std::endl
138                 << "Actual: " << std::endl
139                 << "  " << gotstr << ": " << formatArg(got) << std::endl;
140     }
141 
142     // Called to coerce the second argument into an enum.
143     template <typename T1, typename T2,
144               typename std::enable_if<
145                   std::is_enum<T1>::value && !std::is_same<T1, T2>::value,
146                   bool>::type = true
147              >
assertOp(const char * expstr,const char * gotstr,const T1 & expected,const T2 & got,const char * opstr)148     ::testing::AssertionResult& assertOp(const char* expstr, const char *gotstr,
149                                          const T1& expected, const T2& got,
150                                          const char* opstr) {
151         auto converted_got = static_cast<T1>(got);
152         return assertOp(expstr, gotstr, expected, converted_got, opstr);
153     }
154 
returnDummy()155     ::testing::AssertionResult& returnDummy() {
156         static auto tmp = ::testing::AssertionSuccess();
157         return tmp;
158     }
159     ::testing::AssertionResult cur_failure;
160     bool has_failure = false;
161 };
162 
163 template<>
formatArg(const protocol_binary_response_status & arg)164 inline std::string AssertHelper::formatArg(const protocol_binary_response_status& arg) const {
165     // We want a hex representation
166     std::stringstream ss;
167     ss << memcached_status_2_text(arg) << " (0x" << std::hex << arg << ")";
168     return ss.str();
169 }
170 
171 template<>
formatArg(const protocol_binary_command & cmd)172 inline std::string AssertHelper::formatArg(const protocol_binary_command& cmd) const {
173     std::stringstream ss;
174     ss << memcached_opcode_2_text(cmd) << "(0x" << std::hex << cmd << ")";
175     return ss.str();
176 }
177 
178 template<>
formatArg(const std::string & s)179 inline std::string AssertHelper::formatArg(const std::string& s) const {
180     return s;
181 }
182 
183 #define TESTAPP_EXPECT_EQ(helper, a, b) helper.eq(#a, #b, a, b)
184 #define TESTAPP_EXPECT_NE(helper, a, b) helper.ne(#b, #b, a, b)
185 #define TESTAPP_EXPECT_GE(helper, a, b) helper.ge(#a, #b, a, b)
186 #define TESTAPP_EXPECT_GT(helper, a, b) helper.gt(#a, #b, a, b)
187