MB-24293: Retry atmost 10 times if directory removal fails
[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     try {
140         cb::io::rmrf(path);
141     } catch (std::system_error& e) {
142         throw e;
143     }
144     if (access(path, F_OK) != -1) {
145         std::cerr << "Failed to remove: " << path << " " << std::endl;
146         return FAIL;
147     }
148     return SUCCESS;
149 }
150
151 bool test_setup(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) {
152     wait_for_warmup_complete(h, h1);
153
154     check(set_vbucket_state(h, h1, 0, vbucket_state_active),
155           "Failed to set VB0 state.");
156
157     const auto bucket_type = get_str_stat(h, h1, "ep_bucket_type");
158     if (bucket_type == "persistent") {
159         // Wait for vb0's state (active) to be persisted to disk, that way
160         // we know the KVStore files exist on disk.
161         wait_for_stat_to_be_gte(h, h1, "ep_persist_vbstate_total", 1);
162     } else if (bucket_type == "ephemeral") {
163         // No persistence to wait for here.
164     } else {
165         check(false,
166               (std::string("test_setup: unknown bucket_type '") + bucket_type +
167                "' - cannot continue.")
168                       .c_str());
169         return false;
170     }
171
172     // warmup is complete, notify ep engine that it must now enable
173     // data traffic
174     protocol_binary_request_header *pkt = createPacket(PROTOCOL_BINARY_CMD_ENABLE_TRAFFIC);
175     check(h1->unknown_command(h, NULL, pkt, add_response, testHarness.doc_namespace) == ENGINE_SUCCESS,
176           "Failed to enable data traffic");
177     cb_free(pkt);
178
179     return true;
180 }
181
182 bool teardown(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) {
183     (void)h; (void)h1;
184     vals.clear();
185     return true;
186 }
187
188 bool teardown_v2(engine_test_t* test) {
189     (void)test;
190     vals.clear();
191     return true;
192 }
193
194 std::string get_dbname(const char* test_cfg) {
195     std::string dbname;
196
197     if (!test_cfg) {
198         dbname.assign(dbname_env);
199         return dbname;
200     }
201
202     const char *nm = strstr(test_cfg, "dbname=");
203     if (nm == NULL) {
204         dbname.assign(dbname_env);
205     } else {
206         dbname.assign(nm + 7);
207         std::string::size_type end = dbname.find(';');
208         if (end != dbname.npos) {
209             dbname = dbname.substr(0, end);
210         }
211     }
212     return dbname;
213 }
214
215 enum test_result prepare(engine_test_t *test) {
216 #ifdef __sun
217         // Some of the tests doesn't work on Solaris.. Don't know why yet..
218         if (strstr(test->name, "concurrent set") != NULL ||
219             strstr(test->name, "retain rowid over a soft delete") != NULL)
220         {
221             return SKIPPED;
222         }
223 #endif
224
225     std::string dbname = get_dbname(test->cfg);
226     /* Remove if the same DB directory already exists */
227     try {
228         rmdb(dbname.c_str());
229     } catch (std::system_error& e) {
230         if (e.code() != std::error_code(ENOENT, std::system_category())) {
231             throw e;
232         }
233     }
234     mkdir(dbname.c_str(), 0777);
235     return SUCCESS;
236 }
237
238 enum test_result prepare_ep_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_ephemeral_bucket(engine_test_t* test) {
249     std::string cfg{test->cfg};
250     if (cfg.find("bucket_type=ephemeral") == std::string::npos) {
251         return SKIPPED;
252     }
253
254     // Perform whatever prep the "base class" function wants.
255     return prepare(test);
256 }
257
258 enum test_result prepare_full_eviction(engine_test_t *test) {
259     if (std::string(test->cfg).find("item_eviction_policy=full_eviction")
260             != std::string::npos) {
261         return SKIPPED;
262     }
263
264     // Ephemeral buckets don't support full eviction.
265     if (std::string(test->cfg).find("bucket_type=ephemeral")
266             != std::string::npos) {
267         return SKIPPED;
268     }
269
270     // Perform whatever prep the "base class" function wants.
271     return prepare(test);
272 }
273
274 enum test_result prepare_skip_broken_under_ephemeral(engine_test_t *test) {
275     return prepare_ep_bucket(test);
276 }
277
278 enum test_result prepare_tap(engine_test_t* test) {
279     // Ephemeral buckets don't support TAP.
280     if (std::string(test->cfg).find("bucket_type=ephemeral")
281             != std::string::npos) {
282         return SKIPPED;
283     }
284
285     // Perform whatever prep the "base class" function wants.
286     return prepare(test);
287 }
288 void cleanup(engine_test_t *test, enum test_result result) {
289     (void)result;
290     // Nuke the database files we created
291     std::string dbname = get_dbname(test->cfg);
292     /* Remove only the db file this test created */
293     try {
294         rmdb(dbname.c_str());
295     } catch (std::system_error& e) {
296         if (e.code() != std::error_code(ENOENT, std::system_category())) {
297             throw e;
298         }
299     }
300 }
301
302 // Array of testcases to return back to engine_testapp.
303 static engine_test_t *testcases;
304
305 // Should only one test be run, and if so which number? If -1 then all tests
306 // are run.
307 static int oneTestIdx;
308
309 struct test_harness testHarness;
310
311 // Array of testcases. Provided by the specific testsuite.
312 extern BaseTestCase testsuite_testcases[];
313
314 // Examines the list of tests provided by the specific testsuite
315 // via the testsuite_testcases[] array, populates `testcases` and returns it.
316 MEMCACHED_PUBLIC_API
317 engine_test_t* get_tests(void) {
318
319     // Calculate the size of the tests..
320     int num = 0;
321     while (testsuite_testcases[num].getName() != NULL) {
322         ++num;
323     }
324
325     oneTestIdx = -1;
326     char *testNum = getenv("EP_TEST_NUM");
327     if (testNum) {
328         sscanf(testNum, "%d", &oneTestIdx);
329         if (oneTestIdx < 0 || oneTestIdx > num) {
330             oneTestIdx = -1;
331         }
332     }
333     dbname_env = getenv("EP_TEST_DIR");
334     if (!dbname_env) {
335         dbname_env = default_dbname;
336     }
337
338     if (oneTestIdx == -1) {
339         testcases = static_cast<engine_test_t*>(cb_calloc(num + 1, sizeof(engine_test_t)));
340
341         int ii = 0;
342         for (int jj = 0; jj < num; ++jj) {
343             engine_test_t *r = testsuite_testcases[jj].getTest();
344             if (r != 0) {
345                 testcases[ii++] = *r;
346             }
347         }
348     } else {
349         testcases = static_cast<engine_test_t*>(cb_calloc(1 + 1, sizeof(engine_test_t)));
350
351         engine_test_t *r = testsuite_testcases[oneTestIdx].getTest();
352         if (r != 0) {
353             testcases[0] = *r;
354         }
355     }
356
357     return testcases;
358 }
359
360 MEMCACHED_PUBLIC_API
361 bool setup_suite(struct test_harness *th) {
362     testHarness = *th;
363     return true;
364 }
365
366
367 MEMCACHED_PUBLIC_API
368 bool teardown_suite() {
369     for (int i = 0; testcases[i].name != nullptr; i++) {
370         cb_free((char*)testcases[i].name);
371         cb_free((char*)testcases[i].cfg);
372     }
373     cb_free(testcases);
374     testcases = NULL;
375     return true;
376 }
377
378 /*
379  * Create n_buckets and return how many were actually created.
380  */
381 int create_buckets(const char* cfg, int n_buckets, std::vector<BucketHolder> &buckets) {
382     std::string dbname = get_dbname(cfg);
383
384     for (int ii = 0; ii < n_buckets; ii++) {
385         std::stringstream config, dbpath;
386         dbpath << dbname.c_str() << ii;
387         std::string str_cfg(cfg);
388         /* Find the position of "dbname=" in str_cfg */
389         size_t pos = str_cfg.find("dbname=");
390         if (pos != std::string::npos) {
391             /* Move till end of the dbname */
392             size_t new_pos = str_cfg.find(';', pos);
393             str_cfg.insert(new_pos, std::to_string(ii));
394             config << str_cfg;
395         } else {
396             config << str_cfg << "dbname=" << dbpath.str();
397         }
398
399         try {
400             rmdb(dbpath.str().c_str());
401         } catch (std::system_error& e) {
402             if (e.code() != std::error_code(ENOENT, std::system_category())) {
403                 throw e;
404             }
405         }
406         ENGINE_HANDLE_V1* handle = testHarness.create_bucket(true, config.str().c_str());
407         if (handle) {
408             buckets.push_back(BucketHolder((ENGINE_HANDLE*)handle, handle, dbpath.str()));
409         } else {
410             return ii;
411         }
412     }
413     return n_buckets;
414 }
415
416 void destroy_buckets(std::vector<BucketHolder> &buckets) {
417     for(auto bucket : buckets) {
418         testHarness.destroy_bucket(bucket.h, bucket.h1, false);
419         rmdb(bucket.dbpath.c_str());
420     }
421 }
422
423 void check_key_value(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1,
424                      const char* key, const char* val, size_t vlen,
425                      uint16_t vbucket) {
426     item_info info;
427     check(get_item_info(h, h1, &info, key, vbucket), "checking key and value");
428     checkeq(vlen, info.value[0].iov_len, "Value length mismatch");
429     check(memcmp(info.value[0].iov_base, val, vlen) == 0, "Data mismatch");
430 }
431
432 const void* createTapConn(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1,
433                           const char *name) {
434     const void *cookie = testHarness.create_cookie();
435     testHarness.lock_cookie(cookie);
436     TAP_ITERATOR iter = h1->get_tap_iterator(h, cookie, name,
437                                              strlen(name),
438                                              TAP_CONNECT_FLAG_DUMP, NULL,
439                                              0);
440     check(iter != NULL, "Failed to create a tap iterator");
441     return cookie;
442 }
443
444 bool isWarmupEnabled(ENGINE_HANDLE* h, ENGINE_HANDLE_V1* h1) {
445     return get_bool_stat(h, h1, "ep_warmup");
446 }
447
448 bool isPersistentBucket(ENGINE_HANDLE* h, ENGINE_HANDLE_V1* h1) {
449     return get_str_stat(h, h1, "ep_bucket_type") == "persistent";
450 }
451
452 bool isEphemeralBucket(ENGINE_HANDLE* h, ENGINE_HANDLE_V1* h1) {
453     return get_str_stat(h, h1, "ep_bucket_type") == "ephemeral";
454 }
455
456 bool isTapEnabled(ENGINE_HANDLE* h, ENGINE_HANDLE_V1* h1) {
457     return get_bool_stat(h, h1, "ep_tap");
458 }