/* 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, 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>

#ifdef OPTIMIZER_TRACE

#include <opt_trace.h>
#include <mysys_err.h>                          // for testing of OOM
#include "mysqld.h"                             // system_charset_info
#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
*/
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<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\\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
