/* * 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: 2022-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; static KeywordActionMapping plsql_keywords; void init_keywords() { struct Keyword { const char* z_keyword; skip_action_t action; }; static const Keyword MTL_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 }, {"file_exists", SKIP_LINE }, {"horizontal_results", SKIP_LINE }, {"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 }, {"write_file", SKIP_LINE }, }; const size_t N_MTL_KEYWORDS = sizeof(MTL_KEYWORDS) / sizeof(MTL_KEYWORDS[0]); for (size_t i = 0; i < N_MTL_KEYWORDS; ++i) { mtl_keywords[MTL_KEYWORDS[i].z_keyword] = MTL_KEYWORDS[i].action; } static const Keyword PLSQL_KEYWORDS[] = { {"exit", SKIP_LINE }, {"if", SKIP_BLOCK}, {"while", SKIP_BLOCK}, }; const size_t N_PLSQL_KEYWORDS = sizeof(PLSQL_KEYWORDS) / sizeof(PLSQL_KEYWORDS[0]); for (size_t i = 0; i < N_PLSQL_KEYWORDS; ++i) { plsql_keywords[PLSQL_KEYWORDS[i].z_keyword] = PLSQL_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 { KeywordActionMapping::iterator i = mtl_keywords.find(key); if (i != mtl_keywords.end()) { action = i->second; } } if ((action == SKIP_NOTHING) && (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 ";". KeywordActionMapping::iterator i = plsql_keywords.find(key); if (i != plsql_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: ; } } } }