/* * Copyright (c) 2016 MariaDB Corporation Ab * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file and at www.mariadb.com/bsl11. * * Change Date: 2020-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2 or later of the General * Public License. */ #include "testreader.hh" #include #include #include using std::istream; using std::string; using std::map; namespace { enum skip_action_t { SKIP_NOTHING, // Skip nothing. SKIP_BLOCK, // Skip until the end of next { ... } SKIP_DELIMITER, // Skip the new delimiter. SKIP_LINE, // Skip current line. SKIP_NEXT_STATEMENT, // Skip statement starting on line following this line. SKIP_STATEMENT, // Skip statment starting on this line. SKIP_TERMINATE, // Cannot handle this, terminate. }; typedef std::map KeywordActionMapping; static KeywordActionMapping mtl_keywords; void init_keywords() { struct Keyword { const char* z_keyword; skip_action_t action; }; static const Keyword KEYWORDS[] = { { "append_file", SKIP_LINE }, { "cat_file", SKIP_LINE }, { "change_user", SKIP_LINE }, { "character_set", SKIP_LINE }, { "chmod", SKIP_LINE }, { "connect", SKIP_LINE }, { "connection", SKIP_LINE }, { "copy_file", SKIP_LINE }, { "dec", SKIP_LINE }, { "delimiter", SKIP_DELIMITER }, { "die", SKIP_LINE }, { "diff_files", SKIP_LINE }, { "dirty_close", SKIP_LINE }, { "disable_abort_on_error", SKIP_LINE }, { "disable_connect_log", SKIP_LINE }, { "disable_info", SKIP_LINE }, { "disable_metadata", SKIP_LINE }, { "disable_parsing", SKIP_LINE }, { "disable_ps_protocol", SKIP_LINE }, { "disable_query_log", SKIP_LINE }, { "disable_reconnect", SKIP_LINE }, { "disable_result_log", SKIP_LINE }, { "disable_rpl_parse", SKIP_LINE }, { "disable_session_track_info", SKIP_LINE }, { "disable_warnings", SKIP_LINE }, { "disconnect", SKIP_LINE }, { "echo", SKIP_LINE }, { "enable_abort_on_error", SKIP_LINE }, { "enable_connect_log", SKIP_LINE }, { "enable_info", SKIP_LINE }, { "enable_metadata", SKIP_LINE }, { "enable_parsing", SKIP_LINE }, { "enable_ps_protocol", SKIP_LINE }, { "enable_query_log", SKIP_LINE }, { "enable_reconnect", SKIP_LINE }, { "enable_result_log", SKIP_LINE }, { "enable_rpl_parse", SKIP_LINE }, { "enable_session_track_info", SKIP_LINE }, { "enable_warnings", SKIP_LINE }, { "end_timer", SKIP_LINE }, { "error", SKIP_NEXT_STATEMENT }, { "eval", SKIP_STATEMENT }, { "exec", SKIP_LINE }, { "exit", SKIP_LINE }, { "file_exists", SKIP_LINE }, { "horizontal_results", SKIP_LINE }, { "if", SKIP_BLOCK }, { "inc", SKIP_LINE }, { "let", SKIP_LINE }, { "let", SKIP_LINE }, { "list_files", SKIP_LINE }, { "list_files_append_file", SKIP_LINE }, { "list_files_write_file", SKIP_LINE }, { "lowercase_result", SKIP_LINE }, { "mkdir", SKIP_LINE }, { "move_file", SKIP_LINE }, { "output", SKIP_LINE }, { "perl", SKIP_TERMINATE }, { "ping", SKIP_LINE }, { "print", SKIP_LINE }, { "query", SKIP_LINE }, { "query_get_value", SKIP_LINE }, { "query_horizontal", SKIP_LINE }, { "query_vertical", SKIP_LINE }, { "real_sleep", SKIP_LINE }, { "reap", SKIP_LINE }, { "remove_file", SKIP_LINE }, { "remove_files_wildcard", SKIP_LINE }, { "replace_column", SKIP_LINE }, { "replace_regex", SKIP_LINE }, { "replace_result", SKIP_LINE }, { "require", SKIP_LINE }, { "reset_connection", SKIP_LINE }, { "result", SKIP_LINE }, { "result_format", SKIP_LINE }, { "rmdir", SKIP_LINE }, { "same_master_pos", SKIP_LINE }, { "send", SKIP_LINE }, { "send_eval", SKIP_LINE }, { "send_quit", SKIP_LINE }, { "send_shutdown", SKIP_LINE }, { "skip", SKIP_LINE }, { "sleep", SKIP_LINE }, { "sorted_result", SKIP_LINE }, { "source", SKIP_LINE }, { "start_timer", SKIP_LINE }, { "sync_slave_with_master", SKIP_LINE }, { "sync_with_master", SKIP_LINE }, { "system", SKIP_LINE }, { "vertical_results", SKIP_LINE }, { "while", SKIP_BLOCK }, { "write_file", SKIP_LINE }, }; const size_t N_KEYWORDS = sizeof(KEYWORDS)/sizeof(KEYWORDS[0]); for (size_t i = 0; i < N_KEYWORDS; ++i) { mtl_keywords[KEYWORDS[i].z_keyword] = KEYWORDS[i].action; } } skip_action_t get_action(const string& keyword, const string& delimiter) { skip_action_t action = SKIP_NOTHING; string key(keyword); std::transform(key.begin(), key.end(), key.begin(), ::tolower); if (key == "delimiter") { // DELIMITER is directly understood by the parser so it needs to // be handled explicitly. action = SKIP_DELIMITER; } else if (delimiter == ";") { // Some mysqltest keywords, such as "while", "exit" and "if" are also // PL/SQL keywords. We assume they can only be used in the former role, // if the delimiter is ";". string key(keyword); std::transform(key.begin(), key.end(), key.begin(), ::tolower); KeywordActionMapping::iterator i = mtl_keywords.find(key); if (i != mtl_keywords.end()) { action = i->second; } } return action; } inline void ltrim(std::string &s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); } inline void rtrim(std::string &s) { s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))).base(), s.end()); } void trim(std::string &s) { ltrim(s); rtrim(s); } } namespace maxscale { TestReader::TestReader(istream& in, size_t line) : m_in(in) , m_line(line) , m_delimiter(";") { init(); } TestReader::result_t TestReader::get_statement(std::string& stmt) { bool error = false; // Whether an error has occurred. bool found = false; // Whether we have found a statement. bool skip = false; // Whether next statement should be skipped. stmt.clear(); string line; while (!error && !found && std::getline(m_in, line)) { trim(line); m_line++; if (!line.empty() && (line.at(0) != '#')) { // Ignore comment lines. if ((line.substr(0, 3) == "-- ") || (line.substr(0, 1) == "#")) { continue; } if (!skip) { if (line.substr(0, 2) == "--") { line = line.substr(2); trim(line); } string::iterator i = std::find_if(line.begin(), line.end(), std::ptr_fun(std::isspace)); string keyword = line.substr(0, i - line.begin()); skip_action_t action = get_action(keyword, m_delimiter); switch (action) { case SKIP_NOTHING: break; case SKIP_BLOCK: skip_block(); continue; case SKIP_DELIMITER: line = line.substr(i - line.begin()); trim(line); if (line.length() > 0) { if (line.length() >= m_delimiter.length()) { if (line.substr(line.length() - m_delimiter.length()) == m_delimiter) { m_delimiter = line.substr(0, line.length() - m_delimiter.length()); } else { m_delimiter = line; } } else { m_delimiter = line; } } continue; case SKIP_LINE: continue; case SKIP_NEXT_STATEMENT: skip = true; continue; case SKIP_STATEMENT: skip = true; break; case SKIP_TERMINATE: MXS_ERROR("Cannot handle line %u: %s", (unsigned)m_line, line.c_str()); error = true; break; } } stmt += line; // Look for a ';'. If we are dealing with a one line test statment // the delimiter will in practice be ';' and if it is a multi-line // test statement then the test-script delimiter will be something // else than ';' and ';' will be the delimiter used in the multi-line // statement. string::size_type i = line.find(";"); if (i != string::npos) { // Is there a "-- " or "#" after the delimiter? if ((line.find("-- ", i) != string::npos) || (line.find("#", i) != string::npos)) { // If so, add a newline. Otherwise the rest of the // statement would be included in the comment. stmt += "\n"; } // This is somewhat fragile as a ";", "#" or "-- " inside a // string will trigger this behaviour... } string c; if (line.length() >= m_delimiter.length()) { c = line.substr(line.length() - m_delimiter.length()); } if (c == m_delimiter) { if (c != ";") { // If the delimiter was something else but ';' we need to // remove that before giving the line to the classifiers. stmt.erase(stmt.length() - m_delimiter.length()); } if (!skip) { found = true; } else { skip = false; stmt.clear(); } } else if (!skip) { stmt += " "; } } else if (line.substr(0, 7) == "--error") { // Next statement is supposed to fail, no need to check. skip = true; } } result_t result; if (error) { result = RESULT_ERROR; } else if (found) { result = RESULT_STMT; } else { result = RESULT_EOF; } return result; } // static void TestReader::init() { static bool inited = false; if (!inited) { inited = true; init_keywords(); } } void TestReader::skip_block() { int c; // Find first '{' while (m_in && ((c = m_in.get()) != '{')) { if (c == '\n') { ++m_line; } } int n = 1; while ((n > 0) && m_in) { c = m_in.get(); switch (c) { case '{': ++n; break; case '}': --n; break; case '\n': ++m_line; break; default: ; } } } }