/* Copyright (c) 2011, 2013, 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 as published by the Free Software Foundation; version 2 of the License. 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 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 #ifdef OPTIMIZER_TRACE #include #include // for testing of OOM #include "mysqld.h" // system_charset_info #ifdef HAVE_SYS_WAIT_H #include // 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 */ 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() { 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")); } }; bool TraceContentTest::oom; void my_error_handler(uint error, const char *str, myf MyFlags) { const uint EE= static_cast(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(0xc3); all_chars[129]= static_cast(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\\u0015\\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 #endif // OPTIMIZER_TRACE