unittest/shell_core_t.cc (232 lines of code) (raw):

/* Copyright (c) 2014, 2024, Oracle and/or its affiliates. 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 designed to work 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 either included with the program or referenced in the documentation. 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 */ #include <cstdio> #include <cstdlib> #include <fstream> #include <string> #include "gtest_clean.h" #include "scripting/common.h" #include "scripting/lang_base.h" #include "scripting/types.h" #include "scripting/types_cpp.h" #include "modules/devapi/base_resultset.h" #include "modules/mod_shell_options.h" #include "mysqlshdk/libs/utils/utils_path.h" #include "shellcore/base_session.h" #include "shellcore/shell_core.h" #include "shellcore/shell_resultset_dumper.h" #include "shellcore/shell_sql.h" #include "test_utils.h" #include "utils/utils_file.h" extern "C" const char *g_test_home; namespace shcore { namespace shell_core_tests { class Shell_core_test : public Shell_core_test_wrapper { protected: std::string _file_name; int _ret_val; #ifdef HAVE_JS const std::string to_scripting = "\\js"; #else const std::string to_scripting = "\\py"; #endif virtual void SetUp() { Shell_core_test_wrapper::SetUp(); _interactive_shell->process_line("\\sql"); } void connect() { _interactive_shell->process_line("\\connect --mc " + _mysql_uri); if (!output_handler.std_err.empty()) { std::cerr << "ERROR connecting to " << _mysql_uri << ":" << output_handler.std_err << "\n"; std::cerr << "Test environment is probably wrong. Please check values of " "MYSQL_URI, MYSQL_PORT, MYSQL_PWD environment variables.\n"; FAIL(); } } void process(const std::string &path) { wipe_all(); _file_name = shcore::path::join_path(g_test_home, "data", path); std::ifstream stream(_file_name.c_str()); if (stream.fail()) FAIL(); _ret_val = _interactive_shell->process_stream(stream, _file_name, {}); stream.close(); } }; TEST_F(Shell_core_test, test_process_stream) { _opts->set_interactive(false); connect(); const char *err_table_57 = "Table 'unexisting.whatever' doesn't exist"; const char *err_table_80 = "Unknown database 'unexisting'"; // Successfully processed file _options->force = false; process("sql/sql_ok.sql"); EXPECT_EQ(0, _ret_val); EXPECT_NE(-1, static_cast<int>(output_handler.std_out.find("first_result"))); // Failed without the force option process("sql/sql_err.sql"); EXPECT_EQ(1, _ret_val); EXPECT_NE(-1, static_cast<int>(output_handler.std_out.find("first_result"))); EXPECT_TRUE(std::string::npos != output_handler.std_err.find(err_table_57) || std::string::npos != output_handler.std_err.find(err_table_80)); EXPECT_EQ(-1, static_cast<int>(output_handler.std_out.find("second_result"))); // Failed without the force option _options->force = true; process("sql/sql_err.sql"); EXPECT_EQ(0, _ret_val); // if force is enabled, return code is 0 on error EXPECT_NE(-1, static_cast<int>(output_handler.std_out.find("first_result"))); EXPECT_TRUE(std::string::npos != output_handler.std_err.find(err_table_57) || std::string::npos != output_handler.std_err.find(err_table_80)); EXPECT_NE(-1, static_cast<int>(output_handler.std_out.find("second_result"))); #ifdef HAVE_JS // JS tests: outputs are not validated since in batch mode there's no // autoprinting of resultsets // // Error is also directed to the std::cerr directly _interactive_shell->process_line("\\js"); process("js/js_ok.js"); EXPECT_EQ(0, _ret_val); process("js/js_err.js"); // Error in 5.7 and 8.0 are different EXPECT_TRUE(std::string::npos != output_handler.std_err.find(err_table_57) || std::string::npos != output_handler.std_err.find(err_table_80)); #endif // PY tests: outputs are not validated since in batch mode there's no // autoprinting of resultsets // // Error is also directed to the std::cerr directly _interactive_shell->process_line("\\py"); process("py/py_ok.py"); EXPECT_EQ(0, _ret_val); process("py/py_err.py"); // Error in 5.7 and 8.0 are different EXPECT_TRUE(std::string::npos != output_handler.std_err.find(err_table_57) || std::string::npos != output_handler.std_err.find(err_table_80)); // Closes the connection _interactive_shell->process_line("session.close()"); } #ifdef HAVE_JS TEST_F(Shell_core_test, test_process_js_file_with_params) { // TODO(alfredo) this test and feature are broken.. it was // testing \source on non-interactive no, where the feature works // but in interactive it doesn't. \source must work the same way // in both modes (should be reimplemented) _options->interactive = false; reset_shell(); _interactive_shell->process_line("\\js"); std::string file_name = shcore::path::join_path(g_test_home, "data", "js", "script.js"); _file_name = file_name + " one two"; _interactive_shell->process_line("\\. " + _file_name); MY_EXPECT_STDOUT_CONTAINS("Processing file: " + file_name); MY_EXPECT_STDOUT_CONTAINS("The received arguments were: one and two"); MY_EXPECT_STDERR_CONTAINS(""); wipe_all(); _file_name = "'" + file_name + "' one two"; _interactive_shell->process_line("\\. " + _file_name); MY_EXPECT_STDOUT_CONTAINS("Processing file: " + file_name); MY_EXPECT_STDOUT_CONTAINS("The received arguments were: one and two"); MY_EXPECT_STDERR_CONTAINS(""); wipe_all(); _file_name = "\"" + file_name + "\" one two"; _interactive_shell->process_line("\\. " + _file_name); MY_EXPECT_STDOUT_CONTAINS("Processing file: " + file_name); MY_EXPECT_STDOUT_CONTAINS("The received arguments were: one and two"); MY_EXPECT_STDERR_CONTAINS(""); wipe_all(); } #endif TEST_F(Shell_core_test, test_process_py_file_with_params) { _options->interactive = false; reset_shell(); _interactive_shell->process_line("\\py"); std::string file_name = shcore::path::join_path(g_test_home, "data", "py", "script.py"); _file_name = file_name + " one two"; _interactive_shell->process_line("\\. " + _file_name); MY_EXPECT_STDOUT_CONTAINS("Processing file: " + file_name); MY_EXPECT_STDOUT_CONTAINS("The received arguments were: one and two"); MY_EXPECT_STDERR_CONTAINS(""); wipe_all(); _file_name = "'" + file_name + "' one two"; _interactive_shell->process_line("\\. " + _file_name); MY_EXPECT_STDOUT_CONTAINS("Processing file: " + file_name); MY_EXPECT_STDOUT_CONTAINS("The received arguments were: one and two"); MY_EXPECT_STDERR_CONTAINS(""); wipe_all(); _file_name = "\"" + file_name + "\" one two"; _interactive_shell->process_line("\\. " + _file_name); MY_EXPECT_STDOUT_CONTAINS("Processing file: " + file_name); MY_EXPECT_STDOUT_CONTAINS("The received arguments were: one and two"); MY_EXPECT_STDERR_CONTAINS(""); wipe_all(); } TEST_F(Shell_core_test, python_dictionary_key_handling) { _options->interactive = true; reset_shell(); _interactive_shell->process_line("\\py"); // Tests a string key is correctly retrieved _interactive_shell->process_line("{'type':'sample'}"); MY_EXPECT_STDOUT_CONTAINS("\"type\": \"sample\""); wipe_all(); // Tests an object key retrieves it's string representation _interactive_shell->process_line("{type:'sample'}"); #define CLASS_KEYWORD "class" MY_EXPECT_STDOUT_CONTAINS("\"<" CLASS_KEYWORD " 'type'>\": \"sample\""); #undef CLASS_KEYWORD wipe_all(); wipe_all(); } TEST_F(Shell_core_test, regression_prompt_on_override_session) { connect(); EXPECT_EQ("mysql-sql []> ", _interactive_shell->prompt()); _options->wizards = false; _interactive_shell->shell_context()->set_global( "session", Value(std::static_pointer_cast<Object_bridge>( std::make_shared<mysqlsh::Options>(get_options())))); _options->wizards = true; EXPECT_EQ("mysql-sql []> ", _interactive_shell->prompt()); // The session object has been overriden, even so we need to close th session _interactive_shell->shell_context()->get_dev_session()->close(); } TEST_F(Shell_core_test, process_sql_no_delim_from_stream) { connect(); std::stringstream stream("show databases"); _ret_val = _interactive_shell->process_stream(stream, "STDIN", {}); EXPECT_EQ(0, _ret_val); MY_EXPECT_STDOUT_CONTAINS("| Database"); _interactive_shell->process_line("\\js"); _interactive_shell->process_line("session.close();"); } TEST_F(Shell_core_test, autocache_use_command_node) { execute("\\connect " + _uri); process("sql/simple_schema.sql"); execute(to_scripting); wipe_all(); execute("\\use simple_schema"); execute("print(db.city)"); MY_EXPECT_STDOUT_CONTAINS("<Table:city>"); wipe_all(); execute("print(db.city_list)"); MY_EXPECT_STDOUT_CONTAINS("<Table:city_list>"); wipe_all(); execute("print(db.country_info)"); MY_EXPECT_STDOUT_CONTAINS("<Collection:country_info>"); wipe_all(); execute("session.close();"); } TEST_F(Shell_core_test, autocache_connect_command_node) { connect(); process("sql/simple_schema.sql"); execute(to_scripting); execute("session.close()"); wipe_all(); execute("\\connect " + _uri + "/simple_schema"); execute("print(db.city)"); MY_EXPECT_STDOUT_CONTAINS("<Table:city>"); wipe_all(); execute("print(db.city_list)"); MY_EXPECT_STDOUT_CONTAINS("<Table:city_list>"); wipe_all(); execute("print(db.country_info)"); MY_EXPECT_STDOUT_CONTAINS("<Collection:country_info>"); wipe_all(); execute("session.close();"); } TEST_F(Shell_core_test, autocache_shell_connect_node) { connect(); process("sql/simple_schema.sql"); execute(to_scripting); execute("session.close()"); wipe_all(); execute("shell.connect('" + _uri + "/simple_schema')"); execute("print(db.city)"); MY_EXPECT_STDOUT_CONTAINS("<Table:city>"); wipe_all(); execute("print(db.city_list)"); MY_EXPECT_STDOUT_CONTAINS("<Table:city_list>"); wipe_all(); execute("print(db.country_info)"); MY_EXPECT_STDOUT_CONTAINS("<Collection:country_info>"); wipe_all(); execute("session.close();"); } } // namespace shell_core_tests } // namespace shcore