62bab4c5540e8fe6afdfe856759b8e12bfa6d96b
[ep-engine.git] / tests / ep_testsuite_common.cc
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
18 #include "ep_testsuite_common.h"
19 #include "ep_test_apis.h"
20
21 #include <cstring>
22 #include <iostream>
23 #include <string>
24 #include <sstream>
25
26 #include <sys/stat.h>
27 #ifdef _MSC_VER
28 #include <direct.h>
29 #define mkdir(a, b) _mkdir(a)
30 #else
31 #include <sys/wait.h>
32 #endif
33
34 #include <platform/cb_malloc.h>
35 #include <platform/dirutils.h>
36
37 const char *dbname_env = NULL;
38
39 static enum test_result skipped_test_function(ENGINE_HANDLE *h,
40                                               ENGINE_HANDLE_V1 *h1);
41
42 BaseTestCase::BaseTestCase(const char *_name, const char *_cfg, bool _skip)
43   : name(_name),
44     cfg(_cfg),
45     skip(_skip) {
46 }
47
48 BaseTestCase::BaseTestCase(const BaseTestCase &o)
49   : name(o.name),
50     cfg(o.cfg),
51     skip(o.skip) {
52
53     memset(&test, 0, sizeof(test));
54     test = o.test;
55 }
56
57 TestCase::TestCase(const char *_name,
58                    enum test_result(*_tfun)(ENGINE_HANDLE *, ENGINE_HANDLE_V1 *),
59                    bool(*_test_setup)(ENGINE_HANDLE *, ENGINE_HANDLE_V1 *),
60                    bool(*_test_teardown)(ENGINE_HANDLE *, ENGINE_HANDLE_V1 *),
61                    const char *_cfg,
62                    enum test_result (*_prepare)(engine_test_t *test),
63                    void (*_cleanup)(engine_test_t *test, enum test_result result),
64                    bool _skip)
65   : BaseTestCase(_name, _cfg, _skip) {
66
67     memset(&test, 0, sizeof(test));
68     test.tfun = _tfun;
69     test.test_setup = _test_setup;
70     test.test_teardown = _test_teardown;
71     test.prepare = _prepare;
72     test.cleanup = _cleanup;
73 }
74
75 TestCaseV2::TestCaseV2(const char *_name,
76                        enum test_result(*_tfun)(engine_test_t *),
77                        bool(*_test_setup)(engine_test_t *),
78                        bool(*_test_teardown)(engine_test_t *),
79                        const char *_cfg,
80                        enum test_result (*_prepare)(engine_test_t *test),
81                        void (*_cleanup)(engine_test_t *test, enum test_result result),
82                        bool _skip)
83   : BaseTestCase(_name, _cfg, _skip) {
84
85     memset(&test, 0, sizeof(test));
86     test.api_v2.tfun = _tfun;
87     test.api_v2.test_setup = _test_setup;
88     test.api_v2.test_teardown = _test_teardown;
89     test.prepare = _prepare;
90     test.cleanup = _cleanup;
91 }
92
93 engine_test_t* BaseTestCase::getTest() {
94     engine_test_t *ret = &test;
95
96     std::string nm(name);
97     std::stringstream ss;
98
99     if (cfg != 0) {
100         ss << cfg << ";";
101     } else {
102         ss << "flushall_enabled=true;";
103     }
104
105     // Default to the suite's dbname if the test config didn't already
106     // specify it.
107     if ((cfg == nullptr) ||
108         (std::string(cfg).find("dbname=") == std::string::npos)) {
109         ss << "dbname=" << default_dbname << ";";
110     }
111
112     if (skip) {
113         nm.append(" (skipped)");
114         ret->tfun = skipped_test_function;
115     } else {
116         nm.append(" (couchstore)");
117     }
118
119     ret->name = cb_strdup(nm.c_str());
120     std::string config = ss.str();
121     if (config.length() == 0) {
122         ret->cfg = 0;
123     } else {
124         ret->cfg = cb_strdup(config.c_str());
125     }
126
127     return ret;
128 }
129
130
131 static enum test_result skipped_test_function(ENGINE_HANDLE *h,
132                                               ENGINE_HANDLE_V1 *h1) {
133     (void) h;
134     (void) h1;
135     return SKIPPED;
136 }
137
138 enum test_result rmdb(const char* path) {
139     cb::io::rmrf(path);
140     if (access(path, F_OK) != -1) {
141         std::cerr << "Failed to remove: " << path << " " << std::endl;
142         return FAIL;
143     }
144     return SUCCESS;
145 }
146
147 bool test_setup(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) {
148     wait_for_warmup_complete(h, h1);
149
150     check(set_vbucket_state(h, h1, 0, vbucket_state_active),
151           "Failed to set VB0 state.");
152
153     const auto bucket_type = get_str_stat(h, h1, "ep_bucket_type");
154     if (bucket_type == "persistent") {
155         // Wait for vb0's state (active) to be persisted to disk, that way
156         // we know the KVStore files exist on disk.
157         wait_for_stat_to_be_gte(h, h1, "ep_persist_vbstate_total", 1);
158     } else if (bucket_type == "ephemeral") {
159         // No persistence to wait for here.
160     } else {
161         check(false,
162               (std::string("test_setup: unknown bucket_type '") + bucket_type +
163                "' - cannot continue.")
164                       .c_str());
165         return false;
166     }
167
168     // warmup is complete, notify ep engine that it must now enable
169     // data traffic
170     protocol_binary_request_header *pkt = createPacket(PROTOCOL_BINARY_CMD_ENABLE_TRAFFIC);
171     check(h1->unknown_command(h, NULL, pkt, add_response, testHarness.doc_namespace) == ENGINE_SUCCESS,
172           "Failed to enable data traffic");
173     cb_free(pkt);
174
175     return true;
176 }
177
178 bool teardown(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) {
179     (void)h; (void)h1;
180     vals.clear();
181     return true;
182 }
183
184 bool teardown_v2(engine_test_t* test) {
185     (void)test;
186     vals.clear();
187     return true;
188 }
189
190 std::string get_dbname(const char* test_cfg) {
191     std::string dbname;
192
193     if (!test_cfg) {
194         dbname.assign(dbname_env);
195         return dbname;
196     }
197
198     const char *nm = strstr(test_cfg, "dbname=");
199     if (nm == NULL) {
200         dbname.assign(dbname_env);
201     } else {
202         dbname.assign(nm + 7);
203         std::string::size_type end = dbname.find(';');
204         if (end != dbname.npos) {
205             dbname = dbname.substr(0, end);
206         }
207     }
208     return dbname;
209 }
210
211 enum test_result prepare(engine_test_t *test) {
212 #ifdef __sun
213         // Some of the tests doesn't work on Solaris.. Don't know why yet..
214         if (strstr(test->name, "concurrent set") != NULL ||
215             strstr(test->name, "retain rowid over a soft delete") != NULL)
216         {
217             return SKIPPED;
218         }
219 #endif
220
221     std::string dbname = get_dbname(test->cfg);
222     /* Remove if the same DB directory already exists */
223     rmdb(dbname.c_str());
224     mkdir(dbname.c_str(), 0777);
225     return SUCCESS;
226 }
227
228 enum test_result prepare_ep_bucket(engine_test_t* test) {
229     std::string cfg{test->cfg};
230     if (cfg.find("bucket_type=ephemeral") != std::string::npos) {
231         return SKIPPED;
232     }
233
234     // Perform whatever prep the "base class" function wants.
235     return prepare(test);
236 }
237
238 enum test_result prepare_ephemeral_bucket(engine_test_t* test) {
239     std::string cfg{test->cfg};
240     if (cfg.find("bucket_type=ephemeral") == std::string::npos) {
241         return SKIPPED;
242     }
243
244     // Perform whatever prep the "base class" function wants.
245     return prepare(test);
246 }
247
248 enum test_result prepare_full_eviction(engine_test_t *test) {
249     if (std::string(test->cfg).find("item_eviction_policy=full_eviction")
250             != std::string::npos) {
251         return SKIPPED;
252     }
253
254     // Ephemeral buckets don't support full eviction.
255     if (std::string(test->cfg).find("bucket_type=ephemeral")
256             != std::string::npos) {
257         return SKIPPED;
258     }
259
260     // Perform whatever prep the "base class" function wants.
261     return prepare(test);
262 }
263
264 enum test_result prepare_skip_broken_under_ephemeral(engine_test_t *test) {
265     return prepare_ep_bucket(test);
266 }
267
268 enum test_result prepare_tap(engine_test_t* test) {
269     // Ephemeral buckets don't support TAP.
270     if (std::string(test->cfg).find("bucket_type=ephemeral")
271             != std::string::npos) {
272         return SKIPPED;
273     }
274
275     // Perform whatever prep the "base class" function wants.
276     return prepare(test);
277 }
278 void cleanup(engine_test_t *test, enum test_result result) {
279     (void)result;
280     // Nuke the database files we created
281     std::string dbname = get_dbname(test->cfg);
282     /* Remove only the db file this test created */
283     rmdb(dbname.c_str());
284 }
285
286 // Array of testcases to return back to engine_testapp.
287 static engine_test_t *testcases;
288
289 // Should only one test be run, and if so which number? If -1 then all tests
290 // are run.
291 static int oneTestIdx;
292
293 struct test_harness testHarness;
294
295 // Array of testcases. Provided by the specific testsuite.
296 extern BaseTestCase testsuite_testcases[];
297
298 // Examines the list of tests provided by the specific testsuite
299 // via the testsuite_testcases[] array, populates `testcases` and returns it.
300 MEMCACHED_PUBLIC_API
301 engine_test_t* get_tests(void) {
302
303     // Calculate the size of the tests..
304     int num = 0;
305     while (testsuite_testcases[num].getName() != NULL) {
306         ++num;
307     }
308
309     oneTestIdx = -1;
310     char *testNum = getenv("EP_TEST_NUM");
311     if (testNum) {
312         sscanf(testNum, "%d", &oneTestIdx);
313         if (oneTestIdx < 0 || oneTestIdx > num) {
314             oneTestIdx = -1;
315         }
316     }
317     dbname_env = getenv("EP_TEST_DIR");
318     if (!dbname_env) {
319         dbname_env = default_dbname;
320     }
321
322     if (oneTestIdx == -1) {
323         testcases = static_cast<engine_test_t*>(cb_calloc(num + 1, sizeof(engine_test_t)));
324
325         int ii = 0;
326         for (int jj = 0; jj < num; ++jj) {
327             engine_test_t *r = testsuite_testcases[jj].getTest();
328             if (r != 0) {
329                 testcases[ii++] = *r;
330             }
331         }
332     } else {
333         testcases = static_cast<engine_test_t*>(cb_calloc(1 + 1, sizeof(engine_test_t)));
334
335         engine_test_t *r = testsuite_testcases[oneTestIdx].getTest();
336         if (r != 0) {
337             testcases[0] = *r;
338         }
339     }
340
341     return testcases;
342 }
343
344 MEMCACHED_PUBLIC_API
345 bool setup_suite(struct test_harness *th) {
346     testHarness = *th;
347     return true;
348 }
349
350
351 MEMCACHED_PUBLIC_API
352 bool teardown_suite() {
353     for (int i = 0; testcases[i].name != nullptr; i++) {
354         cb_free((char*)testcases[i].name);
355         cb_free((char*)testcases[i].cfg);
356     }
357     cb_free(testcases);
358     testcases = NULL;
359     return true;
360 }
361
362 /*
363  * Create n_buckets and return how many were actually created.
364  */
365 int create_buckets(const char* cfg, int n_buckets, std::vector<BucketHolder> &buckets) {
366     std::string dbname = get_dbname(cfg);
367
368     for (int ii = 0; ii < n_buckets; ii++) {
369         std::stringstream config, dbpath;
370         dbpath << dbname.c_str() << ii;
371         std::string str_cfg(cfg);
372         /* Find the position of "dbname=" in str_cfg */
373         size_t pos = str_cfg.find("dbname=");
374         if (pos != std::string::npos) {
375             /* Move till end of the dbname */
376             size_t new_pos = str_cfg.find(';', pos);
377             str_cfg.insert(new_pos, std::to_string(ii));
378             config << str_cfg;
379         } else {
380             config << str_cfg << "dbname=" << dbpath.str();
381         }
382
383         rmdb(dbpath.str().c_str());
384         ENGINE_HANDLE_V1* handle = testHarness.create_bucket(true, config.str().c_str());
385         if (handle) {
386             buckets.push_back(BucketHolder((ENGINE_HANDLE*)handle, handle, dbpath.str()));
387         } else {
388             return ii;
389         }
390     }
391     return n_buckets;
392 }
393
394 void destroy_buckets(std::vector<BucketHolder> &buckets) {
395     for(auto bucket : buckets) {
396         testHarness.destroy_bucket(bucket.h, bucket.h1, false);
397         rmdb(bucket.dbpath.c_str());
398     }
399 }
400
401 void check_key_value(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1,
402                      const char* key, const char* val, size_t vlen,
403                      uint16_t vbucket) {
404     item_info info;
405     check(get_item_info(h, h1, &info, key, vbucket), "checking key and value");
406     checkeq(vlen, info.value[0].iov_len, "Value length mismatch");
407     check(memcmp(info.value[0].iov_base, val, vlen) == 0, "Data mismatch");
408 }
409
410 const void* createTapConn(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1,
411                           const char *name) {
412     const void *cookie = testHarness.create_cookie();
413     testHarness.lock_cookie(cookie);
414     TAP_ITERATOR iter = h1->get_tap_iterator(h, cookie, name,
415                                              strlen(name),
416                                              TAP_CONNECT_FLAG_DUMP, NULL,
417                                              0);
418     check(iter != NULL, "Failed to create a tap iterator");
419     return cookie;
420 }
421
422 bool isWarmupEnabled(ENGINE_HANDLE* h, ENGINE_HANDLE_V1* h1) {
423     return get_bool_stat(h, h1, "ep_warmup");
424 }
425
426 bool isPersistentBucket(ENGINE_HANDLE* h, ENGINE_HANDLE_V1* h1) {
427     return get_str_stat(h, h1, "ep_bucket_type") == "persistent";
428 }
429
430 bool isEphemeralBucket(ENGINE_HANDLE* h, ENGINE_HANDLE_V1* h1) {
431     return get_str_stat(h, h1, "ep_bucket_type") == "ephemeral";
432 }
433
434 bool isTapEnabled(ENGINE_HANDLE* h, ENGINE_HANDLE_V1* h1) {
435     return get_bool_stat(h, h1, "ep_tap");
436 }