1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2015 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 #include "testapp_stats.h"
18 
19 #include <gsl/gsl>
20 
21 INSTANTIATE_TEST_CASE_P(TransportProtocols,
22                         StatsTest,
23                         ::testing::Values(TransportProtocols::McbpPlain,
24                                           TransportProtocols::McbpIpv6Plain,
25                                           TransportProtocols::McbpSsl,
26                                           TransportProtocols::McbpIpv6Ssl
27                                          ),
28                         ::testing::PrintToStringParamName());
29 
TEST_P(StatsTest, TestDefaultStats)30 TEST_P(StatsTest, TestDefaultStats) {
31     MemcachedConnection& conn = getConnection();
32     unique_cJSON_ptr stats;
33     stats = conn.stats("");
34 
35     // Don't expect the entire stats set, but we should at least have
36     // the pid
37     EXPECT_NE(nullptr, cJSON_GetObjectItem(stats.get(), "pid"));
38 }
39 
TEST_P(StatsTest, TestGetMeta)40 TEST_P(StatsTest, TestGetMeta) {
41     MemcachedConnection& conn = getConnection();
42 
43     // Set a document
44     Document doc;
45     doc.info.cas = mcbp::cas::Wildcard;
46     doc.info.flags = 0xcaffee;
47     doc.info.id = name;
48     doc.value = to_string(memcached_cfg.get());
49     conn.mutate(doc, 0, MutationType::Set);
50 
51     // Send 10 GET_META, this should not increase the `cmd_get` and `get_hits` stats
52     for (int i = 0; i < 10; i++) {
53         auto meta = conn.getMeta(doc.info.id, 0, GetMetaVersion::V1);
54         EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, meta.first);
55     }
56     auto stats = conn.stats("");
57     cJSON* cmd_get = cJSON_GetObjectItem(stats.get(), "cmd_get");
58     EXPECT_EQ(0, cmd_get->valueint);
59     cJSON* get_hits = cJSON_GetObjectItem(stats.get(), "get_hits");
60     EXPECT_EQ(0, get_hits->valueint);
61 
62     // Now, send 10 GET_META for a document that does not exist, this should
63     // not increase the `cmd_get` and `get_misses` stats or the `get_hits`
64     // stat
65     for (int i = 0; i < 10; i++) {
66         auto meta = conn.getMeta("no_key", 0, GetMetaVersion::V1);
67         EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_KEY_ENOENT, meta.first);
68     }
69     stats = conn.stats("");
70     cmd_get = cJSON_GetObjectItem(stats.get(), "cmd_get");
71     EXPECT_EQ(0, cmd_get->valueint);
72     cJSON* get_misses = cJSON_GetObjectItem(stats.get(), "get_misses");
73     EXPECT_EQ(0, get_misses->valueint);
74     get_hits = cJSON_GetObjectItem(stats.get(), "get_hits");
75     EXPECT_EQ(0, get_hits->valueint);
76 }
77 
TEST_P(StatsTest, StatsResetIsPrivileged)78 TEST_P(StatsTest, StatsResetIsPrivileged) {
79     MemcachedConnection& conn = getConnection();
80     unique_cJSON_ptr stats;
81 
82     try {
83         conn.stats("reset");
84         FAIL() << "reset is a privileged operation";
85     } catch (ConnectionError& error) {
86         EXPECT_TRUE(error.isAccessDenied());
87     }
88 
89     conn.authenticate("@admin", "password", "PLAIN");
90     conn.stats("reset");
91 }
92 
TEST_P(StatsTest, TestReset)93 TEST_P(StatsTest, TestReset) {
94     MemcachedConnection& conn = getConnection();
95     unique_cJSON_ptr stats;
96 
97     stats = conn.stats("");
98     ASSERT_NE(nullptr, stats.get());
99 
100     auto* count = cJSON_GetObjectItem(stats.get(), "cmd_get");
101     ASSERT_NE(nullptr, count);
102     EXPECT_EQ(cJSON_Number, count->type);
103     auto before = count->valueint;
104 
105     for (int ii = 0; ii < 10; ++ii) {
106         EXPECT_THROW(conn.get("foo", 0), ConnectionError);
107     }
108 
109     stats = conn.stats("");
110     count = cJSON_GetObjectItem(stats.get(), "cmd_get");
111     ASSERT_NE(nullptr, count);
112     EXPECT_EQ(cJSON_Number, count->type);
113     EXPECT_NE(before, count->valueint);
114 
115     // the cmd_get counter does work.. now check that reset sets it back..
116     resetBucket();
117 
118     stats = conn.stats("");
119     count = cJSON_GetObjectItem(stats.get(), "cmd_get");
120     ASSERT_NE(nullptr, count);
121     EXPECT_EQ(cJSON_Number, count->type);
122     EXPECT_EQ(0, count->valueint);
123 
124     // Just ensure that the "reset timings" is detected
125     // @todo add a separate test case for cmd timings stats
126     conn.authenticate("@admin", "password", "PLAIN");
127     conn.selectBucket("default");
128     stats = conn.stats("reset timings");
129 
130     // Just ensure that the "reset bogus" is detected..
131     try {
132         conn.stats("reset bogus");
133         FAIL()<<"stats reset bogus should throw an exception (non a valid cmd)";
134     } catch (ConnectionError& error) {
135         EXPECT_TRUE(error.isInvalidArguments());
136     }
137     conn.reconnect();
138 }
139 
140 /**
141  * MB-17815: The cmd_set stat is incremented multiple times if the underlying
142  * engine returns EWOULDBLOCK (which would happen for all operations when
143  * the underlying engine is operating in full eviction mode and the document
144  * isn't resident)
145  */
TEST_P(StatsTest, Test_MB_17815)146 TEST_P(StatsTest, Test_MB_17815) {
147     MemcachedConnection& conn = getConnection();
148 
149     unique_cJSON_ptr stats;
150 
151     stats = conn.stats("");
152     auto* count = cJSON_GetObjectItem(stats.get(), "cmd_set");
153     ASSERT_NE(nullptr, count);
154     EXPECT_EQ(cJSON_Number, count->type);
155     EXPECT_EQ(0, count->valueint);
156 
157     conn.configureEwouldBlockEngine(EWBEngineMode::Sequence,
158                                     ENGINE_EWOULDBLOCK,
159                                     0xfffffffd);
160 
161     Document doc;
162     doc.info.cas = mcbp::cas::Wildcard;
163     doc.info.flags = 0xcaffee;
164     doc.info.id = name;
165     doc.value = to_string(memcached_cfg.get());
166 
167     conn.mutate(doc, 0, MutationType::Add);
168     stats = conn.stats("");
169     count = cJSON_GetObjectItem(stats.get(), "cmd_set");
170     ASSERT_NE(nullptr, count);
171     EXPECT_EQ(cJSON_Number, count->type);
172     EXPECT_EQ(1, count->valueint);
173 }
174 
175 /**
176  * MB-17815: The cmd_set stat is incremented multiple times if the underlying
177  * engine returns EWOULDBLOCK (which would happen for all operations when
178  * the underlying engine is operating in full eviction mode and the document
179  * isn't resident). This test is specfically testing this error case with
180  * append (due to MB-28850) rather than the other MB-17815 test which tests
181  * Add.
182  */
TEST_P(StatsTest, Test_MB_17815_Append)183 TEST_P(StatsTest, Test_MB_17815_Append) {
184     MemcachedConnection& conn = getConnection();
185 
186     auto stats = conn.stats("");
187     auto* count = cJSON_GetObjectItem(stats.get(), "cmd_set");
188     ASSERT_NE(nullptr, count);
189     EXPECT_EQ(cJSON_Number, count->type);
190     EXPECT_EQ(0, count->valueint);
191 
192     // Allow first SET to succeed and then return EWOULDBLOCK for
193     // the Append (2nd op). Set all other operations to fail too since we
194     // do not expect any further operations.
195     conn.configureEwouldBlockEngine(EWBEngineMode::Sequence,
196                                     ENGINE_EWOULDBLOCK,
197                                     0xfffffffe /* Set to 0b11 111 110 */);
198 
199     // Set a document
200     Document doc;
201     doc.info.cas = mcbp::cas::Wildcard;
202     doc.info.flags = 0xcaffee;
203     doc.info.id = name;
204     doc.value = to_string(memcached_cfg.get());
205     conn.mutate(doc, 0, MutationType::Set);
206 
207     // Now append to the same doc
208     conn.mutate(doc, 0, MutationType::Append);
209     stats = conn.stats("");
210     count = cJSON_GetObjectItem(stats.get(), "cmd_set");
211     ASSERT_NE(nullptr, count);
212     EXPECT_EQ(cJSON_Number, count->type);
213     EXPECT_EQ(2, count->valueint);
214 }
215 
216 /**
217  * Verify that cmd_set is updated when we fail to perform the
218  * append operation.
219  */
TEST_P(StatsTest, Test_MB_29259_Append)220 TEST_P(StatsTest, Test_MB_29259_Append) {
221     MemcachedConnection& conn = getConnection();
222 
223     auto stats = conn.stats("");
224     auto* count = cJSON_GetObjectItem(stats.get(), "cmd_set");
225     ASSERT_NE(nullptr, count);
226     EXPECT_EQ(cJSON_Number, count->type);
227     EXPECT_EQ(0, count->valueint);
228 
229 
230     Document doc;
231     doc.info.cas = mcbp::cas::Wildcard;
232     doc.info.flags = 0xcaffee;
233     doc.info.id = name;
234     doc.value = to_string(memcached_cfg.get());
235 
236     // Try to append to non-existing document
237     try {
238         conn.mutate(doc, 0, MutationType::Append);
239         FAIL() << "Append on non-existing document should fail";
240     } catch (const ConnectionError& error) {
241         EXPECT_TRUE(error.isNotStored());
242     }
243 
244     stats = conn.stats("");
245     count = cJSON_GetObjectItem(stats.get(), "cmd_set");
246     ASSERT_NE(nullptr, count);
247     EXPECT_EQ(cJSON_Number, count->type);
248     EXPECT_EQ(1, count->valueint);
249 }
250 
TEST_P(StatsTest, TestAppend)251 TEST_P(StatsTest, TestAppend) {
252     MemcachedConnection& conn = getConnection();
253 
254     // Set a document
255     Document doc;
256     doc.info.cas = mcbp::cas::Wildcard;
257     doc.info.flags = 0xcaffee;
258     doc.info.id = name;
259     doc.value = to_string(memcached_cfg.get());
260     conn.mutate(doc, 0, MutationType::Set);
261 
262     // Send 10 appends, this should increase the `cmd_set` stat by 10
263     for (int i = 0; i < 10; i++) {
264         conn.mutate(doc, 0, MutationType::Append);
265     }
266     auto stats = conn.stats("");
267     cJSON* cmd_set = cJSON_GetObjectItem(stats.get(), "cmd_set");
268     // In total we expect 11 sets, since there was the initial set
269     // and then 10 appends
270     EXPECT_EQ(11, cmd_set->valueint);
271 }
272 
TEST_P(StatsTest, TestSettings)273 TEST_P(StatsTest, TestSettings) {
274     MemcachedConnection& conn = getConnection();
275     // @todo verify that I get all of the expected settings. for now
276     //       just verify that I've got the ones I expect...
277     unique_cJSON_ptr stats;
278     ASSERT_NO_THROW(stats = conn.stats("settings"));
279     ASSERT_NE(nullptr, stats.get());
280     ASSERT_NE(nullptr, cJSON_GetObjectItem(stats.get(), "maxconns"));
281 
282     // skip interfaces....
283 
284     ASSERT_NE(nullptr, cJSON_GetObjectItem(stats.get(), "verbosity"));
285     ASSERT_NE(nullptr, cJSON_GetObjectItem(stats.get(), "num_threads"));
286     ASSERT_NE(nullptr,
287               cJSON_GetObjectItem(stats.get(), "reqs_per_event_high_priority"));
288     ASSERT_NE(nullptr,
289               cJSON_GetObjectItem(stats.get(), "reqs_per_event_med_priority"));
290     ASSERT_NE(nullptr,
291               cJSON_GetObjectItem(stats.get(), "reqs_per_event_low_priority"));
292     ASSERT_NE(nullptr,
293               cJSON_GetObjectItem(stats.get(), "reqs_per_event_def_priority"));
294     ASSERT_NE(nullptr, cJSON_GetObjectItem(stats.get(), "auth_enabled_sasl"));
295     ASSERT_NE(nullptr, cJSON_GetObjectItem(stats.get(), "auth_sasl_engine"));
296 
297     // skip extensions, loggers and daemons
298 
299     // Skip audit.. it is "optional" and we don't pass it to the config
300 }
301 
TEST_P(StatsTest, TestAuditNoAccess)302 TEST_P(StatsTest, TestAuditNoAccess) {
303     MemcachedConnection& conn = getConnection();
304 
305     try {
306         conn.stats("audit");
307         FAIL() << "stats audit should throw an exception (non privileged)";
308     } catch (ConnectionError& error) {
309         EXPECT_TRUE(error.isAccessDenied());
310     }
311 }
312 
TEST_P(StatsTest, TestAudit)313 TEST_P(StatsTest, TestAudit) {
314     MemcachedConnection& conn = getConnection();
315     conn.authenticate("@admin", "password", "PLAIN");
316 
317     unique_cJSON_ptr stats;
318     stats = conn.stats("audit");
319     EXPECT_NE(nullptr, stats.get());
320     EXPECT_EQ(2, cJSON_GetArraySize(stats.get()));
321 
322     auto* enabled = cJSON_GetObjectItem(stats.get(), "enabled");
323     EXPECT_NE(nullptr, enabled) << "Missing field \"enabled\"";
324     EXPECT_EQ(cJSON_False, enabled->type);
325 
326     auto* dropped = cJSON_GetObjectItem(stats.get(), "dropped_events");
327     EXPECT_NE(nullptr, dropped) << "Missing field \"dropped_events\"";
328     EXPECT_EQ(cJSON_Number, dropped->type);
329     EXPECT_EQ(0, dropped->valueint);
330 
331     conn.reconnect();
332 }
333 
TEST_P(StatsTest, TestBucketDetailsNoAccess)334 TEST_P(StatsTest, TestBucketDetailsNoAccess) {
335     MemcachedConnection& conn = getConnection();
336 
337     try {
338         conn.stats("bucket_details");
339         FAIL() <<
340                "stats bucket_details should throw an exception (non privileged)";
341     } catch (ConnectionError& error) {
342         EXPECT_TRUE(error.isAccessDenied());
343     }
344 }
345 
TEST_P(StatsTest, TestBucketDetails)346 TEST_P(StatsTest, TestBucketDetails) {
347     MemcachedConnection& conn = getConnection();
348     conn.authenticate("@admin", "password", "PLAIN");
349 
350     unique_cJSON_ptr stats;
351     stats = conn.stats("bucket_details");
352     ASSERT_NE(nullptr, stats.get());
353     ASSERT_EQ(1, cJSON_GetArraySize(stats.get()));
354     std::string key(stats.get()->child->string);
355     ASSERT_EQ("bucket details", key);
356     unique_cJSON_ptr json(cJSON_Parse(stats.get()->child->valuestring));
357 
358     // bucket details contains a single entry which is named "buckets" and
359     // contains an arrray
360     cJSON* array = cJSON_GetObjectItem(json.get(), "buckets");
361     EXPECT_EQ(cJSON_Array, array->type);
362 
363     // we have two bucket2, nobucket and default
364     EXPECT_EQ(2, cJSON_GetArraySize(array));
365 
366     // Validate each bucket entry (I should probably extend it with checking
367     // of the actual values
368     for (cJSON* bucket = array->child;
369          bucket != nullptr; bucket = bucket->next) {
370         EXPECT_EQ(5, cJSON_GetArraySize(bucket));
371         EXPECT_NE(nullptr, cJSON_GetObjectItem(bucket, "index"));
372         EXPECT_NE(nullptr, cJSON_GetObjectItem(bucket, "state"));
373         EXPECT_NE(nullptr, cJSON_GetObjectItem(bucket, "clients"));
374         EXPECT_NE(nullptr, cJSON_GetObjectItem(bucket, "name"));
375         EXPECT_NE(nullptr, cJSON_GetObjectItem(bucket, "type"));
376     }
377 
378     conn.reconnect();
379 }
380 
TEST_P(StatsTest, TestSchedulerInfo)381 TEST_P(StatsTest, TestSchedulerInfo) {
382     auto stats = getConnection().stats("worker_thread_info");
383     // We should at least have an entry for the first thread
384     EXPECT_NE(nullptr, cJSON_GetObjectItem(stats.get(), "0"));
385 }
386 
TEST_P(StatsTest, TestSchedulerInfo_Aggregate)387 TEST_P(StatsTest, TestSchedulerInfo_Aggregate) {
388     auto stats = getConnection().stats("worker_thread_info aggregate");
389     EXPECT_NE(nullptr, cJSON_GetObjectItem(stats.get(), "aggregate"));
390 }
391 
TEST_P(StatsTest, TestSchedulerInfo_InvalidSubcommand)392 TEST_P(StatsTest, TestSchedulerInfo_InvalidSubcommand) {
393     try {
394         getConnection().stats("worker_thread_info foo");
395         FAIL() << "Invalid subcommand";
396     } catch (const ConnectionError& error) {
397         EXPECT_TRUE(error.isInvalidArguments());
398     }
399 }
400 
TEST_P(StatsTest, TestAggregate)401 TEST_P(StatsTest, TestAggregate) {
402     MemcachedConnection& conn = getConnection();
403     auto stats = conn.stats("aggregate");
404     // Don't expect the entire stats set, but we should at least have
405     // the pid
406     EXPECT_NE(nullptr, cJSON_GetObjectItem(stats.get(), "pid"));
407 }
408 
TEST_P(StatsTest, TestConnections)409 TEST_P(StatsTest, TestConnections) {
410     MemcachedConnection& conn = getConnection();
411     conn.hello("TestConnections", "1.0", "test connections test");
412     unique_cJSON_ptr stats;
413     stats = conn.stats("connections");
414     ASSERT_NE(nullptr, stats.get());
415     // We have at _least_ 2 connections
416     ASSERT_LE(2, cJSON_GetArraySize(stats.get()));
417 
418     int sock = -1;
419 
420     // Unfortuately they're all mapped as a " " : "json" pairs, so lets
421     // validate that at least thats true:
422     for (auto* conn = stats.get()->child; conn != nullptr; conn = conn->next) {
423         unique_cJSON_ptr json(cJSON_Parse(conn->valuestring));
424         ASSERT_NE(nullptr, json.get());
425         // the _this_ pointer should at least be there
426         ASSERT_NE(nullptr, cJSON_GetObjectItem(json.get(), "connection"));
427         if (sock == -1) {
428             auto* ptr = cJSON_GetObjectItem(json.get(), "agent_name");
429             if (ptr != nullptr) {
430                 ASSERT_EQ(cJSON_String, ptr->type);
431                 if (strcmp("TestConnections 1.0", ptr->valuestring) == 0) {
432                     ptr = cJSON_GetObjectItem(json.get(), "socket");
433                     if (ptr != nullptr) {
434                         EXPECT_EQ(cJSON_Number, ptr->type);
435                         sock = gsl::narrow<int>(ptr->valueint);
436                     }
437                 }
438             }
439         }
440     }
441 
442     ASSERT_NE(-1, sock) << "Failed to locate the connection object";
443     stats = conn.stats("connections " + std::to_string(sock));
444     ASSERT_NE(nullptr, stats.get());
445     ASSERT_EQ(1, cJSON_GetArraySize(stats.get()));
446     unique_cJSON_ptr json(cJSON_Parse(stats.get()->child->valuestring));
447     ASSERT_NE(nullptr, json.get());
448     auto* ptr = cJSON_GetObjectItem(json.get(), "socket");
449     ASSERT_NE(nullptr, ptr);
450     EXPECT_EQ(cJSON_Number, ptr->type);
451     EXPECT_EQ(sock, ptr->valueint);
452 }
453 
TEST_P(StatsTest, TestConnectionsInvalidNumber)454 TEST_P(StatsTest, TestConnectionsInvalidNumber) {
455     MemcachedConnection& conn = getConnection();
456     try {
457         auto stats = conn.stats("connections xxx");
458         FAIL() << "Did not detect incorrect connection number";
459 
460     } catch (ConnectionError& error) {
461         EXPECT_TRUE(error.isInvalidArguments());
462     }
463 }
464 
TEST_P(StatsTest, TestTopkeys)465 TEST_P(StatsTest, TestTopkeys) {
466     MemcachedConnection& conn = getConnection();
467 
468     for (int ii = 0; ii < 10; ++ii) {
469         Document doc;
470         doc.info.cas = mcbp::cas::Wildcard;
471         doc.info.flags = 0xcaffee;
472         doc.info.id = name;
473         doc.value = to_string(memcached_cfg.get());
474 
475         conn.mutate(doc, 0, MutationType::Set);
476     }
477 
478     auto stats = conn.stats("topkeys");
479     cJSON* elem = cJSON_GetObjectItem(stats.get(), name.c_str());
480     EXPECT_NE(nullptr, elem);
481 }
482 
TEST_P(StatsTest, TestTopkeysJson)483 TEST_P(StatsTest, TestTopkeysJson) {
484     MemcachedConnection& conn = getConnection();
485 
486     for (int ii = 0; ii < 10; ++ii) {
487         Document doc;
488         doc.info.cas = mcbp::cas::Wildcard;
489         doc.info.flags = 0xcaffee;
490         doc.info.id = name;
491         doc.value = to_string(memcached_cfg.get());
492 
493         conn.mutate(doc, 0, MutationType::Set);
494     }
495 
496     auto stats = conn.stats("topkeys_json");
497     auto* topkeys = cJSON_GetObjectItem(stats.get(), "topkeys_json");
498     ASSERT_NE(nullptr, topkeys);
499     unique_cJSON_ptr value(cJSON_Parse(topkeys->valuestring));
500     ASSERT_NE(nullptr, value.get());
501     bool found = false;
502     cJSON* items = cJSON_GetObjectItem(value.get(), "topkeys");
503     for (auto* child = items->child;
504          child != nullptr; child = child->next) {
505         auto* key = cJSON_GetObjectItem(child, "key");
506         ASSERT_NE(nullptr, key);
507         std::string val(key->valuestring);
508         if (name == val) {
509             found = true;
510             break;
511         }
512     }
513 
514     EXPECT_TRUE(found);
515 }
516 
TEST_P(StatsTest, TestSubdocExecute)517 TEST_P(StatsTest, TestSubdocExecute) {
518     MemcachedConnection& conn = getConnection();
519     unique_cJSON_ptr stats;
520     stats = conn.stats("subdoc_execute");
521 
522     // @todo inspect the content. for now just validate that we've got a
523     //       single element in there..
524     EXPECT_EQ(1, cJSON_GetArraySize(stats.get()));
525     std::string value(stats.get()->child->valuestring);
526     EXPECT_EQ(0, value.find("{\"ns\":"));
527 }
528 
TEST_P(StatsTest, TestResponseStats)529 TEST_P(StatsTest, TestResponseStats) {
530     int successCount = getResponseCount(PROTOCOL_BINARY_RESPONSE_SUCCESS);
531     // 2 successes expected:
532     // 1. The previous stats call sending the JSON
533     // 2. The previous stats call sending a null packet to mark end of stats
534     EXPECT_EQ(successCount + statResps(),
535               getResponseCount(PROTOCOL_BINARY_RESPONSE_SUCCESS));
536 }
537 
TEST_P(StatsTest, TracingStatsIsPrivileged)538 TEST_P(StatsTest, TracingStatsIsPrivileged) {
539     MemcachedConnection& conn = getConnection();
540 
541     try {
542         conn.stats("tracing");
543         FAIL() << "tracing is a privileged operation";
544     } catch (ConnectionError& error) {
545         EXPECT_TRUE(error.isAccessDenied());
546     }
547 
548     conn.authenticate("@admin", "password", "PLAIN");
549     conn.stats("tracing");
550 }
551 
TEST_P(StatsTest, TestTracingStats)552 TEST_P(StatsTest, TestTracingStats) {
553     MemcachedConnection& conn = getConnection();
554     conn.authenticate("@admin", "password", "PLAIN");
555 
556     auto stats = conn.stats("tracing");
557 
558     // Just check that we got some stats, no need to check all of them
559     // as we don't want memcached to be testing phosphor's logic
560     EXPECT_LT(0, cJSON_GetArraySize(stats.get()));
561     auto* enabled = cJSON_GetObjectItem(stats.get(), "log_is_enabled");
562     EXPECT_NE(nullptr, enabled);
563 }
564 
565 /**
566  * Subclass of StatsTest which doesn't have a default bucket; hence connections
567  * will intially not be associated with any bucket.
568  */
569 class NoBucketStatsTest : public StatsTest {
570 public:
SetUpTestCase()571     static void SetUpTestCase() {
572         StatsTest::SetUpTestCase();
573     }
574 
575     // Setup as usual, but delete the default bucket before starting the
576     // testcase and reconnect) so the user isn't associated with any bucket.
577     void SetUp() override {
578         StatsTest::SetUp();
579         DeleteTestBucket();
580         getConnection().reconnect();
581     }
582 
583     // Reverse of above - re-create the default bucket to keep the parent
584     // classes happy.
585     void TearDown() override {
586         CreateTestBucket();
587         StatsTest::TearDown();
588     }
589 };
590 
TEST_P(NoBucketStatsTest, TestTopkeysNoBucket)591 TEST_P(NoBucketStatsTest, TestTopkeysNoBucket) {
592     MemcachedConnection& conn = getConnection();
593     conn.authenticate("@admin", "password", "PLAIN");
594 
595     // The actual request is expected fail with a nobucket exception.
596     EXPECT_THROW(conn.stats("topkeys"), std::runtime_error);
597 }
598 
599 INSTANTIATE_TEST_CASE_P(TransportProtocols,
600                         NoBucketStatsTest,
601                         ::testing::Values(TransportProtocols::McbpPlain,
602                                           TransportProtocols::McbpIpv6Plain,
603                                           TransportProtocols::McbpSsl,
604                                           TransportProtocols::McbpIpv6Ssl
605                                          ),
606                         ::testing::PrintToStringParamName());
607