blob: dabdf0b1f81e147e8aa4503d29f32064f5b2699d [file] [log] [blame]
/*
* cache-test.c -- test the in-memory cache
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 <stdio.h>
#include <string.h>
#include <apr_general.h>
#include <apr_lib.h>
#include <apr_time.h>
#include "svn_pools.h"
#include "private/svn_cache.h"
#include "svn_private_config.h"
#include "../svn_test.h"
/* Create memcached cache if configured */
static svn_error_t *
create_memcache(svn_memcache_t **memcache,
const svn_test_opts_t *opts,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_config_t *config = NULL;
if (opts->config_file)
{
SVN_ERR(svn_config_read3(&config, opts->config_file,
TRUE, FALSE, FALSE, scratch_pool));
}
else if (opts->memcached_server)
{
SVN_ERR(svn_config_create2(&config, FALSE, FALSE, scratch_pool));
svn_config_set(config, SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS,
"key" /* some value; ignored*/,
opts->memcached_server);
}
if (config)
{
SVN_ERR(svn_cache__make_memcache_from_config(memcache, config,
result_pool, scratch_pool));
}
else
*memcache = NULL;
return SVN_NO_ERROR;
}
/* Implements svn_cache__serialize_func_t */
static svn_error_t *
serialize_revnum(void **data,
apr_size_t *data_len,
void *in,
apr_pool_t *pool)
{
*data_len = sizeof(svn_revnum_t);
*data = apr_pmemdup(pool, in, *data_len);
return SVN_NO_ERROR;
}
/* Implements svn_cache__deserialize_func_t */
static svn_error_t *
deserialize_revnum(void **out,
void *data,
apr_size_t data_len,
apr_pool_t *pool)
{
const svn_revnum_t *in_rev = (const svn_revnum_t *) data;
svn_revnum_t *out_rev;
if (data_len != sizeof(*in_rev))
return svn_error_create(SVN_ERR_REVNUM_PARSE_FAILURE, NULL,
_("Bad size for revision number in cache"));
out_rev = apr_palloc(pool, sizeof(*out_rev));
*out_rev = *in_rev;
*out = out_rev;
return SVN_NO_ERROR;
}
static svn_error_t *
basic_cache_test(svn_cache__t *cache,
svn_boolean_t size_is_one,
apr_pool_t *pool)
{
svn_boolean_t found;
svn_revnum_t twenty = 20, thirty = 30, *answer;
apr_pool_t *subpool;
/* We use a subpool for all calls in this test and aggressively
* clear it, to try to find any bugs where the cached values aren't
* actually saved away in the cache's pools. */
subpool = svn_pool_create(pool);
SVN_ERR(svn_cache__get((void **) &answer, &found, cache, "twenty", subpool));
if (found)
return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
"cache found an entry that wasn't there");
svn_pool_clear(subpool);
SVN_ERR(svn_cache__set(cache, "twenty", &twenty, subpool));
svn_pool_clear(subpool);
SVN_ERR(svn_cache__get((void **) &answer, &found, cache, "twenty", subpool));
if (! found)
return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
"cache failed to find entry for 'twenty'");
if (*answer != 20)
return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
"expected 20 but found '%ld'", *answer);
svn_pool_clear(subpool);
SVN_ERR(svn_cache__set(cache, "thirty", &thirty, subpool));
svn_pool_clear(subpool);
SVN_ERR(svn_cache__get((void **) &answer, &found, cache, "thirty", subpool));
if (! found)
return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
"cache failed to find entry for 'thirty'");
if (*answer != 30)
return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
"expected 30 but found '%ld'", *answer);
if (size_is_one)
{
SVN_ERR(svn_cache__get((void **) &answer, &found, cache, "twenty", subpool));
if (found)
return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
"cache found entry for 'twenty' that should have "
"expired");
}
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
static svn_error_t *
test_inprocess_cache_basic(apr_pool_t *pool)
{
svn_cache__t *cache;
/* Create a cache with just one entry. */
SVN_ERR(svn_cache__create_inprocess(&cache,
serialize_revnum,
deserialize_revnum,
APR_HASH_KEY_STRING,
1,
1,
TRUE,
"",
pool));
return basic_cache_test(cache, TRUE, pool);
}
static svn_error_t *
test_memcache_basic(const svn_test_opts_t *opts,
apr_pool_t *pool)
{
svn_cache__t *cache;
svn_memcache_t *memcache = NULL;
const char *prefix = apr_psprintf(pool,
"test_memcache_basic-%" APR_TIME_T_FMT,
apr_time_now());
SVN_ERR(create_memcache(&memcache, opts, pool, pool));
if (! memcache)
return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL,
"not configured to use memcached");
/* Create a memcache-based cache. */
SVN_ERR(svn_cache__create_memcache(&cache,
memcache,
serialize_revnum,
deserialize_revnum,
APR_HASH_KEY_STRING,
prefix,
pool));
return basic_cache_test(cache, FALSE, pool);
}
static svn_error_t *
test_membuffer_cache_basic(apr_pool_t *pool)
{
svn_cache__t *cache;
svn_membuffer_t *membuffer;
SVN_ERR(svn_cache__membuffer_cache_create(&membuffer, 10*1024, 1, 0,
TRUE, TRUE, pool));
/* Create a cache with just one entry. */
SVN_ERR(svn_cache__create_membuffer_cache(&cache,
membuffer,
serialize_revnum,
deserialize_revnum,
APR_HASH_KEY_STRING,
"cache:",
SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY,
FALSE,
FALSE,
pool, pool));
return basic_cache_test(cache, FALSE, pool);
}
/* Implements svn_cache__deserialize_func_t */
static svn_error_t *
raise_error_deserialize_func(void **out,
void *data,
apr_size_t data_len,
apr_pool_t *pool)
{
return svn_error_create(APR_EGENERAL, NULL, NULL);
}
/* Implements svn_cache__partial_getter_func_t */
static svn_error_t *
raise_error_partial_getter_func(void **out,
const void *data,
apr_size_t data_len,
void *baton,
apr_pool_t *result_pool)
{
return svn_error_create(APR_EGENERAL, NULL, NULL);
}
/* Implements svn_cache__partial_setter_func_t */
static svn_error_t *
raise_error_partial_setter_func(void **data,
apr_size_t *data_len,
void *baton,
apr_pool_t *result_pool)
{
return svn_error_create(APR_EGENERAL, NULL, NULL);
}
static svn_error_t *
test_membuffer_serializer_error_handling(apr_pool_t *pool)
{
svn_cache__t *cache;
svn_membuffer_t *membuffer;
svn_revnum_t twenty = 20;
svn_boolean_t found;
void *val;
SVN_ERR(svn_cache__membuffer_cache_create(&membuffer, 10*1024, 1, 0,
TRUE, TRUE, pool));
/* Create a cache with just one entry. */
SVN_ERR(svn_cache__create_membuffer_cache(&cache,
membuffer,
serialize_revnum,
raise_error_deserialize_func,
APR_HASH_KEY_STRING,
"cache:",
SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY,
FALSE,
FALSE,
pool, pool));
SVN_ERR(svn_cache__set(cache, "twenty", &twenty, pool));
/* Test retrieving data from cache using full getter that
always raises an error. */
SVN_TEST_ASSERT_ERROR(
svn_cache__get(&val, &found, cache, "twenty", pool),
APR_EGENERAL);
/* Test retrieving data from cache using partial getter that
always raises an error. */
SVN_TEST_ASSERT_ERROR(
svn_cache__get_partial(&val, &found, cache, "twenty",
raise_error_partial_getter_func,
NULL, pool),
APR_EGENERAL);
/* Create a new cache. */
SVN_ERR(svn_cache__membuffer_cache_create(&membuffer, 10*1024, 1, 0,
TRUE, TRUE, pool));
SVN_ERR(svn_cache__create_membuffer_cache(&cache,
membuffer,
serialize_revnum,
deserialize_revnum,
APR_HASH_KEY_STRING,
"cache:",
SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY,
FALSE,
FALSE,
pool, pool));
/* Store one entry in cache. */
SVN_ERR(svn_cache__set(cache, "twenty", &twenty, pool));
/* Test setting data in cache using partial setter that
always raises an error. */
SVN_TEST_ASSERT_ERROR(
svn_cache__set_partial(cache, "twenty",
raise_error_partial_setter_func,
NULL, pool),
APR_EGENERAL);
return SVN_NO_ERROR;
}
static svn_error_t *
test_memcache_long_key(const svn_test_opts_t *opts,
apr_pool_t *pool)
{
svn_cache__t *cache;
svn_memcache_t *memcache = NULL;
svn_revnum_t fifty = 50, *answer;
svn_boolean_t found = FALSE;
const char *prefix = apr_psprintf(pool,
"test_memcache_long_key-%" APR_TIME_T_FMT,
apr_time_now());
static const char *long_key =
"0123456789" "0123456789" "0123456789" "0123456789" "0123456789" /* 50 */
"0123456789" "0123456789" "0123456789" "0123456789" "0123456789" /* 100 */
"0123456789" "0123456789" "0123456789" "0123456789" "0123456789" /* 150 */
"0123456789" "0123456789" "0123456789" "0123456789" "0123456789" /* 200 */
"0123456789" "0123456789" "0123456789" "0123456789" "0123456789" /* 250 */
"0123456789" "0123456789" "0123456789" "0123456789" "0123456789" /* 300 */
;
SVN_ERR(create_memcache(&memcache, opts, pool, pool));
if (! memcache)
return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL,
"not configured to use memcached");
/* Create a memcache-based cache. */
SVN_ERR(svn_cache__create_memcache(&cache,
memcache,
serialize_revnum,
deserialize_revnum,
APR_HASH_KEY_STRING,
prefix,
pool));
SVN_ERR(svn_cache__set(cache, long_key, &fifty, pool));
SVN_ERR(svn_cache__get((void **) &answer, &found, cache, long_key, pool));
if (! found)
return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
"cache failed to find entry for 'fifty'");
if (*answer != 50)
return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
"expected 50 but found '%ld'", *answer);
return SVN_NO_ERROR;
}
static svn_error_t *
test_membuffer_cache_clearing(apr_pool_t *pool)
{
svn_cache__t *cache;
svn_membuffer_t *membuffer;
svn_boolean_t found;
svn_revnum_t *value;
svn_revnum_t valueA = 12345;
svn_revnum_t valueB = 67890;
/* Create a simple cache for strings, keyed by strings. */
SVN_ERR(svn_cache__membuffer_cache_create(&membuffer, 10*1024, 1, 0,
TRUE, TRUE, pool));
SVN_ERR(svn_cache__create_membuffer_cache(&cache,
membuffer,
serialize_revnum,
deserialize_revnum,
APR_HASH_KEY_STRING,
"cache:",
SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY,
FALSE,
FALSE,
pool, pool));
/* Initially, the cache is empty. */
SVN_ERR(svn_cache__get((void **) &value, &found, cache, "key A", pool));
SVN_TEST_ASSERT(!found);
SVN_ERR(svn_cache__get((void **) &value, &found, cache, "key B", pool));
SVN_TEST_ASSERT(!found);
SVN_ERR(svn_cache__get((void **) &value, &found, cache, "key C", pool));
SVN_TEST_ASSERT(!found);
/* Add entries. */
SVN_ERR(svn_cache__set(cache, "key A", &valueA, pool));
SVN_ERR(svn_cache__set(cache, "key B", &valueB, pool));
/* Added entries should be cached (too small to get evicted already). */
SVN_ERR(svn_cache__get((void **) &value, &found, cache, "key A", pool));
SVN_TEST_ASSERT(found);
SVN_TEST_ASSERT(*value == valueA);
SVN_ERR(svn_cache__get((void **) &value, &found, cache, "key B", pool));
SVN_TEST_ASSERT(found);
SVN_TEST_ASSERT(*value == valueB);
SVN_ERR(svn_cache__get((void **) &value, &found, cache, "key C", pool));
SVN_TEST_ASSERT(!found);
/* Clear the cache. */
SVN_ERR(svn_cache__membuffer_clear(membuffer));
/* The cache is empty again. */
SVN_ERR(svn_cache__get((void **) &value, &found, cache, "key A", pool));
SVN_TEST_ASSERT(!found);
SVN_ERR(svn_cache__get((void **) &value, &found, cache, "key B", pool));
SVN_TEST_ASSERT(!found);
SVN_ERR(svn_cache__get((void **) &value, &found, cache, "key C", pool));
SVN_TEST_ASSERT(!found);
/* But still functional: */
SVN_ERR(svn_cache__set(cache, "key B", &valueB, pool));
SVN_ERR(svn_cache__has_key(&found, cache, "key A", pool));
SVN_TEST_ASSERT(!found);
SVN_ERR(svn_cache__has_key(&found, cache, "key B", pool));
SVN_TEST_ASSERT(found);
SVN_ERR(svn_cache__has_key(&found, cache, "key C", pool));
SVN_TEST_ASSERT(!found);
return SVN_NO_ERROR;
}
/* Implements svn_iter_apr_hash_cb_t. */
static svn_error_t *
null_cache_iter_func(void *baton,
const void *key,
apr_ssize_t klen,
void *val,
apr_pool_t *pool)
{
/* shall never be called */
return svn_error_create(SVN_ERR_TEST_FAILED, NULL, "should not be called");
}
static svn_error_t *
test_null_cache(apr_pool_t *pool)
{
svn_boolean_t found, done;
int *data = NULL;
svn_cache__info_t info;
svn_cache__t *cache;
SVN_ERR(svn_cache__create_null(&cache, "test-dummy", pool));
/* Can't cache anything. */
SVN_TEST_ASSERT(svn_cache__is_cachable(cache, 0) == FALSE);
SVN_TEST_ASSERT(svn_cache__is_cachable(cache, 1) == FALSE);
/* No point in adding data. */
SVN_ERR(svn_cache__set(cache, "data", &data, pool));
SVN_ERR(svn_cache__get((void **)&data, &found, cache, "data", pool));
SVN_TEST_ASSERT(found == FALSE);
SVN_ERR(svn_cache__has_key(&found, cache, "data", pool));
SVN_TEST_ASSERT(found == FALSE);
/* Iteration "works" but is a no-op. */
SVN_ERR(svn_cache__iter(&done, cache, null_cache_iter_func, NULL, pool));
SVN_TEST_ASSERT(done);
/* It shall know its name. */
SVN_ERR(svn_cache__get_info(cache, &info, TRUE, pool));
SVN_TEST_STRING_ASSERT(info.id, "test-dummy");
return SVN_NO_ERROR;
}
static svn_error_t *
test_membuffer_unaligned_string_keys(apr_pool_t *pool)
{
svn_cache__t *cache;
svn_membuffer_t *membuffer;
svn_revnum_t fifty = 50;
svn_revnum_t *answer;
svn_boolean_t found = FALSE;
/* Allocate explicitly to have aligned string and this add one
* to have unaligned string.*/
const char *aligned_key = apr_pstrdup(pool, "fifty");
const char *unaligned_key = apr_pstrdup(pool, "_fifty") + 1;
const char *unaligned_prefix = apr_pstrdup(pool, "_cache:") + 1;
SVN_ERR(svn_cache__membuffer_cache_create(&membuffer, 10*1024, 1, 0,
TRUE, TRUE, pool));
/* Create a cache with just one entry. */
SVN_ERR(svn_cache__create_membuffer_cache(
&cache, membuffer, serialize_revnum, deserialize_revnum,
APR_HASH_KEY_STRING, unaligned_prefix,
SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY, FALSE, FALSE,
pool, pool));
SVN_ERR(svn_cache__set(cache, unaligned_key, &fifty, pool));
SVN_ERR(svn_cache__get((void **) &answer, &found, cache, unaligned_key,
pool));
if (! found)
return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
"cache failed to find entry for 'fifty'");
if (*answer != 50)
return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
"expected 50 but found '%ld'", *answer);
/* Make sure that we get proper result when providing aligned key*/
SVN_ERR(svn_cache__get((void **) &answer, &found, cache, aligned_key,
pool));
if (! found)
return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
"cache failed to find entry for 'fifty'");
if (*answer != 50)
return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
"expected 50 but found '%ld'", *answer);
return SVN_NO_ERROR;
}
static svn_error_t *
test_membuffer_unaligned_fixed_keys(apr_pool_t *pool)
{
svn_cache__t *cache;
svn_membuffer_t *membuffer;
svn_revnum_t fifty = 50;
svn_revnum_t *answer;
svn_boolean_t found = FALSE;
/* Allocate explicitly to have aligned string and this add one
* to have unaligned key.*/
const char *aligned_key = apr_pstrdup(pool, "12345678");
const char *unaligned_key = apr_pstrdup(pool, "_12345678") + 1;
const char *unaligned_prefix = apr_pstrdup(pool, "_cache:") + 1;
SVN_ERR(svn_cache__membuffer_cache_create(&membuffer, 10*1024, 1, 0,
TRUE, TRUE, pool));
/* Create a cache with just one entry. */
SVN_ERR(svn_cache__create_membuffer_cache(
&cache, membuffer, serialize_revnum, deserialize_revnum,
8 /* klen*/,
unaligned_prefix,
SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY, FALSE, FALSE,
pool, pool));
SVN_ERR(svn_cache__set(cache, unaligned_key, &fifty, pool));
SVN_ERR(svn_cache__get((void **) &answer, &found, cache, unaligned_key,
pool));
if (! found)
return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
"cache failed to find entry for '12345678' (unaligned)");
if (*answer != 50)
return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
"expected 50 but found '%ld'", *answer);
/* Make sure that we get proper result when providing aligned key*/
SVN_ERR(svn_cache__get((void **) &answer, &found, cache, aligned_key,
pool));
if (! found)
return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
"cache failed to find entry for '12345678' (aligned)");
if (*answer != 50)
return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
"expected 50 but found '%ld'", *answer);
return SVN_NO_ERROR;
}
/* The test table. */
static int max_threads = 1;
static struct svn_test_descriptor_t test_funcs[] =
{
SVN_TEST_NULL,
SVN_TEST_PASS2(test_inprocess_cache_basic,
"basic inprocess svn_cache test"),
SVN_TEST_OPTS_PASS(test_memcache_basic,
"basic memcache svn_cache test"),
SVN_TEST_OPTS_PASS(test_memcache_long_key,
"memcache svn_cache with very long keys"),
SVN_TEST_PASS2(test_membuffer_cache_basic,
"basic membuffer svn_cache test"),
SVN_TEST_PASS2(test_membuffer_serializer_error_handling,
"test for error handling in membuffer svn_cache"),
SVN_TEST_PASS2(test_membuffer_cache_clearing,
"test clearing a membuffer svn_cache"),
SVN_TEST_PASS2(test_null_cache,
"basic null svn_cache test"),
SVN_TEST_PASS2(test_membuffer_unaligned_string_keys,
"test membuffer cache with unaligned string keys"),
SVN_TEST_PASS2(test_membuffer_unaligned_fixed_keys,
"test membuffer cache with unaligned fixed keys"),
SVN_TEST_NULL
};
SVN_TEST_MAIN