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