ep_testsuite: Expand Delete-with-value test cases 10/76110/16
authorDave Rigby <daver@couchbase.com>
Thu, 30 Mar 2017 11:28:52 +0000 (12:28 +0100)
committerDave Rigby <daver@couchbase.com>
Fri, 7 Apr 2017 14:25:21 +0000 (14:25 +0000)
Expand the ep_testsuite tests for Deletes with a value. Ensure that
all possible state transitions (between Alive, Deleted-with-value &
Deleted-no-value) are handled.

Note that the Delete-with-value -> Delete-no-value transition cannot
be driven correctly from the engine API:-

a) Calling EvpDelete() will fail, as it requires the item is in the
   Alive state.

b) Calling EvpStore(<zero-length-item>, DocState:Deleted) will
   succeed, but results in a memory leak from couchstore when fetching
   the zero-length body. Note: this is a latent bug - see MB-23697.

As such this transition isn't currently handled.

Change-Id: I9ed79e5dd1bc0387fa39a84cd5574a38d696576a
Reviewed-on: http://review.couchbase.org/76110
Tested-by: Build Bot <build@couchbase.com>
Reviewed-by: Jim Walker <jim@couchbase.com>
tests/ep_test_apis.cc
tests/ep_test_apis.h
tests/ep_testsuite_basic.cc

index 7394ce8..6f1fee4 100644 (file)
@@ -402,6 +402,35 @@ ENGINE_ERROR_CODE del(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, const char *key,
                       cas, vbucket, mut_info);
 }
 
+/** Simplified version of store for handling the common case of performing
+ * a delete with a value.
+ */
+ENGINE_ERROR_CODE delete_with_value(ENGINE_HANDLE* h,
+                                    ENGINE_HANDLE_V1* h1,
+                                    const void* cookie,
+                                    uint64_t cas,
+                                    const char* key,
+                                    const char* value) {
+    item* itm = nullptr;
+    auto result = store(h,
+                        h1,
+                        cookie,
+                        OPERATION_SET,
+                        key,
+                        value,
+                        &itm,
+                        cas,
+                        /*vb*/ 0,
+                        /*exp*/ 0,
+                        /*datatype*/ 0,
+                        DocumentState::Deleted);
+    h1->release(h, cookie, itm);
+
+    wait_for_flusher_to_settle(h, h1);
+
+    return result;
+}
+
 void del_with_meta(ENGINE_HANDLE* h,
                    ENGINE_HANDLE_V1* h1,
                    const char* key,
@@ -1047,6 +1076,27 @@ ENGINE_ERROR_CODE verify_key(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1,
     return rv;
 }
 
+std::pair<ENGINE_ERROR_CODE, std::string> get_value(ENGINE_HANDLE* h,
+                                                    ENGINE_HANDLE_V1* h1,
+                                                    const void* cookie,
+                                                    const char* key,
+                                                    uint16_t vbucket,
+                                                    DocStateFilter state) {
+    item* itm = NULL;
+    ENGINE_ERROR_CODE rv = get(h, h1, cookie, &itm, key, vbucket, state);
+    if (rv != ENGINE_SUCCESS) {
+        return {rv, ""};
+    }
+    item_info info;
+    if (!h1->get_item_info(h, cookie, itm, &info)) {
+        return {ENGINE_FAILED, ""};
+    }
+    h1->release(h, cookie, itm);
+    auto value = std::string(reinterpret_cast<char*>(info.value[0].iov_base),
+                             info.value[0].iov_len);
+    return make_pair(rv, value);
+}
+
 bool verify_vbucket_missing(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1,
                             uint16_t vb) {
     char vbid[8];
index 05d9050..1ec7b8d 100644 (file)
@@ -176,6 +176,16 @@ ENGINE_ERROR_CODE del(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, const char *key,
                       uint64_t* cas, uint16_t vbucket, const void* cookie,
                       mutation_descr_t* mut_info);
 
+/** Simplified version of store for handling the common case of performing
+ * a delete with a value.
+ */
+ENGINE_ERROR_CODE delete_with_value(ENGINE_HANDLE* h,
+                                    ENGINE_HANDLE_V1* h1,
+                                    const void* cookie,
+                                    uint64_t cas,
+                                    const char* key,
+                                    const char* value);
+
 void disable_traffic(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1);
 void enable_traffic(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1);
 void evict_key(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, const char *key,
@@ -261,6 +271,18 @@ ENGINE_ERROR_CODE unl(ENGINE_HANDLE* h, ENGINE_HANDLE_V1* h1,
                       uint16_t vb, uint64_t cas = 0);
 ENGINE_ERROR_CODE verify_key(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1,
                              const char* key, uint16_t vbucket = 0);
+
+/**
+ * Attempts to fetch the given key. On success returns ENGINE_SUCCESS and the
+ * value, on failure returns the reason and an empty string.
+ */
+std::pair<ENGINE_ERROR_CODE, std::string> get_value(ENGINE_HANDLE* h,
+                                                    ENGINE_HANDLE_V1* h1,
+                                                    const void* cookie,
+                                                    const char* key,
+                                                    uint16_t vbucket,
+                                                    DocStateFilter state);
+
 bool verify_vbucket_missing(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1,
                             uint16_t vb);
 bool verify_vbucket_state(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, uint16_t vb,
index 258acc8..8ea9e90 100644 (file)
@@ -1329,16 +1329,110 @@ static enum test_result test_mb5215(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) {
 }
 
 /* Testing functionality to store a value for a deleted item
- * and also retrieve the value of a deleted item
+ * and also retrieve the value of a deleted item.
+ * Need to check:
+ *
+ * - Each possible state transition between Alive, Deleted-with-value and
+ *   Deleted-no-value.
  */
-static enum test_result test_delete_with_value(ENGINE_HANDLE *h,
-                                               ENGINE_HANDLE_V1 *h1) {
-    item *i = nullptr;
+static enum test_result test_delete_with_value(ENGINE_HANDLE* h,
+                                               ENGINE_HANDLE_V1* h1) {
+    const uint64_t cas_0 = 0;
+    const uint16_t vbid = 0;
+    const void* cookie = testHarness.create_cookie();
+
+    // Store an initial (not-deleted) value.
     checkeq(ENGINE_SUCCESS,
-            store(h, h1, nullptr, OPERATION_SET, "key1", "somevalue", &i),
+            store(h, h1, cookie, OPERATION_SET, "key", "somevalue", nullptr),
             "Failed set");
+    wait_for_flusher_to_settle(h, h1);
 
-    h1->release(h, nullptr, i);
+    checkeq(uint64_t(1),
+            get_stat<uint64_t>(h, h1, "vb_0:num_items", "vbucket-details 0"),
+            "Unexpected initial item count");
+
+    /* Alive -> Deleted-with-value */
+    checkeq(ENGINE_SUCCESS,
+            delete_with_value(h, h1, cookie, cas_0, "key", "deleted"),
+            "Failed Alive -> Delete-with-value");
+
+    auto res = get_value(h, h1, cookie, "key", vbid, DocStateFilter::Alive);
+    checkeq(ENGINE_KEY_ENOENT,
+            res.first,
+            "Unexpectedly accessed Deleted-with-value via DocState::Alive");
+
+    res = get_value(h, h1, cookie, "key", vbid, DocStateFilter::AliveOrDeleted);
+    checkeq(ENGINE_SUCCESS,
+            res.first,
+            "Failed to fetch Alive -> Delete-with-value");
+    checkeq(std::string("deleted"), res.second, "Unexpected value (deleted)");
+
+    /* Deleted-with-value -> Deleted-with-value (different value). */
+    checkeq(ENGINE_SUCCESS,
+            delete_with_value(h, h1, cookie, cas_0, "key", "deleted 2"),
+            "Failed Deleted-with-value -> Deleted-with-value");
+
+    res = get_value(h, h1, cookie, "key", vbid, DocStateFilter::AliveOrDeleted);
+    checkeq(ENGINE_SUCCESS, res.first, "Failed to fetch key (deleted 2)");
+    checkeq(std::string("deleted 2"),
+            res.second,
+            "Unexpected value (deleted 2)");
+
+    /* Delete-with-value -> Alive */
+    checkeq(ENGINE_SUCCESS,
+            store(h, h1, cookie, OPERATION_SET, "key", "alive 2", nullptr),
+            "Failed Delete-with-value -> Alive");
+    wait_for_flusher_to_settle(h, h1);
+
+    res = get_value(h, h1, cookie, "key", vbid, DocStateFilter::Alive);
+    checkeq(ENGINE_SUCCESS,
+            res.first,
+            "Failed to fetch Delete-with-value -> Alive via DocState::Alive");
+    checkeq(std::string("alive 2"), res.second, "Unexpected value (alive 2)");
+
+    // Also check via DocState::Deleted
+    res = get_value(h, h1, cookie, "key", vbid, DocStateFilter::AliveOrDeleted);
+    checkeq(ENGINE_SUCCESS,
+            res.first,
+            "Failed to fetch Delete-with-value -> Alive via DocState::Deleted");
+    checkeq(std::string("alive 2"),
+            res.second,
+            "Unexpected value (alive 2) via DocState::Deleted");
+
+    /* Alive -> Deleted-no-value */
+    checkeq(ENGINE_SUCCESS,
+            del(h, h1, "key", cas_0, vbid, cookie),
+            "Failed Alive -> Deleted-no-value");
+    wait_for_flusher_to_settle(h, h1);
+
+    res = get_value(h, h1, cookie, "key", vbid, DocStateFilter::Alive);
+    checkeq(ENGINE_KEY_ENOENT,
+            res.first,
+            "Unexpectedly accessed Deleted-no-value via DocState::Alive");
+
+    /* Deleted-no-value -> Delete-with-value */
+    checkeq(ENGINE_SUCCESS,
+            delete_with_value(h, h1, cookie, cas_0, "key", "deleted 3"),
+            "Failed delete with value (deleted 2)");
+
+    res = get_value(h, h1, cookie, "key", vbid, DocStateFilter::AliveOrDeleted);
+    checkeq(ENGINE_SUCCESS, res.first, "Failed to fetch key (deleted 3)");
+    checkeq(std::string("deleted 3"),
+            res.second,
+            "Unexpected value (deleted 3)");
+
+    testHarness.destroy_cookie(cookie);
+
+    return SUCCESS;
+}
+
+/* Similar to test_delete_with_value, except also checks that CAS values
+ */
+static enum test_result test_delete_with_value_cas(ENGINE_HANDLE *h,
+                                                   ENGINE_HANDLE_V1 *h1) {
+    checkeq(ENGINE_SUCCESS,
+            store(h, h1, nullptr, OPERATION_SET, "key1", "somevalue", nullptr),
+            "Failed set");
 
     check(get_meta(h, h1, "key1"), "Get meta failed");
     checkeq(PROTOCOL_BINARY_RESPONSE_SUCCESS,
@@ -1348,12 +1442,10 @@ static enum test_result test_delete_with_value(ENGINE_HANDLE *h,
 
     /* Store a deleted item first with CAS 0 */
     checkeq(ENGINE_SUCCESS,
-            store(h, h1, nullptr, OPERATION_SET, "key1", "deletevalue", &i,
+            store(h, h1, nullptr, OPERATION_SET, "key1", "deletevalue", nullptr,
                   0, 0, 3600, 0x00, DocumentState::Deleted),
             "Failed delete with value");
 
-    h1->release(h, nullptr, i);
-
     check(get_meta(h, h1, "key1"), "Get meta failed");
     checkeq(PROTOCOL_BINARY_RESPONSE_SUCCESS,
             last_status.load(), "Get meta failed");
@@ -1361,6 +1453,7 @@ static enum test_result test_delete_with_value(ENGINE_HANDLE *h,
     checkeq(last_meta.revSeqno, curr_revseqno + 1,
             "rev seqno should have incremented");
 
+    item *i = nullptr;
     checkeq(ENGINE_SUCCESS,
             store(h, h1, nullptr, OPERATION_SET, "key2", "somevalue", &i),
             "Failed set");
@@ -1380,11 +1473,9 @@ static enum test_result test_delete_with_value(ENGINE_HANDLE *h,
     /* Store a deleted item with the existing CAS value */
     checkeq(ENGINE_SUCCESS,
             store(h, h1, nullptr, OPERATION_SET, "key2", "deletevaluewithcas",
-                  &i, info.cas, 0, 3600, 0x00, DocumentState::Deleted),
+                  nullptr, info.cas, 0, 3600, 0x00, DocumentState::Deleted),
             "Failed delete value with cas");
 
-    h1->release(h, nullptr, i);
-
     wait_for_flusher_to_settle(h, h1);
 
     check(get_meta(h, h1, "key2"), "Get meta failed");
@@ -1404,6 +1495,10 @@ static enum test_result test_delete_with_value(ENGINE_HANDLE *h,
     wait_for_flusher_to_settle(h, h1);
 
     check(h1->get_item_info(h, nullptr, i, &info), "Getting item info failed");
+    checkeq(int(DocumentState::Deleted),
+            int(info.document_state),
+            "Incorrect DocState for deleted item");
+    checkne(uint64_t(0), info.cas, "Expected non-zero CAS for deleted item");
 
     h1->release(h, nullptr, i);
 
@@ -1418,11 +1513,9 @@ static enum test_result test_delete_with_value(ENGINE_HANDLE *h,
 
     checkeq(ENGINE_SUCCESS,
             store(h, h1, nullptr, OPERATION_SET, "key2",
-                  "newdeletevaluewithcas", &i, info.cas, 0, 3600, 0x00,
+                  "newdeletevaluewithcas", nullptr, info.cas, 0, 3600, 0x00,
                   DocumentState::Deleted), "Failed delete value with cas");
 
-    h1->release(h, nullptr, i);
-
     wait_for_flusher_to_settle(h, h1);
 
     checkeq(ENGINE_SUCCESS,
@@ -1439,6 +1532,9 @@ static enum test_result test_delete_with_value(ENGINE_HANDLE *h,
 
     check(h1->get_item_info(h, nullptr, i, &info),
           "Getting item info failed");
+    checkeq(int(DocumentState::Deleted),
+            int(info.document_state),
+            "Incorrect DocState for deleted item");
 
     checkeq(static_cast<uint8_t>(DocumentState::Deleted),
             static_cast<uint8_t>(info.document_state),
@@ -2214,6 +2310,8 @@ BaseTestCase testsuite_testcases[] = {
                  NULL, prepare, cleanup),
         TestCase("delete with value", test_delete_with_value, test_setup, teardown,
                  NULL, prepare, cleanup),
+        TestCase("delete with value CAS", test_delete_with_value_cas,
+                 test_setup, teardown, NULL, prepare, cleanup),
         TestCase("set/delete", test_set_delete, test_setup,
                  teardown, NULL, prepare, cleanup),
         TestCase("set/delete (invalid cas)", test_set_delete_invalid_cas,