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

#include "tut_stdafx.h"

#include "base/string_utilities.h"
#include "grtpp.h"

using namespace base;

BEGIN_TEST_DATA_CLASS(string_utilities_test)

  protected:
    std::string long_random_string; // Content doesn't matter. There must be no crash using it.

TEST_DATA_CONSTRUCTOR(string_utilities_test)
{
  for (int i = 0; i < 1000; i++)
  {
    long_random_string += ' ' + rand() * 64 / 32768;
    if (rand() > 16767)
      long_random_string += "\xE3\x8A\xA8"; // Valid Unicode character.
    if (i == 500)
      long_random_string += "\xE3\x8A\xA8"; // Ensure it is there at least once.
  }
}

END_TEST_DATA_CLASS;

TEST_MODULE(string_utilities_test, "string utilities");


/* Testing base::quote_identifier */
TEST_FUNCTION(5)
{
  std::string test = "first_test";

  std::string test_result = base::quote_identifier(test, '`');
  ensure_equals("Unexpected result quoting string", test_result, "`first_test`");

  test = "second_test";
  test_result = base::quote_identifier(test, '\"');
  ensure_equals("Unexpected result quoting string", test_result, "\"second_test\"");

  test = "";
  test_result = base::quote_identifier(test, '\"');
  ensure_equals("Unexpected result quoting string", test_result, "\"\"");

  test = "Unicode \xE3\x8A\xA8"; // UTF-8 encoded: CIRCLED IDEOGRAPH RIGHT
  test_result = base::quote_identifier(test, '%');
  ensure_equals("Unexpected result quoting string", test_result, "%Unicode \xE3\x8A\xA8%");
}

/* Testing base::quote_identifier_if_needed */
TEST_FUNCTION(10)
{
  std::string test = "first_test";

  std::string test_result = base::quote_identifier_if_needed(test, '`');
  ensure_equals("Unexpected result quoting string", test_result, "first_test");

  test = "second_test";
  test_result = base::quote_identifier_if_needed(test, '\"');
  ensure_equals("Unexpected result quoting string", test_result, "second_test");

  test = "Unicode\xE3\x8A\xA8"; // UTF-8 encoded: CIRCLED IDEOGRAPH RIGHT
  test_result = base::quote_identifier_if_needed(test, '%');
  ensure_equals("Unexpected result quoting string", test_result, "Unicode\xE3\x8A\xA8");

  test = "test.case";
  test_result = base::quote_identifier_if_needed(test, '$');
  ensure_equals("Unexpected result quoting string", test_result, "$test.case$");

  // Note: currently there is no support to check if the given string contains the quote char already.
  test = "test$case";
  test_result = base::quote_identifier_if_needed(test, '$');
  ensure_equals("Unexpected result quoting string", test_result, "test$case");

  test = ".test$case";
  test_result = base::quote_identifier_if_needed(test, '$');
  ensure_equals("Unexpected result quoting string", test_result, "$.test$case$");

  test = "test-case";
  test_result = base::quote_identifier_if_needed(test, '`');
  ensure_equals("Unexpected result quoting string", test_result, "`test-case`");

  // Identifiers consisting only of digits cannot be distinguished from normal numbers
  // so they must be quoted.
  test = "12345";
  test_result = base::quote_identifier_if_needed(test, '`');
  ensure_equals("Unexpected result quoting string", test_result, "`12345`");
}

/**
 * Testing base::unquote_identifier
 */
TEST_FUNCTION(15)
{
  std::string test = "\"first_test\"";
  std::string test_result = base::unquote_identifier(test);
  ensure_equals("Unexpected result unquoting string", test_result, "first_test");

  test = "`second_test`";
  test_result = base::unquote_identifier(test);
  ensure_equals("Unexpected result unquoting string", test_result, "second_test");
}

static bool compare_string_lists(const std::vector<std::string> &slist,
                                const char *check[])
{
  size_t i;
  for (i = 0; i < slist.size(); i++)
  {
    if (check[i] == NULL)
    {
      g_message("result has more items than expected\n");
      return false;
    }
    if (slist[i] != check[i])
    {
      g_message("token comparison mismatch");
      for (size_t j= 0; j < slist.size(); j++)
         g_message("%s", slist[j].c_str());
      return false;
    }
  }
  if (check[i])
  {
    g_message("result has fewer items than expeced\n");
    return false;
  }
  return true;
}


TEST_FUNCTION(20)
{
  const char* empty[] = {"", NULL};
  const char* empty2[] = {"", "", NULL};
  const char* empty3[] = {"", "", "", NULL};
  const char* null_empty[] = {"NULL", "", NULL};
  const char* a[] = {"a", NULL};
  const char* a_empty1[] = {"a", "", NULL};
  const char *a_empty2[] = {"a", "", "", NULL};
  const char *ab_empty1[] = {"a", "b", "", NULL};
  const char *ab_empty2[] = {"a", "", "b", NULL};
  const char *ab_empty3[] = {"", "a", "b", NULL};
  const char* null_null[] = {"NULL", "NULL", NULL};
  const char* emptys_null[] = {"''", "NULL", NULL};
  const char* ab_null[] = {"'a,b'", "NULL", NULL};
  const char* ab_xxx[] = {"'a,b'", "\"x\\xx\"", "'fo''bar'", NULL};

  ensure("split tokens empty1", compare_string_lists(base::split_token_list("", ','), empty));
  ensure("split tokens empty2", compare_string_lists(base::split_token_list(",", ','), empty2));
  ensure("split tokens empty2a", compare_string_lists(base::split_token_list(" ,", ','), empty2));
  ensure("split tokens empty2b", compare_string_lists(base::split_token_list(", ", ','), empty2));
  ensure("split tokens empty3", compare_string_lists(base::split_token_list(",,", ','), empty3));
  ensure("split tokens empty4", compare_string_lists(base::split_token_list("NULL,", ','), null_empty));
  ensure("split tokens a", compare_string_lists(base::split_token_list("a", ','), a));
  ensure("split tokens a_empty1", compare_string_lists(base::split_token_list("a,", ','), a_empty1));
  ensure("split tokens a_empty2", compare_string_lists(base::split_token_list("a,,", ','), a_empty2));
  ensure("split tokens ab_empty1", compare_string_lists(base::split_token_list("a,b,", ','), ab_empty1));
  ensure("split tokens ab_empty2", compare_string_lists(base::split_token_list("a,,b", ','), ab_empty2));
  ensure("split tokens ab_empty3", compare_string_lists(base::split_token_list(",a,b", ','), ab_empty3));
  ensure("split tokens null,", compare_string_lists(base::split_token_list("NULL,", ','), null_empty));
  ensure("split tokens null,null", compare_string_lists(base::split_token_list("NULL,NULL", ','), null_null));
  ensure("split tokens '',null", compare_string_lists(base::split_token_list("'',NULL", ','), emptys_null));
  ensure("split tokens 'a,b',null", compare_string_lists(base::split_token_list("'a,b',NULL", ','), ab_null));
  ensure("split tokens 'a,b' , \"x\\xx\",'fo''bar'", 
    compare_string_lists(base::split_token_list("'a,b' , \"x\\xx\",'fo''bar'   ", ','), ab_xxx));
}

// Testing split_by_set.
TEST_FUNCTION(25)
{
  const char *input[] = {NULL};
  ensure("Split by set 1", compare_string_lists(base::split_by_set("", ""), input));
  ensure("Split by set 2", compare_string_lists(base::split_by_set("", " "), input));
  ensure("Split by set 3", compare_string_lists(base::split_by_set("", long_random_string), input));

  const char *input2[] = {long_random_string.c_str(), NULL};
  ensure("Split by set 4", compare_string_lists(base::split_by_set(long_random_string, ""), input2));
  ensure("Split by set 5", base::split_by_set(long_random_string, "\xA8").size() > 1); // Works only because our implementation is not utf-8 aware.

  const char *input3[] = {"Lorem", "ipsum", "dolor", "sit", "amet.", NULL};
  ensure("Split by set 6", compare_string_lists(base::split_by_set("Lorem ipsum dolor sit amet.", " "), input3));
  const char *input4[] = {"Lorem", "ipsum", "dolor sit amet.", NULL};
  ensure("Split by set 7", compare_string_lists(base::split_by_set("Lorem ipsum dolor sit amet.", " ", 2), input4));

  const char *input5[] = {"\"Lorem\"", "\"ipsum\"", "\"dolor\"", "\"sit\"", "\"amet\"", NULL};
  ensure("Split by set 8", compare_string_lists(base::split_by_set("\"Lorem\"\t\"ipsum\"\t\"dolor\"\t\"sit\"\t\"amet\"", "\t"), input5));

  const char *input6[] = {"\"Lorem\"", "\"ipsum\"", "\"dolor\"", "\"sit\"", "\"amet\"", NULL};
  ensure("Split by set 9", compare_string_lists(base::split_by_set("\"Lorem\"\t\"ipsum\"\n\"dolor\"\t\"sit\"\n\"amet\"", " \t\n"), input6));

  const char *input7[] = {"\"Lorem\"", "\"ip", "sum\"", "\"dolor\"", "\"sit\"", "\"amet\"", NULL};
  ensure("Split by set 10", compare_string_lists(base::split_by_set("\"Lorem\"\t\"ip sum\"\n\"dolor\"\t\"sit\"\n\"amet\"", " \t\n"), input7));

  const char *input8[] = {"", "Lorem", "", " ", "ipsum", "", " ", "dolor", "", " ", "sit", "", " ", "amet", "", NULL};
  ensure("Split by set 11", compare_string_lists(base::split_by_set("\"Lorem\", \"ipsum\", \"dolor\", \"sit\", \"amet\"", ",\""), input8));
  ensure("Split by set 12", compare_string_lists(base::split_by_set("\"Lorem\", \"ipsum\", \"dolor\", \"sit\", \"amet\"", ",\"", 100), input8));
  const char *input9[] = {"", "Lorem", "", " ", "ipsum", "", " ", "dolor\", \"sit\", \"amet\"", NULL};
  ensure("Split by set 13", compare_string_lists(base::split_by_set("\"Lorem\", \"ipsum\", \"dolor\", \"sit\", \"amet\"", ",\"", 7), input9));
}

// Testing trim_right, trim_left, trim.
TEST_FUNCTION(30)
{
  ensure_equals("Trim left 1", base::trim_left(""), "");
  ensure_equals("Trim left 2", base::trim_left("                                       "), "");
  ensure_equals("Trim left 3", base::trim_left("           \n\t\t\t\t      a"), "a");
  ensure_equals("Trim left 4", base::trim_left("a           \n\t\t\t\t      "), "a           \n\t\t\t\t      ");
  ensure_equals("Trim left 5", base::trim_left("", long_random_string), "");
  
  ensure_equals("Trim left 6", base::trim_left("\xE3\x8A\xA8\xE3\x8A\xA8\x7F", long_random_string), "\x7F");
  ensure_equals("Trim left 7", base::trim_left("\t\t\tLorem ipsum dolor sit amet\n\n\n"), "Lorem ipsum dolor sit amet\n\n\n");
  ensure_equals("Trim left 8", base::trim_left("\t\t\tLorem ipsum dolor sit amet\n\n\n", "L"), "\t\t\tLorem ipsum dolor sit amet\n\n\n");
  ensure_equals("Trim left 9", base::trim_left("\t\t\tLorem ipsum dolor sit amet\n\n\n", "\t\t\tL"), "orem ipsum dolor sit amet\n\n\n");

  ensure_equals("Trim right 1", base::trim_right(""), "");
  ensure_equals("Trim right 2", base::trim_right("                                       "), "");
  ensure_equals("Trim right 3", base::trim_right("           \n\t\t\t\t      a"), "           \n\t\t\t\t      a");
  ensure_equals("Trim right 4", base::trim_right("a           \n\t\t\t\t      "), "a");
  ensure_equals("Trim right 5", base::trim_right("", long_random_string), "");

  ensure_equals("Trim right 6", base::trim_right("\x7F\xE3\x8A\xA8\xE3\x8A\xA8", long_random_string), "\x7F");
  ensure_equals("Trim right 7", base::trim_right("\t\t\tLorem ipsum dolor sit amet\n\n\n"), "\t\t\tLorem ipsum dolor sit amet");
  ensure_equals("Trim right 8", base::trim_right("\t\t\tLorem ipsum dolor sit amet\n\n\n", "L"), "\t\t\tLorem ipsum dolor sit amet\n\n\n");
  ensure_equals("Trim right 9", base::trim_right("\t\t\tLorem ipsum dolor sit amet\n\n\n", "\n\n\nt"), "\t\t\tLorem ipsum dolor sit ame");

  ensure_equals("Trim 1", base::trim(""), "");
  ensure_equals("Trim 2", base::trim("                                       "), "");
  ensure_equals("Trim 3", base::trim("           \n\t\t\t\t      a"), "a");
  ensure_equals("Trim 4", base::trim("a           \n\t\t\t\t      "), "a");
  ensure_equals("Trim 5", base::trim("", long_random_string), "");

  ensure_equals("Trim 6", base::trim("\xE3\x8A\xA8\xE3\x8A\xA8\x7F\xE3\x8A\xA8\xE3\x8A\xA8", long_random_string), "\x7F");
  ensure_equals("Trim 7", base::trim("\t\t\tLorem ipsum dolor sit amet\n\n\n"), "Lorem ipsum dolor sit amet");
  ensure_equals("Trim 8", base::trim("\t\t\tLorem ipsum dolor sit amet\n\n\n", "L"), "\t\t\tLorem ipsum dolor sit amet\n\n\n");
  ensure_equals("Trim 9", base::trim("\t\t\tLorem ipsum dolor sit amet\n\n\n", "\n\n\t\t\ttL"), "orem ipsum dolor sit ame");

}

/**
 * Testing base::unescape_sql_string, which also includes escape sequence handling.
 */
TEST_FUNCTION(35)
{
  std::string test_result = base::unescape_sql_string("", '`');
  ensure_equals("Unquoting 35.1", test_result, "");

  test_result = base::unescape_sql_string("lorem ipsum dolor sit amet", '"');
  ensure_equals("Unquoting 35.2", test_result, "lorem ipsum dolor sit amet");

  test_result = base::unescape_sql_string("lorem ipsum dolor`` sit amet", '"');
  ensure_equals("Unquoting 35.3", test_result, "lorem ipsum dolor`` sit amet");

  test_result = base::unescape_sql_string("lorem ipsum \"\"dolor sit amet", '"');
  ensure_equals("Unquoting 35.4", test_result, "lorem ipsum \"dolor sit amet");

  test_result = base::unescape_sql_string("lorem \"\"\"\"ipsum \"\"dolor\"\" sit amet", '"');
  ensure_equals("Unquoting 35.5", test_result, "lorem \"\"ipsum \"dolor\" sit amet");

  test_result = base::unescape_sql_string("lorem \\\"\\\"ipsum\"\" \\\\dolor sit amet", '"');
  ensure_equals("Unquoting 35.6", test_result, "lorem \"\"ipsum\" \\dolor sit amet");

  test_result = base::unescape_sql_string("lorem \\\"\\\"ipsum\"\" \\\\dolor sit amet", '\'');
  ensure_equals("Unquoting 35.7", test_result, "lorem \"\"ipsum\"\" \\dolor sit amet");

  // Embedded 0 is more difficult to test due to limitations when comparing strings. So we do this
  // in a separate test.
  test_result = base::unescape_sql_string("lorem\\n ip\\t\\rsum dolor\\b sit \\Zamet", '"');
  ensure_equals("Unquoting 35.8", test_result, "lorem\n ip\t\rsum dolor\b sit \032amet");

  test_result = base::unescape_sql_string("lorem ipsum \\zd\\a\\üolor sit amet", '"');
  ensure_equals("Unquoting 35.9", test_result, "lorem ipsum zdaüolor sit amet");

  test_result = base::unescape_sql_string("\\0\\n\\t\\r\\b\\Z", '"');
  ensure("Unquoting 35.10", 
    test_result[0] == 0 && test_result[1] == 10 && test_result[2] == 9 && test_result[3] == 13 &&
    test_result[4] == '\b' && test_result[5] == 26 
  );

  std::string long_string;
  long_string.resize(2000);
  std::fill(long_string.begin(), long_string.end(), '`');
  test_result = base::unescape_sql_string(long_string, '`');
  ensure_equals("Unquoting 35.11", test_result, long_string.substr(0, 1000));
}

END_TESTS
