/*- * Public Domain 2014-present MongoDB, Inc. * Public Domain 2008-2014 WiredTiger, Inc. * * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "test_util.h" /* * JIRA ticket reference: WT-3338 Test case description: Smoke-test the partial update construction. */ #define DEBUG 0 #define DATASIZE 1024 #define MAX_MODIFY_ENTRIES 37 /* Maximum modify vectors */ static WT_MODIFY entries[MAX_MODIFY_ENTRIES]; /* Entries vector */ static int nentries; /* Entries count */ /* * The replacement bytes array is 2x the maximum replacement string so we can offset into it by the * maximum replacement string and still take a maximum replacement string without going past the end * of the buffer. */ #define MAX_REPL_BYTES 17 static char modify_repl[MAX_REPL_BYTES * 2]; /* Replacement bytes */ static WT_RAND_STATE rnd; /* RNG state */ /* * show -- * Dump out a buffer. */ static void show(WT_ITEM *buf, const char *tag) { size_t i; const uint8_t *a; fprintf(stderr, "%s: %" WT_SIZET_FMT " bytes\n\t", tag, buf->size); for (a = buf->data, i = 0; i < buf->size; ++i, ++a) fprintf(stderr, " %c", isprint(*a) ? *a : '.'); fprintf(stderr, "\n"); } /* * modify_repl_init -- * Initialize the replacement information. */ static void modify_repl_init(void) { size_t i; for (i = 0; i < sizeof(modify_repl); ++i) modify_repl[i] = 'Z' - (i % 26); } /* * modify_build -- * Generate a set of modify vectors. */ static void modify_build(void) { int i; /* Mess up the entries. */ memset(entries, 0xff, sizeof(entries)); /* * Randomly select a number of byte changes, offsets and lengths. Allow a value of 0, the API * should accept it. */ nentries = (int)(__wt_random(&rnd) % (MAX_MODIFY_ENTRIES + 1)); for (i = 0; i < nentries; ++i) { entries[i].data.data = modify_repl + __wt_random(&rnd) % MAX_REPL_BYTES; entries[i].data.size = (size_t)(__wt_random(&rnd) % MAX_REPL_BYTES); entries[i].offset = (size_t)(__wt_random(&rnd) % DATASIZE); entries[i].size = (size_t)(__wt_random(&rnd) % MAX_REPL_BYTES); } #if DEBUG for (i = 0; i < nentries; ++i) printf("%d: {%.*s} %" WT_SIZET_FMT " bytes replacing %" WT_SIZET_FMT " bytes @ %" WT_SIZET_FMT "\n", i, (int)entries[i].data.size, (char *)entries[i].data.data, entries[i].data.size, entries[i].size, entries[i].offset); #endif } /* * slow_apply_api -- * Apply a set of modification changes using a different algorithm. */ static void slow_apply_api(WT_ITEM *orig) { static WT_ITEM _tb; WT_ITEM *ta, *tb, *tmp, _tmp; size_t len, size; int i; ta = orig; tb = &_tb; /* Mess up anything not initialized in the buffers. */ memset((uint8_t *)ta->mem + ta->size, 0xff, ta->memsize - ta->size); memset((uint8_t *)tb->mem, 0xff, tb->memsize); /* * Process the entries to figure out how large a buffer we need. This is a bit pessimistic * because we're ignoring replacement bytes, but it's a simpler calculation. */ for (size = ta->size, i = 0; i < nentries; ++i) { if (entries[i].offset >= size) size = entries[i].offset; size += entries[i].data.size; } testutil_check(__wt_buf_grow(NULL, ta, size)); testutil_check(__wt_buf_grow(NULL, tb, size)); #if DEBUG show(ta, "slow-apply start"); #endif /* * From the starting buffer, create a new buffer b based on changes in the entries array. We're * doing a brute force solution here to test the faster solution implemented in the library. */ for (i = 0; i < nentries; ++i) { /* Take leading bytes from the original, plus any gap bytes. */ if (entries[i].offset >= ta->size) { memcpy(tb->mem, ta->mem, ta->size); if (entries[i].offset > ta->size) memset((uint8_t *)tb->mem + ta->size, '\0', entries[i].offset - ta->size); } else if (entries[i].offset > 0) memcpy(tb->mem, ta->mem, entries[i].offset); tb->size = entries[i].offset; /* Take replacement bytes. */ if (entries[i].data.size > 0) { memcpy((uint8_t *)tb->mem + tb->size, entries[i].data.data, entries[i].data.size); tb->size += entries[i].data.size; } /* Take trailing bytes from the original. */ len = entries[i].offset + entries[i].size; if (ta->size > len) { memcpy((uint8_t *)tb->mem + tb->size, (uint8_t *)ta->mem + len, ta->size - len); tb->size += ta->size - len; } testutil_assert(tb->size <= size); /* Swap the buffers and do it again. */ tmp = ta; ta = tb; tb = tmp; } ta->data = ta->mem; tb->data = tb->mem; /* * The final results may not be in the original buffer, in which case we swap them back around. */ if (ta != orig) { _tmp = *ta; *ta = *tb; *tb = _tmp; } #if DEBUG show(ta, "slow-apply finish"); #endif } /* * compare -- * Compare two results. */ static void compare(WT_ITEM *orig, WT_ITEM *local, WT_ITEM *library) { size_t i, max; const uint8_t *p, *t; max = WT_MIN(local->size, library->size); if (local->size != library->size || memcmp(local->data, library->data, local->size) != 0) { for (i = 0, p = local->data, t = library->data; i < max; ++i, ++p, ++t) if (*p != *t) break; fprintf(stderr, "results differ: "); if (max == 0) fprintf(stderr, "identical up to %" WT_SIZET_FMT " bytes\n", max); else fprintf(stderr, "first mismatch at offset %" WT_SIZET_FMT "\n", i); show(orig, "original"); show(local, "local results"); show(library, "library results"); } testutil_assert( local->size == library->size && memcmp(local->data, library->data, local->size) == 0); } /* * modify_run * Run some tests: * 1. Create an initial value, a copy and a fake cursor to use with the * WiredTiger routines. Generate a set of modify vectors and apply them to * the item stored in the cursor using the modify apply API. Also apply the * same modify vector to one of the copies using a helper routine written * to test the modify API. The final value generated with the modify API * and the helper routine should match. * * 2. Use the initial value and the modified value generated above as * inputs into the calculate-modify API to generate a set of modify * vectors. Apply this generated vector to the initial value using the * modify apply API to obtain a final value. The final value generated * should match the modified value that was used as input to the * calculate-modify API. */ static void modify_run(TEST_OPTS *opts) { WT_CURSOR *cursor, _cursor; WT_DECL_RET; WT_ITEM *localA, _localA, *localB, _localB; WT_SESSION_IMPL *session; size_t len; int i, j; u_char *p; bool verbose; session = (WT_SESSION_IMPL *)opts->session; verbose = opts->verbose; /* Initialize the RNG. */ __wt_random_init_seed(session, &rnd); /* Set up replacement information. */ modify_repl_init(); /* We need three WT_ITEMs, one of them part of a fake cursor. */ localA = &_localA; memset(&_localA, 0, sizeof(_localA)); localB = &_localB; memset(&_localB, 0, sizeof(_localB)); cursor = &_cursor; memset(&_cursor, 0, sizeof(_cursor)); cursor->session = (WT_SESSION *)session; cursor->value_format = "u"; #define NRUNS 10000 for (i = 0; i < NRUNS; ++i) { /* Create an initial value. */ len = (size_t)(__wt_random(&rnd) % MAX_REPL_BYTES); testutil_check(__wt_buf_set(session, localA, modify_repl, len)); for (j = 0; j < 1000; ++j) { /* Make lower case so modifications are easy to see. */ for (p = localA->mem; WT_PTRDIFF(p, localA->mem) < localA->size; p++) *p = __wt_tolower(*p); /* Copy the current value into the second item. */ testutil_check(__wt_buf_set(session, localB, localA->data, localA->size)); /* * Create a random set of modify vectors, run the underlying library modification * function, then compare the result against our implementation of modify. */ modify_build(); testutil_check(__wt_buf_set(session, &cursor->value, localA->data, localA->size)); testutil_check(__wt_modify_apply_api(cursor, entries, nentries)); slow_apply_api(localA); compare(localB, localA, &cursor->value); /* * Call the WiredTiger function to build a modification vector for the change, and * repeat the test using the WiredTiger modification vector, then compare results * against our implementation of modify. */ nentries = WT_ELEMENTS(entries); ret = wiredtiger_calc_modify(opts->session, localB, localA, WT_MAX(localB->size, localA->size) + 100, entries, &nentries); if (ret == WT_NOTFOUND) continue; testutil_check(ret); testutil_check(__wt_buf_set(session, &cursor->value, localB->data, localB->size)); testutil_check(__wt_modify_apply_api(cursor, entries, nentries)); compare(localB, localA, &cursor->value); } if (verbose) { printf("%d (%d%%)\r", i, (i * 100) / NRUNS); fflush(stdout); } } if (verbose) printf("%d (100%%)\n", i); __wt_buf_free(session, localA); __wt_buf_free(session, localB); __wt_buf_free(session, &cursor->value); } int main(int argc, char *argv[]) { TEST_OPTS *opts, _opts; opts = &_opts; memset(opts, 0, sizeof(*opts)); testutil_check(testutil_parse_opts(argc, argv, opts)); testutil_make_work_dir(opts->home); testutil_check(wiredtiger_open(opts->home, NULL, "create", &opts->conn)); testutil_check(opts->conn->open_session(opts->conn, NULL, NULL, &opts->session)); /* Run the test. */ modify_run(opts); testutil_cleanup(opts); return (EXIT_SUCCESS); }