MXS-1512 Add test case for cache options

Here's now tested that the cache properly deals with the configuration
setting "cache_in_transactions" and the current transaction state.
This commit is contained in:
Johan Wikman 2017-11-28 13:40:31 +02:00
parent b225eeff2c
commit 481892b452
2 changed files with 471 additions and 0 deletions

View File

@ -1,4 +1,5 @@
include_directories(..)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../test)
add_library(cachetester
tester.cc
@ -27,6 +28,21 @@ target_link_libraries(testrawstorage cachetester cache maxscale-common)
add_executable(testlrustorage testlrustorage.cc)
target_link_libraries(testlrustorage cachetester cache maxscale-common)
add_executable(test_cacheoptions
test_cacheoptions.cc
../../test/filtermodule.cc
../../test/mock.cc
../../test/mock_backend.cc
../../test/mock_client.cc
../../test/mock_dcb.cc
../../test/mock_routersession.cc
../../test/mock_session.cc
../../test/module.cc
../../test/queryclassifiermodule.cc
)
target_link_libraries(test_cacheoptions maxscale-common)
add_test(TestCache_rules testrules)
add_test(TestCache_inmemory_keygeneration testkeygeneration storage_inmemory ${CMAKE_CURRENT_SOURCE_DIR}/input.test)
@ -39,3 +55,5 @@ add_test(TestCache_storage_inmemory testrawstorage storage_inmemory 0 10 1000 10
#usage: testlrustorage storage-module [threads [time [items [min-size [max-size]]]]]\n"
add_test(TestCache_lru_inmemory testlrustorage storage_inmemory 0 10 1000 1024 1024000)
#add_test(TestCache_lru_rocksdb testlrustorage storage_rocksdb 0 10 1000 1024 1024000)
add_test(TestCache_options test_cacheoptions)

View File

@ -0,0 +1,453 @@
/*
* Copyright (c) 2016 MariaDB Corporation Ab
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
*
* Change Date: 2020-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2 or later of the General
* Public License.
*/
#include <iostream>
#include <sstream>
#include <maxscale/filtermodule.hh>
#include <maxscale/mock/backend.hh>
#include <maxscale/mock/client.hh>
#include <maxscale/mock/routersession.hh>
#include <maxscale/mock/session.hh>
#include "../cachefilter.h"
using namespace std;
using maxscale::FilterModule;
namespace mock = maxscale::mock;
namespace
{
struct CONFIG
{
bool stop_at_first_error;
} config =
{
true, // stop_at_first_error
};
// See https://github.com/mariadb-corporation/MaxScale/blob/2.2/Documentation/Filters/Cache.md#cache_inside_transactions
struct TEST_CASE
{
cache_in_trxs_t cit; /*< How to cache in transactions. */
mxs_session_trx_state_t trx_state; /*< The transaction state. */
bool should_use; /*< Whether the cache should be returned from the cache. */
} TEST_CASES[] =
{
{
CACHE_IN_TRXS_NEVER,
SESSION_TRX_INACTIVE,
true // should_use
},
{
CACHE_IN_TRXS_NEVER,
SESSION_TRX_ACTIVE,
false // should_use
},
{
CACHE_IN_TRXS_NEVER,
SESSION_TRX_READ_ONLY,
false // should_use
},
{
CACHE_IN_TRXS_READ_ONLY,
SESSION_TRX_INACTIVE,
true // should_use
},
{
CACHE_IN_TRXS_READ_ONLY,
SESSION_TRX_ACTIVE,
false // should_use
},
{
CACHE_IN_TRXS_READ_ONLY,
SESSION_TRX_READ_ONLY,
true // should_use
},
{
CACHE_IN_TRXS_ALL,
SESSION_TRX_INACTIVE,
true // should_use
},
{
CACHE_IN_TRXS_ALL,
SESSION_TRX_ACTIVE,
true // should_use
},
{
CACHE_IN_TRXS_ALL,
SESSION_TRX_READ_ONLY,
true // should_use
},
};
const size_t N_TEST_CASES = sizeof(TEST_CASES) / sizeof(TEST_CASES[0]);
const char* to_string(cache_in_trxs_t x)
{
switch (x)
{
case CACHE_IN_TRXS_NEVER:
return "never";
case CACHE_IN_TRXS_READ_ONLY:
return "read_only_transactions";
case CACHE_IN_TRXS_ALL:
return "all_transactions";
default:
ss_dassert(!true);
return NULL;
}
}
ostream& operator << (ostream& out, cache_in_trxs_t x)
{
out << to_string(x);
return out;
}
ostream& operator << (ostream& out, mxs_session_trx_state_t trx_state)
{
out << session_trx_state_to_string(trx_state);
return out;
}
}
namespace
{
static int counter = 0;
string create_unique_select()
{
stringstream ss;
ss << "SELECT col" << ++counter << " FROM tbl";
return ss.str();
}
int test(mock::Session& session,
FilterModule::Session& filter_session,
mock::RouterSession& router_session,
const TEST_CASE& tc)
{
int rv = 0;
mock::Client& client = session.client();
// Let's check that there's nothing pending.
ss_dassert(client.n_responses() == 0);
ss_dassert(router_session.idle());
session.set_trx_state(tc.trx_state);
session.set_autocommit(tc.trx_state == SESSION_TRX_INACTIVE);
string select(create_unique_select());
GWBUF* pStatement;
pStatement = mock::create_com_query(select);
cout << "Performing select: \"" << select << "\"" << flush;
filter_session.routeQuery(pStatement);
if (!router_session.idle())
{
cout << ", reached backend." << endl;
// Let's cause the backend to respond.
router_session.respond();
// And let's verify that the backend is now empty...
ss_dassert(router_session.idle());
// ...and that we have received a response.
ss_dassert(client.n_responses() == 1);
// Let's do the select again.
pStatement = mock::create_com_query(select);
cout << "Performing same select: \"" << select << "\"" << flush;
filter_session.routeQuery(pStatement);
if (tc.should_use)
{
if (!router_session.idle())
{
cout << "\nERROR: Select reached backend and was not provided from cache." << endl;
router_session.respond();
++rv;
}
else
{
cout << ", cache was used." << endl;
// Let's check we did receive a response.
ss_dassert(client.n_responses() == 2);
}
}
else
{
if (router_session.idle())
{
cout << "\nERROR: Select was provided from cache and did not reach backend." << endl;
++rv;
}
else
{
cout << ", reached backend." << endl;
router_session.respond();
}
}
if ((tc.trx_state != SESSION_TRX_INACTIVE) && (tc.trx_state != SESSION_TRX_READ_ONLY))
{
// A transaction, but not a read-only one.
string update("UPDATE tbl SET a=1;");
pStatement = mock::create_com_query("UPDATE tbl SET a=1;");
cout << "Performing update: \"" << update << "\"" << flush;
filter_session.routeQuery(pStatement);
if (router_session.idle())
{
cout << "\n"
<< "ERROR: Did not reach backend." << endl;
++rv;
}
else
{
cout << ", reached backend." << endl;
router_session.respond();
// Let's make the select again.
pStatement = mock::create_com_query(select);
cout << "Performing select: \"" << select << "\"" << flush;
filter_session.routeQuery(pStatement);
if (router_session.idle())
{
cout << "\nERROR: Did not reach backend." << endl;
++rv;
}
else
{
// The select reached the backend, i.e. the cache was not used after
// a non-SELECT.
cout << ", reached backend." << endl;
router_session.respond();
}
}
}
// Irrespective of what was going on above, the cache should now contain the
// original select. So, let's do a select with no transaction.
cout << "Setting transaction state to SESSION_TRX_INACTIVE" << endl;
session.set_trx_state(SESSION_TRX_INACTIVE);
session.set_autocommit(true);
pStatement = mock::create_com_query(select);
cout << "Performing select: \"" << select << "\"" << flush;
filter_session.routeQuery(pStatement);
if (router_session.idle())
{
cout << ", cache was used." << endl;
}
else
{
cout << "\nERROR: cache was not used." << endl;
router_session.respond();
++rv;
}
}
else
{
cout << "\nERROR: Did not reach backend." << endl;
++rv;
}
return rv;
}
int test(FilterModule::Instance& filter_instance, const TEST_CASE& tc)
{
int rv = 0;
mock::ResultSetBackend backend;
mock::RouterSession router_session(&backend);
mock::Client client("bob", "127.0.0.1");
mock::Session session(&client);
auto_ptr<FilterModule::Session> sFilter_session = filter_instance.newSession(&session);
if (sFilter_session.get())
{
router_session.set_as_downstream_on(sFilter_session.get());
client.set_as_upstream_on(*sFilter_session.get());
rv += test(session, *sFilter_session.get(), router_session, tc);
}
else
{
++rv;
}
return rv;
}
int test(FilterModule& filter_module, const TEST_CASE& tc)
{
int rv = 1;
auto_ptr<FilterModule::ConfigParameters> sParameters = filter_module.create_default_parameters();
sParameters->set_value("cache_in_transactions", to_string(tc.cit));
sParameters->set_value("debug", "31");
auto_ptr<FilterModule::Instance> sInstance = filter_module.createInstance("test", NULL, sParameters);
if (sInstance.get())
{
rv = test(*sInstance, tc);
}
return rv;
}
}
namespace
{
int run()
{
int rv = 1;
auto_ptr<FilterModule> sModule = FilterModule::load("cache");
if (sModule.get())
{
if (maxscale::Module::process_init())
{
if (maxscale::Module::thread_init())
{
rv = 0;
for (size_t i = 0; i < N_TEST_CASES; ++i)
{
const TEST_CASE& tc = TEST_CASES[i];
cout << "CIT: " << tc.cit
<< ", TRX_STATE: " << tc.trx_state
<< ", should use: " << tc.should_use
<< endl;
rv += test(*sModule.get(), tc);
cout << endl;
if ((rv != 0) && config.stop_at_first_error)
{
break;
}
}
maxscale::Module::thread_finish();
}
else
{
cerr << "error: Could not perform thread initialization." << endl;
}
maxscale::Module::process_finish();
}
else
{
cerr << "error: Could not perform process initialization." << endl;
}
}
else
{
cerr << "error: Could not load filter module." << endl;
}
return rv;
}
}
namespace
{
char USAGE[] =
"usage: test_dbfwfilter [-d]\n"
"\n"
"-d don't stop at first error\n";
}
int main(int argc, char* argv[])
{
int rv = 0;
int c;
while ((c = getopt(argc, argv, "d")) != -1)
{
switch (c)
{
case 'd':
config.stop_at_first_error = false;
break;
default:
rv = 1;
}
}
if (rv == 0)
{
if (mxs_log_init(NULL, ".", MXS_LOG_TARGET_DEFAULT))
{
if (qc_setup("qc_sqlite", QC_SQL_MODE_DEFAULT, NULL))
{
if (qc_process_init(QC_INIT_SELF))
{
rv = run();
cout << rv << " failures." << endl;
qc_process_end(QC_INIT_SELF);
}
else
{
cerr << "error: Could not initialize query classifier." << endl;
}
}
else
{
cerr << "error: Could not setup query classifier." << endl;
}
mxs_log_finish();
}
}
else
{
cout << USAGE << endl;
}
return rv;
}