用于EagleEye3.0 规则集漏报和误报测试的示例项目,项目收集于github和gitee
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1127 lines
36 KiB

/* Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2.0,
as published by the Free Software Foundation.
This program is also distributed with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have included with MySQL.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License, version 2.0, for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
/**
@file
Unit test of the Optimizer trace API (WL#5257)
*/
// First include (the generated) my_config.h, to get correct platform defines.
#include "my_config.h"
#include <gtest/gtest.h>
#include <sys/types.h>
#include "my_inttypes.h"
#include "my_macros.h"
#include "m_string.h" // llstr
#include "mysys_err.h" // for testing of OOM
#include "sql/mysqld.h" // system_charset_info
#include "sql/opt_trace.h"
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h> // for WEXITSTATUS
#endif
namespace opt_trace_unittest {
const ulonglong all_features = Opt_trace_context::default_features;
/**
@note It is a macro, for proper reporting of line numbers in case of
assertion failure. SCOPED_TRACE will report line number at the
macro expansion site.
*/
#define check_json_compliance(str, length) \
{ \
SCOPED_TRACE(""); \
do_check_json_compliance(str, length); \
}
/**
Checks compliance of a trace with JSON syntax rules.
This is a helper which has interest only when developing the test; once you
know that the produced trace is compliant and has expected content, just
set "expected" to it, add a comparison with "expected", and don't use this
function.
@param str pointer to trace
@param length trace's length
*/
static void do_check_json_compliance(const char *str, size_t length) {
return;
/*
Read from stdin, eliminate comments, parse as JSON. If invalid, an
exception is thrown by Python, uncaught, which produces a non-zero error
code.
*/
#ifndef _WIN32
const char python_cmd[] =
"python -c \""
"import json, re, sys;"
"s= sys.stdin.read();"
"s= re.sub('/\\\\*[ A-Za-z_]* \\\\*/', '', s);"
"json.loads(s, 'utf-8')\"";
// Send the trace to this new process' stdin:
FILE *fd = popen(python_cmd, "w");
ASSERT_TRUE(NULL != fd);
ASSERT_NE(0U, length); // empty is not compliant
ASSERT_EQ(1U, fwrite(str, length, 1, fd));
int rc = pclose(fd);
rc = WEXITSTATUS(rc);
EXPECT_EQ(0, rc);
#endif
}
extern "C" void my_error_handler(uint error, const char *str, myf MyFlags);
class TraceContentTest : public ::testing::Test {
public:
Opt_trace_context trace;
static bool oom; ///< whether we got an OOM error from opt trace
protected:
static void SetUpTestCase() {
system_charset_info = &my_charset_utf8_general_ci;
}
virtual void SetUp() {
/* Save original and install our custom error hook. */
m_old_error_handler_hook = error_handler_hook;
error_handler_hook = my_error_handler;
oom = false;
// Setting debug flags triggers enter/exit trace, so redirect to /dev/null
DBUG_SET("o," IF_WIN("NUL", "/dev/null"));
}
virtual void TearDown() { error_handler_hook = m_old_error_handler_hook; }
static void (*m_old_error_handler_hook)(uint, const char *, myf);
};
bool TraceContentTest::oom;
void (*TraceContentTest::m_old_error_handler_hook)(uint, const char *, myf);
void my_error_handler(uint error, const char *, myf) {
const uint EE = static_cast<uint>(EE_OUTOFMEMORY);
EXPECT_EQ(EE, error);
if (error == EE) TraceContentTest::oom = true;
}
TEST_F(TraceContentTest, ConstructAndDestruct) {}
/** Test empty trace */
TEST_F(TraceContentTest, Empty) {
ASSERT_FALSE(
trace.start(true, false, false, false, -1, 1, ULONG_MAX, all_features));
EXPECT_TRUE(trace.is_started());
EXPECT_TRUE(trace.support_I_S());
/*
Add at least an object to it. A really empty trace ("") is not
JSON-compliant, at least Python's JSON module raises an exception.
*/
{ Opt_trace_object oto(&trace); }
/* End trace */
trace.end();
/* And verify trace's content */
Opt_trace_iterator it(&trace);
/*
ASSERT here, because a failing EXPECT_FALSE would continue into
it.get_value() and segfault.
*/
ASSERT_FALSE(it.at_end());
Opt_trace_info info;
it.get_value(&info);
const char expected[] = "{\n}";
EXPECT_STREQ(expected, info.trace_ptr);
EXPECT_EQ(sizeof(expected) - 1, info.trace_length);
check_json_compliance(info.trace_ptr, info.trace_length);
EXPECT_EQ(0U, info.missing_bytes);
EXPECT_FALSE(info.missing_priv);
EXPECT_FALSE(oom);
/* Should be no more traces */
it.next();
ASSERT_TRUE(it.at_end());
}
/** Test normal usage */
TEST_F(TraceContentTest, NormalUsage) {
ASSERT_FALSE(
trace.start(true, false, true, false, -1, 1, ULONG_MAX, all_features));
{
Opt_trace_object oto(&trace);
oto.add_select_number(123456);
{
Opt_trace_array ota(&trace, "one array");
ota.add(200.4);
{
Opt_trace_object oto1(&trace);
oto1.add_alnum("one key", "one value").add("another key", 100U);
}
ota.add_alnum("one string element");
ota.add(true);
ota.add_hex(12318421343459ULL);
}
oto.add("yet another key", -1000LL);
{
Opt_trace_array ota(&trace, "another array");
ota.add(1LL).add(2).add(3LL).add(4LL);
}
}
trace.end();
Opt_trace_iterator it(&trace);
ASSERT_FALSE(it.at_end());
Opt_trace_info info;
it.get_value(&info);
const char expected[] =
"{\n"
" \"select#\": 123456,\n"
" \"one array\": [\n"
" 200.4,\n"
" {\n"
" \"one key\": \"one value\",\n"
" \"another key\": 100\n"
" },\n"
" \"one string element\",\n"
" true,\n"
" 0x0b341b20dce3\n"
" ] /* one array */,\n"
" \"yet another key\": -1000,\n"
" \"another array\": [\n"
" 1,\n"
" 2,\n"
" 3,\n"
" 4\n"
" ] /* another array */\n"
"}";
EXPECT_STREQ(expected, info.trace_ptr);
EXPECT_EQ(sizeof(expected) - 1, info.trace_length);
check_json_compliance(info.trace_ptr, info.trace_length);
EXPECT_EQ(0U, info.missing_bytes);
EXPECT_FALSE(info.missing_priv);
EXPECT_FALSE(oom);
it.next();
ASSERT_TRUE(it.at_end());
}
/** Test reaction to malformed JSON (object with value without key) */
TEST_F(TraceContentTest, BuggyObject) {
ASSERT_FALSE(
trace.start(true, false, true, false, -1, 1, ULONG_MAX, all_features));
{
Opt_trace_object oto(&trace);
{
Opt_trace_array ota(&trace, "one array");
ota.add(200.4);
{
Opt_trace_object oto1(&trace);
oto1.add_alnum("one value"); // no key, which is wrong
oto1.add(326); // same
Opt_trace_object oto2(&trace); // same
}
ota.add_alnum("one string element");
ota.add(true);
}
oto.add("yet another key", -1000LL);
{
Opt_trace_array ota(&trace, "another array");
ota.add(1LL).add(2LL).add(3LL).add(4LL);
}
}
trace.end();
Opt_trace_iterator it(&trace);
ASSERT_FALSE(it.at_end());
Opt_trace_info info;
it.get_value(&info);
const char expected[] =
"{\n"
" \"one array\": [\n"
" 200.4,\n"
" {\n"
" \"unknown_key_1\": \"one value\",\n"
" \"unknown_key_2\": 326,\n"
" \"unknown_key_3\": {\n"
" }\n"
" },\n"
" \"one string element\",\n"
" true\n"
" ] /* one array */,\n"
" \"yet another key\": -1000,\n"
" \"another array\": [\n"
" 1,\n"
" 2,\n"
" 3,\n"
" 4\n"
" ] /* another array */\n"
"}";
EXPECT_STREQ(expected, info.trace_ptr);
EXPECT_EQ(sizeof(expected) - 1, info.trace_length);
check_json_compliance(info.trace_ptr, info.trace_length);
EXPECT_EQ(0U, info.missing_bytes);
EXPECT_FALSE(info.missing_priv);
EXPECT_FALSE(oom);
it.next();
ASSERT_TRUE(it.at_end());
}
/** Test reaction to malformed JSON (array with value with key) */
TEST_F(TraceContentTest, BuggyArray) {
ASSERT_FALSE(
trace.start(true, false, true, false, -1, 1, ULONG_MAX, all_features));
{
Opt_trace_object oto(&trace);
{
Opt_trace_array ota(&trace, "one array");
ota.add("superfluous key", 200.4); // key, which is wrong
ota.add("not necessary", 326); // same
Opt_trace_object oto2(&trace, "not needed"); // same
}
oto.add("yet another key", -1000LL);
{
Opt_trace_array ota(&trace, "another array");
ota.add(1LL).add(2LL).add(3LL).add(4LL);
}
}
trace.end();
Opt_trace_iterator it(&trace);
ASSERT_FALSE(it.at_end());
Opt_trace_info info;
it.get_value(&info);
const char expected[] =
"{\n"
" \"one array\": [\n"
" 200.4,\n"
" 326,\n"
" {\n"
" } /* not needed */\n"
" ] /* one array */,\n"
" \"yet another key\": -1000,\n"
" \"another array\": [\n"
" 1,\n"
" 2,\n"
" 3,\n"
" 4\n"
" ] /* another array */\n"
"}";
EXPECT_STREQ(expected, info.trace_ptr);
EXPECT_EQ(sizeof(expected) - 1, info.trace_length);
check_json_compliance(info.trace_ptr, info.trace_length);
EXPECT_EQ(0U, info.missing_bytes);
EXPECT_FALSE(info.missing_priv);
EXPECT_FALSE(oom);
it.next();
ASSERT_TRUE(it.at_end());
}
/** Test Opt_trace_disable_I_S */
TEST_F(TraceContentTest, DisableISWithObject) {
ASSERT_FALSE(
trace.start(true, false, true, false, -1, 1, ULONG_MAX, all_features));
{
Opt_trace_object oto(&trace);
{
Opt_trace_array ota(&trace, "one array");
ota.add(200.4);
{
Opt_trace_object oto1(&trace);
oto1.add_alnum("one key", "one value").add("another key", 100LL);
Opt_trace_disable_I_S otd(&trace, true);
oto1.add("a third key", false);
Opt_trace_object oto2(&trace, "a fourth key");
oto2.add("key inside", 1LL);
/* don't disable... but above layer is stronger */
Opt_trace_disable_I_S otd2(&trace, false);
oto2.add("another key inside", 5LL);
// disabling should apply to substatements too:
ASSERT_FALSE(trace.start(true, false, true, false, -1, 1, ULONG_MAX,
all_features));
{ Opt_trace_object oto3(&trace); }
trace.end();
}
ota.add_alnum("one string element");
ota.add(true);
}
Opt_trace_disable_I_S otd2(&trace, false); // don't disable
oto.add("yet another key", -1000LL);
{
Opt_trace_array ota(&trace, "another array");
ota.add(1LL).add(2LL).add(3LL).add(4LL);
}
}
trace.end();
Opt_trace_iterator it(&trace);
ASSERT_FALSE(it.at_end());
Opt_trace_info info;
it.get_value(&info);
const char expected[] =
"{\n"
" \"one array\": [\n"
" 200.4,\n"
" {\n"
" \"one key\": \"one value\",\n"
" \"another key\": 100\n"
" },\n"
" \"one string element\",\n"
" true\n"
" ] /* one array */,\n"
" \"yet another key\": -1000,\n"
" \"another array\": [\n"
" 1,\n"
" 2,\n"
" 3,\n"
" 4\n"
" ] /* another array */\n"
"}";
EXPECT_STREQ(expected, info.trace_ptr);
EXPECT_EQ(sizeof(expected) - 1, info.trace_length);
check_json_compliance(info.trace_ptr, info.trace_length);
EXPECT_EQ(0U, info.missing_bytes);
EXPECT_FALSE(info.missing_priv);
EXPECT_FALSE(oom);
it.next();
ASSERT_TRUE(it.at_end());
}
/** Test Opt_trace_context::disable_I_S_for_this_and_children */
TEST_F(TraceContentTest, DisableISWithCall) {
// Test that it disables even before any start()
trace.disable_I_S_for_this_and_children();
ASSERT_FALSE(
trace.start(true, false, true, false, -1, 1, ULONG_MAX, all_features));
{
Opt_trace_object oto(&trace);
{
Opt_trace_array ota(&trace, "one array");
ota.add(200.4);
{
Opt_trace_object oto1(&trace);
oto1.add_alnum("one key", "one value").add("another key", 100LL);
oto1.add("a third key", false);
Opt_trace_object oto2(&trace, "a fourth key");
oto2.add("key inside", 1LL);
// disabling should apply to substatements too:
ASSERT_FALSE(trace.start(true, false, true, false, -1, 1, ULONG_MAX,
all_features));
{ Opt_trace_object oto3(&trace); }
trace.end();
/* don't disable... but above layer is stronger */
Opt_trace_disable_I_S otd2(&trace, false);
oto2.add("another key inside", 5LL);
// disabling should apply to substatements too:
ASSERT_FALSE(trace.start(true, false, true, false, -1, 1, ULONG_MAX,
all_features));
{ Opt_trace_object oto4(&trace); }
trace.end();
}
ota.add_alnum("one string element");
ota.add(true);
}
oto.add("yet another key", -1000LL);
{
Opt_trace_array ota(&trace, "another array");
ota.add(1LL).add(2LL).add(3LL).add(4LL);
}
}
trace.end();
trace.restore_I_S();
Opt_trace_iterator it(&trace);
ASSERT_TRUE(it.at_end());
}
/** Helper for Trace_settings_test.offset */
void make_one_trace(Opt_trace_context *trace, const char *name, long offset,
long limit) {
ASSERT_FALSE(trace->start(true, false, true, false, offset, limit, ULONG_MAX,
all_features));
{
Opt_trace_object oto(trace);
oto.add(name, 0LL);
}
trace->end();
}
/**
Helper for Trace_settings_test.offset
@param trace The trace context.
@param names A NULL-terminated array of "names".
Checks that the list of traces is as expected.
This macro checks that the first trace contains names[0], that the second
trace contains names[1], etc. That the number of traces is the same as
the number of elements in "names".
@note It is a macro, for proper reporting of line numbers in case of
assertion failure. SCOPED_TRACE will report line number at the
macro expansion site.
*/
#define check(trace, names) \
{ \
SCOPED_TRACE(""); \
do_check(&trace, names); \
}
void do_check(Opt_trace_context *trace, const char **names) {
Opt_trace_iterator it(trace);
Opt_trace_info info;
for (const char **name = names; *name != NULL; name++) {
ASSERT_FALSE(it.at_end());
it.get_value(&info);
const size_t name_len = strlen(*name);
EXPECT_EQ(name_len + 11, info.trace_length);
EXPECT_EQ(0, strncmp(info.trace_ptr + 5, *name, name_len));
EXPECT_EQ(0U, info.missing_bytes);
it.next();
}
ASSERT_TRUE(it.at_end());
}
/** Test offset/limit variables */
TEST_F(TraceContentTest, Offset) {
make_one_trace(&trace, "100", -1 /* offset */, 1 /* limit */);
const char *expected_traces0[] = {"100", NULL};
check(trace, expected_traces0);
make_one_trace(&trace, "101", -1, 1);
/* 101 should have overwritten 100 */
const char *expected_traces1[] = {"101", NULL};
check(trace, expected_traces1);
make_one_trace(&trace, "102", -1, 1);
const char *expected_traces2[] = {"102", NULL};
check(trace, expected_traces2);
trace.reset();
const char *expected_traces_empty[] = {NULL};
check(trace, expected_traces_empty);
make_one_trace(&trace, "103", -3, 2);
make_one_trace(&trace, "104", -3, 2);
make_one_trace(&trace, "105", -3, 2);
make_one_trace(&trace, "106", -3, 2);
make_one_trace(&trace, "107", -3, 2);
make_one_trace(&trace, "108", -3, 2);
make_one_trace(&trace, "109", -3, 2);
const char *expected_traces3[] = {"107", "108", NULL};
check(trace, expected_traces3);
trace.reset();
check(trace, expected_traces_empty);
make_one_trace(&trace, "110", 3, 2);
make_one_trace(&trace, "111", 3, 2);
make_one_trace(&trace, "112", 3, 2);
make_one_trace(&trace, "113", 3, 2);
make_one_trace(&trace, "114", 3, 2);
make_one_trace(&trace, "115", 3, 2);
make_one_trace(&trace, "116", 3, 2);
const char *expected_traces10[] = {"113", "114", NULL};
check(trace, expected_traces10);
trace.reset();
check(trace, expected_traces_empty);
make_one_trace(&trace, "117", 0, 1);
make_one_trace(&trace, "118", 0, 1);
make_one_trace(&trace, "119", 0, 1);
const char *expected_traces17[] = {"117", NULL};
check(trace, expected_traces17);
trace.reset();
make_one_trace(&trace, "120", 0, 0);
make_one_trace(&trace, "121", 0, 0);
make_one_trace(&trace, "122", 0, 0);
const char *expected_traces20[] = {NULL};
check(trace, expected_traces20);
EXPECT_FALSE(oom);
}
/** Test truncation by max_mem_size */
TEST_F(TraceContentTest, MaxMemSize) {
ASSERT_FALSE(trace.start(true, false, false, false, -1, 1,
1000 /* max_mem_size */, all_features));
/* make a "long" trace */
{
Opt_trace_object oto(&trace);
Opt_trace_array ota(&trace, "one array");
for (int i = 0; i < 100; i++) {
ota.add_alnum("make it long");
}
}
trace.end();
Opt_trace_iterator it(&trace);
ASSERT_FALSE(it.at_end());
Opt_trace_info info;
it.get_value(&info);
const char expected[] =
"{\n"
" \"one array\": [\n"
" \"make it long\",\n"
" \"make it long\",\n";
/*
Without truncation the trace would take:
2+17+3+1+20*100 = 2023
*/
EXPECT_EQ(996U, info.trace_length);
EXPECT_EQ(1027U, info.missing_bytes); // 996+1027=2023
EXPECT_FALSE(info.missing_priv);
EXPECT_FALSE(oom);
EXPECT_EQ(0, strncmp(expected, info.trace_ptr, sizeof(expected) - 1));
it.next();
ASSERT_TRUE(it.at_end());
}
/** Test how truncation by max_mem_size affects next traces */
TEST_F(TraceContentTest, MaxMemSize2) {
ASSERT_FALSE(trace.start(true, false, false, false, -2, 2,
21 /* max_mem_size */, all_features));
/* make a "long" trace */
{
Opt_trace_object oto(&trace);
oto.add_alnum("some key1", "make it long");
}
trace.end();
/* A second similar trace */
ASSERT_FALSE(trace.start(true, false, false, false, -2, 2, 21, all_features));
{
Opt_trace_object oto(&trace);
oto.add_alnum("some key2", "make it long");
}
trace.end();
Opt_trace_iterator it(&trace);
ASSERT_FALSE(it.at_end());
Opt_trace_info info;
it.get_value(&info);
EXPECT_EQ(17U, info.trace_length);
EXPECT_EQ(16U, info.missing_bytes);
EXPECT_FALSE(info.missing_priv);
EXPECT_FALSE(oom);
it.next();
ASSERT_FALSE(it.at_end());
it.get_value(&info);
/* 2nd trace completely empty as first trace left no room */
EXPECT_EQ(0U, info.trace_length);
EXPECT_EQ(33U, info.missing_bytes);
it.next();
ASSERT_TRUE(it.at_end());
/*
3rd trace; the first one should automatically be purged, thus the 3rd
should have a bit of room.
*/
ASSERT_FALSE(trace.start(true, false, false, false, -2, 2, 21, all_features));
{
Opt_trace_object oto(&trace);
oto.add_alnum("some key3", "make it long");
}
trace.end();
Opt_trace_iterator it2(&trace);
ASSERT_FALSE(it2.at_end());
it2.get_value(&info);
EXPECT_EQ(0U, info.trace_length);
EXPECT_EQ(33U, info.missing_bytes);
EXPECT_FALSE(info.missing_priv);
EXPECT_FALSE(oom);
it2.next();
it2.get_value(&info);
/*
3rd one had room. A bit less than first, because just reading the second
with the iterator has reallocated the second from 0 to 8 bytes...
*/
EXPECT_EQ(14U, info.trace_length);
EXPECT_EQ(19U, info.missing_bytes);
EXPECT_FALSE(info.missing_priv);
it2.next();
ASSERT_TRUE(it2.at_end());
}
void open_object(uint count, Opt_trace_context *trace, bool simulate_oom) {
if (count == 0) return;
count--;
char key[4];
/*
Add 100 to always have a key of length 3, this matters to
TraceContentTest.Indent.
We could use just a fixed string, but it would cause an assertion failure
(due to invalid JSON, which itself is conceivable in case of OOM).
*/
llstr(100 + count, key);
if (simulate_oom) {
if (count == 90) DBUG_SET("+d,opt_trace_oom_in_open_struct");
/*
Now we let 80 objects be created, so that one of them surely hits
re-allocation and OOM failure.
*/
if (count == 10) DBUG_SET("-d,opt_trace_oom_in_open_struct");
}
Opt_trace_object oto(trace, key);
open_object(count, trace, simulate_oom);
}
#ifndef DBUG_OFF
/// Test reaction to out-of-memory condition in trace buffer
TEST_F(TraceContentTest, OOMinBuffer) {
ASSERT_FALSE(
trace.start(true, false, false, false, -1, 1, ULONG_MAX, all_features));
{
Opt_trace_object oto(&trace);
{
Opt_trace_array ota(&trace, "one array");
DBUG_SET("+d,opt_trace_oom_in_buffers");
for (int i = 0; i < 30; i++)
ota.add_alnum("_______________________________________________");
DBUG_SET("-d,opt_trace_oom_in_buffers");
}
}
trace.end();
Opt_trace_iterator it(&trace);
ASSERT_FALSE(it.at_end());
Opt_trace_info info;
it.get_value(&info);
EXPECT_EQ(0U, info.missing_bytes);
EXPECT_FALSE(info.missing_priv);
it.next();
ASSERT_TRUE(it.at_end());
EXPECT_TRUE(oom);
}
/// Test reaction to out-of-memory condition in book-keeping data structures
TEST_F(TraceContentTest, OOMinBookKeeping) {
ASSERT_FALSE(
trace.start(true, false, false, false, -1, 1, ULONG_MAX, all_features));
{
Opt_trace_object oto(&trace);
open_object(100, &trace, true);
}
trace.end();
Opt_trace_iterator it(&trace);
ASSERT_FALSE(it.at_end());
Opt_trace_info info;
it.get_value(&info);
EXPECT_EQ(0U, info.missing_bytes);
EXPECT_FALSE(info.missing_priv);
it.next();
ASSERT_TRUE(it.at_end());
EXPECT_TRUE(oom);
}
/// Test reaction to OOM when purging traces
TEST_F(TraceContentTest, OOMinPurge) {
make_one_trace(&trace, "103", -3, 2);
make_one_trace(&trace, "104", -3, 2);
DBUG_SET("+d,opt_trace_oom_in_purge");
make_one_trace(&trace, "105", -3, 2);
make_one_trace(&trace, "106", -3, 2);
make_one_trace(&trace, "107", -3, 2);
make_one_trace(&trace, "108", -3, 2);
make_one_trace(&trace, "109", -3, 2);
make_one_trace(&trace, "110", -3, 2);
make_one_trace(&trace, "111", -3, 2);
make_one_trace(&trace, "112", -3, 2);
make_one_trace(&trace, "113", -3, 2);
make_one_trace(&trace, "114", -3, 2);
make_one_trace(&trace, "115", -3, 2);
make_one_trace(&trace, "116", -3, 2);
make_one_trace(&trace, "117", -3, 2);
make_one_trace(&trace, "118", -3, 2);
make_one_trace(&trace, "119", -3, 2);
make_one_trace(&trace, "120", -3, 2);
make_one_trace(&trace, "121", -3, 2);
make_one_trace(&trace, "122", -3, 2); // purge first fails here
DBUG_SET("-d,opt_trace_oom_in_purge");
// 122 could not purge 119, so we should see 119 and 120
const char *expected_traces3[] = {"119", "120", NULL};
check(trace, expected_traces3);
EXPECT_TRUE(oom);
// Back to normal:
oom = false;
make_one_trace(&trace, "123", -3, 2); // purge succeeds
const char *expected_traces4[] = {"121", "122", NULL};
check(trace, expected_traces4);
EXPECT_FALSE(oom);
}
#endif // !DBUG_OFF
/** Test filtering by feature */
TEST_F(TraceContentTest, FilteringByFeature) {
ASSERT_FALSE(trace.start(true, false, false, false, -1, 1, ULONG_MAX,
Opt_trace_context::MISC));
{
Opt_trace_object oto(&trace);
{
Opt_trace_array ota(&trace, "one array");
ota.add(200.4);
{
Opt_trace_object oto1(&trace, Opt_trace_context::GREEDY_SEARCH);
oto1.add_alnum("one key", "one value").add("another key", 100LL);
Opt_trace_object oto2(&trace, "a fourth key", Opt_trace_context::MISC);
oto2.add("another key inside", 5LL);
}
ota.add(true);
}
{
Opt_trace_object oto3(&trace, "key for oto3",
Opt_trace_context::GREEDY_SEARCH);
oto3.add("etc", 25);
}
oto.add("yet another key", -1000LL);
{
Opt_trace_array ota(&trace, "another array");
ota.add(1LL).add(2LL).add(3LL).add(4LL);
}
}
trace.end();
Opt_trace_iterator it(&trace);
ASSERT_FALSE(it.at_end());
Opt_trace_info info;
it.get_value(&info);
const char expected[] =
"{\n"
" \"one array\": [\n"
" 200.4,\n"
" \"...\",\n"
" true\n"
" ],\n"
" \"key for oto3\": \"...\",\n"
" \"yet another key\": -1000,\n"
" \"another array\": [\n"
" 1,\n"
" 2,\n"
" 3,\n"
" 4\n"
" ]\n"
"}";
EXPECT_STREQ(expected, info.trace_ptr);
EXPECT_EQ(sizeof(expected) - 1, info.trace_length);
check_json_compliance(info.trace_ptr, info.trace_length);
EXPECT_EQ(0U, info.missing_bytes);
EXPECT_FALSE(info.missing_priv);
EXPECT_FALSE(oom);
it.next();
ASSERT_TRUE(it.at_end());
}
/** Test escaping of characters */
TEST_F(TraceContentTest, Escaping) {
ASSERT_FALSE(
trace.start(true, false, true, false, -1, 1, ULONG_MAX, all_features));
// All ASCII 0-127 chars are valid UTF8 encodings
char all_chars[130];
for (uint c = 0; c < sizeof(all_chars) - 2; c++) all_chars[c] = c;
// Now a character with a two-byte code in utf8: ä
all_chars[128] = static_cast<char>(0xc3);
all_chars[129] = static_cast<char>(0xa4);
// all_chars is used both as query...
trace.set_query(all_chars, sizeof(all_chars), system_charset_info);
{
Opt_trace_object oto(&trace);
// ... and inside the trace:
oto.add_utf8("somekey", all_chars, sizeof(all_chars));
}
trace.end();
Opt_trace_iterator it(&trace);
ASSERT_FALSE(it.at_end());
Opt_trace_info info;
it.get_value(&info);
// we get the trace escaped, JSON-compliant:
const char expected[] =
"{\n"
" \"somekey\": "
"\"\\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\u0008\\t\\n"
"\\u000b\\u000c\\r\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u001"
"5\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f "
"!\\\"#$%&'()*+,-./"
"0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`"
"abcdefghijklmnopqrstuvwxyz{|}~ä\"\n"
"}";
EXPECT_STREQ(expected, info.trace_ptr);
EXPECT_EQ(sizeof(expected) - 1, info.trace_length);
check_json_compliance(info.trace_ptr, info.trace_length);
EXPECT_EQ(0U, info.missing_bytes);
EXPECT_FALSE(info.missing_priv);
EXPECT_FALSE(oom);
EXPECT_EQ(sizeof(all_chars), info.query_length);
// we get the query unescaped, verbatim, not 0-terminated:
EXPECT_EQ(0, memcmp(all_chars, info.query_ptr, sizeof(all_chars)));
EXPECT_EQ(system_charset_info, info.query_charset);
it.next();
ASSERT_TRUE(it.at_end());
}
/** Test how the system handles non-UTF8 characters, a violation of its API */
TEST_F(TraceContentTest, NonUtf8) {
ASSERT_FALSE(
trace.start(true, false, true, false, -1, 1, ULONG_MAX, all_features));
/*
A string which starts with invalid utf8 (the four first bytes are éèÄà in
latin1).
In utf8, the following holds
- E0->EF can only be the start of a 3-byte sequence
- C2->DF 2-byte
- ASCII a single-byte sequence
*/
const char all_chars[] =
"\xe9\xe8\xc4\xe0"
"ABC";
// We declare a query in latin1
trace.set_query(all_chars, sizeof(all_chars), &my_charset_latin1);
{
Opt_trace_object oto(&trace);
/*
We pass the non-utf8-compliant string to add_utf8() (violating the
API). We get it back unchanged. The trace system could try to be robust,
detecting and sanitizing wrong characters (replacing them with '?'); but
it does not bother, as the MySQL Server normally does not violate the
API.
*/
oto.add_utf8("somekey", all_chars, sizeof(all_chars) - 1);
}
trace.end();
Opt_trace_iterator it(&trace);
ASSERT_FALSE(it.at_end());
Opt_trace_info info;
it.get_value(&info);
// This is not UTF8-compliant and thus not JSON-compliant.
const char expected[] =
"{\n"
" \"somekey\": \""
"\xe9\xe8\xc4\xe0"
"ABC\"\n"
"}";
EXPECT_STREQ(expected, info.trace_ptr);
EXPECT_EQ(sizeof(expected) - 1, info.trace_length);
EXPECT_EQ(0U, info.missing_bytes);
EXPECT_FALSE(info.missing_priv);
EXPECT_FALSE(oom);
EXPECT_EQ(sizeof(all_chars), info.query_length);
// we get the query unescaped, verbatim, not 0-terminated:
EXPECT_EQ(0, memcmp(all_chars, info.query_ptr, sizeof(all_chars)));
it.next();
ASSERT_TRUE(it.at_end());
}
/**
Test indentation by many blanks.
By creating a 100-level deep structure, we force an indentation which
enters the while() block in Opt_trace_stmt::next_line().
*/
TEST_F(TraceContentTest, Indent) {
ASSERT_FALSE(
trace.start(true, false, false, false, -1, 1, ULONG_MAX, all_features));
{
Opt_trace_object oto(&trace);
open_object(100, &trace, false);
}
trace.end();
Opt_trace_iterator it(&trace);
ASSERT_FALSE(it.at_end());
Opt_trace_info info;
it.get_value(&info);
/*
Formula for the expected size.
Before the Nth call to open_object(), indentation inside the innermost
empty object is noted I(N); so the relationship between the size before
Nth call and the size after Nth call is:
S(N+1) = S(N)
+ I(N) (indentation before added '"xxx": {\n' )
+ 9 (length of added '"xxx": {\n' )
+ I(N) (indentation before added '}\n' )
+ 2 (length of added '}\n' )
and the indentation is increased by two as we are one level deeper:
I(N+1) = I(N) + 2
With S(1) = 3 (length of '{\n}') and I(1) = 2.
So I(N) = 2 * N and
S(N+1) - S(N) = 11 + 4 * N
So S(N) = 3 + 11 * (N - 1) + 2 * N * (N - 1).
For 100 calls, the final size is S(101) = 21303.
Each call adds 10 non-space characters, so there should be
21303
- 10 * 100 (added non-spaces characters)
- 3 (non-spaces of initial object before first function call)
= 20300 spaces.
*/
EXPECT_EQ(21303U, info.trace_length);
uint spaces = 0;
for (uint i = 0; i < info.trace_length; i++)
if (info.trace_ptr[i] == ' ') spaces++;
EXPECT_EQ(20300U, spaces);
check_json_compliance(info.trace_ptr, info.trace_length);
EXPECT_EQ(0U, info.missing_bytes);
EXPECT_FALSE(info.missing_priv);
EXPECT_FALSE(oom);
it.next();
ASSERT_TRUE(it.at_end());
}
/** Test Opt_trace_context::missing_privilege() */
TEST_F(TraceContentTest, MissingPrivilege) {
ASSERT_FALSE(
trace.start(true, false, true, false, 0, 100, ULONG_MAX, all_features));
{
Opt_trace_object oto(&trace);
{
Opt_trace_array ota(&trace, "one array");
ota.add(200.4);
{
Opt_trace_object oto1(&trace);
oto1.add_alnum("one key", "one value").add("another key", 100LL);
oto1.add("a third key", false);
Opt_trace_object oto2(&trace, "a fourth key");
oto2.add("key inside", 1LL);
ASSERT_FALSE(trace.start(true, false, true, false, 0, 100, ULONG_MAX,
all_features));
{
Opt_trace_object oto3(&trace);
trace.missing_privilege();
ASSERT_FALSE(trace.start(true, false, true, false, 0, 100, ULONG_MAX,
all_features));
{
Opt_trace_object oto4(&trace);
oto4.add_alnum("in4", "key4");
}
trace.end();
}
trace.end(); // this should restore I_S support
// so this should be visible
oto2.add("another key inside", 5LL);
// and this new sub statement too:
ASSERT_FALSE(trace.start(true, false, true, false, 0, 100, ULONG_MAX,
all_features));
{
Opt_trace_object oto5(&trace);
oto5.add("in5", true);
}
trace.end();
}
ota.add_alnum("one string element");
ota.add(true);
}
oto.add("yet another key", -1000LL);
{
Opt_trace_array ota(&trace, "another array");
ota.add(1LL).add(2LL).add(3LL).add(4LL);
}
}
trace.end();
Opt_trace_iterator it(&trace);
ASSERT_FALSE(it.at_end());
Opt_trace_info info;
it.get_value(&info);
const char expected[] =
"{\n"
" \"one array\": [\n"
" 200.4,\n"
" {\n"
" \"one key\": \"one value\",\n"
" \"another key\": 100,\n"
" \"a third key\": false,\n"
" \"a fourth key\": {\n"
" \"key inside\": 1,\n"
" \"another key inside\": 5\n"
" } /* a fourth key */\n"
" },\n"
" \"one string element\",\n"
" true\n"
" ] /* one array */,\n"
" \"yet another key\": -1000,\n"
" \"another array\": [\n"
" 1,\n"
" 2,\n"
" 3,\n"
" 4\n"
" ] /* another array */\n"
"}";
EXPECT_STREQ(expected, info.trace_ptr);
EXPECT_EQ(sizeof(expected) - 1, info.trace_length);
check_json_compliance(info.trace_ptr, info.trace_length);
EXPECT_EQ(0U, info.missing_bytes);
EXPECT_FALSE(info.missing_priv);
EXPECT_FALSE(oom);
it.next();
ASSERT_FALSE(it.at_end());
// Now the substatement with a missing privilege
it.get_value(&info);
const char expected2[] = ""; // because of missing privilege...
EXPECT_STREQ(expected2, info.trace_ptr);
EXPECT_EQ(sizeof(expected2) - 1, info.trace_length);
EXPECT_EQ(0U, info.missing_bytes);
EXPECT_TRUE(info.missing_priv); // ... tested here.
it.next();
ASSERT_FALSE(it.at_end());
// And now the last substatement, visible
it.get_value(&info);
const char expected3[] =
"{\n"
" \"in5\": true\n"
"}";
EXPECT_STREQ(expected3, info.trace_ptr);
EXPECT_EQ(sizeof(expected3) - 1, info.trace_length);
check_json_compliance(info.trace_ptr, info.trace_length);
EXPECT_EQ(0U, info.missing_bytes);
EXPECT_FALSE(info.missing_priv);
it.next();
ASSERT_TRUE(it.at_end());
}
/** Test Opt_trace_context::missing_privilege() on absent trace */
TEST_F(TraceContentTest, MissingPrivilege2) {
/*
Ask for neither I_S not debug output, and no
missing_privilege() support
*/
ASSERT_FALSE(
trace.start(false, false, true, false, 0, 100, ULONG_MAX, all_features));
EXPECT_FALSE(trace.is_started());
trace.end();
/*
Ask for neither I_S not debug output, but ask that
missing_privilege() is supported.
*/
ASSERT_FALSE(
trace.start(false, true, true, false, 0, 100, ULONG_MAX, all_features));
EXPECT_TRUE(trace.is_started());
trace.missing_privilege();
// This above should make the substatement below not be traced:
ASSERT_FALSE(
trace.start(true, false, true, false, 0, 100, ULONG_MAX, all_features));
{
Opt_trace_object oto5(&trace);
oto5.add("in5", true);
}
trace.end();
trace.end();
Opt_trace_iterator it(&trace);
ASSERT_TRUE(it.at_end());
}
/**
Test an optimization: that no Opt_trace_stmt is created in common case
where all statements and substatements ask neither for I_S nor for DBUG,
nor for support of missing_privilege() function.
*/
TEST_F(TraceContentTest, NoOptTraceStmt) {
ASSERT_FALSE(
trace.start(false, false, false, false, -1, 1, ULONG_MAX, all_features));
EXPECT_FALSE(trace.is_started());
// one substatement:
ASSERT_FALSE(
trace.start(false, false, false, false, -1, 1, ULONG_MAX, all_features));
EXPECT_FALSE(trace.is_started());
// another one deeper nested:
ASSERT_FALSE(
trace.start(false, false, false, false, -1, 1, ULONG_MAX, all_features));
EXPECT_FALSE(trace.is_started());
trace.end();
trace.end();
trace.end();
}
} // namespace opt_trace_unittest