Add WithMeta unit tests 81/76381/14
authorJim Walker <jim@couchbase.com>
Wed, 5 Apr 2017 15:20:00 +0000 (16:20 +0100)
committerDave Rigby <daver@couchbase.com>
Tue, 18 Apr 2017 11:58:41 +0000 (11:58 +0000)
Create a test-suite using the SingleThreaded store test to drive
the with_meta commands.

The test-suite so far tries to exercise many of the error and success
paths.

The intent is to give a starting point for more comprehensive and easy
to understand *with_meta tests as opposed to the extension of the
testapp tests.

Change-Id: I4bdfec9879de3a9671fed820f9b5aab0ac7d2c71
Reviewed-on: http://review.couchbase.org/76381
Tested-by: Build Bot <build@couchbase.com>
Reviewed-by: Dave Rigby <daver@couchbase.com>
CMakeLists.txt
tests/mock/mock_synchronous_ep_engine.cc
tests/mock/mock_synchronous_ep_engine.h
tests/module_tests/evp_store_with_meta.cc [new file with mode: 0644]
tests/module_tests/kv_bucket_test.cc
tests/module_tests/kv_bucket_test.h

index 19f6b3a..df7b93b 100644 (file)
@@ -258,6 +258,7 @@ ADD_EXECUTABLE(ep-engine_ep_unit_tests
                tests/module_tests/evp_store_rollback_test.cc
                tests/module_tests/evp_store_test.cc
                tests/module_tests/evp_store_single_threaded_test.cc
+               tests/module_tests/evp_store_with_meta.cc
                tests/module_tests/executorpool_test.cc
                tests/module_tests/failover_table_test.cc
                tests/module_tests/futurequeue_test.cc
index 69130e3..5bd1ef4 100644 (file)
@@ -61,6 +61,8 @@ SynchronousEPEngine::SynchronousEPEngine(const std::string& extra_config)
     tapConfig = new TapConfig(*this);
 
     enableTraffic(true);
+
+    maxItemSize = configuration.getMaxItemSize();
 }
 
 void SynchronousEPEngine::setKVBucket(std::unique_ptr<KVBucket> store) {
index 71358b9..d3645bd 100644 (file)
@@ -65,4 +65,8 @@ public:
     std::unique_ptr<KVBucket> public_makeBucket(Configuration& config) {
         return makeBucket(config);
     }
+
+    bool public_enableTraffic(bool enable) {
+        return enableTraffic(enable);
+    }
 };
diff --git a/tests/module_tests/evp_store_with_meta.cc b/tests/module_tests/evp_store_with_meta.cc
new file mode 100644 (file)
index 0000000..ed8c1ed
--- /dev/null
@@ -0,0 +1,1032 @@
+/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil
+ * -*- */
+/*
+ *     Copyright 2017 Couchbase, Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ */
+
+#include "evp_store_single_threaded_test.h"
+
+class WithMetaTest : public SingleThreadedEPBucketTest {
+public:
+    void SetUp() override {
+        SingleThreadedEPBucketTest::SetUp();
+        store->setVBucketState(vbid, vbucket_state_active, false);
+        expiry = ep_real_time() + 31557600; // +1 year in seconds
+    }
+
+    void enableLww() {
+        if (!config_string.empty()) {
+            config_string += ";";
+        }
+        config_string += "conflict_resolution_type=lww";
+    }
+
+    /**
+     * Build a *_with_meta packet, defaulting a number of arguments (keeping
+     * some of the test bodies smaller)
+     */
+    std::vector<char> buildWithMeta(protocol_binary_command op,
+                                    ItemMetaData itemMeta,
+                                    const std::string& key,
+                                    const std::string& value) const {
+        return buildWithMetaPacket(op,
+                                   0 /*datatype*/,
+                                   vbid,
+                                   0 /*opaque*/,
+                                   0 /*cas*/,
+                                   itemMeta,
+                                   key,
+                                   value,
+                                   {},
+                                   0);
+    }
+
+    /**
+     * Given a buffer of data representing a with_meta packet, update the meta
+     * Allows test to avoid lots of allocation/copying when creating inputs.
+     */
+    static void updateMeta(std::vector<char>& wm,
+                           uint64_t cas,
+                           uint64_t revSeq,
+                           uint32_t flags,
+                           uint32_t exp) {
+        auto packet = reinterpret_cast<protocol_binary_request_set_with_meta*>(
+                wm.data());
+        packet->message.body.cas = htonll(cas);
+        packet->message.body.seqno = htonll(revSeq);
+        packet->message.body.expiration = htonl(exp);
+        packet->message.body.flags = flags;
+    }
+
+    /**
+     * Given a buffer of data representing a with_meta packet, update the meta
+     * Allows test to avoid lots of allocation/copying when creating inputs.
+     */
+    static void updateMeta(std::vector<char>& wm,
+                           const ItemMetaData& itemMeta) {
+        updateMeta(wm,
+                   itemMeta.cas,
+                   itemMeta.revSeqno,
+                   itemMeta.flags,
+                   uint32_t(itemMeta.exptime));
+    }
+
+    /**
+     * Call the correct engine function for the op (set vs delete)
+     */
+    ENGINE_ERROR_CODE callEngine(protocol_binary_command op,
+                                 std::vector<char>& wm) {
+        if (op == PROTOCOL_BINARY_CMD_DEL_WITH_META ||
+            op == PROTOCOL_BINARY_CMD_DELQ_WITH_META) {
+            return engine->deleteWithMeta(
+                    cookie,
+                    reinterpret_cast<protocol_binary_request_delete_with_meta*>(
+                            wm.data()),
+                    this->addResponse,
+                    DocNamespace::DefaultCollection);
+        } else {
+            return engine->setWithMeta(
+                    cookie,
+                    reinterpret_cast<protocol_binary_request_set_with_meta*>(
+                            wm.data()),
+                    this->addResponse,
+                    DocNamespace::DefaultCollection);
+        }
+    }
+
+    /**
+     * Get the item and check its value, if called for a delete, assuming
+     * delete with value
+     */
+    void checkGetItem(
+            const std::string& key,
+            const std::string& expectedValue,
+            ItemMetaData expectedMeta,
+            ENGINE_ERROR_CODE expectedGetReturnValue = ENGINE_SUCCESS) {
+        auto result = store->get({key, DocNamespace::DefaultCollection},
+                                 vbid,
+                                 nullptr,
+                                 GET_DELETED_VALUE);
+
+        ASSERT_EQ(expectedGetReturnValue, result.getStatus());
+
+        if (expectedGetReturnValue == ENGINE_SUCCESS) {
+            EXPECT_EQ(0,
+                      strncmp(expectedValue.data(),
+                              result.getValue()->getData(),
+                              result.getValue()->getNBytes()));
+            EXPECT_EQ(expectedMeta.cas, result.getValue()->getCas());
+            EXPECT_EQ(expectedMeta.revSeqno, result.getValue()->getRevSeqno());
+            EXPECT_EQ(expectedMeta.flags, result.getValue()->getFlags());
+            EXPECT_EQ(expectedMeta.exptime, result.getValue()->getExptime());
+            delete result.getValue();
+        }
+    }
+
+    void oneOp(protocol_binary_command op,
+               ItemMetaData itemMeta,
+               int options,
+               protocol_binary_response_status expectedResponseStatus,
+               const std::string& key,
+               const std::string& value) {
+        auto swm = buildWithMetaPacket(op,
+                                       0 /*datatype*/,
+                                       vbid,
+                                       0 /*opaque*/,
+                                       0 /*cas*/,
+                                       itemMeta,
+                                       key,
+                                       value,
+                                       {},
+                                       options);
+        EXPECT_EQ(ENGINE_SUCCESS, callEngine(op, swm));
+        EXPECT_EQ(expectedResponseStatus, getAddResponseStatus());
+    }
+
+    /**
+     * Run one op and check the result
+     */
+    void oneOpAndCheck(protocol_binary_command op,
+                       ItemMetaData itemMeta,
+                       int options,
+                       bool withValue,
+                       protocol_binary_response_status expectedResponseStatus,
+                       ENGINE_ERROR_CODE expectedGetReturnValue) {
+        std::string key = "mykey";
+        std::string value;
+        if (withValue) {
+            value = createXattrValue("myvalue"); // xattr but stored as raw
+        }
+        oneOp(op,
+              itemMeta,
+              options,
+              expectedResponseStatus,
+              key,
+              value);
+        checkGetItem(key, value, itemMeta, expectedGetReturnValue);
+    }
+
+    // *_with_meta with winning mutations
+    struct TestData {
+        ItemMetaData meta;
+        protocol_binary_response_status expectedStatus;
+    };
+
+    /**
+     * The conflict_win test is reused by seqno/lww and is intended to
+     * test each winning op/meta input
+     */
+    void conflict_win(protocol_binary_command op,
+                      int options,
+                      const std::array<TestData, 4>& testData,
+                      const ItemMetaData& itemMeta);
+    /**
+     * The conflict_win test is reused by seqno/lww and is intended to
+     * test each winning op/meta input
+     */
+    void conflict_lose(protocol_binary_command op,
+                       int options,
+                       bool withValue,
+                       const std::array<TestData, 4>& testData,
+                       const ItemMetaData& itemMeta);
+
+    /**
+     * The conflict_del_lose_xattr test demonstrates how a delete never gets
+     * to compare xattrs when in conflict.
+     */
+    void conflict_del_lose_xattr(protocol_binary_command op,
+                                 int options,
+                                 bool withValue);
+
+    /**
+     * The conflict_lose_xattr test demonstrates how a set gets
+     * to compare xattrs when in conflict, and the server doc would win.
+     */
+    void conflict_lose_xattr(protocol_binary_command op,
+                             int options,
+                             bool withValue);
+    /**
+     * Initialise an expiry value which allows us to set/get items without them
+     * expiring, i.e. a few years of expiry wiggle room
+     */
+    time_t expiry;
+};
+
+class WithMetaLwwTest : public WithMetaTest {
+public:
+    void SetUp() override {
+        enableLww();
+        WithMetaTest::SetUp();
+    }
+};
+
+class DelWithMetaTest
+        : public WithMetaTest,
+          public ::testing::WithParamInterface<
+                  ::testing::tuple<bool, protocol_binary_command>> {
+public:
+    void SetUp() override {
+        withValue = ::testing::get<0>(GetParam());
+        op = ::testing::get<1>(GetParam());
+        WithMetaTest::SetUp();
+    }
+
+    protocol_binary_command op;
+    bool withValue;
+};
+
+class DelWithMetaLwwTest
+        : public WithMetaTest,
+          public ::testing::WithParamInterface<
+                  ::testing::tuple<bool, protocol_binary_command>> {
+public:
+    void SetUp() override {
+        withValue = ::testing::get<0>(GetParam());
+        op = ::testing::get<1>(GetParam());
+        enableLww();
+        WithMetaTest::SetUp();
+    }
+
+    protocol_binary_command op;
+    bool withValue;
+};
+
+class AllWithMetaTest
+        : public WithMetaTest,
+          public ::testing::WithParamInterface<protocol_binary_command> {};
+
+class AddSetWithMetaTest
+        : public WithMetaTest,
+          public ::testing::WithParamInterface<protocol_binary_command> {};
+
+class AddSetWithMetaLwwTest
+        : public WithMetaTest,
+          public ::testing::WithParamInterface<protocol_binary_command> {
+public:
+    void SetUp() override {
+        enableLww();
+        WithMetaTest::SetUp();
+    }
+};
+
+TEST_P(AddSetWithMetaTest, basic) {
+    ItemMetaData itemMeta{0xdeadbeef, 0xf00dcafe, 0xfacefeed, expiry};
+    oneOpAndCheck(GetParam(),
+                  itemMeta,
+                  0, // no-options
+                  true /*set a value*/,
+                  PROTOCOL_BINARY_RESPONSE_SUCCESS,
+                  ENGINE_SUCCESS);
+}
+
+TEST_F(WithMetaTest, basicAdd) {
+    ItemMetaData itemMeta{0xdeadbeef, 0xf00dcafe, 0xfacefeed, expiry};
+    oneOpAndCheck(PROTOCOL_BINARY_CMD_ADD_WITH_META,
+                  itemMeta,
+                  0, // no-options
+                  true /*set a value*/,
+                  PROTOCOL_BINARY_RESPONSE_SUCCESS,
+                  ENGINE_SUCCESS);
+
+    oneOpAndCheck(PROTOCOL_BINARY_CMD_ADD_WITH_META,
+                  itemMeta,
+                  0, // no-options
+                  true /*set a value*/,
+                  PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS, // can't do a second add
+                  ENGINE_SUCCESS); // can still get the key
+}
+
+TEST_P(DelWithMetaTest, basic) {
+    ItemMetaData itemMeta{0xdeadbeef, 0xf00dcafe, 0xfacefeed, expiry};
+    // A delete_w_meta against an empty bucket queues a BGFetch (get = ewblock)
+    // A delete_w_meta(with_value) sets the new value (get = success)
+    oneOpAndCheck(op,
+                  itemMeta,
+                  0, // no-options
+                  withValue,
+                  PROTOCOL_BINARY_RESPONSE_SUCCESS,
+                  withValue ? ENGINE_SUCCESS : ENGINE_EWOULDBLOCK);
+}
+
+TEST_P(AllWithMetaTest, invalidCas) {
+    // 0 CAS in the item meta is invalid
+    ItemMetaData itemMeta{0 /*cas*/, 0, 0, 0};
+    oneOpAndCheck(GetParam(),
+                  itemMeta,
+                  0, // no-options
+                  true /*set a value*/,
+                  PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS,
+                  ENGINE_KEY_ENOENT);
+
+    // -1 CAS in the item meta is invalid
+    itemMeta.cas = ~0ull;
+    oneOpAndCheck(GetParam(),
+                  itemMeta,
+                  0, // no-options
+                  true /*set a value*/,
+                  PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS,
+                  ENGINE_KEY_ENOENT);
+}
+
+TEST_P(DelWithMetaTest, invalidCas) {
+    // 0 CAS in the item meta is invalid
+    ItemMetaData itemMeta{0 /*cas*/, 0, 0, 0};
+    oneOpAndCheck(op,
+                  itemMeta,
+                  0, // no-options
+                  withValue /*set a value*/,
+                  PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS,
+                  ENGINE_KEY_ENOENT);
+
+    // -1 CAS in the item meta is invalid
+    itemMeta.cas = ~0ull;
+    oneOpAndCheck(op,
+                  itemMeta,
+                  0, // no-options
+                  withValue /*set a value*/,
+                  PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS,
+                  ENGINE_KEY_ENOENT);
+}
+
+TEST_P(AllWithMetaTest, failForceAccept) {
+    // FORCE_ACCEPT_WITH_META_OPS not allowed unless we're LWW
+    ItemMetaData itemMeta{1, 0, 0, expiry};
+    oneOpAndCheck(GetParam(),
+                  itemMeta,
+                  FORCE_ACCEPT_WITH_META_OPS,
+                  true /*set a value*/,
+                  PROTOCOL_BINARY_RESPONSE_EINVAL,
+                  ENGINE_KEY_ENOENT);
+}
+
+TEST_P(AddSetWithMetaLwwTest, allowForceAccept) {
+    // FORCE_ACCEPT_WITH_META_OPS ok on LWW
+    ItemMetaData itemMeta{1, 0, 0, expiry};
+    oneOpAndCheck(GetParam(),
+                  itemMeta,
+                  FORCE_ACCEPT_WITH_META_OPS,
+                  true /*set a value*/,
+                  PROTOCOL_BINARY_RESPONSE_SUCCESS,
+                  ENGINE_SUCCESS);
+}
+
+TEST_P(DelWithMetaLwwTest, allowForceAccept) {
+    // FORCE_ACCEPT_WITH_META_OPS ok on LWW
+    ItemMetaData itemMeta{1, 0, 0, expiry};
+    oneOpAndCheck(op,
+                  itemMeta,
+                  FORCE_ACCEPT_WITH_META_OPS,
+                  withValue,
+                  PROTOCOL_BINARY_RESPONSE_SUCCESS,
+                  withValue ? ENGINE_SUCCESS : ENGINE_EWOULDBLOCK);
+}
+
+TEST_P(AllWithMetaTest, regenerateCASInvalid) {
+    // REGENERATE_CAS cannot be by itself
+    ItemMetaData itemMeta{1, 0, 0, expiry};
+    oneOpAndCheck(GetParam(),
+                  itemMeta,
+                  REGENERATE_CAS,
+                  true,
+                  PROTOCOL_BINARY_RESPONSE_EINVAL,
+                  ENGINE_KEY_ENOENT);
+}
+
+TEST_P(AllWithMetaTest, regenerateCAS) {
+    // Test that
+    uint64_t cas = 1;
+    auto swm =
+            buildWithMetaPacket(GetParam(),
+                                0 /*datatype*/,
+                                vbid /*vbucket*/,
+                                0 /*opaque*/,
+                                0 /*cas*/,
+                                {cas, 0, 0, 0},
+                                "mykey",
+                                "myvalue",
+                                {},
+                                SKIP_CONFLICT_RESOLUTION_FLAG | REGENERATE_CAS);
+
+    EXPECT_EQ(ENGINE_SUCCESS, callEngine(GetParam(), swm));
+    EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, getAddResponseStatus());
+    auto result = store->get({"mykey", DocNamespace::DefaultCollection},
+                             vbid,
+                             nullptr,
+                             GET_DELETED_VALUE);
+    ASSERT_EQ(ENGINE_SUCCESS, result.getStatus());
+    EXPECT_NE(cas, result.getValue()->getCas()) << "CAS didn't change";
+    delete result.getValue();
+}
+
+TEST_P(AllWithMetaTest, invalid_extlen) {
+    // extlen must be very specific values
+    std::string key = "mykey";
+    std::string value = "myvalue";
+    ItemMetaData itemMeta{1, 0, 0, 0};
+
+    auto swm = buildWithMetaPacket(GetParam(),
+                                   0 /*datatype*/,
+                                   vbid /*vbucket*/,
+                                   0 /*opaque*/,
+                                   0 /*cas*/,
+                                   itemMeta,
+                                   key,
+                                   value);
+
+    auto packet = reinterpret_cast<protocol_binary_request_set_with_meta*>(
+            swm.data());
+    // futz the extlen (yes yes AFL fuzz would be ace)
+    for (uint8_t e = 0; e < 0xff; e++) {
+        if (e == 24 || e == 26 || e == 28 || e == 30) {
+            // Skip the valid sizes
+            continue;
+        }
+        packet->message.header.request.extlen = e;
+        EXPECT_EQ(ENGINE_SUCCESS, callEngine(GetParam(), swm));
+        EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_EINVAL, getAddResponseStatus());
+        checkGetItem(key, value, itemMeta, ENGINE_KEY_ENOENT);
+    }
+}
+
+TEST_P(AllWithMetaTest, nmvb) {
+    std::string key = "mykey";
+    std::string value = "myvalue";
+    auto swm = buildWithMetaPacket(GetParam(),
+                                   0 /*datatype*/,
+                                   vbid + 1 /*vbucket*/,
+                                   0 /*opaque*/,
+                                   0 /*cas*/,
+                                   {1, 0, 0, 0},
+                                   key,
+                                   value);
+    EXPECT_EQ(ENGINE_SUCCESS, callEngine(GetParam(), swm));
+    EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_NOT_MY_VBUCKET, getAddResponseStatus());
+
+    // Set a dead VB
+    EXPECT_EQ(ENGINE_SUCCESS,
+              store->setVBucketState(vbid + 1, vbucket_state_dead, false));
+    EXPECT_EQ(ENGINE_SUCCESS, callEngine(GetParam(), swm));
+    EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_NOT_MY_VBUCKET, getAddResponseStatus());
+
+    // update the VB in the packet to the pending one
+    auto packet = reinterpret_cast<protocol_binary_request_header*>(swm.data());
+    packet->request.vbucket = htons(vbid + 2);
+    EXPECT_EQ(ENGINE_SUCCESS,
+              store->setVBucketState(vbid + 2, vbucket_state_pending, false));
+    EXPECT_EQ(ENGINE_EWOULDBLOCK, callEngine(GetParam(), swm));
+    EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, getAddResponseStatus());
+
+    // Re-run the op now active, else we have a memory leak
+    EXPECT_EQ(ENGINE_SUCCESS,
+              store->setVBucketState(vbid + 2, vbucket_state_active, false));
+    EXPECT_EQ(ENGINE_SUCCESS, callEngine(GetParam(), swm));
+}
+
+TEST_P(AllWithMetaTest, takeoverBackedup) {
+    ItemMetaData itemMeta{1, 0, 0, expiry};
+    auto swm = buildWithMetaPacket(GetParam(),
+                                   0 /*datatype*/,
+                                   vbid /*vbucket*/,
+                                   0 /*opaque*/,
+                                   0 /*cas*/,
+                                   itemMeta,
+                                   "mykey",
+                                   "myvalue");
+
+    store->getVBucket(vbid)->setTakeoverBackedUpState(true);
+    oneOpAndCheck(GetParam(),
+                  itemMeta,
+                  0,
+                  true,
+                  PROTOCOL_BINARY_RESPONSE_ETMPFAIL,
+                  ENGINE_KEY_ENOENT);
+}
+
+TEST_P(AllWithMetaTest, degraded) {
+    ItemMetaData itemMeta{1, 0, 0, expiry};
+    auto swm = buildWithMetaPacket(GetParam(),
+                                   0 /*datatype*/,
+                                   vbid /*vbucket*/,
+                                   0 /*opaque*/,
+                                   0 /*cas*/,
+                                   itemMeta,
+                                   "mykey",
+                                   "myvalue");
+
+    engine->public_enableTraffic(false);
+    oneOpAndCheck(GetParam(),
+                  itemMeta,
+                  0,
+                  true,
+                  PROTOCOL_BINARY_RESPONSE_ETMPFAIL,
+                  ENGINE_KEY_ENOENT);
+}
+
+void WithMetaTest::conflict_lose(protocol_binary_command op,
+                                 int options,
+                                 bool withValue,
+                                 const std::array<TestData, 4>& testData,
+                                 const ItemMetaData& itemMeta) {
+    std::string value;
+    if (withValue) {
+        value = createXattrValue("myvalue");
+    }
+    std::string key = "mykey";
+    // First add a document so we have something to conflict with
+    auto swm = buildWithMetaPacket(PROTOCOL_BINARY_CMD_ADD_WITH_META,
+                                   0,
+                                   vbid /*vbucket*/,
+                                   0 /*opaque*/,
+                                   0 /*cas*/,
+                                   itemMeta,
+                                   key,
+                                   value,
+                                   {},
+                                   options);
+
+    EXPECT_EQ(ENGINE_SUCCESS,
+              callEngine(PROTOCOL_BINARY_CMD_ADD_WITH_META, swm));
+    EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, getAddResponseStatus());
+
+    for (const auto& td : testData) {
+        oneOp(op, td.meta, options, td.expectedStatus, key, value);
+    }
+}
+
+// store a document then <op>_with_meta with equal ItemMeta but xattr on
+void WithMetaTest::conflict_del_lose_xattr(protocol_binary_command op,
+                                           int options,
+                                           bool withValue) {
+    ItemMetaData itemMeta{
+            100 /*cas*/, 100 /*revSeq*/, 100 /*flags*/, expiry /*expiry*/};
+    std::string value;
+    if (withValue) {
+        value = createXattrValue("myvalue");
+    }
+    std::string key = "mykey";
+    // First add a document so we have something to conflict with
+    auto swm = buildWithMetaPacket(PROTOCOL_BINARY_CMD_ADD_WITH_META,
+                                   0 /*xattr off*/,
+                                   vbid /*vbucket*/,
+                                   0 /*opaque*/,
+                                   0 /*cas*/,
+                                   itemMeta,
+                                   key,
+                                   value,
+                                   {},
+                                   options);
+
+    EXPECT_EQ(ENGINE_SUCCESS,
+              callEngine(PROTOCOL_BINARY_CMD_ADD_WITH_META, swm));
+    EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, getAddResponseStatus());
+
+    // revSeqno/cas/exp/flags equal, xattr on, conflict (a set would win)
+    swm = buildWithMetaPacket(op,
+                              PROTOCOL_BINARY_DATATYPE_XATTR,
+                              vbid /*vbucket*/,
+                              0 /*opaque*/,
+                              0 /*cas*/,
+                              itemMeta,
+                              key,
+                              value,
+                              {},
+                              options);
+    EXPECT_EQ(ENGINE_SUCCESS, callEngine(op, swm));
+    EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS, getAddResponseStatus());
+}
+
+void WithMetaTest::conflict_lose_xattr(protocol_binary_command op,
+                                       int options,
+                                       bool withValue) {
+    ItemMetaData itemMeta{
+            100 /*cas*/, 100 /*revSeq*/, 100 /*flags*/, expiry /*expiry*/};
+    std::string value;
+    if (withValue) {
+        value = createXattrValue("myvalue");
+    }
+    std::string key = "mykey";
+    // First add a document so we have something to conflict with
+    auto swm = buildWithMetaPacket(PROTOCOL_BINARY_CMD_ADD_WITH_META,
+                                   PROTOCOL_BINARY_DATATYPE_XATTR,
+                                   vbid /*vbucket*/,
+                                   0 /*opaque*/,
+                                   0 /*cas*/,
+                                   itemMeta,
+                                   key,
+                                   value,
+                                   {},
+                                   options);
+
+    EXPECT_EQ(ENGINE_SUCCESS,
+              callEngine(PROTOCOL_BINARY_CMD_ADD_WITH_META, swm));
+    EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, getAddResponseStatus());
+
+    // revSeqno/cas/exp/flags equal, xattr off, conflict (a set would win)
+    swm = buildWithMetaPacket(op,
+                              0,
+                              vbid /*vbucket*/,
+                              0 /*opaque*/,
+                              0 /*cas*/,
+                              itemMeta,
+                              key,
+                              value,
+                              {},
+                              options);
+    EXPECT_EQ(ENGINE_SUCCESS, callEngine(op, swm));
+    EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS, getAddResponseStatus());
+}
+
+TEST_P(DelWithMetaTest, conflict_lose) {
+    ItemMetaData itemMeta{
+            100 /*cas*/, 100 /*revSeq*/, 100 /*flags*/, expiry /*expiry*/};
+
+    // Conflict test order: 1) seqno 2) cas 3) expiry 4) flags 5) xattr
+    // However deletes only check 1 and 2.
+
+    std::array<TestData, 4> data;
+
+    // 1) revSeqno is less and everything else larger. Expect conflict
+    data[0] = {{101, 99, 101, expiry + 1},
+               PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS};
+    // 2. revSeqno is equal, cas is less, others are larger. Expect conflict
+    data[1] = {{99, 100, 101, expiry + 1},
+               PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS};
+    // 3. revSeqno/cas/flags equal, exp larger. Conflict as exp not checked
+    data[2] = {{100, 100, 100, expiry + 1},
+               PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS};
+    // 4. revSeqno/cas/exp equal, flags larger. Conflict as exp not checked
+    data[3] = {{100, 100, 200, expiry}, PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS};
+
+    conflict_lose(op, 0, withValue, data, itemMeta);
+}
+
+TEST_P(DelWithMetaLwwTest, conflict_lose) {
+    ItemMetaData itemMeta{
+            100 /*cas*/, 100 /*revSeq*/, 100 /*flags*/, expiry /*expiry*/};
+
+    // Conflict test order: 1) cas 2) seqno 3) expiry 4) flags 5) xattr
+    // However deletes only check 1 and 2.
+
+    std::array<TestData, 4> data;
+    // 1) cas is less and everything else larger. Expect conflict
+    data[0] = {{99, 101, 101, expiry + 1},
+               PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS};
+    // 2. cas is equal, revSeqno is less, others are larger. Expect conflict
+    data[1] = {{100, 99, 101, expiry + 1},
+               PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS};
+    // 3. revSeqno/cas/flags equal, exp larger. Conflict as exp not checked
+    data[2] = {{100, 100, 100, expiry + 1},
+               PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS};
+    // 4. revSeqno/cas/exp equal, flags larger. Conflict as exp not checked
+    data[3] = {{100, 100, 200, expiry}, PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS};
+
+    conflict_lose(op, FORCE_ACCEPT_WITH_META_OPS, withValue, data, itemMeta);
+}
+
+TEST_P(DelWithMetaTest, conflict_xattr_lose) {
+    conflict_del_lose_xattr(op, 0, withValue);
+}
+
+TEST_P(DelWithMetaLwwTest, conflict_xattr_lose) {
+    conflict_del_lose_xattr(op, FORCE_ACCEPT_WITH_META_OPS, withValue);
+}
+
+TEST_P(AddSetWithMetaTest, conflict_lose) {
+    ItemMetaData itemMeta{
+            100 /*cas*/, 100 /*revSeq*/, 100 /*flags*/, expiry /*expiry*/};
+
+    // Conflict test order: 1) seqno 2) cas 3) expiry 4) flags 5) xattr
+    std::array<TestData, 4> data;
+    // 1) revSeqno is less and everything else larger. Expect conflict
+    data[0] = {{101, 99, 101, expiry + 1},
+               PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS};
+    // 2. revSeqno is equal, cas is less, others are larger. Expect conflict
+    data[1] = {{99, 100, 101, expiry + 1},
+               PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS};
+    // 3. revSeqno/cas equal, flags larger, exp less, conflict
+    data[2] = {{100, 100, 101, expiry - 1},
+               PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS};
+    // 4. revSeqno/cas/exp equal, flags less, conflict
+    data[3] = {{100, 100, 99, expiry}, PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS};
+
+    conflict_lose(
+            GetParam(), 0 /*options*/, true /*withValue*/, data, itemMeta);
+}
+
+TEST_P(AddSetWithMetaLwwTest, conflict_lose) {
+    ItemMetaData itemMeta{
+            100 /*cas*/, 100 /*revSeq*/, 100 /*flags*/, expiry /*expiry*/};
+
+    // Conflict test order: 1) cas 2) seqno 3) expiry 4) flags 5) xattr
+    std::array<TestData, 4> data;
+    // 1) cas is less and everything else larger. Expect conflict
+    data[0] = {{99, 101, 101, expiry + 1},
+               PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS};
+    // 2. cas is equal, revSeq is less, others are larger. Expect conflict
+    data[1] = {{100, 99, 101, expiry + 1},
+               PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS};
+    // 3. revSeqno/cas equal, flags larger, exp less, conflict
+    data[2] = {{100, 100, 101, expiry - 1},
+               PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS};
+    // 4. revSeqno/cas/exp equal, flags less, conflict
+    data[3] = {{100, 100, 99, expiry}, PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS};
+
+    conflict_lose(GetParam(), FORCE_ACCEPT_WITH_META_OPS, true, data, itemMeta);
+}
+
+TEST_P(AddSetWithMetaTest, conflict_xattr_lose) {
+    conflict_lose_xattr(GetParam(), 0 /*options*/, true /*withvalue*/);
+}
+
+TEST_P(AddSetWithMetaLwwTest, conflict_xattr_lose) {
+    conflict_lose_xattr(
+            GetParam(), FORCE_ACCEPT_WITH_META_OPS, true /*withvalue*/);
+}
+
+// This test will store an item with this meta data then store again
+// using the testData entries
+void WithMetaTest::conflict_win(protocol_binary_command op,
+                                int options,
+                                const std::array<TestData, 4>& testData,
+                                const ItemMetaData& itemMeta) {
+    bool isDelete = op == PROTOCOL_BINARY_CMD_DEL_WITH_META ||
+                    op == PROTOCOL_BINARY_CMD_DELQ_WITH_META;
+    bool isSet = op == PROTOCOL_BINARY_CMD_SET_WITH_META ||
+                 op == PROTOCOL_BINARY_CMD_SETQ_WITH_META;
+
+    int counter = 0;
+    for (auto& td : testData) {
+        // Set our "target" (new key each iteration)
+        std::string key = "mykey" + std::to_string(counter);
+        key.push_back(op); // and the op for test uniqueness
+        std::string value = "newvalue" + std::to_string(counter);
+        auto swm = buildWithMetaPacket(PROTOCOL_BINARY_CMD_SET_WITH_META,
+                                       0 /*datatype*/,
+                                       vbid /*vbucket*/,
+                                       0 /*opaque*/,
+                                       0 /*cas*/,
+                                       itemMeta,
+                                       key,
+                                       "myvalue",
+                                       {},
+                                       options);
+
+        EXPECT_EQ(ENGINE_SUCCESS,
+                  callEngine(PROTOCOL_BINARY_CMD_SET_WITH_META, swm));
+        EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, getAddResponseStatus())
+                << "Failed to set the target key:" << key;
+
+        // Next the test packet (always with a value).
+        auto wm = buildWithMetaPacket(op,
+                                      0 /*datatype*/,
+                                      vbid /*vbucket*/,
+                                      0 /*opaque*/,
+                                      0 /*cas*/,
+                                      td.meta,
+                                      key,
+                                      value,
+                                      {},
+                                      options);
+
+        // Now set/add/del against the item using the test iteration metadata
+        EXPECT_EQ(ENGINE_SUCCESS, callEngine(op, wm));
+
+        auto status = getAddResponseStatus();
+        if (isDelete) {
+            EXPECT_EQ(td.expectedStatus, status)
+                    << "Failed delete for iteration " << counter;
+            if (status == PROTOCOL_BINARY_RESPONSE_SUCCESS) {
+                checkGetItem(key, value, td.meta);
+            }
+        } else {
+            EXPECT_TRUE(isSet);
+            EXPECT_EQ(td.expectedStatus, status) << "Failed set for iteration "
+                                                 << counter;
+            checkGetItem(key, value, td.meta);
+        }
+        counter++;
+    }
+
+    // ... Finally give an Item with a datatype (not xattr)
+    std::string key = "mykey" + std::to_string(counter);
+    key.push_back(op); // and the op for test uniqueness
+    auto swm = buildWithMetaPacket(PROTOCOL_BINARY_CMD_ADD_WITH_META,
+                                   PROTOCOL_BINARY_DATATYPE_JSON,
+                                   vbid /*vbucket*/,
+                                   0 /*opaque*/,
+                                   0 /*cas*/,
+                                   itemMeta,
+                                   key,
+                                   "myvalue",
+                                   {},
+                                   options);
+
+    EXPECT_EQ(ENGINE_SUCCESS,
+              callEngine(PROTOCOL_BINARY_CMD_ADD_WITH_META, swm));
+    EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, getAddResponseStatus());
+
+    // And test same cas/seq/exp/flags but marked with xattr
+    auto xattrValue = createXattrValue("xattr_value");
+    swm = buildWithMetaPacket(op,
+                              PROTOCOL_BINARY_DATATYPE_XATTR,
+                              vbid /*vbucket*/,
+                              0 /*opaque*/,
+                              0 /*cas*/,
+                              itemMeta,
+                              key,
+                              xattrValue,
+                              {},
+                              options);
+    EXPECT_EQ(ENGINE_SUCCESS, callEngine(op, swm));
+
+    if (isSet) {
+        EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, getAddResponseStatus());
+        checkGetItem(key, xattrValue, itemMeta);
+    } else {
+        EXPECT_TRUE(isDelete);
+        // del fails as conflict resolution won't get to the XATTR test
+        EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS, getAddResponseStatus());
+    }
+}
+
+TEST_F(WithMetaTest, set_conflict_win) {
+    ItemMetaData itemMeta{
+            100 /*cas*/, 100 /*revSeq*/, 100 /*flags*/, expiry /*expiry*/};
+    std::array<TestData, 4> data = {
+            {{{100, 101, 100, expiry}, // ... mutate with higher seq
+              PROTOCOL_BINARY_RESPONSE_SUCCESS},
+             {{101, 100, 100, expiry}, // ... mutate with same but higher cas
+              PROTOCOL_BINARY_RESPONSE_SUCCESS},
+             {{100,
+               100,
+               100,
+               expiry + 1}, // ... mutate with same but higher exp
+              PROTOCOL_BINARY_RESPONSE_SUCCESS},
+             {{100, 100, 101, expiry}, // ... mutate with same but higher flags
+              PROTOCOL_BINARY_RESPONSE_SUCCESS}}};
+
+    conflict_win(PROTOCOL_BINARY_CMD_SET_WITH_META, 0, data, itemMeta);
+    conflict_win(PROTOCOL_BINARY_CMD_SETQ_WITH_META, 0, data, itemMeta);
+}
+
+TEST_F(WithMetaTest, del_conflict_win) {
+    ItemMetaData itemMeta{
+            100 /*cas*/, 100 /*revSeq*/, 100 /*flags*/, expiry /*expiry*/};
+    std::array<TestData, 4> data = {{
+            {{100, 101, 100, expiry}, // ... mutate with higher seq
+             PROTOCOL_BINARY_RESPONSE_SUCCESS},
+            {{101, 100, 100, expiry}, // ... mutate with same but higher cas
+             PROTOCOL_BINARY_RESPONSE_SUCCESS},
+            {{100, 100, 100, expiry + 1}, // ... mutate with same but higher exp
+             PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS}, // delete ignores expiry
+            {{100, 100, 101, expiry}, // ... mutate with same but higher flags
+             PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS} // delete ignores flags
+    }};
+
+    conflict_win(PROTOCOL_BINARY_CMD_DEL_WITH_META, 0, data, itemMeta);
+    conflict_win(PROTOCOL_BINARY_CMD_DELQ_WITH_META, 0, data, itemMeta);
+}
+
+TEST_F(WithMetaLwwTest, set_conflict_win) {
+    ItemMetaData itemMeta{
+            100 /*cas*/, 100 /*revSeq*/, 100 /*flags*/, expiry /*expiry*/};
+    std::array<TestData, 4> data = {
+            {{{101, 100, 100, expiry}, // ... mutate with higher cas
+              PROTOCOL_BINARY_RESPONSE_SUCCESS},
+             {{100, 101, 100, expiry}, // ... mutate with same but higher seq
+              PROTOCOL_BINARY_RESPONSE_SUCCESS},
+             {{100,
+               100,
+               100,
+               expiry + 1}, // ... mutate with same but higher exp
+              PROTOCOL_BINARY_RESPONSE_SUCCESS},
+             {{100, 100, 101, expiry}, // ... mutate with same but higher flags
+              PROTOCOL_BINARY_RESPONSE_SUCCESS}}};
+
+    conflict_win(PROTOCOL_BINARY_CMD_SET_WITH_META,
+                 FORCE_ACCEPT_WITH_META_OPS,
+                 data,
+                 itemMeta);
+    conflict_win(PROTOCOL_BINARY_CMD_SETQ_WITH_META,
+                 FORCE_ACCEPT_WITH_META_OPS,
+                 data,
+                 itemMeta);
+}
+
+TEST_F(WithMetaLwwTest, del_conflict_win) {
+    ItemMetaData itemMeta{
+            100 /*cas*/, 100 /*revSeq*/, 100 /*flags*/, expiry /*expiry*/};
+    std::array<TestData, 4> data = {{
+            {{101, 100, 100, expiry}, // ... mutate with higher cas
+             PROTOCOL_BINARY_RESPONSE_SUCCESS},
+            {{100, 101, 100, expiry}, // ... mutate with same but higher seq
+             PROTOCOL_BINARY_RESPONSE_SUCCESS},
+            {{100, 100, 100, expiry + 1}, // ... mutate with same but higher exp
+             PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS}, // delete ignores expiry
+            {{100, 100, 101, expiry}, // ... mutate with same but higher flags
+             PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS} // delete ignores flags
+    }};
+
+    conflict_win(PROTOCOL_BINARY_CMD_DEL_WITH_META,
+                 FORCE_ACCEPT_WITH_META_OPS,
+                 data,
+                 itemMeta);
+    conflict_win(PROTOCOL_BINARY_CMD_DELQ_WITH_META,
+                 FORCE_ACCEPT_WITH_META_OPS,
+                 data,
+                 itemMeta);
+}
+
+TEST_P(AllWithMetaTest, markJSON) {
+    // Write a XATTR doc with JSON body, expect the doc to be marked as JSON
+    auto value = createXattrValue(R"({"json":"yesplease"})");
+    auto swm = buildWithMetaPacket(GetParam(),
+                                   PROTOCOL_BINARY_DATATYPE_XATTR,
+                                   vbid /*vbucket*/,
+                                   0 /*opaque*/,
+                                   0 /*cas*/,
+                                   {100, 100, 100, expiry},
+                                   "json",
+                                   value);
+    EXPECT_EQ(ENGINE_SUCCESS, callEngine(GetParam(), swm));
+    EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, getAddResponseStatus());
+
+    auto result = store->get({"json", DocNamespace::DefaultCollection},
+                             vbid,
+                             nullptr,
+                             GET_DELETED_VALUE);
+    ASSERT_EQ(ENGINE_SUCCESS, result.getStatus());
+    EXPECT_EQ(0,
+              strncmp(value.data(),
+                      result.getValue()->getData(),
+                      result.getValue()->getNBytes()));
+    EXPECT_EQ(PROTOCOL_BINARY_DATATYPE_JSON | PROTOCOL_BINARY_DATATYPE_XATTR,
+              result.getValue()->getDataType());
+    delete result.getValue();
+}
+
+auto opcodeValues = ::testing::Values(PROTOCOL_BINARY_CMD_SET_WITH_META,
+                                      PROTOCOL_BINARY_CMD_SETQ_WITH_META,
+                                      PROTOCOL_BINARY_CMD_ADD_WITH_META,
+                                      PROTOCOL_BINARY_CMD_ADDQ_WITH_META,
+                                      PROTOCOL_BINARY_CMD_DEL_WITH_META,
+                                      PROTOCOL_BINARY_CMD_DELQ_WITH_META);
+
+auto addSetOpcodeValues = ::testing::Values(PROTOCOL_BINARY_CMD_SET_WITH_META,
+                                            PROTOCOL_BINARY_CMD_SETQ_WITH_META,
+                                            PROTOCOL_BINARY_CMD_ADD_WITH_META,
+                                            PROTOCOL_BINARY_CMD_ADDQ_WITH_META);
+
+auto deleteOpcodeValues = ::testing::Values(PROTOCOL_BINARY_CMD_DEL_WITH_META,
+                                            PROTOCOL_BINARY_CMD_DELQ_WITH_META);
+
+struct PrintToStringCombinedName {
+    std::string
+    operator()(const ::testing::TestParamInfo<
+               ::testing::tuple<bool, protocol_binary_command>>& info) const {
+        std::string rv = std::to_string(::testing::get<1>(info.param));
+        if (::testing::get<0>(info.param)) {
+            rv += "_with_value";
+        }
+        return rv;
+    }
+};
+
+INSTANTIATE_TEST_CASE_P(DelWithMeta,
+                        DelWithMetaTest,
+                        ::testing::Combine(::testing::Bool(),
+                                           deleteOpcodeValues),
+                        PrintToStringCombinedName());
+
+INSTANTIATE_TEST_CASE_P(DelWithMetaLww,
+                        DelWithMetaLwwTest,
+                        ::testing::Combine(::testing::Bool(),
+                                           deleteOpcodeValues),
+                        PrintToStringCombinedName());
+
+INSTANTIATE_TEST_CASE_P(AddSetWithMeta,
+                        AddSetWithMetaTest,
+                        addSetOpcodeValues,
+                        ::testing::PrintToStringParamName());
+
+INSTANTIATE_TEST_CASE_P(AddSetWithMetaLww,
+                        AddSetWithMetaLwwTest,
+                        addSetOpcodeValues,
+                        ::testing::PrintToStringParamName());
+
+INSTANTIATE_TEST_CASE_P(AddSetDelMeta,
+                        AllWithMetaTest,
+                        opcodeValues,
+                        ::testing::PrintToStringParamName());
index 4739fd8..4d64a68 100644 (file)
 #include <chrono>
 #include <thread>
 
+#include <string_utilities.h>
+#include <xattr/blob.h>
+#include <xattr/utils.h>
+
 void KVBucketTest::SetUp() {
     // Paranoia - kill any existing files in case they are left over
     // from a previous run.
@@ -184,6 +188,131 @@ void KVBucketTest::initializeExpiryPager() {
     store->initializeExpiryPager(engine->getConfiguration());
 }
 
+/**
+ * Create a del_with_meta packet with the key/body (body can be empty)
+ */
+std::vector<char> KVBucketTest::buildWithMetaPacket(
+        protocol_binary_command opcode,
+        protocol_binary_datatype_t datatype,
+        uint16_t vbucket,
+        uint32_t opaque,
+        uint64_t cas,
+        ItemMetaData metaData,
+        const std::string& key,
+        const std::string& body,
+        const std::vector<char>& emd,
+        int options) {
+    EXPECT_EQ(sizeof(protocol_binary_request_set_with_meta),
+              sizeof(protocol_binary_request_delete_with_meta));
+
+    size_t size = sizeof(protocol_binary_request_set_with_meta);
+    // body at least the meta
+    size_t extlen = (sizeof(uint32_t) * 2) + (sizeof(uint64_t) * 2);
+    size_t bodylen = extlen;
+    if (options) {
+        size += sizeof(uint32_t);
+        bodylen += sizeof(uint32_t);
+        extlen += sizeof(uint32_t);
+    }
+    if (!emd.empty()) {
+        EXPECT_TRUE(emd.size() < std::numeric_limits<uint16_t>::max());
+        size += sizeof(uint16_t) + emd.size();
+        bodylen += sizeof(uint16_t) + emd.size();
+        extlen += sizeof(uint16_t);
+    }
+    size += body.size();
+    bodylen += body.size();
+    size += key.size();
+    bodylen += key.size();
+
+    protocol_binary_request_set_with_meta header;
+    header.message.header.request.magic = PROTOCOL_BINARY_REQ;
+    header.message.header.request.opcode = opcode;
+    header.message.header.request.keylen = htons(key.size());
+    header.message.header.request.extlen = uint8_t(extlen);
+    header.message.header.request.datatype = datatype;
+    header.message.header.request.vbucket = htons(vbucket);
+    header.message.header.request.bodylen = htonl(bodylen);
+    header.message.header.request.opaque = opaque;
+    header.message.header.request.cas = htonll(cas);
+    header.message.body.flags = metaData.flags;
+    header.message.body.expiration = htonl(metaData.exptime);
+    header.message.body.seqno = htonll(metaData.revSeqno);
+    header.message.body.cas = htonll(metaData.cas);
+
+    std::vector<char> packet;
+    packet.reserve(size);
+    packet.insert(packet.end(),
+                  reinterpret_cast<char*>(&header),
+                  reinterpret_cast<char*>(&header) +
+                          sizeof(protocol_binary_request_set_with_meta));
+
+    if (options) {
+        options = htonl(options);
+        std::copy_n(reinterpret_cast<char*>(&options),
+                    sizeof(uint32_t),
+                    std::back_inserter(packet));
+    }
+
+    if (!emd.empty()) {
+        uint16_t emdSize = htons(emd.size());
+        std::copy_n(reinterpret_cast<char*>(&emdSize),
+                    sizeof(uint16_t),
+                    std::back_inserter(packet));
+    }
+
+    std::copy_n(key.c_str(), key.size(), std::back_inserter(packet));
+    std::copy_n(body.c_str(), body.size(), std::back_inserter(packet));
+    packet.insert(packet.end(), emd.begin(), emd.end());
+    return packet;
+}
+
+std::string KVBucketTest::createXattrValue(const std::string& body) {
+    cb::xattr::Blob blob;
+
+    // Add a few XAttrs
+    blob.set(to_const_byte_buffer("user"),
+             to_const_byte_buffer("{\"author\":\"bubba\"}"));
+    blob.set(to_const_byte_buffer("_sync"),
+             to_const_byte_buffer("{\"cas\":\"0xdeadbeefcafefeed\"}"));
+    blob.set(to_const_byte_buffer("meta"),
+             to_const_byte_buffer("{\"content-type\":\"text\"}"));
+
+    auto xattrValue = blob.finalize();
+
+    // append body to the xattrs and store in data
+    std::string data;
+    std::copy_n(xattrValue.buf, xattrValue.len, std::back_inserter(data));
+    std::copy_n(body.c_str(), body.size(), std::back_inserter(data));
+
+    return data;
+}
+
+bool KVBucketTest::addResponse(const void* k,
+                               uint16_t keylen,
+                               const void* ext,
+                               uint8_t extlen,
+                               const void* body,
+                               uint32_t bodylen,
+                               uint8_t datatype,
+                               uint16_t status,
+                               uint64_t pcas,
+                               const void* cookie) {
+    addResponseStatus = protocol_binary_response_status(status);
+    return true;
+}
+
+protocol_binary_response_status KVBucketTest::getAddResponseStatus(
+        protocol_binary_response_status newval) {
+    protocol_binary_response_status rv = addResponseStatus;
+    addResponseStatus = newval;
+    return rv;
+}
+
+protocol_binary_response_status KVBucketTest::addResponseStatus =
+        PROTOCOL_BINARY_RESPONSE_SUCCESS;
+
+
 // getKeyStats tests //////////////////////////////////////////////////////////
 
 // Check that keystats on resident items works correctly.
index bf6bce1..f212127 100644 (file)
@@ -105,6 +105,46 @@ public:
 
     void initializeExpiryPager();
 
+    /**
+     * Create a *_with_meta packet with the key/body
+     * Allows *_with_meta to be invoked via EventuallyPersistentEngine which
+     * begins with a packet
+     */
+    static std::vector<char> buildWithMetaPacket(
+            protocol_binary_command opcode,
+            protocol_binary_datatype_t datatype,
+            uint16_t vbucket,
+            uint32_t opaque,
+            uint64_t cas,
+            ItemMetaData metaData,
+            const std::string& key,
+            const std::string& body,
+            const std::vector<char>& emd = {},
+            int options = 0);
+
+    static bool addResponse(const void* k,
+                            uint16_t keylen,
+                            const void* ext,
+                            uint8_t extlen,
+                            const void* body,
+                            uint32_t bodylen,
+                            uint8_t datatype,
+                            uint16_t status,
+                            uint64_t pcas,
+                            const void* cookie);
+
+    /**
+     * Create an XATTR document using the supplied string as the body
+     * @returns string containing the new value
+     */
+    static std::string createXattrValue(const std::string& body);
+
+    static protocol_binary_response_status getAddResponseStatus(
+            protocol_binary_response_status newval =
+                    PROTOCOL_BINARY_RESPONSE_SUCCESS);
+
+    static protocol_binary_response_status addResponseStatus;
+
     static const char test_dbname[];
 
     std::string config_string;