unittest/mysqlsh_plugins_t.cc (1,335 lines of code) (raw):
/*
* Copyright (c) 2018, 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 <thread>
#include "unittest/gtest_clean.h"
#include "unittest/test_utils.h"
#include "unittest/test_utils/command_line_test.h"
#include "unittest/test_utils/mocks/gmock_clean.h"
#include "mysqlshdk/include/shellcore/scoped_contexts.h"
#include "mysqlshdk/libs/utils/utils_file.h"
#include "mysqlshdk/libs/utils/utils_general.h"
#include "mysqlshdk/libs/utils/utils_path.h"
extern bool g_test_parallel_execution;
namespace tests {
using shcore::path::join_path;
class Mysqlsh_extension_test : public Command_line_test {
protected:
void SetUp() override {
Command_line_test::SetUp();
cleanup();
shcore::setenv("MYSQLSH_PROMPT_THEME", "invalid");
shcore::setenv("MYSQLSH_TERM_COLOR_MODE", "nocolor");
shcore::create_directory(get_plugin_folder(), true);
}
void TearDown() override {
Command_line_test::TearDown();
cleanup();
}
std::string get_plugin_folder() const {
return join_path(shcore::get_user_config_path(), get_plugin_folder_name());
}
void write_plugin(const std::string &name, const std::string &contents) {
shcore::create_file(join_path(get_plugin_folder(), name), contents);
}
void delete_plugin(const std::string &name) {
shcore::delete_file(join_path(get_plugin_folder(), name));
}
void run(const std::vector<std::string> &extra = {}) {
shcore::create_file(k_file, shcore::str_join(m_test_input, "\n"));
std::vector<const char *> args = {_mysqlsh, _uri.c_str(), "--interactive"};
for (const auto &e : extra) {
args.emplace_back(e.c_str());
}
args.emplace_back(nullptr);
const auto user_config = std::string{"MYSQLSH_USER_CONFIG_HOME="} +
shcore::get_user_config_path();
EXPECT_EQ(0, execute(args, nullptr, k_file, {user_config}))
<< "unexpected exit code";
}
void run_cli_plugin(const std::vector<std::string> &extra = {}) {
std::vector<const char *> args = {_mysqlsh};
for (const auto &e : extra) {
args.emplace_back(e.c_str());
}
args.emplace_back(nullptr);
const auto user_config = std::string{"MYSQLSH_USER_CONFIG_HOME="} +
shcore::get_user_config_path();
execute(args, nullptr, nullptr, {user_config});
}
std::string expected_output() const {
return shcore::str_join(m_expected_output, "\n");
}
const std::vector<std::string> &get_expected_output() const {
return m_expected_output;
}
void add_test(const std::string &input, const std::string &output = "") {
m_test_input.emplace_back(input);
if (!output.empty()) {
m_expected_output.emplace_back(output);
}
}
void add_js_test(const std::string &input, const std::string &output = "") {
#ifdef HAVE_JS
add_test(input, output);
#else
(void)input;
(void)output;
#endif // HAVE_JS
}
void add_py_test(const std::string &input, const std::string &output = "") {
#ifdef HAVE_PYTHON
add_test(input, output);
#else
(void)input;
(void)output;
#endif // HAVE_PYTHON
}
void add_expected_log(const std::string &expected) {
m_expected_log_output.emplace_back(expected);
}
void add_unexpected_log(const std::string &expected) {
m_unexpected_log_output.emplace_back(expected);
}
void add_expected_js_log(const std::string &expected) {
#ifdef HAVE_JS
add_expected_log(expected);
#else
(void)expected;
#endif // HAVE_JS
}
void add_expected_py_log(const std::string &expected) {
#ifdef HAVE_PYTHON
add_expected_log(expected);
#else
(void)expected;
#endif // HAVE_PYTHON
}
void add_unexpected_js_log(const std::string &unexpected) {
#ifdef HAVE_JS
add_unexpected_log(unexpected);
#else
(void)unexpected;
#endif // HAVE_JS
}
void add_unexpected_py_log(const std::string &unexpected) {
#ifdef HAVE_PYTHON
add_unexpected_log(unexpected);
#else
(void)unexpected;
#endif // HAVE_PYTHON
}
void validate_log() const {
const auto log = read_log_file();
SCOPED_TRACE("Validate log file contests");
for (const auto &entry : m_expected_log_output) {
EXPECT_THAT(log, ::testing::HasSubstr(entry));
}
for (const auto &entry : m_unexpected_log_output) {
EXPECT_THAT(log, Not(::testing::HasSubstr(entry)));
}
}
static void wipe_log_file() {
const auto log = shcore::current_logger()->logfile_name();
{
std::ifstream f(log);
if (!f.good()) {
throw std::runtime_error("Failed to wipe log file '" + log +
"': " + strerror(errno));
}
f.close();
}
{
// truncate file
std::ofstream out(log, std::ios::out | std::ios::trunc);
out.close();
}
}
static std::string read_log_file() {
return shcore::get_text_file(shcore::current_logger()->logfile_name(),
false);
}
virtual std::string get_plugin_folder_name() const = 0;
private:
void cleanup() {
shcore::unsetenv("MYSQLSH_PROMPT_THEME");
shcore::unsetenv("MYSQLSH_TERM_COLOR_MODE");
wipe_out();
const auto folder = get_plugin_folder();
if (shcore::is_folder(folder)) {
shcore::remove_directory(folder, true);
}
if (shcore::is_file(k_file)) {
shcore::delete_file(k_file);
}
}
std::vector<std::string> m_test_input;
std::vector<std::string> m_expected_output;
std::vector<std::string> m_expected_log_output;
std::vector<std::string> m_unexpected_log_output;
static const char k_file[];
};
const char Mysqlsh_extension_test::k_file[] = "plugin_test";
class Mysqlsh_reports_test : public Mysqlsh_extension_test {
protected:
std::string get_plugin_folder_name() const override { return "init.d"; }
};
TEST_F(Mysqlsh_reports_test, WL11263_TSF8_1) {
// WL11263_TSF8_1 - Validate that the registered reports are loaded (are
// available) when Shell starts.
// create first JS plugin file
write_plugin("first.js", R"(function report(s) {
println('first JS report');
return {'report' : []};
}
shell.registerReport('first_js', 'print', report);
)");
// create second JS plugin file - upper-case extension
write_plugin("second.JS", R"(function report(s) {
println('second JS report');
return {'report' : []};
}
shell.registerReport('second_js', 'print', report);
)");
// create first PY plugin file
write_plugin("third.py", R"(def report(s):
print('third PY report');
return {'report' : []};
shell.register_report('third_py', 'print', report);
)");
// create second PY plugin file - upper-case extension
write_plugin("fourth.PY", R"(def report(s):
print('fourth PY report');
return {'report' : []};
shell.register_report('fourth_py', 'print', report);
)");
// check if first JS report is available
add_js_test("\\show first_js", "first JS report");
// check if second JS report is available
add_js_test("\\show second_js", "second JS report");
// check if first PY report is available
add_py_test("\\show third_py", "third PY report");
// check if second PY report is available
add_py_test("\\show fourth_py", "fourth PY report");
// run the test
run();
// check the output
MY_EXPECT_CMD_OUTPUT_CONTAINS(expected_output());
wipe_out();
}
TEST_F(Mysqlsh_reports_test, WL11263_TSF8_2) {
// WL11263_TSF8_2 - Try to use registered plugin files that doesn't
// anymore in the Shell config path, call the report and analyze the
// behaviour.
// create JS plugin file
write_plugin("plugin.js", R"(function js_report(s) {
println('JS report');
return {'report' : []};
}
shell.registerReport('js_report', 'print', js_report);
)");
// create PY plugin file
write_plugin("plugin.py", R"(def py_report(s):
print('PY report');
return {'report' : []};
shell.register_report('py_report', 'print', py_report);
)");
add_js_test("\\js", "Switching to JavaScript mode...");
add_js_test("os.sleep(2)");
add_py_test("\\py", "Switching to Python mode...");
add_py_test("import time");
add_py_test("time.sleep(2)");
// check if JS report is available
add_js_test("\\show js_report", "JS report");
// check if PY report is available
add_py_test("\\show py_report", "PY report");
auto thd = mysqlsh::spawn_scoped_thread([this] {
shcore::sleep_ms(1000);
delete_plugin("plugin.js");
delete_plugin("plugin.py");
});
// run the test - start in SQL mode so we can switch to another one
run({"--sql"});
// check the output
MY_EXPECT_CMD_OUTPUT_CONTAINS(expected_output());
wipe_out();
thd.join();
}
TEST_F(Mysqlsh_reports_test, WL11263_TSF8_3) {
// WL11263_TSF8_3 - Register plugin files that contains code in a language
// different than the scripting languages supported, call the report and
// analyze the behaviour.
static constexpr auto cpp_code = R"(int main(int argc, char* argv[]) {
return 0;
}
)";
// create JS file with C++ code
write_plugin("cpp_as.js", cpp_code);
// create PY file with C++ code
write_plugin("cpp_as.py", cpp_code);
// check if only build-in reports are available
add_test("\\show", "Available reports: query, thread, threads.");
// log file should contain information about erroneous plugin files
add_expected_js_log("Error loading JavaScript file");
add_expected_js_log("cpp_as.js");
add_expected_py_log("Error loading Python file");
add_expected_py_log("cpp_as.py");
// run the test
run();
// check log contents
validate_log();
// check the output
MY_EXPECT_CMD_OUTPUT_CONTAINS(
"WARNING: Found errors loading startup files, for more details look at "
"the log at:");
MY_EXPECT_CMD_OUTPUT_CONTAINS(expected_output());
}
TEST_F(Mysqlsh_reports_test, WL11263_TSF8_4) {
// WL11263_TSF8_4 - Validate that the plugin files doesn't influence the
// global scripting context. Try to create global functions and variables,
// and to set a new value to global objects (like session or dba), the context
// must not change.
// create JS plugin file
write_plugin("plugin.js", R"(function js_report(s) {
println('JS report');
return {'report' : []};
}
shell.registerReport('js_report', 'print', js_report);
dba = 'DBA set from JS';
global_js_variable = 'JS variable';
)");
// create PY plugin file
write_plugin("plugin.py", R"(def py_report(s):
print('PY report');
return {'report' : []};
shell.register_report('py_report', 'print', py_report);
dba = 'DBA set from PY'
global_py_variable = 'PY variable'
)");
// check if JS report is available
add_js_test("\\show js_report", "JS report");
// check if PY report is available
add_py_test("\\show py_report", "PY report");
add_js_test("\\js", "Switching to JavaScript mode...");
add_js_test("js_report(null, null);",
"js_report is not defined (ReferenceError)");
add_js_test("global_js_variable",
"global_js_variable is not defined (ReferenceError)");
add_js_test("dba", "<Dba>");
add_py_test("\\py", "Switching to Python mode...");
add_py_test("js_report(Null, Null);", R"(Traceback (most recent call last):
File "<string>", line 1, in <module>
NameError: name 'js_report' is not defined)");
add_py_test("global_py_variable", R"(Traceback (most recent call last):
File "<string>", line 1, in <module>
NameError: name 'global_py_variable' is not defined)");
add_py_test("dba", "<Dba>");
// run the test - start in SQL mode so we can switch to another one
run({"--sql"});
// check the output
MY_EXPECT_CMD_OUTPUT_CONTAINS(expected_output());
wipe_out();
}
TEST_F(Mysqlsh_reports_test, WL11263_TSF8_5) {
// WL11263_TSF8_5 - Register a plugin file setting an undefined method, call
// the report and analyze the behaviour.
// create JS plugin file which tries to register undefined method
write_plugin("plugin.js", R"(
shell.registerReport('undefined_js_report', 'print', undefined_js_report);
)");
// create PY plugin file file which tries to register undefined method
write_plugin("plugin.py", R"(
shell.register_report('undefined_py_report', 'print', undefined_py_report);
)");
// check if JS report is not available
add_js_test("\\show undefined_js_report",
"Unknown report: undefined_js_report");
// check if PY report is not available
add_py_test("\\show undefined_py_report",
"Unknown report: undefined_py_report");
// log file should contain information about erroneous plugin files
add_expected_js_log("Error loading JavaScript file");
add_expected_js_log("plugin.js");
add_expected_js_log("undefined_js_report is not defined");
add_expected_py_log("Error loading Python file");
add_expected_py_log("plugin.py");
add_expected_py_log("name 'undefined_py_report' is not defined");
// run the test
run();
// check the output
MY_EXPECT_CMD_OUTPUT_CONTAINS(
"WARNING: Found errors loading startup files, for more details look at "
"the log at:");
MY_EXPECT_CMD_OUTPUT_CONTAINS(expected_output());
// check log contents
validate_log();
}
TEST_F(Mysqlsh_reports_test, relative_require) {
// write a simple report
write_plugin("report.js", R"(
println('Hello from report!');
const mod = require('./dir/one.js');
mod.fun();
)");
// create modules in a path relative to plugin
const auto folder = join_path(get_plugin_folder(), "dir");
shcore::create_directory(folder);
shcore::create_file(join_path(folder, "one.js"), R"(
println('Hello from module one.js!');
const two = require('./two');
exports.fun = function() {
println('Hello from fun(), module one.js!');
two.fun();
}
)");
shcore::create_file(join_path(folder, "two.js"), R"(
println('Hello from module two.js!');
exports.fun = function() {
println('Hello from fun(), module two.js!');
}
)");
// run the test
run();
// validate output
MY_EXPECT_CMD_OUTPUT_CONTAINS(R"(
No default schema selected; type \use <schema> to set one.
Hello from report!
Hello from module one.js!
Hello from module two.js!
Hello from fun(), module one.js!
Hello from fun(), module two.js!
NOTE: MYSQLSH_PROMPT_THEME prompt theme file 'invalid' does not exist.
Bye!
)");
}
class Mysqlsh_plugin_test : public Mysqlsh_extension_test {
public:
std::string get_plugin_folder() const {
return join_path(shcore::get_library_folder(), get_plugin_folder_name());
}
std::string get_user_plugin_folder() const {
return join_path(shcore::get_user_config_path(), get_plugin_folder_name());
}
void write_plugin(const std::string &name, const std::string &contents,
const std::string &extension) {
shcore::create_directory(join_path(get_plugin_folder(), name));
shcore::create_file(
join_path(get_plugin_folder(), name, "init" + extension), contents);
}
void write_user_plugin(const std::string &name, const std::string &contents,
const std::string &extension) {
shcore::create_directory(join_path(get_user_plugin_folder(), name));
shcore::create_file(
join_path(get_user_plugin_folder(), name, "init" + extension),
contents);
}
void delete_plugin(const std::string &name) {
auto path = join_path(get_plugin_folder(), name);
if (shcore::is_folder(path)) {
shcore::remove_directory(path);
} else {
FAIL() << "Error deleting unexisting plugin: " << path.c_str()
<< std::endl;
}
}
void delete_user_plugin(const std::string &name) {
auto path = join_path(get_user_plugin_folder(), name);
if (shcore::is_folder(path)) {
shcore::remove_directory(path);
} else {
FAIL() << "Error deleting unexisting plugin: " << path.c_str()
<< std::endl;
}
}
protected:
std::string get_plugin_folder_name() const override { return "plugins"; }
};
TEST_F(Mysqlsh_plugin_test, WL13051_OK_shell_plugins) {
if (g_test_parallel_execution) {
SKIP_TEST("Skipping tests for parallel execution.");
}
// create first-js plugin, which defines a custom report
write_plugin("first-js", R"(function report(s) {
println('first JS report');
return {'report' : []};
}
shell.registerReport('first_js', 'print', report);
)",
".js");
// create third-py plugin, which defines a custom report
write_plugin("third-py", R"(def report(s):
print('third PY report');
return {'report' : []};
shell.register_report('third_py', 'print', report);
)",
".py");
// check if first_js report is available
add_js_test("\\show first_js", "first JS report");
add_py_test("\\py", "Switching to Python mode...");
add_py_test("\\show third_py", "third PY report");
add_expected_js_log(
"The 'first_js' report of type 'print' has been registered.");
add_expected_js_log(
"The 'third_py' report of type 'print' has been registered.");
run({"--log-level=debug"});
// check the output
MY_EXPECT_CMD_OUTPUT_CONTAINS(expected_output());
validate_log();
wipe_out();
delete_plugin("first-js");
delete_plugin("third-py");
}
TEST_F(Mysqlsh_plugin_test, WL13051_OK_user_plugins) {
// create second-js plugin - which defines a new global object
write_user_plugin("second-js", R"(function sample() {
println('first object defined in JS');
}
var obj = shell.createExtensionObject();
shell.addExtensionObjectMember(obj, "testFunction", sample);
shell.registerGlobal('jsObject', obj);
)",
".js");
// create fourth-py plugin - which defines a new global object
write_user_plugin("fourth-py", R"(def describe():
print('first object defined in PY')
obj = shell.create_extension_object()
shell.add_extension_object_member(obj, "selfDescribe", describe);
shell.register_global('pyObject', obj);
)",
".py");
// This plugin is correctly defined except that the folder starts with . so it
// will be ignored by the loader
write_user_plugin(".git", R"(def describe():
print('first object defined in PY')
obj = shell.create_extension_object()
shell.add_extension_object_member(obj, "selfDescribe", describe);
shell.register_global('pyObject', obj);
)",
".py");
add_expected_js_log(
join_path(get_user_plugin_folder(), "second-js", "init.js"));
add_expected_py_log(
join_path(get_user_plugin_folder(), "fourth-py", "init.py"));
add_unexpected_py_log(join_path(get_user_plugin_folder(), ".git", "init.py"));
// check if jsObject.testFunction was properly registered in JS
add_js_test("jsObject.testFunction()", "first object defined in JS");
// check if jsObject.test_function was properly registered in PY
add_py_test("\\py", "Switching to Python mode...");
add_py_test("jsObject.test_function()", "first object defined in JS");
// check if pyObject.self_describe was properly registered in PY
add_py_test("pyObject.self_describe()", "first object defined in PY");
// check if pyObject.selfDescribe was properly registered in JS
add_js_test("\\js", "Switching to JavaScript mode...");
add_js_test("pyObject.selfDescribe()", "first object defined in PY");
// run the test
run({"--log-level=debug"});
// check the output
MY_EXPECT_CMD_OUTPUT_CONTAINS(expected_output());
wipe_out();
validate_log();
delete_user_plugin("second-js");
delete_user_plugin("fourth-py");
delete_user_plugin(".git");
}
TEST_F(Mysqlsh_plugin_test, WL13051_multiple_init_files) {
// create first JS plugin file
write_user_plugin("error-one", R"(function report(s) {
println('first JS report');
return {'report' : []};
}
shell.registerReport('first_js', 'print', report);
)",
".js");
write_user_plugin("error-one", R"(function report(s) {
println('first JS report');
return {'report' : []};
}
shell.registerReport('first_js', 'print', report);
)",
".py");
// check if first JS report is available
add_js_test("\\show first_js", "Unknown report: first_js");
add_expected_js_log(
"Warning: Found multiple plugin initialization files for plugin "
"'error-one'");
// run the test
run();
// check the output
MY_EXPECT_CMD_OUTPUT_CONTAINS(expected_output());
MY_EXPECT_CMD_OUTPUT_CONTAINS(
"WARNING: Found multiple plugin initialization files for plugin "
"'error-one'");
wipe_out();
validate_log();
delete_user_plugin("error-one");
}
TEST_F(Mysqlsh_plugin_test, WL13051_no_init_files) {
// Create folder in the plugins directory
shcore::create_directory(
join_path(get_user_plugin_folder(), "invalid-plugin"));
shcore::create_directory(join_path(get_user_plugin_folder(), ".git"));
add_expected_py_log(
"Missing initialization file for plugin 'invalid-plugin'");
add_unexpected_py_log("Missing initialization file for plugin '.git'");
// run the test
run({"--log-level=debug"});
// check the output
MY_EXPECT_CMD_OUTPUT_CONTAINS(expected_output());
MY_EXPECT_CMD_OUTPUT_NOT_CONTAINS(
"WARNING: Missing initialization file for plugin "
"'invalid-plugin'");
validate_log();
delete_user_plugin("invalid-plugin");
delete_user_plugin(".git");
}
TEST_F(Mysqlsh_plugin_test, WL13051_errors_in_js_plugin) {
// create first JS plugin file
write_user_plugin("error-two", R"(function report(s) {
println('first JS report'; // <-- The error
return {'report' : []};
}
shell.registerReport('first_js', 'print', report);
)",
".js");
add_expected_js_log("Error loading JavaScript file");
// run the test
run();
// check the output
MY_EXPECT_CMD_OUTPUT_CONTAINS(
"WARNING: Found errors loading plugins, for more details look at "
"the log at:");
wipe_out();
validate_log();
delete_user_plugin("error-two");
}
TEST_F(Mysqlsh_plugin_test, WL13051_errors_in_py_plugin) {
// create first JS plugin file
write_user_plugin("error-two", R"(def report(s):
print ("first PY report" // <-- The error
return {"report" : []};
}
shell.register_report('first_py', 'print', report);
)",
".py");
add_expected_py_log("Error loading Python file");
// run the test
run();
// check the output
MY_EXPECT_CMD_OUTPUT_CONTAINS(
"WARNING: Found errors loading plugins, for more details look at "
"the log at:");
wipe_out();
validate_log();
delete_user_plugin("error-two");
}
// This test emulates a plugin that has the function definition
// in subfolders, while only the function registration resides
// in the init.py file
TEST_F(Mysqlsh_plugin_test, py_plugin_with_imports) {
// Creates complex_py plugin initialization file
write_user_plugin("complex_py", R"(
print("Plugin File Path: {0}".format(__file__))
# loads a function definition from src package
from complex_py.src import definition
# loads a sibling script
from complex_py import sibling
# Executes the plugin registration
obj = shell.create_extension_object()
shell.add_extension_object_member(obj, "test_package", definition.my_function);
shell.add_extension_object_member(obj, "test_sibling", sibling.my_function);
shell.register_global('pyObject', obj);
)",
".py");
auto user_plugins = get_user_plugin_folder();
auto main_package = join_path(user_plugins, "complex_py");
auto src_package = join_path(main_package, "src");
// Creates __init__.py to make the main plugin a package
shcore::create_file(join_path(main_package, "__init__.py"), "");
// Creates a sibling script with a function definition
shcore::create_file(join_path(main_package, "sibling.py"),
R"(def my_function():
print("Function at complex_py/sibling.py"))");
// Creates the src sub-package with a function definition
shcore::create_directory(src_package);
shcore::create_file(join_path(src_package, "__init__.py"), "");
shcore::create_file(join_path(src_package, "definition.py"),
R"(def my_function():
print("Function at complex_py/src/definition.py"))");
add_py_test("pyObject.test_package()",
"Function at complex_py/src/definition.py");
add_py_test("pyObject.test_sibling()", "Function at complex_py/sibling.py");
add_expected_py_log(
"The 'test_package' function has been registered into an unregistered "
"extension object.");
add_expected_py_log(
"The 'test_sibling' function has been registered into an unregistered "
"extension object.");
add_expected_py_log(
"The 'pyObject' extension object has been registered as a global "
"object.");
// run the test
run({"--log-level=debug"});
// check the output
MY_EXPECT_CMD_OUTPUT_NOT_CONTAINS("WARNING: Found errors loading plugins");
std::string expected("Plugin File Path: ");
expected += join_path(get_user_plugin_folder(), "complex_py", "init.py");
MY_EXPECT_CMD_OUTPUT_CONTAINS(expected.c_str());
MY_EXPECT_CMD_OUTPUT_CONTAINS(expected_output().c_str());
validate_log();
wipe_out();
delete_user_plugin("complex_py");
}
// This test emulates a folder in plugins that contains subfolders
// with plugins
TEST_F(Mysqlsh_plugin_test, py_multi_plugins) {
// Creates multi_plugins plugin initialization file
auto user_plugins = get_user_plugin_folder();
auto main_package = join_path(user_plugins, "multi_plugins");
// Creates __init__.py to make the main plugin a package
shcore::create_directory(main_package);
shcore::create_file(join_path(main_package, "__init__.py"), "");
shcore::create_file(join_path(main_package, "plugin_common.py"),
R"(import mysqlsh
def global_function(caller):
print("Global function called from {0}".format(caller)))");
auto create_plugin = [main_package](const std::string &name,
const std::string &greeting) {
auto plugin = join_path(main_package, name);
shcore::create_directory(plugin);
std::string code =
R"(from multi_plugins import plugin_common
# The implementation of the function to be added to demo
def hello_function():
print("Hello World From {0}".format("%s"))
def global_function():
plugin_common.global_function("%s")
# Ensures the demo objects is created if not exists already
if 'demo' in globals():
global_obj = demo
else:
global_obj = shell.create_extension_object()
shell.register_global("demo", global_obj)
# Registers the function
shell.add_extension_object_member(global_obj, "hello_%s", hello_function)
shell.add_extension_object_member(global_obj, "common_%s", global_function)
)";
shcore::create_file(
join_path(plugin, "init.py"),
shcore::str_format(code.c_str(), greeting.c_str(), greeting.c_str(),
name.c_str(), name.c_str()));
};
// Creates the several plugins
create_plugin("mx", "Mexico");
create_plugin("pl", "Poland");
create_plugin("pt", "Portugal");
create_plugin("us", "United States");
create_plugin("at", "Austria");
add_py_test("demo.hello_at()", "Hello World From Austria");
add_py_test("demo.hello_mx()", "Hello World From Mexico");
add_py_test("demo.hello_pl()", "Hello World From Poland");
add_py_test("demo.hello_pt()", "Hello World From Portugal");
add_py_test("demo.hello_us()", "Hello World From United States");
add_py_test("demo.common_at()", "Global function called from Austria");
add_py_test("demo.common_mx()", "Global function called from Mexico");
add_py_test("demo.common_pl()", "Global function called from Poland");
add_py_test("demo.common_pt()", "Global function called from Portugal");
add_py_test("demo.common_us()", "Global function called from United States");
add_expected_py_log(
"The 'demo' extension object has been registered as a global object.");
add_expected_py_log(
"The 'hello_mx' function has been registered into the 'demo' extension "
"object.");
add_expected_py_log(
"The 'hello_pl' function has been registered into the 'demo' extension "
"object.");
add_expected_py_log(
"The 'hello_pt' function has been registered into the 'demo' extension "
"object.");
add_expected_py_log(
"The 'hello_us' function has been registered into the 'demo' extension "
"object.");
add_expected_py_log(
"The 'hello_at' function has been registered into the 'demo' extension "
"object.");
add_expected_py_log(
"The 'common_mx' function has been registered into the 'demo' extension "
"object.");
add_expected_py_log(
"The 'common_pl' function has been registered into the 'demo' extension "
"object.");
add_expected_py_log(
"The 'common_pt' function has been registered into the 'demo' extension "
"object.");
add_expected_py_log(
"The 'common_us' function has been registered into the 'demo' extension "
"object.");
add_expected_py_log(
"The 'common_at' function has been registered into the 'demo' extension "
"object.");
// run the test
run({"--log-level=debug"});
// check the output
MY_EXPECT_CMD_OUTPUT_CONTAINS(expected_output().c_str());
MY_EXPECT_CMD_OUTPUT_NOT_CONTAINS("WARNING: Found errors loading plugins");
validate_log();
wipe_out();
delete_user_plugin("multi_plugins");
}
#ifdef PYTHON_DEPS
// This test emulates a plugins that uses the oci
TEST_F(Mysqlsh_plugin_test, oci_and_paramiko_plugin) {
// Creates oci-paramiko plugin initialization file
write_user_plugin("oci-paramiko", R"(import oci
import paramiko
print("OCI Version: {0}".format(oci.__version__))
print("Paramiko Version: {0}".format(paramiko.__version__))
)",
".py");
// run the test
run();
// check the output
MY_EXPECT_CMD_OUTPUT_NOT_CONTAINS("WARNING: Found errors loading plugins");
// NOTE: it is not important to check the OCI version, but just make sure that
// there were no failures
MY_EXPECT_CMD_OUTPUT_NOT_CONTAINS("No module named 'oci'");
MY_EXPECT_CMD_OUTPUT_NOT_CONTAINS("No module named 'paramiko'");
MY_EXPECT_CMD_OUTPUT_CONTAINS("OCI Version: ");
MY_EXPECT_CMD_OUTPUT_CONTAINS("Paramiko Version: ");
wipe_out();
delete_user_plugin("oci-paramiko");
}
#endif
TEST_F(Mysqlsh_plugin_test, error_reporting) {
write_user_plugin("error-reporting",
R"(obj = shell.create_extension_object()
def throw_error(code, msg):
import mysqlsh
raise mysqlsh.Error(code, msg)
def connect(url):
import mysqlsh
mysqlsh.mysql.get_session(url)
shell.add_extension_object_member(obj, "throwError", throw_error, {
"brief":"Test error throw",
"parameters": [
{
"name": "code",
"type": "integer",
},
{
"name": "msg",
"type": "string",
}
]});
shell.add_extension_object_member(obj, "connect", connect, {
"brief":"connect",
"parameters": [
{
"name": "url",
"type": "string",
}
]});
shell.register_global('errtest', obj)
)",
".py");
add_py_test("\\py");
add_py_test("errtest.throw_error(0, 'py error got thrown')");
add_py_test("errtest.throw_error(99999, 'py another error got thrown')");
add_py_test("errtest.connect('rooot:r@127.0.0.1:" + _mysql_port + "')");
add_js_test("\\js");
add_js_test("errtest.throwError(0, 'js error got thrown')");
add_js_test("errtest.throwError(99998, 'js another error got thrown')");
add_js_test("errtest.connect('raaat:r@127.0.0.1:" + _mysql_port + "')");
run();
MY_EXPECT_CMD_OUTPUT_NOT_CONTAINS("WARNING: Found errors loading plugins");
MY_EXPECT_CMD_OUTPUT_CONTAINS(
"RuntimeError: errtest.throw_error: py error got thrown");
MY_EXPECT_CMD_OUTPUT_CONTAINS(
"mysqlsh.Error: Shell Error (99999): errtest.throw_error: py another "
"error got thrown");
MY_EXPECT_CMD_OUTPUT_CONTAINS(
"mysqlsh.DBError: MySQL Error (1045): errtest.connect: "
"mysql.get_session: Access denied for user 'rooot'@'localhost' (using "
"password: YES)");
MY_EXPECT_CMD_OUTPUT_CONTAINS("js error got thrown");
MY_EXPECT_CMD_OUTPUT_CONTAINS("js another error got thrown");
MY_EXPECT_CMD_OUTPUT_CONTAINS("");
wipe_out();
delete_user_plugin("error-reporting");
}
TEST_F(Mysqlsh_plugin_test, relative_require) {
// write a simple plugin
write_user_plugin("plugin", R"(
println('Hello from plugin!');
const mod = require('./dir/one.js');
mod.fun();
)",
".js");
// create modules in a path relative to plugin
const auto folder = join_path(get_user_plugin_folder(), "plugin", "dir");
shcore::create_directory(folder);
shcore::create_file(join_path(folder, "one.js"), R"(
println('Hello from module one.js!');
const two = require('./two');
exports.fun = function() {
println('Hello from fun(), module one.js!');
two.fun();
}
)");
shcore::create_file(join_path(folder, "two.js"), R"(
println('Hello from module two.js!');
exports.fun = function() {
println('Hello from fun(), module two.js!');
}
)");
// run the test
run();
// validate output
MY_EXPECT_CMD_OUTPUT_CONTAINS(R"(
No default schema selected; type \use <schema> to set one.
Hello from plugin!
Hello from module one.js!
Hello from module two.js!
Hello from fun(), module one.js!
Hello from fun(), module two.js!
NOTE: MYSQLSH_PROMPT_THEME prompt theme file 'invalid' does not exist.
Bye!
)");
// cleanup
delete_user_plugin("plugin");
}
TEST_F(Mysqlsh_plugin_test, py_plugin_use_with_named_args) {
// create fourth-py plugin - which defines a new global object
write_user_plugin("named-args-py",
R"(def describe(one, two, three, four, five):
print(one, two, three, four, five)
obj = shell.create_extension_object()
shell.add_extension_object_member(obj, "selfDescribe", describe,{
"brief": "Creates a function to test KW arg passing.",
"parameters": [
{
"name": "one",
"brief": "The first mandatory parameter.",
"type": "integer",
},
{
"name": "two",
"brief": "The second mandatory parameter.",
"type": "string",
},
{
"name": "three",
"brief": "The first optional parameter.",
"type": "integer",
"required": False,
"default": 3
},
{
"name": "four",
"brief": "The second optional parameter.",
"type": "string",
"required": False,
"default": "fourth"
},
{
"name": "five",
"brief": "The second optional parameter.",
"type": "integer",
"required": False,
"default": 5
},
]
});
shell.register_global('pyObject', obj);
)",
".py");
// check if jsObject.test_function was properly registered in PY
add_py_test("\\py");
// TEST: Missing required parameters
add_py_test("pyObject.self_describe()",
"pyObject.self_describe: Invalid number of arguments, expected 2 "
"to 5 but got 0");
add_py_test("pyObject.self_describe(1)",
"pyObject.self_describe: Invalid number of arguments, expected 2 "
"to 5 but got 1");
add_py_test("pyObject.self_describe(1, five=10)",
"pyObject.self_describe: Missing value for argument 'two'");
// TEST: Providing invalid keyword parameters
add_py_test(
"pyObject.self_describe(1,'some',five=10, six=6)",
"ValueError: pyObject.self_describe: Invalid keyword argument: 'six'");
add_py_test("pyObject.self_describe(1,'some',five=10, six=6, seven=7)",
"ValueError: pyObject.self_describe: Invalid keyword "
"arguments: 'seven', 'six'");
// TEST: Passing keyword parameters in addition to positional parameters
add_py_test("pyObject.self_describe(1,'some',one=2)",
"ValueError: pyObject.self_describe: Got multiple values for "
"argument 'one'");
// TEST: Passing positional parameter using keyword parameter
add_py_test("pyObject.self_describe(1, two=10)",
"TypeError: pyObject.self_describe: Argument 'two' is "
"expected to be a string");
// TEST: Passing keyword parameter skipping optional parameters
add_py_test("pyObject.self_describe(1,'some',four='other')",
"1 some 3 other 5");
add_py_test("pyObject.self_describe(1,'some',five=10)", "1 some 3 fourth 10");
// TEST: Skipping optional parameters
add_py_test("pyObject.self_describe(1,'some')", "1 some 3 fourth 5");
// TEST: Passing optional parameters as positional
add_py_test("pyObject.self_describe(1,'some',7,'world')", "1 some 7 world 5");
// TEST: Passing all the parameters as keyword parameters
add_py_test(
"pyObject.self_describe(one=5, two='five', three=8, four='eight', "
"five=15)",
"5 five 8 eight 15");
// run the test
run({"--log-level=debug"});
// check the output
for (const auto &output : get_expected_output()) {
MY_EXPECT_CMD_OUTPUT_CONTAINS(output);
}
delete_user_plugin("named-args-py");
}
TEST_F(Mysqlsh_plugin_test, py_kwargs_from_python_correct_registration) {
write_user_plugin("kwargs-py-py-correct",
R"(obj = shell.create_extension_object()
def print_args(name, lname, **kwargs):
data = []
data.append(name)
data.append(lname)
for key, val in kwargs.items():
data.append("{} = {}".format(key, val))
print(", ".join(data))
shell.add_extension_object_member(obj, "printArgs", print_args, {
"brief":"Testing usage of kwargs",
"parameters": [
{
"name": "name",
"type": "string",
},
{
"name": "lname",
"type": "string",
},
{
"name": "kwargs",
"type": "dictionary",
"required": False,
}
]});
shell.register_global('kwtest', obj)
)",
".py");
add_py_test("\\py");
// TEST: Not passing kwargs
add_py_test("kwtest.print_args('John', 'Doe')", "John, Doe");
// TEST: Using named parameters without kwargs
add_py_test("kwtest.print_args(name='Jane', lname='Doe')", "Jane, Doe");
// TEST: Passing kwargs in a dictionary
add_py_test("kwtest.print_args('Jack', 'Doe', {'option':'value'})",
"Jack, Doe, option = value");
// TEST: Passing kwargs
add_py_test("kwtest.print_args(name='Jack', lname='Sparrow', uses='sword')",
"Jack, Sparrow, uses = sword");
// TEST: Passing named argument for parameter already defined
add_py_test("kwtest.print_args('Black', 'Pearl', name='dissappears')",
"ValueError: kwtest.print_args: Got multiple values for "
"argument 'name'");
// TEST: Passing named argument for parameter already defined but in a
// dictionary (See BUG#31500843 For More Details)
add_py_test("kwtest.print_args('Black', 'Pearl', {'name': 'allowed'})",
"ScriptingError: kwtest.print_args: User-defined "
"function threw an exception: print_args() got multiple values "
"for argument 'name'");
// run the test
run({"--log-level=debug"});
// check the output
for (const auto &output : get_expected_output()) {
MY_EXPECT_CMD_OUTPUT_CONTAINS(output);
}
delete_user_plugin("kwargs-py-py-correct");
}
TEST_F(Mysqlsh_plugin_test, py_kwargs_from_javascript_correct_registration) {
write_user_plugin("kwargs-py-js-correct",
R"(obj = shell.create_extension_object()
def print_args(name, lname, **kwargs):
data = []
data.append(name)
data.append(lname)
for key, val in kwargs.items():
data.append("{} = {}".format(key, val))
print(", ".join(data))
shell.add_extension_object_member(obj, "printArgs", print_args, {
"brief":"Testing usage of kwargs",
"parameters": [
{
"name": "name",
"type": "string",
},
{
"name": "lname",
"type": "string",
},
{
"name": "kwargs",
"type": "dictionary",
"required": False,
}
]});
shell.register_global('kwtest', obj)
)",
".py");
add_py_test("\\js");
// TEST: Not passing kwargs
add_py_test("kwtest.printArgs('John', 'Doe')", "John, Doe");
// TEST: Passing kwargs in a dictionary
add_py_test("kwtest.printArgs('Jack', 'Doe', {'option':'value'})",
"Jack, Doe, option = value");
// TEST: Passing named argument for parameter already defined but in a
// dictionary (See BUG#31500843 For More Details)
add_py_test("kwtest.printArgs('Black', 'Pearl', {name: 'allowed'})",
"kwtest.printArgs: User-defined function threw an exception: "
"print_args() got multiple values for argument 'name'");
// run the test
run({"--log-level=debug"});
// check the output
for (const auto &output : get_expected_output()) {
MY_EXPECT_CMD_OUTPUT_CONTAINS(output);
}
delete_user_plugin("kwargs-py-js-correct");
}
TEST_F(Mysqlsh_plugin_test, py_kwargs_from_python_mismatched_registration) {
write_user_plugin("kwargs-py-py-mistmatched",
R"(obj = shell.create_extension_object()
def print_args(name, lname, **kwargs):
data = []
data.append(name)
data.append(lname)
for key, val in kwargs.items():
data.append("{} = {}".format(key, val))
print(", ".join(data))
shell.add_extension_object_member(obj, "printArgs", print_args, {
"brief":"Testing usage of kwargs",
"parameters": [
{
"name": "first",
"type": "string",
},
{
"name": "second",
"type": "string",
},
{
"name": "kwargs",
"type": "dictionary",
"required": False,
}
]});
shell.register_global('kwtest', obj)
)",
".py");
add_py_test("\\py");
// TEST: Not passing kwargs
add_py_test("kwtest.print_args('John', 'Doe')", "John, Doe");
// TEST: Using named parameters without kwargs
add_py_test("kwtest.print_args(first='Jane', second='Doe')", "Jane, Doe");
// TEST: Passing kwargs in a dictionary
add_py_test("kwtest.print_args('Jack', 'Doe', {'option':'value'})",
"Jack, Doe, option = value");
// TEST: Passing kwargs
add_py_test("kwtest.print_args(first='Jack', second='Sparrow', uses='sword')",
"Jack, Sparrow, uses = sword");
// TEST: Passing named argument for parameter already defined
add_py_test("kwtest.print_args('Black', 'Pearl', first='dissappears')",
"ValueError: kwtest.print_args: Got multiple values for "
"argument 'first'");
// TEST: Passing named argument for parameter already defined but in a
// dictionary (See BUG#31500843 For More Details)
add_py_test("kwtest.print_args('Black', 'Pearl', {'first': 'allowed'})",
"Black, Pearl, first = allowed");
// run the test
run({"--log-level=debug"});
// check the output
for (const auto &output : get_expected_output()) {
MY_EXPECT_CMD_OUTPUT_CONTAINS(output);
}
delete_user_plugin("kwargs-py-py-mistmatched");
}
TEST_F(Mysqlsh_plugin_test,
py_kwargs_from_javascript_mistmatched_registration) {
write_user_plugin("kwargs-py-js-mismatched",
R"(obj = shell.create_extension_object()
def print_args(name, lname, **kwargs):
data = []
data.append(name)
data.append(lname)
for key, val in kwargs.items():
data.append("{} = {}".format(key, val))
print(", ".join(data))
shell.add_extension_object_member(obj, "printArgs", print_args, {
"brief":"Testing usage of kwargs",
"parameters": [
{
"name": "first",
"type": "string",
},
{
"name": "second",
"type": "string",
},
{
"name": "kwargs",
"type": "dictionary",
"required": False,
}
]});
shell.register_global('kwtest', obj)
)",
".py");
add_js_test("\\js");
// TEST: Not passing kwargs
add_js_test("kwtest.printArgs('John', 'Doe')", "John, Doe");
// TEST: Passing kwargs in a dictionary
add_js_test("kwtest.printArgs('Jack', 'Doe', {'option':'value'})",
"Jack, Doe, option = value");
// TEST: Passing named argument for parameter already defined but in a
// dictionary (See BUG#31500843 For More Details)
add_js_test("kwtest.printArgs('Black', 'Pearl', {'first': 'allowed'})",
"Black, Pearl, first = allowed");
// run the test
run({"--log-level=debug"});
// check the output
for (const auto &output : get_expected_output()) {
MY_EXPECT_CMD_OUTPUT_CONTAINS(output);
}
delete_user_plugin("kwargs-py-js-mismatched");
}
TEST_F(Mysqlsh_plugin_test, py_not_kwargs_from_python_correct_registration) {
write_user_plugin("kwargs-py-py-correct",
R"(obj = shell.create_extension_object()
def print_args(name, lname, kwargs):
data = []
data.append(name)
data.append(lname)
if (kwargs):
for key, val in kwargs.items():
data.append("{} = {}".format(key, val))
print(", ".join(data))
shell.add_extension_object_member(obj, "printArgs", print_args, {
"brief":"Testing usage of kwargs",
"parameters": [
{
"name": "name",
"type": "string",
},
{
"name": "lname",
"type": "string",
},
{
"name": "kwargs",
"type": "dictionary",
"required": False,
}
]});
shell.register_global('kwtest', obj)
)",
".py");
add_py_test("\\py");
// TEST: Not passing kwargs
add_py_test("kwtest.print_args('John', 'Doe')", "John, Doe");
// TEST: Using named parameters without kwargs
add_py_test("kwtest.print_args(name='Jane', lname='Doe')", "Jane, Doe");
// TEST: Passing kwargs in a dictionary
add_py_test("kwtest.print_args('Jack', 'Doe', {'option':'value'})",
"Jack, Doe, option = value");
// TEST: Passing kwargs
add_py_test("kwtest.print_args(name='Jack', lname='Sparrow', uses='sword')",
"Jack, Sparrow, uses = sword");
// TEST: Passing named argument for parameter already defined
add_py_test("kwtest.print_args('Black', 'Pearl', name='dissappears')",
"ValueError: kwtest.print_args: Got multiple values for "
"argument 'name'");
// TEST: Passing option named as parameter, it's handled as option
add_py_test("kwtest.print_args('Black', 'Pearl', {'name': 'allowed'})",
"Black, Pearl, name = allowed");
// run the test
run({"--log-level=debug"});
// check the output
for (const auto &output : get_expected_output()) {
MY_EXPECT_CMD_OUTPUT_CONTAINS(output);
}
delete_user_plugin("kwargs-py-py-correct");
}
TEST_F(Mysqlsh_plugin_test, py_no_kwargs_from_javascript_correct_registration) {
write_user_plugin("kwargs-py-js-correct",
R"(obj = shell.create_extension_object()
def print_args(name, lname, kwargs=None):
data = []
data.append(name)
data.append(lname)
if kwargs:
for key, val in kwargs.items():
data.append("{} = {}".format(key, val))
print(", ".join(data))
shell.add_extension_object_member(obj, "printArgs", print_args, {
"brief":"Testing usage of kwargs",
"parameters": [
{
"name": "name",
"type": "string",
},
{
"name": "lname",
"type": "string",
},
{
"name": "kwargs",
"type": "dictionary",
"required": False,
}
]});
shell.register_global('kwtest', obj)
)",
".py");
add_py_test("\\js");
// TEST: Not passing kwargs
add_py_test("kwtest.printArgs('John', 'Doe')", "John, Doe");
// TEST: Passing kwargs in a dictionary
add_py_test("kwtest.printArgs('Jack', 'Doe', {'option':'value'})",
"Jack, Doe, option = value");
// TEST: Passing option named as argument, it's processed as option
add_py_test("kwtest.printArgs('Black', 'Pearl', {name: 'allowed'})",
"Black, Pearl, name = allowed");
// run the test
run({"--log-level=debug"});
// check the output
for (const auto &output : get_expected_output()) {
MY_EXPECT_CMD_OUTPUT_CONTAINS(output);
}
delete_user_plugin("kwargs-py-js-correct");
}
TEST_F(Mysqlsh_plugin_test, py_kwargs_additional_tests) {
write_user_plugin("kwargs-py-py-correct",
R"(obj = shell.create_extension_object()
def print_args(name, options, **kwargs):
data = []
data.append(name)
if options:
for key in sorted(options.keys()):
data.append("Option {} = {}".format(key, options[key]))
for key in sorted(kwargs.keys()):
data.append("Arg {} = {}".format(key, kwargs[key]))
print(", ".join(data))
shell.add_extension_object_member(obj, "printArgs", print_args, {
"brief":"Testing usage of kwargs",
"parameters": [
{
"name": "name",
"type": "string",
},
{
"name": "options",
"type": "dictionary",
},
{
"name": "kwargs",
"type": "dictionary",
"required": False,
}
]});
# Same as before but options is optional
shell.add_extension_object_member(obj, "printArgs2", print_args, {
"brief":"Testing usage of kwargs",
"parameters": [
{
"name": "name",
"type": "string",
},
{
"name": "options",
"type": "dictionary",
"required": False,
},
{
"name": "kwargs",
"type": "dictionary",
"required": False,
}
]});
shell.register_global('kwtest', obj)
)",
".py");
add_py_test("\\py");
// TEST: Not passing kwargs
add_py_test(
"kwtest.print_args(name='John', options={'first':'val'}, "
"{'name':'Mike'})",
"SyntaxError: positional argument follows keyword argument");
add_py_test(
"kwtest.print_args('John', {'host':'localhost', 'port': 3306}, "
"host='127.0.0.1', port=3307)",
"John, Option host = localhost, Option port = 3306, Arg host = "
"127.0.0.1, Arg port = 3307");
add_py_test(
"kwtest.print_args(name='John', options={'host':'localhost', 'port': "
"3308}, host='127.0.0.1', port=3309)",
"John, Option host = localhost, Option port = 3308, Arg host = "
"127.0.0.1, Arg port = 3309");
// No option is defined and options is mandatory
add_py_test("kwtest.print_args(name='John', host='127.0.0.1', port=3307)",
"ValueError: kwtest.print_args: Missing value "
"for argument 'options'");
// No option is defined and options is optional
add_py_test("kwtest.print_args2(name='John', host='127.0.0.1', port=3307)",
"John, Arg host = 127.0.0.1, Arg port = 3307");
// run the test
run({"--log-level=debug"});
// check the output
for (const auto &output : get_expected_output()) {
MY_EXPECT_CMD_OUTPUT_CONTAINS(output);
}
delete_user_plugin("kwargs-py-py-correct");
}
TEST_F(Mysqlsh_plugin_test, py_lots_of_params_plugin) {
// create fourth-py plugin - which defines a new global object
write_user_plugin(
"lot-params",
R"(def describe(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12):
print(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12)
obj = shell.create_extension_object()
shell.add_extension_object_member(obj, "selfDescribe", describe,{
"brief": "Creates a function to test KW arg passing.",
"parameters": [
{
"name": "one",
"type": "integer",
},
{
"name": "two",
"type": "integer",
},
{
"name": "three",
"type": "integer",
},
{
"name": "four",
"type": "integer",
},
{
"name": "five",
"type": "integer",
},
{
"name": "six",
"type": "string",
},
{
"name": "seven",
"type": "string",
},
{
"name": "eight",
"type": "string",
},
{
"name": "nine",
"type": "string",
},
{
"name": "ten",
"type": "string",
},
{
"name": "eleven",
"type": "integer",
"required": False,
"default": 3
},
{
"name": "twelve",
"type": "string",
"required": False,
"default": "some value"
}
]
});
shell.register_global('pyObject', obj);
)",
".py");
// check if jsObject.test_function was properly registered in PY
add_py_test("\\py");
// TEST: Missing required parameters
add_py_test(
"pyObject.self_describe()",
"pyObject.self_describe: Invalid number of arguments, expected 10 "
"to 12 but got 0");
add_py_test(
"pyObject.self_describe(1)",
"pyObject.self_describe: Invalid number of arguments, expected 10 "
"to 12 but got 1");
add_py_test("pyObject.self_describe(1, five=10)",
"pyObject.self_describe: Missing value for argument 'two'");
// TEST: Passing keyword parameter skipping optional parameters
// The missed parameter (11) takes its default value of 3
add_py_test(
"pyObject.self_describe(1,2,3,4,5,'six', 'seven', 'eight', 'nine', "
"'ten',twelve='other')",
"1 2 3 4 5 six seven eight nine ten 3 other");
// TEST: Passing all parameters as positional
add_py_test(
"pyObject.self_describe(2,4,6,8,10,'6A','7B','8C', '9D', '10E', 11, "
"'12G')",
"2 4 6 8 10 6A 7B 8C 9D 10E 11 12G");
// TEST: Passing all parameters as keyword parameters
add_py_test(
"pyObject.self_describe(one=3, two=6, three=9, four=12, five=15, "
"six='6th', seven='7th', eight='8th', nine='9th', ten='10th', "
"eleven=11, twelve='12th')",
"3 6 9 12 15 6th 7th 8th 9th 10th 11 12th");
// TEST: Passing all parameters as keyword parameters scrambled
add_py_test(
"pyObject.self_describe(twelve='12th', nine='9th', six='6th', one=1, "
"two=6, three=9, four=12, five=15, seven='7th', eight='8th', ten='10th', "
"eleven=11)",
"1 6 9 12 15 6th 7th 8th 9th 10th 11 12th");
// run the test
run({"--log-level=debug"});
// check the output
for (const auto &output : get_expected_output()) {
MY_EXPECT_CMD_OUTPUT_CONTAINS(output);
}
delete_user_plugin("lot-params");
}
TEST_F(Mysqlsh_plugin_test, bug31693096) {
// JS registers a global object with a method which takes one optional param
write_user_plugin("bug31693096", R"(var gl = shell.createExtensionObject();
shell.addExtensionObjectMember(gl, 'f', function f() { println('hey!'); }, {
'parameters': [{'name': 'a0', 'type': 'string', 'required': false}]
});
shell.registerGlobal('gl', gl);
)",
".js");
// this method is called without parameters from Python
add_py_test("\\py", "Switching to Python mode...");
add_py_test("gl.f()", "hey!");
// run the test
run();
// check the output
MY_EXPECT_CMD_OUTPUT_CONTAINS(expected_output());
delete_user_plugin("bug31693096");
}
} // namespace tests