/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */ #undef NDEBUG #include "config.h" #include #include #include #include #include #include "basic_engine_testsuite.h" struct test_harness test_harness; /* * Make sure that get_info returns something and that repeated calls to it * return the same something. */ static enum test_result get_info_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { const engine_info *info = h1->get_info(h); assert(info != NULL); assert(info == h1->get_info(h)); return SUCCESS; } /* * Make sure that the structure returned by get_info has a non-null description. */ static enum test_result get_info_description_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { const engine_info *info = h1->get_info(h); assert(info->description != NULL); return SUCCESS; } /* * Make sure that the structure returned by get_info has a valid number of * features and that the size of the feautes array equals that value */ static enum test_result get_info_features_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { const engine_info *info = h1->get_info(h); uint32_t nfeats = info->num_features; assert (nfeats > 0); const feature_info *fi = info->features; while (nfeats-- > 0) { assert(fi++ != NULL); } return SUCCESS; } /* * Make sure we can successfully allocate an item, allocate op returns success * and that item struct is populated */ static enum test_result allocate_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { item *test_item = NULL; void *key = "akey"; assert(h1->allocate(h, NULL, &test_item, key, strlen(key), 1,1,1) == ENGINE_SUCCESS); assert(test_item != NULL); h1->release(h,NULL,test_item); return SUCCESS; } /* * Verify set behavior */ static enum test_result set_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { item *it; void *key = "key"; assert(h1->allocate(h, NULL, &it, key, strlen(key), 1, 1, 0) == ENGINE_SUCCESS); uint64_t prev_cas; uint64_t cas = 0; for (int ii = 0; ii < 10; ++ii) { prev_cas = cas; assert(h1->store(h, NULL, it, &cas, OPERATION_SET,0) == ENGINE_SUCCESS); assert(cas != prev_cas); } h1->release(h, NULL, it); return SUCCESS; } /* * Verify add behavior */ static enum test_result add_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { item *it; void *key = "key"; assert(h1->allocate(h, NULL, &it, key, strlen(key), 1, 1, 0) == ENGINE_SUCCESS); uint64_t cas; for (int ii = 0; ii < 10; ++ii) { ENGINE_ERROR_CODE ret = h1->store(h, NULL, it, &cas, OPERATION_ADD, 0); if (ii == 0) { assert(ret == ENGINE_SUCCESS); assert(cas != 0); } else { assert(ret == ENGINE_NOT_STORED); } } h1->release(h, NULL, it); return SUCCESS; } /* * Verify replace behavior */ static enum test_result replace_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { item *it; void *key = "key"; assert(set_test(h, h1) == SUCCESS); assert(h1->allocate(h, NULL, &it, key, strlen(key), sizeof(int), 1, 0) == ENGINE_SUCCESS); item_info item_info = { .nvalue = 1 }; assert(h1->get_item_info(h, NULL, it, &item_info) == true); uint64_t prev_cas; uint64_t cas = 0; for (int ii = 0; ii < 10; ++ii) { prev_cas = cas; *(int*)(item_info.value[0].iov_base) = ii; assert(h1->store(h, NULL, it, &cas, OPERATION_REPLACE,0) == ENGINE_SUCCESS); assert(cas != prev_cas); } h1->release(h, NULL, it); assert(h1->get(h, NULL, &it, key, strlen(key), 0) == ENGINE_SUCCESS); assert(h1->get_item_info(h, NULL, it, &item_info) == true); assert(item_info.value[0].iov_len == sizeof(int)); assert(*(int*)(item_info.value[0].iov_base) == 9); h1->release(h, NULL, it); return SUCCESS; } /* * Verify append behavior */ static enum test_result append_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { item *it; void *key = "key"; uint64_t cas; assert(h1->allocate(h, NULL, &it, key, strlen(key), 5, 1, 0) == ENGINE_SUCCESS); item_info item_info = { .nvalue = 1 }; assert(h1->get_item_info(h, NULL, it, &item_info) == true); memcpy(item_info.value[0].iov_base, "HELLO", 5); assert(h1->store(h, NULL, it, &cas, OPERATION_SET, 0) == ENGINE_SUCCESS); h1->release(h, NULL, it); assert(h1->allocate(h, NULL, &it, key, strlen(key), 6, 1, 0) == ENGINE_SUCCESS); item_info.nvalue = 1; assert(h1->get_item_info(h, NULL, it, &item_info) == true); memcpy(item_info.value[0].iov_base, " WORLD", 6); assert(h1->store(h, NULL, it, &cas, OPERATION_APPEND, 0) == ENGINE_SUCCESS); h1->release(h, NULL, it); assert(h1->get(h, NULL, &it, key, strlen(key), 0) == ENGINE_SUCCESS); assert(h1->get_item_info(h, NULL, it, &item_info) == true); assert(item_info.value[0].iov_len == 11); assert(memcmp(item_info.value[0].iov_base, "HELLO WORLD", 11) == 0); h1->release(h, NULL, it); return SUCCESS; } /* * Verify prepend behavior */ static enum test_result prepend_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { item *it; void *key = "key"; uint64_t cas; assert(h1->allocate(h, NULL, &it, key, strlen(key), 5, 1, 0) == ENGINE_SUCCESS); item_info item_info = { .nvalue = 1 }; assert(h1->get_item_info(h, NULL, it, &item_info) == true); memcpy(item_info.value[0].iov_base, "HELLO", 5); assert(h1->store(h, NULL, it, &cas, OPERATION_SET, 0) == ENGINE_SUCCESS); h1->release(h, NULL, it); assert(h1->allocate(h, NULL, &it, key, strlen(key), 6, 1, 0) == ENGINE_SUCCESS); item_info.nvalue = 1; assert(h1->get_item_info(h, NULL, it, &item_info) == true); memcpy(item_info.value[0].iov_base, " WORLD", 6); assert(h1->store(h, NULL, it, &cas, OPERATION_PREPEND, 0) == ENGINE_SUCCESS); h1->release(h, NULL, it); assert(h1->get(h, NULL, &it, key, strlen(key), 0) == ENGINE_SUCCESS); assert(h1->get_item_info(h, NULL, it, &item_info) == true); assert(item_info.value[0].iov_len == 11); assert(memcmp(item_info.value[0].iov_base, " WORLDHELLO", 11) == 0); h1->release(h, NULL, it); return SUCCESS; } /* * Make sure when we can successfully store an item after it has been allocated * and that the cas for the stored item has been generated. */ static enum test_result store_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { item *test_item = NULL; void *key = "bkey"; uint64_t cas = 0; assert(h1->allocate(h, NULL, &test_item, key, strlen(key), 1,1,1) == ENGINE_SUCCESS); assert(h1->store(h, NULL, test_item, &cas, OPERATION_SET,0) == ENGINE_SUCCESS); assert(cas != 0); h1->release(h,NULL,test_item); return SUCCESS; } /* * Make sure when we can successfully retrieve an item that has been stored in * the engine */ static enum test_result get_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { item *test_item = NULL; item *test_item_get = NULL; void *key = "get_test_key"; uint64_t cas = 0; assert(h1->allocate(h, NULL, &test_item, key, strlen(key), 1,0, 0) == ENGINE_SUCCESS); assert(h1->store(h, NULL, test_item, &cas, OPERATION_SET,0) == ENGINE_SUCCESS); assert(h1->get(h,NULL,&test_item_get,key,strlen(key),0) == ENGINE_SUCCESS); h1->release(h,NULL,test_item); h1->release(h,NULL,test_item_get); return SUCCESS; } static enum test_result expiry_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { item *test_item = NULL; item *test_item_get = NULL; void *key = "get_test_key"; uint64_t cas = 0; assert(h1->allocate(h, NULL, &test_item, key, strlen(key), 1, 0, 10) == ENGINE_SUCCESS); assert(h1->store(h, NULL, test_item, &cas, OPERATION_SET, 0) == ENGINE_SUCCESS); test_harness.time_travel(11); assert(h1->get(h,NULL,&test_item_get,key,strlen(key),0) == ENGINE_KEY_ENOENT); h1->release(h,NULL,test_item); return SUCCESS; } /* * Make sure that we can release an item. For the most part all this test does * is ensure that thinds dont go splat when we call release. It does nothing to * ensure that release did much of anything. */ static enum test_result release_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { item *test_item = NULL; void *key = "release_test_key"; uint64_t cas = 0; assert(h1->allocate(h, NULL, &test_item, key, strlen(key), 1, 0, 0) == ENGINE_SUCCESS); assert(h1->store(h, NULL, test_item, &cas, OPERATION_SET,0) == ENGINE_SUCCESS); h1->release(h, NULL, test_item); return SUCCESS; } /* * Make sure that we can remove an item and that after the item has been * removed it can not be retrieved. */ static enum test_result remove_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { item *test_item = NULL; void *key = "remove_test_key"; uint64_t cas = 0; assert(h1->allocate(h, NULL, &test_item, key, strlen(key), 1,0, 0) == ENGINE_SUCCESS); assert(h1->store(h, NULL, test_item, &cas, OPERATION_SET,0) == ENGINE_SUCCESS); assert(h1->remove(h, NULL, key, strlen(key), cas, 0) == ENGINE_SUCCESS); item *check_item = test_item; assert(h1->get(h, NULL, &check_item, key, strlen(key), 0) == ENGINE_KEY_ENOENT); assert(check_item == NULL); h1->release(h, NULL, test_item); return SUCCESS; } /* * Make sure we can arithmetic operations to set the initial value of a key and * to then later increment that value */ static enum test_result incr_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { item *test_item = NULL; void *key = "incr_test_key"; uint64_t cas = 0; uint64_t res = 0; assert(h1->allocate(h, NULL, &test_item, key, strlen(key), 1, 0, 0) == ENGINE_SUCCESS); assert(h1->arithmetic(h, NULL, key, strlen(key), true, true, 0, 1, 0, &cas, &res, 0 ) == ENGINE_SUCCESS); assert(res == 1); assert(h1->arithmetic(h, NULL, key, strlen(key), true, false, 1, 0, 0, &cas, &res, 0 ) == ENGINE_SUCCESS); assert(res == 2); h1->release(h, NULL, test_item); return SUCCESS; } static void *incr_test_main(void *arg) { ENGINE_HANDLE *h = arg; ENGINE_HANDLE_V1 *h1 = arg; void *key = "incr_test_key"; uint64_t cas = 0; uint64_t res = 0; for (int ii = 0; ii < 1000; ++ii) { assert(h1->arithmetic(h, NULL, key, strlen(key), false, false, 1, 0, 0, &cas, &res, 0 ) == ENGINE_SUCCESS); } return NULL; } /* * Make sure we can arithmetic operations to set the initial value of a key and * to then later increment that value */ static enum test_result mt_incr_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { #ifdef __arm__ const int max_threads = 1; #else const int max_threads = 30; #endif pthread_t tid[max_threads]; if (max_threads < 2) { return SKIPPED; } item *test_item = NULL; void *key = "incr_test_key"; uint64_t cas = 0; uint64_t res = 0; assert(h1->allocate(h, NULL, &test_item, key, strlen(key), 1, 0, 0) == ENGINE_SUCCESS); assert(h1->arithmetic(h, NULL, key, strlen(key), true, true, 0, 1, 0, &cas, &res, 0 ) == ENGINE_SUCCESS); h1->release(h, NULL, test_item); for (int ii = 0; ii < max_threads; ++ii) { assert(pthread_create(&tid[ii], NULL, incr_test_main, h) == 0); } for (int ii = 0; ii < max_threads; ++ii) { void *ret; assert(pthread_join(tid[ii], &ret) == 0); assert(ret == NULL); } return SUCCESS; } /* * Make sure we can arithmetic operations to set the initial value of a key and * to then later decrement that value */ static enum test_result decr_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { item *test_item = NULL; void *key = "decr_test_key"; uint64_t cas = 0; uint64_t res = 0; assert(h1->allocate(h, NULL, &test_item, key, strlen(key), 1,0, 0) == ENGINE_SUCCESS); assert(h1->arithmetic(h, NULL, key, strlen(key), false, true, 0, 1, 0, &cas, &res, 0 ) == ENGINE_SUCCESS); assert(res == 1); assert(h1->arithmetic(h, NULL, key, strlen(key), false, false, 1, 0, 0, &cas, &res, 0 ) == ENGINE_SUCCESS); assert(res == 0); h1->release(h, NULL, test_item); return SUCCESS; } /* * Make sure we can successfully perform a flush operation and that any item * stored before the flush can not be retrieved */ static enum test_result flush_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { item *test_item = NULL; void *key = "flush_test_key"; uint64_t cas = 0; test_harness.time_travel(3); assert(h1->allocate(h, NULL, &test_item, key, strlen(key), 1, 0, 0) == ENGINE_SUCCESS); assert(h1->store(h, NULL, test_item, &cas, OPERATION_SET,0) == ENGINE_SUCCESS); assert(h1->flush(h, NULL, 0) == ENGINE_SUCCESS); item *check_item = test_item; assert(h1->get(h, NULL, &check_item, key, strlen(key), 0) == ENGINE_KEY_ENOENT); assert(check_item == NULL); h1->release(h, NULL, test_item); return SUCCESS; } /* * Make sure we can successfully retrieve the item info struct for an item and * that the contents of the item_info are as expected. */ static enum test_result get_item_info_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { item *test_item = NULL; char *key = "get_item_info_test_key"; uint64_t cas = 0; const rel_time_t exp = 1; item_info ii = { .nvalue = 1 }; assert(h1->allocate(h, NULL, &test_item, key, strlen(key), 1,0, exp) == ENGINE_SUCCESS); assert(h1->store(h, NULL, test_item, &cas, OPERATION_SET,0) == ENGINE_SUCCESS); /* Had this been actual code, there'd be a connection here */ assert(h1->get_item_info(h, NULL, test_item, &ii) == true); assert(ii.cas == cas); assert(ii.flags == 0); assert(strcmp(key,ii.key) == 0); assert(ii.nkey == strlen(key)); assert(ii.nbytes == 1); assert(ii.exptime == exp); h1->release(h, NULL, test_item); return SUCCESS; } static enum test_result item_set_cas_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { item *test_item = NULL; char *key = "item_set_cas_test_key"; uint64_t cas = 0; const rel_time_t exp = 1; item_info ii = { .nvalue = 1 }; assert(h1->allocate(h, NULL, &test_item, key, strlen(key), 1,0, exp) == ENGINE_SUCCESS); assert(h1->store(h, NULL, test_item, &cas, OPERATION_SET,0) == ENGINE_SUCCESS); uint64_t newcas = cas + 1; h1->item_set_cas(h, NULL, test_item, newcas); assert(h1->get_item_info(h, NULL, test_item, &ii) == true); assert(ii.cas == newcas); h1->release(h, NULL, test_item); return SUCCESS; } uint32_t evictions; static void eviction_stats_handler(const char *key, const uint16_t klen, const char *val, const uint32_t vlen, const void *cookie) { if (memcmp(key, "evictions", klen) == 0) { char buffer[vlen + 1]; memcpy(buffer, val, vlen); buffer[vlen] = '\0'; evictions = atoi(buffer); } } static enum test_result lru_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { item *test_item = NULL; const char *hot_key = "hot_key"; uint64_t cas = 0; assert(h1->allocate(h, NULL, &test_item, hot_key, strlen(hot_key), 4096, 0, 0) == ENGINE_SUCCESS); assert(h1->store(h, NULL, test_item, &cas, OPERATION_SET,0) == ENGINE_SUCCESS); h1->release(h, NULL, test_item); int ii; for (ii = 0; ii < 250; ++ii) { assert(h1->get(h, NULL, &test_item, hot_key, strlen(hot_key), 0) == ENGINE_SUCCESS); h1->release(h, NULL, test_item); char key[1024]; size_t keylen = snprintf(key, sizeof(key), "lru_test_key_%08d", ii); assert(h1->allocate(h, NULL, &test_item, key, keylen, 4096, 0, 0) == ENGINE_SUCCESS); assert(h1->store(h, NULL, test_item, &cas, OPERATION_SET,0) == ENGINE_SUCCESS); h1->release(h, NULL, test_item); assert(h1->get_stats(h, NULL, NULL, 0, eviction_stats_handler) == ENGINE_SUCCESS); if (evictions == 2) { break; } } assert(ii < 250); for (int jj = 0; jj <= ii; ++jj) { char key[1024]; size_t keylen = snprintf(key, sizeof(key), "lru_test_key_%08d", jj); if (jj == 0 || jj == 1) { assert(h1->get(h, NULL, &test_item, key, keylen, 0) == ENGINE_KEY_ENOENT); } else { assert(h1->get(h, NULL, &test_item, key, keylen, 0) == ENGINE_SUCCESS); assert(test_item != NULL); h1->release(h, NULL, test_item); } } return SUCCESS; } static enum test_result get_stats_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { return PENDING; } static enum test_result reset_stats_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { return PENDING; } static enum test_result get_stats_struct_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { return PENDING; } static enum test_result aggregate_stats_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { return PENDING; } static protocol_binary_response_header *last_response; static void release_last_response(void) { free(last_response); last_response = NULL; } static bool response_handler(const void *key, uint16_t keylen, const void *ext, uint8_t extlen, const void *body, uint32_t bodylen, uint8_t datatype, uint16_t status, uint64_t cas, const void *cookie) { assert(last_response == NULL); last_response = malloc(sizeof(*last_response) + keylen + extlen + bodylen); if (last_response == NULL) { return false; } protocol_binary_response_header *r = last_response; r->response.magic = PROTOCOL_BINARY_RES; r->response.opcode = 0xff; // we don't know this! r->response.keylen = htons(keylen); r->response.extlen = extlen; r->response.datatype = PROTOCOL_BINARY_RAW_BYTES; r->response.status = htons(status); r->response.bodylen = htonl(keylen + extlen + bodylen); r->response.opaque = 0xffffff; // we don't know this r->response.cas = cas; char *ptr = (void*)(r + 1); memcpy(ptr, ext, extlen); ptr += extlen; memcpy(ptr, key, keylen); ptr += keylen; memcpy(ptr, body, bodylen); return true; } static enum test_result touch_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { union request { protocol_binary_request_touch touch; char buffer[512]; }; void *key = "get_test_key"; size_t keylen = strlen(key); union request r = { .touch = { .message = { .header.request = { .magic = PROTOCOL_BINARY_REQ, .opcode = PROTOCOL_BINARY_CMD_TOUCH, .keylen = htons((uint16_t)keylen), .extlen = 4, .datatype = PROTOCOL_BINARY_RAW_BYTES, .vbucket = 0, .bodylen = htonl(keylen + 4), .opaque = 0xdeadbeef, .cas = 0 }, .body = { .expiration = htonl(10) } } } }; memcpy(r.buffer + sizeof(r.touch.bytes), key, keylen); ENGINE_ERROR_CODE ret; ret = h1->unknown_command(h, NULL, &r.touch.message.header, response_handler); assert(ret == ENGINE_SUCCESS); assert(last_response != NULL); assert(ntohs(last_response->response.status) == PROTOCOL_BINARY_RESPONSE_KEY_ENOENT); assert(last_response->response.keylen == 0); assert(last_response->response.extlen == 0); assert(last_response->response.bodylen == 0); release_last_response(); // store and get a key assert(get_test(h, h1) == SUCCESS); // Set expiry time to 10 secs.. ret = h1->unknown_command(h, NULL, &r.touch.message.header, response_handler); assert(ret == ENGINE_SUCCESS); assert(last_response != NULL); assert(ntohs(last_response->response.status) == PROTOCOL_BINARY_RESPONSE_SUCCESS); assert(last_response->response.keylen == 0); assert(last_response->response.extlen == 0); assert(last_response->response.bodylen == 0); release_last_response(); // time-travel 11 secs.. test_harness.time_travel(11); // The item should have expired now... item *item = NULL; assert(h1->get(h, NULL, &item, key, keylen, 0) == ENGINE_KEY_ENOENT); // Verify that it doesn't accept bogus packets. extlen is mandatory r.touch.message.header.request.extlen = 0; r.touch.message.header.request.bodylen = htonl(keylen); ret = h1->unknown_command(h, NULL, &r.touch.message.header, response_handler); assert(ret == ENGINE_SUCCESS); assert(last_response != NULL); assert(ntohs(last_response->response.status) == PROTOCOL_BINARY_RESPONSE_EINVAL); release_last_response(); // key is mandatory! r.touch.message.header.request.extlen = 4; r.touch.message.header.request.keylen = 0; r.touch.message.header.request.bodylen = htonl(4); ret = h1->unknown_command(h, NULL, &r.touch.message.header, response_handler); assert(ret == ENGINE_SUCCESS); assert(last_response != NULL); assert(ntohs(last_response->response.status) == PROTOCOL_BINARY_RESPONSE_EINVAL); release_last_response(); return SUCCESS; } static enum test_result gat_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { union request { protocol_binary_request_gat gat; char buffer[512]; }; void *key = "get_test_key"; size_t keylen = strlen(key); union request r = { .gat = { .message = { .header.request = { .magic = PROTOCOL_BINARY_REQ, .opcode = PROTOCOL_BINARY_CMD_GAT, .keylen = htons((uint16_t)keylen), .extlen = 4, .datatype = PROTOCOL_BINARY_RAW_BYTES, .vbucket = 0, .bodylen = htonl(keylen + 4), .opaque = 0xdeadbeef, .cas = 0 }, .body = { .expiration = htonl(10) } } } }; memcpy(r.buffer + sizeof(r.gat.bytes), key, keylen); ENGINE_ERROR_CODE ret; ret = h1->unknown_command(h, NULL, &r.gat.message.header, response_handler); assert(ret == ENGINE_SUCCESS); assert(last_response != NULL); assert(ntohs(last_response->response.status) == PROTOCOL_BINARY_RESPONSE_KEY_ENOENT); assert(last_response->response.keylen == 0); assert(last_response->response.extlen == 0); assert(last_response->response.bodylen == 0); release_last_response(); // store and get a key assert(get_test(h, h1) == SUCCESS); // Set expiry time to 10 secs.. ret = h1->unknown_command(h, NULL, &r.gat.message.header, response_handler); assert(ret == ENGINE_SUCCESS); assert(last_response != NULL); assert(ntohs(last_response->response.status) == PROTOCOL_BINARY_RESPONSE_SUCCESS); assert(last_response->response.keylen == 0); assert(last_response->response.extlen == 4); assert(ntohl(last_response->response.bodylen) == 5); // get_test sets 1 byte datalen release_last_response(); // time-travel 11 secs.. test_harness.time_travel(11); // The item should have expired now... item *item = NULL; assert(h1->get(h, NULL, &item, key, keylen, 0) == ENGINE_KEY_ENOENT); // Verify that it doesn't accept bogus packets. extlen is mandatory r.gat.message.header.request.extlen = 0; r.gat.message.header.request.bodylen = htonl(keylen); ret = h1->unknown_command(h, NULL, &r.gat.message.header, response_handler); assert(ret == ENGINE_SUCCESS); assert(last_response != NULL); assert(ntohs(last_response->response.status) == PROTOCOL_BINARY_RESPONSE_EINVAL); release_last_response(); // key is mandatory! r.gat.message.header.request.extlen = 4; r.gat.message.header.request.keylen = 0; r.gat.message.header.request.bodylen = htonl(4); ret = h1->unknown_command(h, NULL, &r.gat.message.header, response_handler); assert(ret == ENGINE_SUCCESS); assert(last_response != NULL); assert(ntohs(last_response->response.status) == PROTOCOL_BINARY_RESPONSE_EINVAL); release_last_response(); return SUCCESS; } static enum test_result gatq_test(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) { union request { protocol_binary_request_gat gat; char buffer[512]; }; void *key = "get_test_key"; size_t keylen = strlen(key); union request r = { .gat = { .message = { .header.request = { .magic = PROTOCOL_BINARY_REQ, .opcode = PROTOCOL_BINARY_CMD_GATQ, .keylen = htons((uint16_t)keylen), .extlen = 4, .datatype = PROTOCOL_BINARY_RAW_BYTES, .vbucket = 0, .bodylen = htonl(keylen + 4), .opaque = 0xdeadbeef, .cas = 0 }, .body = { .expiration = htonl(10) } } } }; memcpy(r.buffer + sizeof(r.gat.bytes), key, keylen); ENGINE_ERROR_CODE ret; ret = h1->unknown_command(h, NULL, &r.gat.message.header, response_handler); assert(ret == ENGINE_SUCCESS); // GATQ is quiet and should not produce any result assert(last_response == NULL); // store and get a key assert(get_test(h, h1) == SUCCESS); // Set expiry time to 10 secs.. ret = h1->unknown_command(h, NULL, &r.gat.message.header, response_handler); assert(ret == ENGINE_SUCCESS); assert(last_response != NULL); assert(ntohs(last_response->response.status) == PROTOCOL_BINARY_RESPONSE_SUCCESS); assert(last_response->response.keylen == 0); assert(last_response->response.extlen == 4); assert(ntohl(last_response->response.bodylen) == 5); // get_test sets 1 byte datalen release_last_response(); // time-travel 11 secs.. test_harness.time_travel(11); // The item should have expired now... item *item = NULL; assert(h1->get(h, NULL, &item, key, keylen, 0) == ENGINE_KEY_ENOENT); // Verify that it doesn't accept bogus packets. extlen is mandatory r.gat.message.header.request.extlen = 0; r.gat.message.header.request.bodylen = htonl(keylen); ret = h1->unknown_command(h, NULL, &r.gat.message.header, response_handler); assert(ret == ENGINE_SUCCESS); assert(last_response != NULL); assert(ntohs(last_response->response.status) == PROTOCOL_BINARY_RESPONSE_EINVAL); release_last_response(); // key is mandatory! r.gat.message.header.request.extlen = 4; r.gat.message.header.request.keylen = 0; r.gat.message.header.request.bodylen = htonl(4); ret = h1->unknown_command(h, NULL, &r.gat.message.header, response_handler); assert(ret == ENGINE_SUCCESS); assert(last_response != NULL); assert(ntohs(last_response->response.status) == PROTOCOL_BINARY_RESPONSE_EINVAL); release_last_response(); return SUCCESS; } MEMCACHED_PUBLIC_API engine_test_t* get_tests(void) { static engine_test_t tests[] = { {"get info test", get_info_test, NULL, NULL, NULL}, {"get info description test", get_info_description_test, NULL, NULL, NULL}, {"get info features test", get_info_features_test, NULL, NULL, NULL}, {"allocate test", allocate_test, NULL, NULL, NULL}, {"set test", set_test, NULL, NULL, NULL}, {"add test", add_test, NULL, NULL, NULL}, {"replace test", replace_test, NULL, NULL, NULL}, {"append test", append_test, NULL, NULL, NULL}, {"prepend test", prepend_test, NULL, NULL, NULL}, {"store test", store_test, NULL, NULL, NULL}, {"get test", get_test, NULL, NULL, NULL}, {"expiry test", expiry_test, NULL, NULL, NULL}, {"remove test", remove_test, NULL, NULL, NULL}, {"release test", release_test, NULL, NULL, NULL}, {"incr test", incr_test, NULL, NULL, NULL}, {"mt incr test", mt_incr_test, NULL, NULL, NULL}, {"decr test", decr_test, NULL, NULL, NULL}, {"flush test", flush_test, NULL, NULL, NULL}, {"get item info test", get_item_info_test, NULL, NULL, NULL}, {"set cas test", item_set_cas_test, NULL, NULL, NULL}, {"LRU test", lru_test, NULL, NULL, "cache_size=48"}, {"get stats test", get_stats_test, NULL, NULL, NULL}, {"reset stats test", reset_stats_test, NULL, NULL, NULL}, {"get stats struct test", get_stats_struct_test, NULL, NULL, NULL}, {"aggregate stats test", aggregate_stats_test, NULL, NULL, NULL}, {"touch", touch_test, NULL, NULL, NULL}, {"Get And Touch", gat_test, NULL, NULL, NULL}, {"Get And Touch Quiet", gatq_test, NULL, NULL, NULL}, {NULL, NULL, NULL, NULL, NULL} }; return tests; } MEMCACHED_PUBLIC_API bool setup_suite(struct test_harness *th) { test_harness = *th; return true; }