1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
3 * Copyright 2016 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.
20 #include <platform/dirutils.h>
22 #include "callbacks.h"
23 #include "couch-kvstore/couch-kvstore.h"
25 #include "src/internal.h"
26 #include "tests/module_tests/test_helpers.h"
27 #include "tests/test_fileops.h"
29 #include <gmock/gmock.h>
30 #include <gtest/gtest.h>
32 #include <unordered_map>
35 class WriteCallback : public Callback<mutation_result> {
39 void callback(mutation_result &result) {
45 class StatsCallback : public Callback<kvstats_ctx> {
49 void callback(kvstats_ctx &result) {
55 class KVStoreTestCacheCallback : public Callback<CacheLookup> {
57 KVStoreTestCacheCallback(int64_t s, int64_t e, uint16_t vbid) :
58 start(s), end(e), vb(vbid) { }
60 void callback(CacheLookup &lookup) {
61 EXPECT_EQ(vb, lookup.getVBucketId());
62 EXPECT_LE(start, lookup.getBySeqno());
63 EXPECT_LE(lookup.getBySeqno(), end);
72 class GetCallback : public Callback<GetValue> {
74 GetCallback(ENGINE_ERROR_CODE _expectedErrorCode = ENGINE_SUCCESS) :
75 expectCompressed(false),
76 expectedErrorCode(_expectedErrorCode) { }
78 GetCallback(bool expect_compressed,
79 ENGINE_ERROR_CODE _expectedErrorCode = ENGINE_SUCCESS) :
80 expectCompressed(expect_compressed),
81 expectedErrorCode(_expectedErrorCode) { }
83 void callback(GetValue &result) {
84 EXPECT_EQ(expectedErrorCode, result.getStatus());
85 if (result.getStatus() == ENGINE_SUCCESS) {
86 if (expectCompressed) {
87 EXPECT_EQ(PROTOCOL_BINARY_DATATYPE_SNAPPY,
88 result.getValue()->getDataType());
89 result.getValue()->decompressValue();
94 result.getValue()->getData(),
95 result.getValue()->getNBytes()));
96 delete result.getValue();
101 bool expectCompressed;
102 ENGINE_ERROR_CODE expectedErrorCode;
105 class BloomFilterCallback : public Callback<std::string&, bool&> {
107 BloomFilterCallback() {}
108 void callback(std::string& ra, bool& rb) override {}
111 class ExpiryCallback : public Callback<std::string&, uint64_t&> {
114 void callback(std::string& ra, uint64_t& rb) override {}
118 * Utility template for generating callbacks for various
119 * KVStore functions from a lambda/std::function
121 template <typename... RV>
122 class CustomCallback : public Callback<RV...> {
124 CustomCallback(std::function<void(RV...)> _cb)
127 : cb([](RV... val){}) {}
129 void callback(RV&...result) {
134 std::function<void(RV...)> cb;
138 * Callback that can be given a lambda to use, specifically
139 * for the Rollback callback
141 class CustomRBCallback : public RollbackCB {
143 CustomRBCallback(std::function<void(GetValue)> _cb)
146 : cb([](GetValue val){}) {}
148 void callback(GetValue &result) {
150 delete result.getValue();
154 std::function<void(GetValue)> cb;
157 // Initializes a KVStore
158 static void initialize_kv_store(KVStore* kvstore) {
159 std::string failoverLog("");
160 // simulate the setVbState by incrementing the rev
161 kvstore->incrementRevision(0);
162 vbucket_state state(vbucket_state_active, 0, 0, 0, 0, 0, 0, 0, failoverLog);
163 // simulate the setVbState by incrementing the rev
164 kvstore->incrementRevision(0);
165 kvstore->snapshotVBucket(0, state,
166 VBStatePersist::VBSTATE_PERSIST_WITHOUT_COMMIT);
169 // Creates and initializes a KVStore with the given config
170 static std::unique_ptr<KVStore> setup_kv_store(KVStoreConfig& config) {
171 auto kvstore = KVStoreFactory::create(config);
172 initialize_kv_store(kvstore.rw.get());
173 return std::move(kvstore.rw);
176 /* Test callback for stats handling.
177 * 'cookie' is a std::unordered_map<std::string, std::string) which stats
178 * are accumulated in.
180 static void add_stat_callback(const char *key, const uint16_t klen,
181 const char *val, const uint32_t vlen,
182 const void *cookie) {
183 auto* map = reinterpret_cast<std::map<std::string, std::string>*>(
184 const_cast<void*>(cookie));
185 ASSERT_NE(nullptr, map);
186 map->insert(std::make_pair(std::string(key, klen),
187 std::string(val, vlen)));
191 * Test fixture for KVStore tests. Inherited by both ForestDB and
192 * Couchstore test fixtures.
194 class KVStoreTest : public ::testing::Test {
196 void SetUp() override {
197 auto* info = ::testing::UnitTest::GetInstance()->current_test_info();
198 data_dir = std::string(info->test_case_name()) + "_" + info->name() +
202 void TearDown() override {
203 cb::io::rmrf(data_dir);
206 std::string data_dir;
209 /// Test fixture for tests which run on both Couchstore and ForestDB.
210 class CouchAndForestTest : public KVStoreTest,
211 public ::testing::WithParamInterface<std::string> {
214 /// Test fixture for tests which run only on Couchstore.
215 class CouchKVStoreTest : public KVStoreTest {
218 /* Test basic set / get of a document */
219 TEST_P(CouchAndForestTest, BasicTest) {
220 KVStoreConfig config(
221 1024, 4, data_dir, GetParam(), 0, false /*persistnamespace*/);
222 auto kvstore = setup_kv_store(config);
225 StoredDocKey key = makeStoredDocKey("key");
226 Item item(key, 0, 0, "value", 5);
228 kvstore->set(item, wc);
230 EXPECT_TRUE(kvstore->commit(nullptr /*no collections manifest*/));
233 kvstore->get(key, 0, gc);
236 TEST_F(CouchKVStoreTest, CompressedTest) {
237 KVStoreConfig config(
238 1024, 4, data_dir, "couchdb", 0, false /*persistnamespace*/);
239 auto kvstore = setup_kv_store(config);
243 uint8_t datatype = PROTOCOL_BINARY_RAW_BYTES;
245 for (int i = 1; i <= 5; i++) {
246 std::string key("key" + std::to_string(i));
247 Item item(makeStoredDocKey(key),
248 0, 0, "value", 5, &datatype, 1, 0, i);
249 kvstore->set(item, wc);
253 kvstore->commit(nullptr /*no collections manifest*/);
255 std::shared_ptr<Callback<GetValue> > cb(new GetCallback(true/*expectcompressed*/));
256 std::shared_ptr<Callback<CacheLookup> > cl(new KVStoreTestCacheCallback(1, 5, 0));
257 ScanContext* scanCtx;
258 scanCtx = kvstore->initScanContext(cb, cl, 0, 1,
259 DocumentFilter::ALL_ITEMS,
260 ValueFilter::VALUES_COMPRESSED);
262 ASSERT_NE(nullptr, scanCtx);
263 EXPECT_EQ(scan_success, kvstore->scan(scanCtx));
264 kvstore->destroyScanContext(scanCtx);
267 // Verify the stats returned from operations are accurate.
268 TEST_F(CouchKVStoreTest, StatsTest) {
269 KVStoreConfig config(
270 1024, 4, data_dir, "couchdb", 0, false /*persistnamespace*/);
271 auto kvstore = setup_kv_store(config);
273 // Perform a transaction with a single mutation (set) in it.
275 const std::string key{"key"};
276 const std::string value{"value"};
277 Item item(makeStoredDocKey(key), 0, 0, value.c_str(), value.size());
279 kvstore->set(item, wc);
282 EXPECT_TRUE(kvstore->commit(nullptr /*no collections manifest*/));
283 // Check statistics are correct.
284 std::map<std::string, std::string> stats;
285 kvstore->addStats(add_stat_callback, &stats);
286 EXPECT_EQ("1", stats["rw_0:io_num_write"]);
287 const size_t io_write_bytes = stoul(stats["rw_0:io_write_bytes"]);
288 EXPECT_EQ(key.size() + value.size() +
289 MetaData::getMetaDataSize(MetaData::Version::V1),
292 // Hard to determine exactly how many bytes should have been written, but
293 // expect non-zero, and least as many as the actual documents.
294 const size_t io_total_write_bytes = stoul(stats["rw_0:io_total_write_bytes"]);
295 EXPECT_GT(io_total_write_bytes, 0);
296 EXPECT_GE(io_total_write_bytes, io_write_bytes);
299 // Verify the compaction stats returned from operations are accurate.
300 TEST_F(CouchKVStoreTest, CompactStatsTest) {
301 KVStoreConfig config(
302 1, 4, data_dir, "couchdb", 0, false /*persistnamespace*/);
303 auto kvstore = setup_kv_store(config);
305 // Perform a transaction with a single mutation (set) in it.
307 const std::string key{"key"};
308 const std::string value{"value"};
309 Item item(makeStoredDocKey(key), 0, 0, value.c_str(), value.size());
311 kvstore->set(item, wc);
313 EXPECT_TRUE(kvstore->commit(nullptr /*no collections manifest*/));
315 std::shared_ptr<Callback<std::string&, bool&> >
316 filter(new BloomFilterCallback());
317 std::shared_ptr<Callback<std::string&, uint64_t&> >
318 expiry(new ExpiryCallback());
321 cctx.purge_before_seq = 0;
322 cctx.purge_before_ts = 0;
324 cctx.drop_deletes = 0;
327 EXPECT_TRUE(kvstore->compactDB(&cctx));
328 // Check statistics are correct.
329 std::map<std::string, std::string> stats;
330 kvstore->addStats(add_stat_callback, &stats);
331 EXPECT_EQ("1", stats["rw_0:io_num_write"]);
332 const size_t io_write_bytes = stoul(stats["rw_0:io_write_bytes"]);
334 // Hard to determine exactly how many bytes should have been written, but
335 // expect non-zero, and at least twice as many as the actual documents for
336 // the total and once as many for compaction alone.
337 const size_t io_total_write_bytes = stoul(stats["rw_0:io_total_write_bytes"]);
338 const size_t io_compaction_write_bytes = stoul(stats["rw_0:io_compaction_write_bytes"]);
339 EXPECT_GT(io_total_write_bytes, 0);
340 EXPECT_GT(io_compaction_write_bytes, 0);
341 EXPECT_GT(io_total_write_bytes, io_compaction_write_bytes);
342 EXPECT_GE(io_total_write_bytes, io_write_bytes * 2);
343 EXPECT_GE(io_compaction_write_bytes, io_write_bytes);
346 // Regression test for MB-17517 - ensure that if a couchstore file has a max
347 // CAS of -1, it is detected and reset to zero when file is loaded.
348 TEST_F(CouchKVStoreTest, MB_17517MaxCasOfMinus1) {
349 KVStoreConfig config(
350 1024, 4, data_dir, "couchdb", 0, false /*persistnamespace*/);
351 auto kvstore = KVStoreFactory::create(config);
352 ASSERT_NE(nullptr, kvstore.rw);
355 std::string failoverLog("[]");
356 vbucket_state state(vbucket_state_active, /*ckid*/0, /*maxDelSeqNum*/0,
357 /*highSeqno*/0, /*purgeSeqno*/0, /*lastSnapStart*/0,
358 /*lastSnapEnd*/0, /*maxCas*/-1, failoverLog);
359 EXPECT_TRUE(kvstore.rw->snapshotVBucket(
360 /*vbid*/ 0, state, VBStatePersist::VBSTATE_PERSIST_WITHOUT_COMMIT));
361 EXPECT_EQ(~0ull, kvstore.rw->listPersistedVbuckets()[0]->maxCas);
363 // Close the file, then re-open.
364 kvstore = KVStoreFactory::create(config);
365 EXPECT_NE(nullptr, kvstore.rw);
367 // Check that our max CAS was repaired on startup.
368 EXPECT_EQ(0u, kvstore.rw->listPersistedVbuckets()[0]->maxCas);
371 // Regression test for MB-19430 - ensure that an attempt to get the
372 // item count from a file which doesn't exist yet propagates the
373 // error so the caller can detect (and retry as necessary).
374 TEST_F(CouchKVStoreTest, MB_18580_ENOENT) {
375 KVStoreConfig config(
376 1024, 4, data_dir, "couchdb", 0, false /*persistnamespace*/);
377 // Create a read-only kvstore (which disables item count caching), then
378 // attempt to get the count from a non-existent vbucket.
379 auto kvstore = KVStoreFactory::create(config);
380 ASSERT_NE(nullptr, kvstore.ro);
382 // Expect to get a system_error (ENOENT)
383 EXPECT_THROW(kvstore.ro->getDbFileInfo(0), std::system_error);
387 * The CouchKVStoreErrorInjectionTest cases utilise GoogleMock to inject
388 * errors into couchstore as if they come from the filesystem in order
389 * to observe how CouchKVStore handles the error and logs it.
391 * The GoogleMock framework allows expectations to be set on how an object
392 * will be called and how it will respond. Generally we will set a Couchstore
393 * FileOps instance to return an error code on the 'nth' call as follows:
395 * EXPECT_CALL(ops, open(_, _, _, _)).Times(AnyNumber());
396 * EXPECT_CALL(ops, open(_, _, _, _))
397 * .WillOnce(Return(COUCHSTORE_ERROR_OPEN_FILE)).RetiresOnSaturation();
398 * EXPECT_CALL(ops, open(_, _, _, _)).Times(n).RetiresOnSaturation();
400 * We will additionally set an expectation on the LoggerMock regarding how it
401 * will be called by CouchKVStore. In this instance we have set an expectation
402 * that the logger will be called with a logging level greater than or equal
403 * to info, and the log message will contain the error string that corresponds
404 * to `COUCHSTORE_ERROR_OPEN_FILE`.
406 * EXPECT_CALL(logger, mlog(_, _)).Times(AnyNumber());
407 * EXPECT_CALL(logger, mlog(Ge(EXTENSION_LOG_WARNING),
408 * VCE(COUCHSTORE_ERROR_OPEN_FILE))
409 * ).Times(1).RetiresOnSaturation();
412 using namespace testing;
415 * The MockLogger is used to verify that the logger is called with certain
416 * parameters / messages.
418 * The MockLogger is slightly misleading in that it mocks a function that
419 * is not on the API of the logger, instead mocking a function that is
420 * called with the preformatted log message.
422 class MockLogger : public Logger {
425 ON_CALL(*this, mlog(_, _)).WillByDefault(Invoke([](EXTENSION_LOG_LEVEL sev,
426 const std::string& msg){
430 void vlog(EXTENSION_LOG_LEVEL severity, const char* fmt, va_list va) const override {
431 mlog(severity, vatos(fmt, va));
434 MOCK_CONST_METHOD2(mlog, void(EXTENSION_LOG_LEVEL severity,
435 const std::string& message));
439 * Convert fmt cstring and a variadic arguments list to a string
441 static std::string vatos(const char* fmt, va_list va) {
442 std::vector<char> buffer;
447 buffer.resize(vsnprintf(nullptr, 0, fmt, cpy) + 1);
450 // Write to vector and return as string
451 vsnprintf(buffer.data(), buffer.size(), fmt, va);
452 return std::string(buffer.data());
458 * VCE: Verify Couchstore Error
460 * This is a GoogleMock matcher which will match against a string
461 * which has the corresponding message for the passed couchstore
462 * error code in it. e.g.
464 * VCE(COUCHSTORE_ERROR_WRITE)
466 * will match against a string which contains 'error writing to file'.
468 MATCHER_P(VCE, value, "is string of %(value)") {
469 return arg.find(couchstore_strerror(value)) != std::string::npos;
473 * CouchKVStoreErrorInjectionTest is used for tests which verify
474 * log messages from error injection in couchstore.
476 class CouchKVStoreErrorInjectionTest : public ::testing::Test {
478 CouchKVStoreErrorInjectionTest()
479 : data_dir("CouchKVStoreErrorInjectionTest.db"),
480 ops(create_default_file_ops()),
481 config(KVStoreConfig(1024,
486 false /*persistnamespace*/)
488 .setBuffered(false)) {
490 cb::io::rmrf(data_dir.c_str());
491 } catch (std::system_error& e) {
492 if (e.code() != std::error_code(ENOENT, std::system_category())) {
496 kvstore.reset(new CouchKVStore(config, ops));
497 initialize_kv_store(kvstore.get());
499 ~CouchKVStoreErrorInjectionTest() {
500 cb::io::rmrf(data_dir.c_str());
504 void generate_items(size_t count) {
505 for(unsigned i(0); i < count; i++) {
506 std::string key("key" + std::to_string(i));
507 items.push_back(Item(makeStoredDocKey(key), 0, 0, "value", 5,
508 nullptr, 0, 0, i + 1));
512 void populate_items(size_t count) {
513 generate_items(count);
514 CustomCallback<mutation_result> set_callback;
516 for(const auto& item: items) {
517 kvstore->set(item, set_callback);
519 kvstore->commit(nullptr /*no collections manifest*/);
522 vb_bgfetch_queue_t make_bgfetch_queue() {
523 vb_bgfetch_queue_t itms;
524 for(const auto& item: items) {
525 vb_bgfetch_item_ctx_t ctx;
526 ctx.isMetaOnly = false;
527 itms[item.getKey()] = std::move(ctx);
533 const std::string data_dir;
535 ::testing::NiceMock<MockOps> ops;
536 ::testing::NiceMock<MockLogger> logger;
538 KVStoreConfig config;
539 std::unique_ptr<CouchKVStore> kvstore;
540 std::vector<Item> items;
545 * Injects error during CouchKVStore::openDB_retry/couchstore_open_db_ex
547 TEST_F(CouchKVStoreErrorInjectionTest, openDB_retry_open_db_ex) {
549 CustomCallback<mutation_result> set_callback;
552 kvstore->set(items.front(), set_callback);
554 /* Establish Logger expectation */
555 EXPECT_CALL(logger, mlog(_, _)).Times(AnyNumber());
556 EXPECT_CALL(logger, mlog(Ge(EXTENSION_LOG_NOTICE),
557 VCE(COUCHSTORE_ERROR_OPEN_FILE))
558 ).Times(1).RetiresOnSaturation();
560 /* Establish FileOps expectation */
561 EXPECT_CALL(ops, open(_, _, _, _)).Times(AnyNumber());
562 EXPECT_CALL(ops, open(_, _, _, _))
563 .WillOnce(Return(COUCHSTORE_ERROR_OPEN_FILE)).RetiresOnSaturation();
565 kvstore->commit(nullptr /*no collections manifest*/);
570 * Injects error during CouchKVStore::openDB/couchstore_open_db_ex
572 TEST_F(CouchKVStoreErrorInjectionTest, openDB_open_db_ex) {
574 CustomCallback<mutation_result> set_callback;
577 kvstore->set(items.front(), set_callback);
579 /* Establish Logger expectation */
580 EXPECT_CALL(logger, mlog(_, _)).Times(AnyNumber());
581 EXPECT_CALL(logger, mlog(Ge(EXTENSION_LOG_WARNING),
582 VCE(COUCHSTORE_ERROR_OPEN_FILE))
583 ).Times(1).RetiresOnSaturation();
585 /* Establish FileOps expectation */
586 EXPECT_CALL(ops, open(_, _, _, _))
587 .WillRepeatedly(Return(COUCHSTORE_ERROR_OPEN_FILE)).RetiresOnSaturation();
589 kvstore->commit(nullptr /*no collections manifest*/);
594 * Injects error during CouchKVStore::commit/couchstore_save_documents
596 TEST_F(CouchKVStoreErrorInjectionTest, commit_save_documents) {
598 CustomCallback<mutation_result> set_callback;
601 kvstore->set(items.front(), set_callback);
603 /* Establish Logger expectation */
604 EXPECT_CALL(logger, mlog(_, _)).Times(AnyNumber());
605 EXPECT_CALL(logger, mlog(Ge(EXTENSION_LOG_WARNING),
606 VCE(COUCHSTORE_ERROR_WRITE))
607 ).Times(1).RetiresOnSaturation();
609 /* Establish FileOps expectation */
610 EXPECT_CALL(ops, pwrite(_, _, _, _, _))
611 .WillOnce(Return(COUCHSTORE_ERROR_WRITE)).RetiresOnSaturation();
613 kvstore->commit(nullptr /*no collections manifest*/);
619 * Injects error during CouchKVStore::commit/couchstore_save_local_document
621 TEST_F(CouchKVStoreErrorInjectionTest, commit_save_local_document) {
623 CustomCallback<mutation_result> set_callback;
626 kvstore->set(items.front(), set_callback);
628 /* Establish Logger expectation */
629 EXPECT_CALL(logger, mlog(_, _)).Times(AnyNumber());
630 EXPECT_CALL(logger, mlog(Ge(EXTENSION_LOG_WARNING),
631 VCE(COUCHSTORE_ERROR_WRITE))
632 ).Times(1).RetiresOnSaturation();
634 /* Establish FileOps expectation */
635 EXPECT_CALL(ops, pwrite(_, _, _, _, _))
636 .WillOnce(Return(COUCHSTORE_ERROR_WRITE)).RetiresOnSaturation();
637 EXPECT_CALL(ops, pwrite(_, _, _, _, _)).Times(6).RetiresOnSaturation();
639 kvstore->commit(nullptr /*no collections manifest*/);
645 * Injects error during CouchKVStore::commit/couchstore_commit
647 TEST_F(CouchKVStoreErrorInjectionTest, commit_commit) {
649 CustomCallback<mutation_result> set_callback;
652 kvstore->set(items.front(), set_callback);
654 /* Establish Logger expectation */
655 EXPECT_CALL(logger, mlog(_, _)).Times(AnyNumber());
656 EXPECT_CALL(logger, mlog(Ge(EXTENSION_LOG_WARNING),
657 VCE(COUCHSTORE_ERROR_WRITE))
658 ).Times(1).RetiresOnSaturation();
660 /* Establish FileOps expectation */
661 EXPECT_CALL(ops, pwrite(_, _, _, _, _))
662 .WillOnce(Return(COUCHSTORE_ERROR_WRITE)).RetiresOnSaturation();
663 EXPECT_CALL(ops, pwrite(_, _, _, _, _)).Times(8).RetiresOnSaturation();
665 kvstore->commit(nullptr /*no collections manifest*/);
670 * Injects error during CouchKVStore::get/couchstore_docinfo_by_id
672 TEST_F(CouchKVStoreErrorInjectionTest, get_docinfo_by_id) {
674 CustomCallback<GetValue> get_callback;
676 /* Establish Logger expectation */
677 EXPECT_CALL(logger, mlog(_, _)).Times(AnyNumber());
678 EXPECT_CALL(logger, mlog(Ge(EXTENSION_LOG_WARNING),
679 VCE(COUCHSTORE_ERROR_READ))
680 ).Times(1).RetiresOnSaturation();
682 /* Establish FileOps expectation */
683 EXPECT_CALL(ops, pread(_, _, _, _, _))
684 .WillOnce(Return(COUCHSTORE_ERROR_READ)).RetiresOnSaturation();
685 EXPECT_CALL(ops, pread(_, _, _, _, _)).Times(3).RetiresOnSaturation();
686 kvstore->get(items.front().getKey(), 0, get_callback);
692 * Injects error during CouchKVStore::get/couchstore_open_doc_with_docinfo
694 TEST_F(CouchKVStoreErrorInjectionTest, get_open_doc_with_docinfo) {
696 CustomCallback<GetValue> get_callback;
698 /* Establish Logger expectation */
699 EXPECT_CALL(logger, mlog(_, _)).Times(AnyNumber());
700 EXPECT_CALL(logger, mlog(Ge(EXTENSION_LOG_WARNING),
701 VCE(COUCHSTORE_ERROR_READ))
702 ).Times(1).RetiresOnSaturation();
704 /* Establish FileOps expectation */
705 EXPECT_CALL(ops, pread(_, _, _, _, _))
706 .WillOnce(Return(COUCHSTORE_ERROR_READ)).RetiresOnSaturation();
707 EXPECT_CALL(ops, pread(_, _, _, _, _)).Times(5).RetiresOnSaturation();
708 kvstore->get(items.front().getKey(), 0, get_callback);
714 * Injects error during CouchKVStore::getMulti/couchstore_docinfos_by_id
716 TEST_F(CouchKVStoreErrorInjectionTest, getMulti_docinfos_by_id) {
718 vb_bgfetch_queue_t itms(make_bgfetch_queue());
721 /* Establish Logger expectation */
722 EXPECT_CALL(logger, mlog(_, _)).Times(AnyNumber());
723 EXPECT_CALL(logger, mlog(Ge(EXTENSION_LOG_WARNING),
724 VCE(COUCHSTORE_ERROR_READ))
725 ).Times(1).RetiresOnSaturation();
727 /* Establish FileOps expectation */
728 EXPECT_CALL(ops, pread(_, _, _, _, _))
729 .WillOnce(Return(COUCHSTORE_ERROR_READ)).RetiresOnSaturation();
730 EXPECT_CALL(ops, pread(_, _, _, _, _)).Times(3).RetiresOnSaturation();
731 kvstore->getMulti(0, itms);
738 * Injects error during CouchKVStore::getMulti/couchstore_open_doc_with_docinfo
740 TEST_F(CouchKVStoreErrorInjectionTest, getMulti_open_doc_with_docinfo) {
742 vb_bgfetch_queue_t itms(make_bgfetch_queue());
744 /* Check preconditions */
745 ASSERT_EQ(0, kvstore->getKVStoreStat().numGetFailure);
747 /* Establish FileOps expectation */
748 EXPECT_CALL(ops, pread(_, _, _, _, _))
749 .WillOnce(Return(COUCHSTORE_ERROR_READ)).RetiresOnSaturation();
750 EXPECT_CALL(ops, pread(_, _, _, _, _)).Times(5).RetiresOnSaturation();
751 kvstore->getMulti(0, itms);
753 EXPECT_EQ(1, kvstore->getKVStoreStat().numGetFailure);
758 * Injects error during CouchKVStore::compactDB/couchstore_compact_db_ex
760 TEST_F(CouchKVStoreErrorInjectionTest, compactDB_compact_db_ex) {
764 cctx.purge_before_seq = 0;
765 cctx.purge_before_ts = 0;
767 cctx.drop_deletes = 0;
771 /* Establish Logger expectation */
772 EXPECT_CALL(logger, mlog(_, _)).Times(AnyNumber());
773 EXPECT_CALL(logger, mlog(Ge(EXTENSION_LOG_WARNING),
774 VCE(COUCHSTORE_ERROR_OPEN_FILE))
775 ).Times(1).RetiresOnSaturation();
777 /* Establish FileOps expectation */
778 EXPECT_CALL(ops, open(_, _, _, _))
779 .WillOnce(Return(COUCHSTORE_ERROR_OPEN_FILE)).RetiresOnSaturation();
780 EXPECT_CALL(ops, open(_, _, _, _)).Times(1).RetiresOnSaturation();
781 kvstore->compactDB(&cctx);
786 * Injects error during CouchKVStore::getNumItems/couchstore_changes_count
788 TEST_F(CouchKVStoreErrorInjectionTest, getNumItems_changes_count) {
791 /* Establish FileOps expectation */
792 EXPECT_CALL(ops, pread(_, _, _, _, _))
793 .WillOnce(Return(COUCHSTORE_ERROR_READ)).RetiresOnSaturation();
794 EXPECT_CALL(ops, pread(_, _, _, _, _)).Times(3).RetiresOnSaturation();
796 kvstore->getNumItems(0, 0, 100000);
797 EXPECT_TRUE(false) << "kvstore->getNumItems(0, 0, 100000); should "
798 "have thrown a runtime_error";
799 } catch (const std::runtime_error& e) {
800 EXPECT_THAT(std::string(e.what()), VCE(COUCHSTORE_ERROR_READ));
807 * Injects error during CouchKVStore::reset/couchstore_commit
809 TEST_F(CouchKVStoreErrorInjectionTest, reset_commit) {
812 /* Establish Logger expectation */
813 EXPECT_CALL(logger, mlog(_, _)).Times(AnyNumber());
814 EXPECT_CALL(logger, mlog(Ge(EXTENSION_LOG_WARNING),
815 VCE(COUCHSTORE_ERROR_READ))
816 ).Times(1).RetiresOnSaturation();
818 /* Establish FileOps expectation */
819 EXPECT_CALL(ops, sync(_, _))
820 .WillOnce(Return(COUCHSTORE_ERROR_READ)).RetiresOnSaturation();
827 * Injects error during CouchKVStore::initScanContext/couchstore_changes_count
829 TEST_F(CouchKVStoreErrorInjectionTest, initScanContext_changes_count) {
831 auto cb(std::make_shared<CustomCallback<GetValue>>());
832 auto cl(std::make_shared<CustomCallback<CacheLookup>>());
834 /* Establish FileOps expectation */
835 EXPECT_CALL(ops, pread(_, _, _, _, _))
836 .WillOnce(Return(COUCHSTORE_ERROR_READ)).RetiresOnSaturation();
837 EXPECT_CALL(ops, pread(_, _, _, _, _)).Times(3).RetiresOnSaturation();
839 ScanContext* scanCtx = nullptr;
840 scanCtx = kvstore->initScanContext(cb, cl, 0, 0,
841 DocumentFilter::ALL_ITEMS,
842 ValueFilter::VALUES_DECOMPRESSED);
843 EXPECT_EQ(nullptr, scanCtx)
844 << "kvstore->initScanContext(cb, cl, 0, 0, "
845 "DocumentFilter::ALL_ITEMS, "
846 "ValueFilter::VALUES_DECOMPRESSED); should "
847 "have returned NULL";
849 kvstore->destroyScanContext(scanCtx);
854 * Injects error during CouchKVStore::scan/couchstore_changes_since
856 TEST_F(CouchKVStoreErrorInjectionTest, scan_changes_since) {
858 auto cb(std::make_shared<CustomCallback<GetValue>>());
859 auto cl(std::make_shared<CustomCallback<CacheLookup>>());
860 auto scan_context = kvstore->initScanContext(cb, cl, 0, 0,
861 DocumentFilter::ALL_ITEMS,
862 ValueFilter::VALUES_DECOMPRESSED);
864 /* Establish Logger expectation */
865 EXPECT_CALL(logger, mlog(_, _)).Times(AnyNumber());
866 EXPECT_CALL(logger, mlog(Ge(EXTENSION_LOG_WARNING),
867 VCE(COUCHSTORE_ERROR_READ))
868 ).Times(1).RetiresOnSaturation();
870 /* Establish FileOps expectation */
871 EXPECT_CALL(ops, pread(_, _, _, _, _))
872 .WillOnce(Return(COUCHSTORE_ERROR_READ)).RetiresOnSaturation();
874 kvstore->scan(scan_context);
877 kvstore->destroyScanContext(scan_context);
881 * Injects error during CouchKVStore::recordDbDump/couchstore_open_doc_with_docinfo
883 TEST_F(CouchKVStoreErrorInjectionTest, recordDbDump_open_doc_with_docinfo) {
885 auto cb(std::make_shared<CustomCallback<GetValue>>());
886 auto cl(std::make_shared<CustomCallback<CacheLookup>>());
887 auto scan_context = kvstore->initScanContext(cb, cl, 0, 0,
888 DocumentFilter::ALL_ITEMS,
889 ValueFilter::VALUES_DECOMPRESSED);
891 /* Establish Logger expectation */
892 EXPECT_CALL(logger, mlog(_, _)).Times(AnyNumber());
893 EXPECT_CALL(logger, mlog(Ge(EXTENSION_LOG_WARNING),
894 VCE(COUCHSTORE_ERROR_READ))
895 ).Times(1).RetiresOnSaturation();
897 /* Establish FileOps expectation */
898 EXPECT_CALL(ops, pread(_, _, _, _, _))
899 .WillOnce(Return(COUCHSTORE_ERROR_READ)).RetiresOnSaturation();
900 EXPECT_CALL(ops, pread(_, _, _, _, _)).Times(2).RetiresOnSaturation();
902 kvstore->scan(scan_context);
905 kvstore->destroyScanContext(scan_context);
909 * Injects error during CouchKVStore::rollback/couchstore_changes_count/1
911 TEST_F(CouchKVStoreErrorInjectionTest, rollback_changes_count1) {
913 CustomCallback<mutation_result> set_callback;
915 for(const auto item: items) {
917 kvstore->set(item, set_callback);
918 kvstore->commit(nullptr /*no collections manifest*/);
921 auto rcb(std::make_shared<CustomRBCallback>());
923 /* Establish Logger expectation */
924 EXPECT_CALL(logger, mlog(_, _)).Times(AnyNumber());
925 EXPECT_CALL(logger, mlog(Ge(EXTENSION_LOG_WARNING),
926 VCE(COUCHSTORE_ERROR_READ))
927 ).Times(1).RetiresOnSaturation();
929 /* Establish FileOps expectation */
930 EXPECT_CALL(ops, pread(_, _, _, _, _))
931 .WillOnce(Return(COUCHSTORE_ERROR_READ)).RetiresOnSaturation();
932 EXPECT_CALL(ops, pread(_, _, _, _, _)).Times(3).RetiresOnSaturation();
934 kvstore->rollback(0, 5, rcb);
939 * Injects error during CouchKVStore::rollback/couchstore_rewind_header
941 TEST_F(CouchKVStoreErrorInjectionTest, rollback_rewind_header) {
943 CustomCallback<mutation_result> set_callback;
945 for(const auto item: items) {
947 kvstore->set(item, set_callback);
948 kvstore->commit(nullptr /*no collections manifest*/);
951 auto rcb(std::make_shared<CustomRBCallback>());
953 /* Establish Logger expectation */
954 EXPECT_CALL(logger, mlog(_, _)).Times(AnyNumber());
955 EXPECT_CALL(logger, mlog(Ge(EXTENSION_LOG_WARNING),
956 VCE(COUCHSTORE_ERROR_DB_NO_LONGER_VALID))
957 ).Times(1).RetiresOnSaturation();
959 /* Establish FileOps expectation */
960 EXPECT_CALL(ops, pread(_, _, _, _, _))
961 /* Doing an ALLOC_FAIL as Couchstore will just
962 * keep rolling back otherwise */
963 .WillOnce(Return(COUCHSTORE_ERROR_ALLOC_FAIL)).RetiresOnSaturation();
964 EXPECT_CALL(ops, pread(_, _, _, _, _)).Times(9).RetiresOnSaturation();
966 kvstore->rollback(0, 5, rcb);
971 * Injects error during CouchKVStore::rollback/couchstore_changes_count/2
973 TEST_F(CouchKVStoreErrorInjectionTest, rollback_changes_count2) {
975 CustomCallback<mutation_result> set_callback;
977 for(const auto item: items) {
979 kvstore->set(item, set_callback);
980 kvstore->commit(nullptr /*no collections manifest*/);
983 auto rcb(std::make_shared<CustomRBCallback>());
985 /* Establish Logger expectation */
986 EXPECT_CALL(logger, mlog(_, _)).Times(AnyNumber());
987 EXPECT_CALL(logger, mlog(Ge(EXTENSION_LOG_WARNING),
988 VCE(COUCHSTORE_ERROR_READ))
989 ).Times(1).RetiresOnSaturation();
991 /* Establish FileOps expectation */
992 EXPECT_CALL(ops, pread(_, _, _, _, _))
993 .WillOnce(Return(COUCHSTORE_ERROR_READ)).RetiresOnSaturation();
994 EXPECT_CALL(ops, pread(_, _, _, _, _)).Times(11).RetiresOnSaturation();
996 kvstore->rollback(0, 5, rcb);
1001 * Injects error during CouchKVStore::readVBState/couchstore_open_local_document
1003 TEST_F(CouchKVStoreErrorInjectionTest, readVBState_open_local_document) {
1005 CustomCallback<mutation_result> set_callback;
1007 for(const auto item: items) {
1009 kvstore->set(item, set_callback);
1010 kvstore->commit(nullptr /*no collections manifest*/);
1013 auto rcb(std::make_shared<CustomRBCallback>());
1015 /* Establish Logger expectation */
1016 EXPECT_CALL(logger, mlog(_, _)).Times(AnyNumber());
1017 EXPECT_CALL(logger, mlog(Ge(EXTENSION_LOG_WARNING),
1018 VCE(COUCHSTORE_ERROR_READ))
1019 ).Times(1).RetiresOnSaturation();
1021 /* Establish FileOps expectation */
1022 EXPECT_CALL(ops, pread(_, _, _, _, _))
1023 .WillOnce(Return(COUCHSTORE_ERROR_READ)).RetiresOnSaturation();
1024 EXPECT_CALL(ops, pread(_, _, _, _, _)).Times(20).RetiresOnSaturation();
1026 kvstore->rollback(0, 5, rcb);
1031 * Injects error during CouchKVStore::getAllKeys/couchstore_all_docs
1033 TEST_F(CouchKVStoreErrorInjectionTest, getAllKeys_all_docs) {
1036 auto adcb(std::make_shared<CustomCallback<const DocKey&>>());
1037 StoredDocKey start = makeStoredDocKey("");
1039 /* Establish Logger expectation */
1040 EXPECT_CALL(logger, mlog(_, _)).Times(AnyNumber());
1041 EXPECT_CALL(logger, mlog(Ge(EXTENSION_LOG_WARNING),
1042 VCE(COUCHSTORE_ERROR_READ))
1043 ).Times(1).RetiresOnSaturation();
1045 /* Establish FileOps expectation */
1046 EXPECT_CALL(ops, pread(_, _, _, _, _))
1047 .WillOnce(Return(COUCHSTORE_ERROR_READ)).RetiresOnSaturation();
1048 EXPECT_CALL(ops, pread(_, _, _, _, _)).Times(3).RetiresOnSaturation();
1051 kvstore->getAllKeys(0, start, 1, adcb);
1056 * Injects error during CouchKVStore::closeDB/couchstore_close_file
1058 TEST_F(CouchKVStoreErrorInjectionTest, closeDB_close_file) {
1060 /* Establish Logger expectation */
1061 EXPECT_CALL(logger, mlog(_, _)).Times(AnyNumber());
1062 EXPECT_CALL(logger, mlog(Ge(EXTENSION_LOG_WARNING),
1063 VCE(COUCHSTORE_ERROR_FILE_CLOSE))
1064 ).Times(1).RetiresOnSaturation();
1066 /* Establish FileOps expectation */
1067 EXPECT_CALL(ops, close(_, _)).Times(AnyNumber());
1068 EXPECT_CALL(ops, close(_, _))
1069 .WillOnce(DoAll(IgnoreResult(Invoke(ops.get_wrapped(),
1070 &FileOpsInterface::close)),
1071 Return(COUCHSTORE_ERROR_FILE_CLOSE)))
1072 .RetiresOnSaturation();
1078 class MockCouchRequest : public CouchRequest {
1096 uint8_t legacyDeleted; // allow testing via 19byte meta document
1098 static const size_t sizeofV0 = 16;
1099 static const size_t sizeofV1 = 18;
1100 static const size_t sizeofV2 = 19;
1103 MockCouchRequest(const Item& it,
1105 MutationRequestCallback& cb,
1107 : CouchRequest(it, rev, cb, del, false /*persist namespace*/) {
1110 ~MockCouchRequest() {}
1112 // Update what will be written as 'metadata'
1113 void writeMetaData(MetaData& meta, size_t size) {
1114 std::memcpy(dbDocInfo.rev_meta.buf, &meta, size);
1115 dbDocInfo.rev_meta.size = size;
1119 class MockCouchKVStore : public CouchKVStore {
1121 MockCouchKVStore(KVStoreConfig& config) : CouchKVStore(config) {
1124 // Mocks original code but returns the IORequest for fuzzing
1125 MockCouchRequest* setAndReturnRequest(const Item &itm, Callback<mutation_result> &cb) {
1127 throw std::logic_error("MockCouchKVStore::set: Not valid on a read-only "
1130 if (!intransaction) {
1131 throw std::invalid_argument("MockCouchKVStore::set: intransaction must be "
1132 "true to perform a set operation.");
1135 bool deleteItem = false;
1136 MutationRequestCallback requestcb;
1137 uint64_t fileRev = dbFileRevMap[itm.getVBucketId()];
1139 // each req will be de-allocated after commit
1140 requestcb.setCb = &cb;
1141 MockCouchRequest *req = new MockCouchRequest(itm, fileRev, requestcb, deleteItem);
1142 pendingReqsQ.push_back(req);
1148 // Explicitly test couchstore (not valid for ForestDB)
1149 // Intended to ensure we can read and write couchstore files and
1150 // parse metadata we store in them.
1152 class CouchstoreTest : public ::testing::Test {
1155 : data_dir("CouchstoreTest.db"),
1157 config(KVStoreConfig(1024,
1162 false /*persistnamespace*/)
1163 .setBuffered(false)) {
1165 cb::io::rmrf(data_dir.c_str());
1166 } catch (std::system_error& e) {
1167 if (e.code() != std::error_code(ENOENT, std::system_category())) {
1171 kvstore.reset(new MockCouchKVStore(config));
1173 std::string failoverLog("");
1174 // simulate a setVBState - increment the rev and then persist the
1176 kvstore->incrementRevision(0);
1177 vbucket_state state(vbucket_state_active, 0, 0, 0, 0, 0, 0, 0,
1179 // simulate a setVBState - increment the dbFile revision
1180 kvstore->incrementRevision(0);
1181 kvstore->snapshotVBucket(0, state,
1182 VBStatePersist::VBSTATE_PERSIST_WITHOUT_COMMIT);
1186 cb::io::rmrf(data_dir.c_str());
1190 std::string data_dir;
1191 std::unique_ptr<MockCouchKVStore> kvstore;
1193 KVStoreConfig config;
1197 class MockedGetCallback : public Callback<T> {
1199 MockedGetCallback() {}
1201 ~MockedGetCallback() {
1202 delete savedValue.getValue();
1205 void callback(GetValue& value){
1206 status(value.getStatus());
1207 if (value.getStatus() == ENGINE_SUCCESS) {
1208 EXPECT_CALL(*this, value("value"));
1209 cas(value.getValue()->getCas());
1210 expTime(value.getValue()->getExptime());
1211 flags(value.getValue()->getFlags());
1212 datatype(protocol_binary_datatype_t(value.getValue()->getDataType()));
1213 this->value(std::string(value.getValue()->getData(),
1214 value.getValue()->getNBytes()));
1220 return savedValue.getValue();
1224 * Define a number of mock methods that will be invoked by the
1225 * callback method. Functions can then setup expectations of the
1226 * value of each method e.g. expect cas to be -1
1228 MOCK_METHOD1_T(status, void(ENGINE_ERROR_CODE));
1229 MOCK_METHOD1_T(cas, void(uint64_t));
1230 MOCK_METHOD1_T(expTime, void(uint32_t));
1231 MOCK_METHOD1_T(flags, void(uint32_t));
1232 MOCK_METHOD1_T(datatype, void(protocol_binary_datatype_t));
1233 MOCK_METHOD1_T(value, void(std::string));
1235 GetValue savedValue;
1239 * The overall aim of these tests is to create an Item, write it to disk
1240 * then read it back from disk and look at various fields which are
1241 * built from the couchstore rev_meta feature.
1243 * Validation of the Item read from disk is performed by the GetCallback.
1244 * A number of validators can be called upon which compare the disk Item
1245 * against an expected Item.
1247 * The MockCouchKVStore exposes some of the internals of the class so we
1248 * can inject custom metadata by using ::setAndReturnRequest instead of ::set
1251 TEST_F(CouchstoreTest, noMeta) {
1252 StoredDocKey key = makeStoredDocKey("key");
1253 Item item(key, 0, 0, "value", 5);
1256 auto request = kvstore->setAndReturnRequest(item, wc);
1258 // Now directly mess with the metadata of the value which will be written
1259 MockCouchRequest::MetaData meta;
1260 request->writeMetaData(meta, 0); // no meta!
1262 kvstore->commit(nullptr /*no collections manifest*/);
1264 GetCallback gc(ENGINE_TMPFAIL);
1265 kvstore->get(key, 0, gc);
1268 TEST_F(CouchstoreTest, shortMeta) {
1269 StoredDocKey key = makeStoredDocKey("key");
1270 Item item(key, 0, 0, "value", 5);
1273 auto request = kvstore->setAndReturnRequest(item, wc);
1275 // Now directly mess with the metadata of the value which will be written
1276 MockCouchRequest::MetaData meta;
1277 request->writeMetaData(meta, 4); // not enough meta!
1278 kvstore->commit(nullptr /*no collections manifest*/);
1280 GetCallback gc(ENGINE_TMPFAIL);
1281 kvstore->get(key, 0, gc);
1284 TEST_F(CouchstoreTest, testV0MetaThings) {
1285 StoredDocKey key = makeStoredDocKey("key");
1286 // Baseline test, just writes meta things and reads them
1287 // via standard interfaces
1288 // Ensure CAS, exptime and flags are set to something.
1290 0x01020304/*flags*/, 0xaa00bb11/*expiry*/,
1293 0xf00fcafe11225566ull);
1297 kvstore->set(item, wc);
1298 kvstore->commit(nullptr /*no collections manifest*/);
1300 MockedGetCallback<GetValue> gc;
1301 EXPECT_CALL(gc, status(ENGINE_SUCCESS));
1302 EXPECT_CALL(gc, cas(0xf00fcafe11225566ull));
1303 EXPECT_CALL(gc, expTime(0xaa00bb11));
1304 EXPECT_CALL(gc, flags(0x01020304));
1305 EXPECT_CALL(gc, datatype(PROTOCOL_BINARY_RAW_BYTES));
1306 kvstore->get(key, 0, gc);
1309 TEST_F(CouchstoreTest, testV1MetaThings) {
1310 // Baseline test, just writes meta things and reads them
1311 // via standard interfaces
1312 // Ensure CAS, exptime and flags are set to something.
1313 uint8_t datatype = PROTOCOL_BINARY_DATATYPE_JSON; //lies, but non-zero
1314 StoredDocKey key = makeStoredDocKey("key");
1316 0x01020304/*flags*/, 0xaa00bb11,/*expiry*/
1318 &datatype, 1, /*ext_meta is v1 extension*/
1319 0xf00fcafe11225566ull);
1320 EXPECT_NE(0, datatype); // make sure we writing non-zero
1323 kvstore->set(item, wc);
1324 kvstore->commit(nullptr /*no collections manifest*/);
1326 MockedGetCallback<GetValue> gc;
1327 EXPECT_CALL(gc, status(ENGINE_SUCCESS));
1328 EXPECT_CALL(gc, cas(0xf00fcafe11225566ull));
1329 EXPECT_CALL(gc, expTime(0xaa00bb11));
1330 EXPECT_CALL(gc, flags(0x01020304));
1331 EXPECT_CALL(gc, datatype(PROTOCOL_BINARY_DATATYPE_JSON));
1333 kvstore->get(key, 0, gc);
1336 TEST_F(CouchstoreTest, fuzzV0) {
1337 StoredDocKey key = makeStoredDocKey("key");
1338 Item item(key, 0, 0, "value", 5);
1341 auto request = kvstore->setAndReturnRequest(item, wc);
1343 // Now directly mess with the metadata of the value which will be written
1344 MockCouchRequest::MetaData meta;
1345 meta.cas = 0xf00fcafe11225566ull;
1346 meta.expiry = 0xaa00bb11;
1347 meta.flags = 0x01020304;
1348 request->writeMetaData(meta, MockCouchRequest::MetaData::sizeofV0);
1349 kvstore->commit(nullptr /*no collections manifest*/);
1351 // CAS is byteswapped when read back
1352 MockedGetCallback<GetValue> gc;
1353 EXPECT_CALL(gc, status(ENGINE_SUCCESS));
1354 EXPECT_CALL(gc, cas(htonll(0xf00fcafe11225566ull)));
1355 EXPECT_CALL(gc, expTime(htonl(0xaa00bb11)));
1356 EXPECT_CALL(gc, flags(0x01020304));
1357 EXPECT_CALL(gc, datatype(PROTOCOL_BINARY_RAW_BYTES));
1358 kvstore->get(key, 0, gc);
1361 TEST_F(CouchstoreTest, fuzzV1) {
1362 StoredDocKey key = makeStoredDocKey("key");
1363 Item item(key, 0, 0, "value", 5);
1366 auto request = kvstore->setAndReturnRequest(item, wc);
1368 // Now directly mess with the metadata of the value which will be written
1369 MockCouchRequest::MetaData meta;
1370 meta.cas = 0xf00fcafe11225566ull;
1371 meta.expiry = 0xaa00bb11;
1372 meta.flags = 0x01020304;
1375 request->writeMetaData(meta, MockCouchRequest::MetaData::sizeofV1);
1376 kvstore->commit(nullptr /*no collections manifest*/);
1377 MockedGetCallback<GetValue> gc;
1378 uint8_t expectedDataType = 33;
1379 EXPECT_CALL(gc, status(ENGINE_SUCCESS));
1380 EXPECT_CALL(gc, cas(htonll(0xf00fcafe11225566ull)));
1381 EXPECT_CALL(gc, expTime(htonl(0xaa00bb11)));
1382 EXPECT_CALL(gc, flags(0x01020304));
1383 EXPECT_CALL(gc, datatype(protocol_binary_datatype_t(expectedDataType)));
1384 kvstore->get(key, 0, gc);
1387 TEST_F(CouchstoreTest, testV0WriteReadWriteRead) {
1388 // Ensure CAS, exptime and flags are set to something.
1389 uint8_t datatype = PROTOCOL_BINARY_DATATYPE_JSON; //lies, but non-zero
1390 StoredDocKey key = makeStoredDocKey("key");
1392 0x01020304/*flags*/, 0xaa00bb11,/*expiry*/
1394 &datatype, 1, /*ext_meta is v1 extension*/
1395 0xf00fcafe11225566ull);
1397 EXPECT_NE(0, datatype); // make sure we writing non-zero values
1399 // Write an item with forced (valid) V0 meta
1400 MockCouchRequest::MetaData meta;
1401 meta.cas = 0xf00fcafe11225566ull;
1402 meta.expiry = 0xaa00bb11;
1403 meta.flags = 0x01020304;
1407 auto request = kvstore->setAndReturnRequest(item, wc);
1409 // Force the meta to be V0
1410 request->writeMetaData(meta, MockCouchRequest::MetaData::sizeofV0);
1413 kvstore->commit(nullptr /*no collections manifest*/);
1415 // Read back, are V1 fields sane?
1416 MockedGetCallback<GetValue> gc;
1417 EXPECT_CALL(gc, status(ENGINE_SUCCESS));
1418 EXPECT_CALL(gc, cas(htonll(0xf00fcafe11225566ull)));
1419 EXPECT_CALL(gc, expTime(htonl(0xaa00bb11)));
1420 EXPECT_CALL(gc, flags(0x01020304));
1421 EXPECT_CALL(gc, datatype(protocol_binary_datatype_t(meta.ext2)));
1422 kvstore->get(key, 0, gc);
1424 // Write back the item we read (this will write out V1 meta)
1426 kvstore->set(*gc.getValue(), wc);
1427 kvstore->commit(nullptr /*no collections manifest*/);
1429 // Read back, is conf_res_mode sane?
1430 MockedGetCallback<GetValue> gc2;
1431 EXPECT_CALL(gc2, status(ENGINE_SUCCESS));
1432 EXPECT_CALL(gc2, cas(htonll(0xf00fcafe11225566ull)));
1433 EXPECT_CALL(gc2, expTime(htonl(0xaa00bb11)));
1434 EXPECT_CALL(gc2, flags(0x01020304));
1435 EXPECT_CALL(gc2, datatype(protocol_binary_datatype_t(meta.ext2)));
1436 kvstore->get(key, 0, gc2);
1439 TEST_F(CouchstoreTest, testV2WriteRead) {
1440 // Ensure CAS, exptime and flags are set to something.
1441 uint8_t datatype = PROTOCOL_BINARY_DATATYPE_JSON; //lies, but non-zero
1442 StoredDocKey key = makeStoredDocKey("key");
1444 0x01020304/*flags*/, 0xaa00bb11,/*expiry*/
1446 &datatype, 1, /*ext_meta is v1 extension*/
1447 0xf00fcafe11225566ull);
1449 EXPECT_NE(0, datatype); // make sure we writing non-zero values
1451 // Write an item with forced (valid) V2 meta
1452 // In 4.6 we removed the extra conflict resolution byte, so be sure we
1453 // operate correctly if a document has V2 meta.
1454 MockCouchRequest::MetaData meta;
1455 meta.cas = 0xf00fcafe11225566ull;
1456 meta.expiry = 0xaa00bb11;
1457 meta.flags = 0x01020304;
1458 meta.ext1 = FLEX_META_CODE;
1459 meta.ext2 = datatype;
1460 meta.legacyDeleted = 0x01;
1464 auto request = kvstore->setAndReturnRequest(item, wc);
1466 // Force the meta to be V2 (19 bytes)
1467 request->writeMetaData(meta, MockCouchRequest::MetaData::sizeofV2);
1470 kvstore->commit(nullptr /*no collections manifest*/);
1472 // Read back successful, the extra byte will of been dropped.
1473 MockedGetCallback<GetValue> gc;
1474 EXPECT_CALL(gc, status(ENGINE_SUCCESS));
1475 EXPECT_CALL(gc, cas(htonll(0xf00fcafe11225566ull)));
1476 EXPECT_CALL(gc, expTime(htonl(0xaa00bb11)));
1477 EXPECT_CALL(gc, flags(0x01020304));
1478 EXPECT_CALL(gc, datatype(protocol_binary_datatype_t(meta.ext2)));
1479 kvstore->get(key, 0, gc);
1482 class CouchKVStoreMetaData : public ::testing::Test {
1485 TEST_F(CouchKVStoreMetaData, basic) {
1486 // Lock down the size assumptions.
1487 EXPECT_EQ(16, MetaData::getMetaDataSize(MetaData::Version::V0));
1488 EXPECT_EQ(16 + 2, MetaData::getMetaDataSize(MetaData::Version::V1));
1489 EXPECT_EQ(16 + 2 + 1, MetaData::getMetaDataSize(MetaData::Version::V2));
1492 TEST_F(CouchKVStoreMetaData, overlay) {
1493 std::vector<char> data(16);
1495 meta.buf = data.data();
1496 meta.size = data.size();
1497 auto metadata = MetaDataFactory::createMetaData(meta);
1498 EXPECT_EQ(MetaData::Version::V0, metadata->getVersionInitialisedFrom());
1500 data.resize(16 + 2);
1501 meta.buf = data.data();
1502 meta.size = data.size();
1503 metadata = MetaDataFactory::createMetaData(meta);
1504 EXPECT_EQ(MetaData::Version::V1, metadata->getVersionInitialisedFrom());
1506 // Even with a 19 byte (v2) meta, the expectation is we become V1
1507 data.resize(16 + 2 + 1);
1508 meta.buf = data.data();
1509 meta.size = data.size();
1510 metadata = MetaDataFactory::createMetaData(meta);
1511 EXPECT_EQ(MetaData::Version::V1, metadata->getVersionInitialisedFrom());
1513 // Buffers too large and small
1514 data.resize(16 + 2 + 1 + 1);
1515 meta.buf = data.data();
1516 meta.size = data.size();
1517 EXPECT_THROW(MetaDataFactory::createMetaData(meta), std::logic_error);
1520 meta.buf = data.data();
1521 meta.size = data.size();
1522 EXPECT_THROW(MetaDataFactory::createMetaData(meta), std::logic_error);
1525 TEST_F(CouchKVStoreMetaData, overlayExpands1) {
1526 std::vector<char> data(16);
1529 meta.buf = data.data();
1530 meta.size = data.size();
1532 // V0 in yet V1 "moved out"
1533 auto metadata = MetaDataFactory::createMetaData(meta);
1534 EXPECT_EQ(MetaData::Version::V0, metadata->getVersionInitialisedFrom());
1535 out.size = MetaData::getMetaDataSize(MetaData::Version::V1);
1536 out.buf = new char[out.size];
1537 metadata->copyToBuf(out);
1538 EXPECT_EQ(out.size, MetaData::getMetaDataSize(MetaData::Version::V1));
1540 // We created a copy of the metadata so we must cleanup
1544 TEST_F(CouchKVStoreMetaData, overlayExpands2) {
1545 std::vector<char> data(16 + 2);
1548 meta.buf = data.data();
1549 meta.size = data.size();
1551 // V1 in V1 "moved out"
1552 auto metadata = MetaDataFactory::createMetaData(meta);
1553 EXPECT_EQ(MetaData::Version::V1, metadata->getVersionInitialisedFrom());
1554 out.size = MetaData::getMetaDataSize(MetaData::Version::V1);
1555 out.buf = new char[out.size];
1556 metadata->copyToBuf(out);
1557 EXPECT_EQ(out.size, MetaData::getMetaDataSize(MetaData::Version::V1));
1559 // We created a copy of the metadata so we must cleanup
1563 TEST_F(CouchKVStoreMetaData, writeToOverlay) {
1564 std::vector<char> data(16);
1567 meta.buf = data.data();
1568 meta.size = data.size();
1570 // Test that we can initialise from V0 but still set
1571 // all fields of all versions
1572 auto metadata = MetaDataFactory::createMetaData(meta);
1573 EXPECT_EQ(MetaData::Version::V0, metadata->getVersionInitialisedFrom());
1575 uint64_t cas = 0xf00f00ull;
1576 uint32_t exp = 0xcafe1234;
1577 uint32_t flags = 0xc0115511;
1578 metadata->setCas(cas);
1579 metadata->setExptime(exp);
1580 metadata->setFlags(flags);
1581 metadata->setDataType(PROTOCOL_BINARY_DATATYPE_JSON);
1583 // Check they all read back
1584 EXPECT_EQ(cas, metadata->getCas());
1585 EXPECT_EQ(exp, metadata->getExptime());
1586 EXPECT_EQ(flags, metadata->getFlags());
1587 EXPECT_EQ(FLEX_META_CODE, metadata->getFlexCode());
1588 EXPECT_EQ(PROTOCOL_BINARY_DATATYPE_JSON, metadata->getDataType());
1590 // Now we move the metadata out, this will give back a V1 structure
1591 out.size = MetaData::getMetaDataSize(MetaData::Version::V1);
1592 out.buf = new char[out.size];
1593 metadata->copyToBuf(out);
1594 metadata = MetaDataFactory::createMetaData(out);
1595 EXPECT_EQ(MetaData::Version::V1, metadata->getVersionInitialisedFrom()); // Is it V1?
1597 // All the written fields should be the same
1598 // Check they all read back
1599 EXPECT_EQ(cas, metadata->getCas());
1600 EXPECT_EQ(exp, metadata->getExptime());
1601 EXPECT_EQ(flags, metadata->getFlags());
1602 EXPECT_EQ(FLEX_META_CODE, metadata->getFlexCode());
1603 EXPECT_EQ(PROTOCOL_BINARY_DATATYPE_JSON, metadata->getDataType());
1604 EXPECT_EQ(out.size, MetaData::getMetaDataSize(MetaData::Version::V1));
1606 // We moved the metadata so we must cleanup
1611 // Test that assignment operates as expected (we use this in edit_docinfo_hook)
1613 TEST_F(CouchKVStoreMetaData, assignment) {
1614 std::vector<char> data(16);
1616 meta.buf = data.data();
1617 meta.size = data.size();
1618 auto metadata = MetaDataFactory::createMetaData(meta);
1619 uint64_t cas = 0xf00f00ull;
1620 uint32_t exp = 0xcafe1234;
1621 uint32_t flags = 0xc0115511;
1622 metadata->setCas(cas);
1623 metadata->setExptime(exp);
1624 metadata->setFlags(flags);
1625 metadata->setDataType( PROTOCOL_BINARY_DATATYPE_JSON);
1627 // Create a second metadata to write into
1628 auto copy = MetaDataFactory::createMetaData();
1630 // Copy overlaid into managed
1633 // Test that the copy doesn't write to metadata
1634 copy->setExptime(100);
1635 EXPECT_EQ(exp, metadata->getExptime());
1637 EXPECT_EQ(cas, copy->getCas());
1638 EXPECT_EQ(100, copy->getExptime());
1639 EXPECT_EQ(flags, copy->getFlags());
1640 EXPECT_EQ(FLEX_META_CODE, copy->getFlexCode());
1641 EXPECT_EQ(PROTOCOL_BINARY_DATATYPE_JSON, copy->getDataType());
1643 // And a final assignment
1644 auto copy2 = MetaDataFactory::createMetaData();
1647 // test that copy2 doesn't update copy
1649 EXPECT_NE(99, copy->getCas());
1652 EXPECT_EQ(99, copy2->getCas());
1653 EXPECT_EQ(100, copy2->getExptime());
1654 EXPECT_EQ(flags, copy2->getFlags());
1655 EXPECT_EQ(FLEX_META_CODE, copy2->getFlexCode());
1656 EXPECT_EQ(PROTOCOL_BINARY_DATATYPE_JSON, copy2->getDataType());
1659 #ifdef EP_USE_FORESTDB
1660 // Test cases which run on both Couchstore and ForestDB
1661 INSTANTIATE_TEST_CASE_P(CouchstoreAndForestDB,
1663 ::testing::Values("couchdb", "forestdb"),
1664 [] (const ::testing::TestParamInfo<std::string>& info) {
1668 INSTANTIATE_TEST_CASE_P(CouchstoreAndForestDB,
1670 ::testing::Values("couchdb"),
1671 [] (const ::testing::TestParamInfo<std::string>& info) {