1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
3 * Copyright 2015 Couchbase, Inc
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 #include "ep_testsuite_common.h"
19 #include "ep_test_apis.h"
29 #define mkdir(a, b) _mkdir(a)
34 #include <platform/cb_malloc.h>
35 #include <platform/dirutils.h>
37 const char *dbname_env = NULL;
39 static enum test_result skipped_test_function(ENGINE_HANDLE *h,
40 ENGINE_HANDLE_V1 *h1);
42 BaseTestCase::BaseTestCase(const char *_name, const char *_cfg, bool _skip)
48 BaseTestCase::BaseTestCase(const BaseTestCase &o)
53 memset(&test, 0, sizeof(test));
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 *),
62 enum test_result (*_prepare)(engine_test_t *test),
63 void (*_cleanup)(engine_test_t *test, enum test_result result),
65 : BaseTestCase(_name, _cfg, _skip) {
67 memset(&test, 0, sizeof(test));
69 test.test_setup = _test_setup;
70 test.test_teardown = _test_teardown;
71 test.prepare = _prepare;
72 test.cleanup = _cleanup;
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 *),
80 enum test_result (*_prepare)(engine_test_t *test),
81 void (*_cleanup)(engine_test_t *test, enum test_result result),
83 : BaseTestCase(_name, _cfg, _skip) {
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;
93 engine_test_t* BaseTestCase::getTest() {
94 engine_test_t *ret = &test;
102 ss << "flushall_enabled=true;";
105 // Default to the suite's dbname if the test config didn't already
107 if ((cfg == nullptr) ||
108 (std::string(cfg).find("dbname=") == std::string::npos)) {
109 ss << "dbname=" << default_dbname << ";";
113 nm.append(" (skipped)");
114 ret->tfun = skipped_test_function;
116 nm.append(" (couchstore)");
119 ret->name = cb_strdup(nm.c_str());
120 std::string config = ss.str();
121 if (config.length() == 0) {
124 ret->cfg = cb_strdup(config.c_str());
131 static enum test_result skipped_test_function(ENGINE_HANDLE *h,
132 ENGINE_HANDLE_V1 *h1) {
138 enum test_result rmdb(const char* path) {
140 if (access(path, F_OK) != -1) {
141 std::cerr << "Failed to remove: " << path << " " << std::endl;
147 bool test_setup(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) {
148 wait_for_warmup_complete(h, h1);
150 check(set_vbucket_state(h, h1, 0, vbucket_state_active),
151 "Failed to set VB0 state.");
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.
162 (std::string("test_setup: unknown bucket_type '") + bucket_type +
163 "' - cannot continue.")
168 // warmup is complete, notify ep engine that it must now enable
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");
178 bool teardown(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) {
184 bool teardown_v2(engine_test_t* test) {
190 std::string get_dbname(const char* test_cfg) {
194 dbname.assign(dbname_env);
198 const char *nm = strstr(test_cfg, "dbname=");
200 dbname.assign(dbname_env);
202 dbname.assign(nm + 7);
203 std::string::size_type end = dbname.find(';');
204 if (end != dbname.npos) {
205 dbname = dbname.substr(0, end);
211 enum test_result prepare(engine_test_t *test) {
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)
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);
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) {
234 // Perform whatever prep the "base class" function wants.
235 return prepare(test);
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) {
244 // Perform whatever prep the "base class" function wants.
245 return prepare(test);
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) {
254 // Ephemeral buckets don't support full eviction.
255 if (std::string(test->cfg).find("bucket_type=ephemeral")
256 != std::string::npos) {
260 // Perform whatever prep the "base class" function wants.
261 return prepare(test);
264 enum test_result prepare_skip_broken_under_ephemeral(engine_test_t *test) {
265 return prepare_ep_bucket(test);
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) {
275 // Perform whatever prep the "base class" function wants.
276 return prepare(test);
278 void cleanup(engine_test_t *test, enum test_result 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());
286 // Array of testcases to return back to engine_testapp.
287 static engine_test_t *testcases;
289 // Should only one test be run, and if so which number? If -1 then all tests
291 static int oneTestIdx;
293 struct test_harness testHarness;
295 // Array of testcases. Provided by the specific testsuite.
296 extern BaseTestCase testsuite_testcases[];
298 // Examines the list of tests provided by the specific testsuite
299 // via the testsuite_testcases[] array, populates `testcases` and returns it.
301 engine_test_t* get_tests(void) {
303 // Calculate the size of the tests..
305 while (testsuite_testcases[num].getName() != NULL) {
310 char *testNum = getenv("EP_TEST_NUM");
312 sscanf(testNum, "%d", &oneTestIdx);
313 if (oneTestIdx < 0 || oneTestIdx > num) {
317 dbname_env = getenv("EP_TEST_DIR");
319 dbname_env = default_dbname;
322 if (oneTestIdx == -1) {
323 testcases = static_cast<engine_test_t*>(cb_calloc(num + 1, sizeof(engine_test_t)));
326 for (int jj = 0; jj < num; ++jj) {
327 engine_test_t *r = testsuite_testcases[jj].getTest();
329 testcases[ii++] = *r;
333 testcases = static_cast<engine_test_t*>(cb_calloc(1 + 1, sizeof(engine_test_t)));
335 engine_test_t *r = testsuite_testcases[oneTestIdx].getTest();
345 bool setup_suite(struct test_harness *th) {
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);
363 * Create n_buckets and return how many were actually created.
365 int create_buckets(const char* cfg, int n_buckets, std::vector<BucketHolder> &buckets) {
366 std::string dbname = get_dbname(cfg);
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));
380 config << str_cfg << "dbname=" << dbpath.str();
383 rmdb(dbpath.str().c_str());
384 ENGINE_HANDLE_V1* handle = testHarness.create_bucket(true, config.str().c_str());
386 buckets.push_back(BucketHolder((ENGINE_HANDLE*)handle, handle, dbpath.str()));
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());
401 void check_key_value(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1,
402 const char* key, const char* val, size_t vlen,
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");
410 const void* createTapConn(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1,
412 const void *cookie = testHarness.create_cookie();
413 testHarness.lock_cookie(cookie);
414 TAP_ITERATOR iter = h1->get_tap_iterator(h, cookie, name,
416 TAP_CONNECT_FLAG_DUMP, NULL,
418 check(iter != NULL, "Failed to create a tap iterator");
422 bool isWarmupEnabled(ENGINE_HANDLE* h, ENGINE_HANDLE_V1* h1) {
423 return get_bool_stat(h, h1, "ep_warmup");
426 bool isPersistentBucket(ENGINE_HANDLE* h, ENGINE_HANDLE_V1* h1) {
427 return get_str_stat(h, h1, "ep_bucket_type") == "persistent";
430 bool isEphemeralBucket(ENGINE_HANDLE* h, ENGINE_HANDLE_V1* h1) {
431 return get_str_stat(h, h1, "ep_bucket_type") == "ephemeral";
434 bool isTapEnabled(ENGINE_HANDLE* h, ENGINE_HANDLE_V1* h1) {
435 return get_bool_stat(h, h1, "ep_tap");