unittest/scripts/setup_js/setup.js (1,565 lines of code) (raw):

var sandbox_uris = {} sandbox_uris[__mysql_sandbox_port1] = __sandbox_uri1; sandbox_uris[__mysql_sandbox_port2] = __sandbox_uri2; sandbox_uris[__mysql_sandbox_port3] = __sandbox_uri3; var add_instance_options = { HoSt: localhost, port: 0000, PassWord: 'root', scheme: 'mysql' }; // Gets the initial UUID of the given instance caching the data // for further requests var sandbox_uuids = {} function get_sandbox_uuid(port) { if (!sandbox_uuids.hasOwnProperty(port)) { var mySession = mysql.getSession(sandbox_uris[port]); var result = mySession.runSql("select @@server_uuid as uuid"); sandbox_uuids[port] = result.fetchOneObject().uuid; mySession.close() } return sandbox_uuids[port]; } // Gets the initial auto.cnf file for the given sandbox var auto_cnfs = {} var auto_cnf_paths = {} function get_sandbox_auto_cnf(port) { if (!auto_cnfs.hasOwnProperty(port)) { var auto_path = testutil.getSandboxLogPath(port); var auto_path = auto_path.replace("error.log", "auto.cnf"); auto_cnf_paths[port] = auto_path; auto_cnfs[port] = os.loadTextFile(auto_path); } return auto_cnfs[port]; } // Update the sandbox UUID on auto.cnf replacing the last // characters of the current UUID with the value provided // at sequence function update_sandbox_uuid(port, sequence) { var uuid = get_sandbox_uuid(port) var new_uuid = uuid.substring(0, uuid.length - sequence.length) + sequence; var new_auto_cnf = []; function update_auto_cnf(value, index, array) { if (value.startsWith("server-uuid")) { new_auto_cnf.push("server-uuid = " + new_uuid); } else { new_auto_cnf.push(value); } } var auto_cnf = get_sandbox_auto_cnf(port) auto_cnf.split("\n").forEach(update_auto_cnf); auto_cnfs[port] = new_auto_cnf.join("\n"); sandbox_uuids[port] = new_uuid; testutil.createFile(auto_cnf_paths[port], auto_cnfs[port]); return new_uuid; } function create_root_from_anywhere(session, clear_super_read_only) { var super_read_only = get_sysvar(session, "super_read_only"); var super_read_only_enabled = super_read_only === "ON"; session.runSql("SET SQL_LOG_BIN=0"); if (clear_super_read_only && super_read_only_enabled) set_sysvar(session, "super_read_only", 0); session.runSql("CREATE USER root@'%' IDENTIFIED BY 'root'"); session.runSql("GRANT ALL ON *.* to root@'%' WITH GRANT OPTION"); if (clear_super_read_only && super_read_only_enabled) set_sysvar(session, "super_read_only", 1); session.runSql("SET SQL_LOG_BIN=1"); } function get_socket_path(session, uri = undefined) { if (undefined === uri) { uri = session.uri; } var row = session.runSql(`SELECT @@${"mysql" === shell.parseUri(uri).scheme ? "socket" : "mysqlx_socket"}, @@datadir`).fetchOne(); if (row[0][0] == '/' || __os_type == 'windows') p = row[0]; else if (row[1][row[1].length - 1] == '/') p = row[1] + row[0]; else p = row[1] + "/" + row[0]; if (p.length > 100) { testutil.fail("socket path is too long (>100): " + p); } return p; } function run_nolog(session, query) { session.runSql("set session sql_log_bin=0"); if (type(query) == "String") { session.runSql(query); } else { for (var s of query) { session.runSql(s); } } session.runSql("set session sql_log_bin=1"); } function genlog_last_timestamp(port) { var logs = testutil.readGeneralLog(port); if (logs) { return logs[logs.length - 1]["timestamp"]; } return null; } function genlog_filter_reads(logs, pattern) { var out = []; for (log of logs) { sql = log["sql"]; if (pattern === undefined) { if (sql.match("select 1") || sql.match(/select.*from.*/i) || sql.match(/(show|begin|start transaction|rollback|.*@@hostname|.*@@server_uuid|select.*gtid)/i) || sql.match(/set (session|@)/i) || sql.match(/select\s*(service_get_write_locks|service_get_read_locks|service_release_locks)\(.*\)/i)) { } else { out.push(log); } } else { if (sql.match(pattern) || sql.match("select 1") || sql.match(/select\s*(service_get_write_locks|service_get_read_locks|service_release_locks)\(.*\)/i)) { } else { out.push(log); } } } return out; } function prepare_genlog_nop_check(port) { return { port: port, ts: genlog_last_timestamp(port) }; } function EXPECT_GENLOG_NOP(state, extra_filter) { var logs = testutil.readGeneralLog(state["port"], state["ts"]); logs = genlog_filter_reads(logs); if (extra_filter) { logs = genlog_filter_reads(logs, extra_filter); } EXPECT_EQ([], logs); } function EXPECT_DRYRUN(fun, port, extra_filter) { if (__test_execution_mode != 'replay') var state = prepare_genlog_nop_check(port); fun(); if (__test_execution_mode != 'replay') EXPECT_GENLOG_NOP(state, extra_filter); } var kGrantsForPerformanceSchema = ["GRANT SELECT ON `performance_schema`.`replication_applier_configuration` TO `admin`@`%` WITH GRANT OPTION", "GRANT SELECT ON `performance_schema`.`replication_applier_status_by_coordinator` TO `admin`@`%` WITH GRANT OPTION", "GRANT SELECT ON `performance_schema`.`replication_applier_status_by_worker` TO `admin`@`%` WITH GRANT OPTION", "GRANT SELECT ON `performance_schema`.`replication_applier_status` TO `admin`@`%` WITH GRANT OPTION", "GRANT SELECT ON `performance_schema`.`replication_connection_configuration` TO `admin`@`%` WITH GRANT OPTION", "GRANT SELECT ON `performance_schema`.`replication_connection_status` TO `admin`@`%` WITH GRANT OPTION", "GRANT SELECT ON `performance_schema`.`replication_group_member_stats` TO `admin`@`%` WITH GRANT OPTION", "GRANT SELECT ON `performance_schema`.`replication_group_members` TO `admin`@`%` WITH GRANT OPTION", "GRANT SELECT ON `performance_schema`.`threads` TO `admin`@`%` WITH GRANT OPTION", "GRANT SELECT ON `mysql`.`func` TO `admin`@`%` WITH GRANT OPTION"]; /** * Verifies if a variable is defined, returning true or false accordingly * @param cb An anonymous function that simply executes the variable to be * verified, example: * * defined(function(){myVar}) * * Will return true if myVar is defined or false if not. */ function defined(cb) { try { cb(); return true; } catch { return false } } function hasOciEnvironment(context) { if (['OS', 'MDS'].indexOf(context) == -1) { return false } let variables = ['OCI_CONFIG_HOME', 'OCI_COMPARTMENT_ID', 'OS_NAMESPACE', 'OS_BUCKET_NAME']; if (context == 'MDS') { variables = variables.concat(['MDS_URI']); } let missing = []; for (var index = 0; index < variables.length; index++) { if (!defined(function () { eval(variables[index]) })) { missing.push(variables[index]) } } if (missing.length) { return false; } return true; } function hasAwsEnvironment() { const variables = ['MYSQLSH_S3_BUCKET_NAME']; const missing = []; for (const variable of variables) { if (!defined(function () { eval(variable) })) { missing.push(variable); } } if (missing.length) { println(`Missing AWS Variables: ${missing}`); return false; } return true; } function hasAuthEnvironment(context) { if (['LDAP_SIMPLE', 'LDAP_SASL', 'LDAP_KERBEROS', 'KERBEROS', 'FIDO', 'OCI_AUTH'].indexOf(context) == -1) { return false } var variables = []; if (context == 'LDAP_SIMPLE') { variables = ['LDAP_SIMPLE_SERVER_HOST', 'LDAP_SIMPLE_SERVER_PORT', 'LDAP_SIMPLE_BIND_BASE_DN', 'LDAP_SIMPLE_USER', 'LDAP_SIMPLE_PWD', 'LDAP_SIMPLE_AUTH_STRING']; } else if (context == 'LDAP_SASL') { variables = ['LDAP_SASL_SERVER_HOST', 'LDAP_SASL_SERVER_PORT', 'LDAP_SASL_BIND_BASE_DN', 'LDAP_SASL_USER', 'LDAP_SASL_PWD', 'LDAP_SASL_GROUP_SEARCH_FILTER']; } else if (context == 'LDAP_KERBEROS') { variables = ['LDAP_KERBEROS_SERVER_HOST', 'LDAP_KERBEROS_SERVER_PORT', 'LDAP_KERBEROS_BIND_BASE_DN', 'LDAP_KERBEROS_USER_SEARCH_ATTR', 'LDAP_KERBEROS_USER', 'LDAP_KERBEROS_PWD', 'LDAP_KERBEROS_AUTH_STRING', 'LDAP_KERBEROS_DOMAIN']; } else if (context == 'KERBEROS') { variables = ['KERBEROS_USER', 'KERBEROS_PWD', 'KERBEROS_DOMAIN']; } else if (context == 'OCI_AUTH') { variables = ['OCI_AUTH_URI', 'OCI_AUTH_CONFIG_FILE', 'OCI_AUTH_PROFILE']; } let missing = []; for (var index = 0; index < variables.length; index++) { if (!defined(function () { eval(variables[index]) })) { missing.push(variables[index]) } } if (missing.length) { shell.log("warning", `Missing Variables: ${missing}`) return false; } return true; } function debugAuthPlugins() { return os.getenv("MYSQLSH_DEBUG_AUTH_PLUGINS") || false; } function getAuthServerConfig(context) { if (!isAuthMethodSupported(context)) { return {}; } const ext = __os_type == "windows" ? "dll" : "so"; if (context == 'LDAP_SIMPLE') { var server_conf = { "plugin-load-add": `authentication_ldap_simple.${ext}`, "authentication_ldap_simple_server_host": `${LDAP_SIMPLE_SERVER_HOST}`, "authentication_ldap_simple_server_port": parseInt(`${LDAP_SIMPLE_SERVER_PORT}`), "authentication_ldap_simple_bind_base_dn": `${LDAP_SIMPLE_BIND_BASE_DN}` }; if (__os_type == 'windows') { server_conf['named_pipe'] = 1; server_conf['socket'] = "MyNamedPipe"; } return server_conf; } else if (context == 'LDAP_SASL') { return { "plugin-load-add": `authentication_ldap_sasl.${ext}`, "authentication_ldap_sasl_server_host": `${LDAP_SASL_SERVER_HOST}`, "authentication_ldap_sasl_server_port": parseInt(`${LDAP_SASL_SERVER_PORT}`), "authentication_ldap_sasl_group_search_filter": `${LDAP_SASL_GROUP_SEARCH_FILTER}`, "authentication_ldap_sasl_bind_base_dn": `${LDAP_SASL_BIND_BASE_DN}`, "authentication_ldap_sasl_log_status": 5, "log_error_verbosity": 3 }; } else if (context == 'LDAP_KERBEROS') { return { "plugin-load-add": `authentication_ldap_sasl.${ext}`, "authentication_ldap_sasl_server_host": `${LDAP_KERBEROS_SERVER_HOST}`, "authentication_ldap_sasl_server_port": parseInt(`${LDAP_KERBEROS_SERVER_PORT}`), "authentication_ldap_sasl_bind_base_dn": `${LDAP_KERBEROS_BIND_BASE_DN}`, "authentication_ldap_sasl_user_search_attr": `${LDAP_KERBEROS_USER_SEARCH_ATTR}`, "authentication_ldap_sasl_bind_root_dn": `${LDAP_KERBEROS_BIND_ROOT_DN}`, "authentication_ldap_sasl_bind_root_pwd": `${LDAP_KERBEROS_BIND_ROOT_PWD}`, "authentication_ldap_sasl_group_search_filter": `${LDAP_KERBEROS_GROUP_SEARCH_FILTER}`, "authentication_ldap_sasl_auth_method_name": 'GSSAPI', "authentication_ldap_sasl_log_status": 5, "log_error_verbosity": 3, "net_read_timeout": 360, "connect_timeout": 360 }; } else if (context == 'KERBEROS') { const keytab_file = os.path.join(__tmp_dir, "mtr.keytab"); testutil.cpfile(os.path.join(__data_path, "keytab", "mtr.keytab"), keytab_file); return { "plugin-load-add": `authentication_kerberos.${ext}`, "authentication_kerberos_service_principal": `mysql_service/kerberos_auth_host@${LDAP_KERBEROS_DOMAIN}`, "authentication_kerberos_service_key_tab": keytab_file, "net_read_timeout": 360, "connect_timeout": 360, "log_error_verbosity": 3 }; } else if (context == 'FIDO') { return { "plugin-load-add": `authentication_fido.${ext}` }; } return {}; } function isAuthMethodSupported(context) { if (['LDAP_SIMPLE', 'LDAP_SASL', 'LDAP_KERBEROS', 'KERBEROS', 'FIDO', 'OCI_AUTH'].indexOf(context) == -1) { return false; } var plugin = ''; if (context == 'LDAP_SIMPLE') { plugin = 'authentication_ldap_simple'; } else if (context == 'LDAP_SASL') { plugin = 'authentication_ldap_sasl'; } else if (context == 'LDAP_KERBEROS') { plugin = 'authentication_ldap_sasl'; } else if (context == 'KERBEROS') { plugin = 'authentication_kerberos'; } else if (context == 'FIDO') { plugin = 'authentication_fido'; } else if (context == 'OCI_AUTH') { // we're using an external server, so this is always supported return true; } var plugin_supported = true; const s = shell.openSession(__mysqluripwd); try { ensure_plugin_enabled(plugin, s); } catch (error) { plugin_supported = false; } if (plugin_supported) { if (context == 'LDAP_KERBEROS') { try { s.runSql("SET @@GLOBAL.authentication_ldap_sasl_auth_method_name = 'GSSAPI'"); } catch (error) { plugin_supported = false; } } try { ensure_plugin_disabled(plugin, s); } catch (error) { // ignore any errors } } s.close(); return plugin_supported; } function set_sysvar(session, variable, value) { session.runSql("SET GLOBAL " + variable + " = ?", [value]); } function get_sysvar(session, variable, type) { var close_session = false; var pos = 0; var query = ""; // Check if the variable session is an int, if so it's the member_port and we need to establish a session if (typeof session == "number") { session = shell.connect("mysql://root:root@localhost:" + session); close_session = true; } if (type === undefined) type = "GLOBAL"; if (type == "GLOBAL") { query = "SELECT @@GLOBAL." + variable; } else if (type == "PERSISTED") { query = "SELECT * from performance_schema.persisted_variables WHERE Variable_name like '%" + variable + "%'"; pos = 1; } var row = session.runSql(query).fetchOne(); // Close the session if established in this function if (close_session) session.close(); if (row == null) return ""; return row[pos]; } function ensure_plugin_enabled(plugin_name, session, plugin_soname) { if (plugin_soname === undefined) plugin_soname = plugin_name; var os = session.runSql('select @@version_compile_os').fetchOne()[0]; if (os == "Win32" || os == "Win64") { session.runSql("INSTALL PLUGIN " + plugin_name + " SONAME '" + plugin_soname + ".dll';"); } else { session.runSql("INSTALL PLUGIN " + plugin_name + " SONAME '" + plugin_soname + ".so';"); } } function ensure_plugin_disabled(plugin_name, session) { var rs = session.runSql("SELECT COUNT(1) FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME like '" + plugin_name + "';"); var is_installed = rs.fetchOne()[0]; if (is_installed) { session.runSql("UNINSTALL PLUGIN " + plugin_name + ";"); } } function restart_gr_plugin(session, close) { close = typeof close !== 'undefined' ? close : false; var owns_session = false; // Check if the variable session is an int, if so it's the member_port and we need to establish a session if (typeof session == "number") { session = shell.connect("mysql://root:root@localhost:" + session); owns_session = true; } session.runSql("STOP GROUP_REPLICATION;"); session.runSql("START GROUP_REPLICATION;"); // Close the session if established in this function if (owns_session && close) session.close(); } function get_query_single_result(session, query) { var close_session = false; // Check if the variable session is an int, if so it's the member_port and we need to establish a session if (typeof session == "number") { session = shell.connect("mysql://root:root@localhost:" + session); close_session = true; } if (query === undefined) testutil.fail("query argument for get_query_single_result() can't be empty"); var row = session.runSql(query).fetchOne(); // Close the session if established in this function if (close_session) session.close(); if (row == null) testutil.fail("Query returned no results"); return row[0]; } function get_persisted_gr_sysvars(session) { var close_session = false; // Check if the variable session is an int, if so it's the member_port and we need to establish a session if (typeof session == "number") { session = shell.connect("mysql://root:root@localhost:" + session); close_session = true; } var query = "SELECT * from performance_schema.persisted_variables WHERE Variable_name like '%group_replication%' ORDER BY Variable_name"; var ret = ""; var result = session.runSql(query); var row = result.fetchOne(); if (row == null) { if (close_session) session.close(); testutil.fail("Query returned no results"); } while (row) { var ret_line = row[0] + " = " + row[1]; ret += "\n" + ret_line; row = result.fetchOne(); } // Close the session if established in this function if (close_session) session.close(); return ret; } function number_of_gr_recovery_accounts(session) { // All internal recovery users have the prefix: 'mysql_innodb_cluster_'. var res = session.runSql( "SELECT COUNT(*) FROM mysql.user u " + "WHERE u.user LIKE 'mysql_innodb_cluster_%'"); var row = res.fetchOne(); return row[0]; } function get_all_gr_recovery_accounts(session) { var close_session = false; // Check if the variable session is an int, if so it's the member_port and we need to establish a session if (typeof session == "number") { session = shell.connect("mysql://root:root@localhost:" + session); close_session = true; } var query = "SELECT USER, HOST FROM mysql.user WHERE USER LIKE 'mysql_innodb_cluster_%'"; var ret = ""; var result = session.runSql(query); var row = result.fetchOne(); if (row == null) { if (close_session) session.close(); testutil.fail("Query returned no results"); } while (row) { var ret_line = row[0] + ", " + row[1]; ret += "\n" + ret_line; row = result.fetchOne(); } // Close the session if established in this function if (close_session) session.close(); return ret; } function get_gr_recovery_user_passwd(session) { var close_session = false; // Check if the variable session is an int, if so it's the member_port and we need to establish a session if (typeof session == "number") { session = shell.connect("mysql://root:root@localhost:" + session); close_session = true; } var query = "SELECT User_name, User_password FROM mysql.slave_master_info"; var ret = ""; var result = session.runSql(query); var row = result.fetchOne(); if (row == null) { if (close_session) session.close(); testutil.fail("Query returned no results"); } while (row) { var ret_line = row[0] + ", " + row[1]; ret += "\n" + ret_line; row = result.fetchOne(); } // Close the session if established in this function if (close_session) session.close(); return ret; } function enable_auto_rejoin(session, port) { var close_session = false; // Check if the variable session is an int, if so it's the member_port and we need to establish a session if (typeof session == "number") { var port = session; session = shell.connect("mysql://root:root@localhost:" + session); close_session = true; } testutil.changeSandboxConf(port, "group_replication_start_on_boot", "ON"); if (__version_num > 80011) session.runSql("RESET PERSIST group_replication_start_on_boot"); // Close the session if established in this function if (close_session) session.close(); } function disable_auto_rejoin(session, port) { var close_session = false; // Check if the variable session is an int, if so it's the member_port and we need to establish a session if (typeof session == "number") { var port = session; session = shell.openSession("mysql://root:root@localhost:" + session); close_session = true; } testutil.changeSandboxConf(port, "group_replication_start_on_boot", "OFF"); if (__version_num > 80011) { try { session.runSql("RESET PERSIST group_replication_start_on_boot"); } catch (e) { if (e.cause.code != 3615) { throw e; } } } // Close the session if established in this function if (close_session) session.close(); } function wait_member_state_from(session, member_port, state) { // wait for the given member to appear in the given state from the point of // view of a specific session const timeout = 60; for (i = 0; i < timeout; i++) { var r = session.runSql("SELECT member_state FROM performance_schema.replication_group_members WHERE member_port=?", [member_port]).fetchOne(); if (!r) { testutil.fail("member_state query returned no rows for " + member_port); break; } if (r[0] == state) return; os.sleep(1); } testutil.fail("Timeout while waiting for " + member_port + " to become " + state + " when queried from " + session.runSql("select @@port").fetchOne()[0]); } function wait_channel_ready(session, port, channel) { wait(10, 0.5, function () { row = session.runSql("select s.service_state, port from performance_schema.replication_connection_status s join performance_schema.replication_connection_configuration c on s.channel_name = c.channel_name where s.channel_name=?", [channel]).fetchOne(); return row && row[0] == "ON" && row[1] == port; }); } function wait_channel_reconnecting(session, channel) { wait(10, 0.5, function () { row = session.runSql("select s.service_state, port from performance_schema.replication_connection_status s join performance_schema.replication_connection_configuration c on s.channel_name = c.channel_name where s.channel_name=?", [channel]).fetchOne(); return row && row[0] == "CONNECTING"; }); } function normalize_cluster_options(options) { // normalize output of cluster.options(), to make order of tags to be always the same options["defaultReplicaSet"]["tags"][".global"] = options["defaultReplicaSet"]["tags"]["global"]; delete options["defaultReplicaSet"]["tags"]["global"]; return options; } function normalize_rs_options(options) { // normalize output of rs.options(), to make order of tags to be always the same options["replicaSet"]["tags"][".global"] = options["replicaSet"]["tags"]["global"]; delete options["replicaSet"]["tags"]["global"]; return options; } // Check if the instance exists in the Metadata schema function exist_in_metadata_schema(port) { if (typeof port == "number") { var result = session.runSql("SELECT COUNT(*) FROM mysql_innodb_cluster_metadata.instances WHERE instance_name = '" + hostname + ":" + port + "';"); } else { var result = session.runSql( 'SELECT COUNT(*) FROM mysql_innodb_cluster_metadata.instances where CAST(mysql_server_uuid AS CHAR CHARACTER SET ASCII) = CAST(@@server_uuid AS CHAR CHARACTER SET ASCII);'); } var row = result.fetchOne(); return row[0] != 0; } function reset_instance(session) { session.runSql("STOP SLAVE"); try { session.runSql("STOP group_replication"); } catch (e) { } session.runSql("SET GLOBAL super_read_only=0"); session.runSql("SET GLOBAL read_only=0"); session.runSql("DROP SCHEMA IF EXISTS mysql_innodb_cluster_metadata"); var r = session.runSql("SHOW SCHEMAS"); var rows = r.fetchAll(); for (var i in rows) { var row = rows[i]; if (["mysql", "performance_schema", "sys", "information_schema"].includes(row[0])) continue; session.runSql("DROP SCHEMA " + row[0]); } var r = session.runSql("SELECT user,host FROM mysql.user"); var rows = r.fetchAll(); for (var i in rows) { var row = rows[i]; if (["mysql.sys", "mysql.session", "mysql.infoschema"].includes(row[0])) continue; if (row[0] == "root" && (row[1] == "localhost" || row[1] == "%")) continue; session.runSql("DROP USER ?@?", [row[0], row[1]]); } session.runSql("RESET MASTER"); session.runSql("RESET SLAVE ALL"); } var SANDBOX_PORTS = [__mysql_sandbox_port1, __mysql_sandbox_port2, __mysql_sandbox_port3]; var SANDBOX_LOCAL_URIS = [__sandbox_uri1, __sandbox_uri2, __sandbox_uri3]; var SANDBOX_URIS = [__hostname_uri1, __hostname_uri2, __hostname_uri3]; var s = mysql.getSession(__mysqluripwd + "?ssl-mode=disabled"); var r = s.runSql("SELECT @@hostname, @@report_host").fetchOne(); var __mysql_hostname = r[0]; var __mysql_report_host = r[1]; s.close() var __mysql_host = __mysql_report_host ? __mysql_report_host : __mysql_hostname; // Address that appear in pre-configured sandboxes that set report_host to 127.0.0.1 var __address1 = "127.0.0.1:" + __mysql_sandbox_port1; var __address2 = "127.0.0.1:" + __mysql_sandbox_port2; var __address3 = "127.0.0.1:" + __mysql_sandbox_port3; var __address4 = "127.0.0.1:" + __mysql_sandbox_port4; var __address5 = "127.0.0.1:" + __mysql_sandbox_port5; var __address6 = "127.0.0.1:" + __mysql_sandbox_port6; // Address that appear in raw sandboxes, that show the real hostname var __address1r = __mysql_host + ":" + __mysql_sandbox_port1; var __address2r = __mysql_host + ":" + __mysql_sandbox_port2; var __address3r = __mysql_host + ":" + __mysql_sandbox_port3; var __address4r = __mysql_host + ":" + __mysql_sandbox_port4; var __address5r = __mysql_host + ":" + __mysql_sandbox_port5; var __address6r = __mysql_host + ":" + __mysql_sandbox_port6; var __address1h = hostname + ":" + __mysql_sandbox_port1; var __address2h = hostname + ":" + __mysql_sandbox_port2; var __address3h = hostname + ":" + __mysql_sandbox_port3; var __address4h = hostname + ":" + __mysql_sandbox_port4; var __address5h = hostname + ":" + __mysql_sandbox_port5; var __address6h = hostname + ":" + __mysql_sandbox_port6; var CLUSTER_ADMIN = "AdminUser"; var CLUSTER_ADMIN_PWD = "AdminUserPwd"; var __sandbox_admin_uri1 = 'mysql://AdminUser:AdminUserPwd@localhost:' + __mysql_sandbox_port1; var __sandbox_admin_uri2 = 'mysql://AdminUser:AdminUserPwd@localhost:' + __mysql_sandbox_port2; var __sandbox_admin_uri3 = 'mysql://AdminUser:AdminUserPwd@localhost:' + __mysql_sandbox_port3; var __sandbox_admin_uri4 = 'mysql://AdminUser:AdminUserPwd@localhost:' + __mysql_sandbox_port4; // JSON utils // Find a key in a json object recursively function json_find_key(json, key) { if (key in json) { return json[key]; } for (var k in json) { if (type(json[k]) == 'Object' || type(json[k]) == 'm.Map') { var o = json_find_key(json[k], key); if (o != undefined) return o; } else if (type(json[k]) == 'Array' || type(json[k]) == 'm.Array') { for (var i in json[k]) { if (type(json[k][i]) != "String") { var o = json_find_key(json[k][i], key); if (o != undefined) return o; } } } } return undefined; } function EXPECT_REPLICAS_USE_SSL(session, num_replicas) { var res = session.runSql("SELECT t.thread_id, t.processlist_id, s.variable_value FROM performance_schema.threads t JOIN performance_schema.status_by_thread s ON t.thread_id = s.thread_id WHERE t.processlist_command = 'Binlog Dump GTID' AND s.variable_name = 'Ssl_cipher'"); var count = 0; for (row = res.fetchOne(); row; row = res.fetchOne()) { print(row); EXPECT_NE("", row[2] || "", "Replication thread " + row[1] + " not using Ssl"); count += 1; } EXPECT_EQ(num_replicas, count, "# of replicas"); } function wait(timeout, wait_interval, condition) { if (__replaying) wait_interval = 0; waiting = 0; res = condition(); while (!res && waiting < timeout) { os.sleep(wait_interval); waiting = waiting + 1; res = condition(); } return res; } // -------- function begin_dba_log_sql(level) { shell.options["dba.logSql"] = level ? level : 1; var dummy = testutil.fetchDbaSqlLog(true); } function end_dba_log_sql(level) { var logs = testutil.fetchDbaSqlLog(false); shell.options["dba.logSql"] = 0; return logs; } // -------- Test Expectations function EXPECT_NO_SQL(instance, logs, allowed_stmts) { var fail = false; for (var i in logs) { var line = logs[i]; if (line.startsWith(instance)) { var bad = false; for (var j in allowed_stmts) { if (!line.split(": ")[1].startsWith(allowed_stmts[j])) { bad = true; break; } } if (bad) { println("UNEXPECTED SQL DETECTED:", line); fail = true; } } } EXPECT_FALSE(fail); } function EXPECT_SQL(instance, logs, expected_stmt) { var fail = true; for (var i in logs) { var line = logs[i]; if (line.startsWith(instance)) { if (line.split(": ")[1].startsWith(expected_stmt)) { fail = false; break; } } } if (fail) { println("MISSING EXPECTED SQL:"); for (var i in logs) { var line = logs[i]; if (line.startsWith(instance)) { println("\t", line); } } } EXPECT_FALSE(fail); } function EXPECT_EQ(expected, actual, note) { if (note === undefined) note = ""; if (repr(expected) != repr(actual)) { var context = "<b>Context:</b> " + __test_context + "\n<red>Tested values don't match as expected:</red> " + note + "\n\t<yellow>Actual: </yellow> " + repr(actual) + "\n\t<yellow>Expected:</yellow> " + repr(expected); testutil.fail(context); } } function EXPECT_EQ_ONEOF(expected, actual, note) { if (note === undefined) note = ""; for (var e of expected) { if (repr(e) == repr(actual)) return; } var context = "<b>Context:</b> " + __test_context + "\n<red>Tested values don't match as expected:</red> " + note + "\n\t<yellow>Actual: </yellow> " + repr(actual) + "\n\t<yellow>Expected (one of):</yellow> " + repr(expected); testutil.fail(context); } function EXPECT_GT(value1, value2, note) { if (note === undefined) note = ""; if (value1 <= value2) { var context = "<b>Context:</b> " + __test_context + "\n<red>EXPECT_GT failed:</red> " + note + "\n\t" + repr(value1) + " expected to be > " + repr(value2) + " but isn't."; testutil.fail(context); } } function EXPECT_LT(value1, value2, note) { if (note === undefined) note = ""; if (value1 >= value2) { var context = "<b>Context:</b> " + __test_context + "\n<red>EXPECT_LT failed:</red> " + note + "\n\t" + repr(value1) + " expected to be < " + repr(value2) + " but isn't."; testutil.fail(context); } } function EXPECT_JSON_EQ(expected, actual, note) { function compare_values(expected, actual, path) { if (typeof expected != typeof actual) { println(" =>", path, "mismatched values. Expected:\n", expected, "\nActual:\n", actual, "\n"); return 1; } else if (typeof expected == "object") { return compare_objects(expected, actual, path); } else { jexpected = JSON.stringify(expected, undefined, 2); jactual = JSON.stringify(actual, undefined, 2); if (jexpected != jactual) { println(" =>", path, "mismatched values. Expected:\n", jexpected, "\nActual:\n", jactual, "\n"); return 1; } } return 0; } function compare_objects(expected, actual, path) { ndiffs = 0 for (k in expected) { if (actual[k] === undefined) { ndiffs += 1; println(" =>", path + "." + k, "expected but missing from result"); } else { ndiffs += compare_values(expected[k], actual[k], path + "." + k); } } for (k in actual) { if (expected[k] === undefined) { ndiffs += 1; println(" =>", path + "." + k, "unexpected but found in result"); } } return ndiffs; } if (note === undefined) note = ""; if (typeof expected == "object" && typeof actual == "object" && !Array.isArray(expected) && !Array.isArray(actual)) { diffs = compare_objects(expected, actual, "$"); if (diffs > 0) { var context = "<b>Context:</b> " + __test_context + "\n<red>Tested values don't match as expected.</red> " + note + "\n\t<yellow>Actual:</yellow> " + JSON.stringify(actual, undefined, 2) + "\n\t<yellow>Expected:</yellow> " + JSON.stringify(expected, undefined, 2); testutil.fail(context); } } else { expected = JSON.stringify(expected, undefined, 2); actual = JSON.stringify(actual, undefined, 2); if (expected != actual) { var context = "<b>Context:</b> " + __test_context + "\n<red>Tested values don't match as expected:</red> " + note + "\n\t<yellow>Actual:</yellow> " + actual + "\n\t<yellow>Expected:</yellow> " + expected; testutil.fail(context); } } } function EXPECT_CONTAINS(expected, actual) { if (actual.indexOf(expected) < 0) { var context = "<b>Context:</b> " + __test_context + "\n<red>Tested text doesn't contain expected text:</red>\n\t<yellow>Actual:</yellow> " + actual + "\n\t<yellow>Expected:</yellow> " + expected; testutil.fail(context); } } function EXPECT_ARRAY_CONTAINS(expected, actual) { for (var i = 0; i < actual.length; ++i) { if (expected === actual[i]) return; } var context = "<b>Context:</b> " + __test_context + "\n<red>Tested array doesn't contain expected value:</red>\n\t<yellow>Actual:</yellow> " + JSON.stringify(actual) + "\n\t<yellow>Expected:</yellow> " + expected; testutil.fail(context); } function EXPECT_NE(expected, actual, note) { if (note === undefined) note = ""; if (expected == actual) { var context = "<b>Context:</b> " + __test_context + "\n<red>Tested values don't differ as expected:</red> " + note + "\n\t<yellow>Actual Value:</yellow> " + actual + "\n\t<yellow>Checked Value:</yellow> " + expected; testutil.fail(context); } } function EXPECT_NOT_CONTAINS(expected, actual) { if (actual.indexOf(expected) >= 0) { var context = "<b>Context:</b> " + __test_context + "\n<red>Tested text contains checked text, but it shouldn't:</red>\n\t<yellow>Value:</yellow> " + actual + "\n\t<yellow>Checked Value:</yellow> " + expected; testutil.fail(context); } } function EXPECT_ARRAY_NOT_CONTAINS(expected, actual) { for (var i = 0; i < actual.length; ++i) { if (expected === actual[i]) { var context = "<b>Context:</b> " + __test_context + "\n<red>Tested array contains checked value, but it shouldn't:</red>\n\t<yellow>Array:</yellow> " + JSON.stringify(actual) + "\n\t<yellow>Checked Value:</yellow> " + expected; testutil.fail(context); } } } function EXPECT_TRUE(value, note) { if (note === undefined) note = ""; else note = ": " + note; if (!value) { var context = "<b>Context:</b> " + __test_context + "\n<red>Tested value expected to be true but is false</red>" + note; testutil.fail(context); } } function EXPECT_FALSE(value, note) { if (note === undefined) note = ""; else note = ": " + note; if (value) { var context = "<b>Context:</b> " + __test_context + "\n<red>Tested value expected to be false but is true</red>" + note; testutil.fail(context); } } function get_exception_message(exception) { if (exception.cause != undefined) { if (exception.cause.message != undefined) { return exception.cause.message; } } return exception.message } function EXPECT_THROWS(func, etext) { if (typeof (etext) != "string" && typeof (etext) != "object") { testutil.fail("EXPECT_THROWS expects string or array, " + typeof (etext) + " given"); } try { func(); testutil.fail("<b>Context:</b> " + __test_context + "\n<red>Missing expected exception throw like " + etext + "</red>"); } catch (err) { message = get_exception_message(err); testutil.dprint("Got exception as expected: " + message); if (typeof (etext) === "string") { if (message.indexOf(etext) < 0) { testutil.fail("<b>Context:</b> " + __test_context + "\n<red>Exception expected:</red> " + etext + "\n\t<yellow>Actual:</yellow> " + message); } } else if (typeof (etext) === "object") { var found = false; for (i in etext) { if (message.indexOf(etext[i]) >= 0) { found = true; break; } } if (!found) { var msg = "<b>Context:</b> " + __test_context + "\n<red>One of the exceptions expected:</red>\n"; for (i in etext) { msg += etext[i] + "\n"; } msg += "<yellow>Actual:</yellow> " + message; testutil.fail(msg); } } } } function EXPECT_THROWS_TYPE(func, etext, type) { try { func(); testutil.fail("<b>Context:</b> " + __test_context + "\n<red>Missing expected exception throw like " + etext + "</red>"); } catch (err) { testutil.dprint("Caught exception: " + JSON.stringify(err)); if (err.message.indexOf(etext) < 0) { testutil.fail("<b>Context:</b> " + __test_context + "\n<red>Exception expected:</red> " + etext + "\n\t<yellow>Actual:</yellow> " + err.message); } if (err.cause.type !== type) { testutil.fail("<b>Context:</b> " + __test_context + "\n<red>Exception type expected:</red> " + type + "\n\t<yellow>Actual:</yellow> " + err.type); } } } function EXPECT_THROWS_LIKE(func, pattern) { try { func(); testutil.fail("<b>Context:</b> " + __test_context + "\n<red>Missing expected exception throw like " + String(etext) + "</red>"); } catch (err) { msg = get_exception_message(err); testutil.dprint("Caught exception: " + msg); if (!pattern.test(msg)) { testutil.fail("<b>Context:</b> " + __test_context + "\n<red>Exception expected:</red> " + String(pattern) + "\n\t<yellow>Actual:</yellow> " + err.message); } } } function EXPECT_NO_THROWS(func, context) { try { func(); } catch (err) { testutil.dprint("Unexpected exception: " + JSON.stringify(err)); testutil.fail("<b>Context:</b> " + __test_context + "\n<red>Unexpected exception thrown (" + context + "): " + err.message + "</red>"); } } function EXPECT_OUTPUT_CONTAINS(text) { var out = testutil.fetchCapturedStdout(false); var err = testutil.fetchCapturedStderr(false); if (out.indexOf(text) < 0 && err.indexOf(text) < 0) { var context = "<b>Context:</b> " + __test_context + "\n<red>Missing output:</red> " + text + "\n<yellow>Actual stdout:</yellow> " + out + "\n<yellow>Actual stderr:</yellow> " + err; testutil.fail(context); } } function EXPECT_OUTPUT_CONTAINS_ONE_OF(text) { var out = testutil.fetchCapturedStdout(false); var err = testutil.fetchCapturedStderr(false); var found = false; for (i in text) { if (out.indexOf(text[i]) >= 0 || err.indexOf(text[i]) >= 0) { found = true; break; } } if (!found) { var context = "<b>Context:</b> " + __test_context + "\n<red>Missing output:\n</red>"; for (i in text) { context += text[i] + "\n"; } context += "<yellow>Actual stdout:</yellow> " + out + "\n<yellow>Actual stderr:</yellow> " + err; testutil.fail(context); } } function EXPECT_OUTPUT_NOT_CONTAINS(text) { var out = testutil.fetchCapturedStdout(false); var err = testutil.fetchCapturedStderr(false); if (out.indexOf(text) >= 0 || err.indexOf(text) >= 0) { var context = "<b>Context:</b> " + __test_context + "\n<red>Output unexpectedly contains:</red> " + text + "\n<yellow>Actual stdout:</yellow> " + out + "\n<yellow>Actual stderr:</yellow> " + err; testutil.fail(context); } } function EXPECT_OUTPUT_EMPTY() { var out = testutil.fetchCapturedStdout(false); var err = testutil.fetchCapturedStderr(false); if (out.length > 0 || err.length > 0) { var context = "<b>Context:</b> " + __test_context + "\n<red>Output not empty as expected:</red>\n<yellow>Actual stdout:</yellow> " + out + "\n<yellow>Actual stderr:</yellow> " + err; testutil.fail(context); } } function EXPECT_STDOUT_CONTAINS(text) { var out = testutil.fetchCapturedStdout(false); var err = testutil.fetchCapturedStderr(false); if (out.indexOf(text) < 0) { var context = "<b>Context:</b> " + __test_context + "\n<red>Missing output:</red> " + text + "\n<yellow>Actual stdout:</yellow> " + out + "\n<yellow>Actual stderr:</yellow> " + err; testutil.fail(context); } } function EXPECT_STDOUT_CONTAINS_ONE_OF(text) { var out = testutil.fetchCapturedStdout(false); var err = testutil.fetchCapturedStderr(false); var found = false; for (i in text) { if (out.indexOf(text[i]) >= 0) { found = true; break; } } if (!found) { var context = "<b>Context:</b> " + __test_context + "\n<red>Missing STDOUT output:</red>\n"; for (i in text) { context += text[i] + "\n"; } context += "\n<yellow>Actual stdout:</yellow> " + out + "\n<yellow>Actual stderr:</yellow> " + err; testutil.fail(context); } } function EXPECT_STDERR_CONTAINS_ONE_OF(text) { var out = testutil.fetchCapturedStdout(false); var err = testutil.fetchCapturedStderr(false); var found = false; for (i in text) { if (err.indexOf(text[i]) >= 0) { found = true; break; } } if (!found) { var context = "<b>Context:</b> " + __test_context + "\n<red>Missing STDERR output:</red>\n"; for (i in text) { context += text[i] + "\n"; } context += "\n<yellow>Actual stdout:</yellow> " + out + "\n<yellow>Actual stderr:</yellow> " + err; testutil.fail(context); } } function EXPECT_STDERR_CONTAINS(text) { var out = testutil.fetchCapturedStdout(false); var err = testutil.fetchCapturedStderr(false); if (err.indexOf(text) < 0) { var context = "<b>Context:</b> " + __test_context + "\n<red>Missing error output:</red> " + text + "\n<yellow>Actual stdout:</yellow> " + out + "\n<yellow>Actual stderr:</yellow> " + err; testutil.fail(context); } } function __split_trim_join(text) { const needle = '\n'; const s = text.split(needle); s.forEach(function (item, index) { this[index] = item.trimEnd(); }, s); return { 'str': s.join(needle), 'array': s }; } function __check_wildcard_match(expected, actual) { if (0 === expected.length) { return expected == actual; } const needle = '[[*]]'; const strings = expected.split(needle); var start = 0; for (const str of strings) { const idx = actual.indexOf(str, start); if (idx < 0) { return false; } start = idx + str.length; } if (strings[strings.length - 1] === '') { // expected ends with wildcard start = actual.length; } return start === actual.length; } function __multi_value_compare(expected, actual) { const start = expected.indexOf('{{'); if (start < 0) { return __check_wildcard_match(expected, actual); } else { const end = expected.indexOf('}}'); const pre = expected.substring(0, start); const post = expected.substring(end + 2); const opts = expected.substring(start + 2, end); for (const item of opts.split('|')) { if (__check_wildcard_match(pre + item + post, actual)) { return true; } } return false; } } function __find_line_matching(expected, actual_lines, start_at = 0) { var actual_index = start_at; while (actual_index < actual_lines.length) { if (__multi_value_compare(expected, actual_lines[actual_index])) { break; } else { ++actual_index; } } return actual_index; } function __diff_with_error(arr, error, idx) { const needle = '\n'; const copy = [...arr]; copy[idx] += error; return copy.join(needle); } function __check_multiline_expect(expected_lines, actual_lines) { var diff = ''; var matches = true; const needle = '\n'; while ('' === expected_lines[0] || '[[*]]' === expected_lines[0]) { expected_lines.shift(); } var actual_index = __find_line_matching(expected_lines[0], actual_lines); if (actual_index < actual_lines.length) { for (var expected_index = 0; expected_index < expected_lines.length; ++expected_index) { if ('[[*]]' === expected_lines[expected_index]) { if (expected_index == expected_lines.length - 1) { continue; // Ignores [[*]] if at the end of the expectation } else { ++expected_index; actual_index = __find_line_matching(expected_lines[expected_index], actual_lines, actual_index + expected_index - 1); if (actual_index < actual_lines.length) { actual_index -= expected_index; } else { matches = false; diff = __diff_with_error(expected_lines, '<yellow><------ INCONSISTENCY</yellow>', expected_index); break; } } } if ((actual_index + expected_index) >= actual_lines.length) { matches = false; diff = __diff_with_error(expected_lines, '<yellow><------ MISSING</yellow>', expected_index); break; } if (!__multi_value_compare(expected_lines[expected_index], actual_lines[actual_index + expected_index])) { matches = false; diff = __diff_with_error(expected_lines, '<yellow><------ INCONSISTENCY</yellow>', expected_index); break; } } } else { matches = false; diff = __diff_with_error(expected_lines, '<yellow><------ INCONSISTENCY</yellow>', 0); } return { 'matches': matches, 'diff': diff }; } function __check_output_contains_multiline(input_text, output_type) { if (!["both", "err", "out"].includes(output_type)) testutil.fail("EXPECT_CONTAINS_MULTILINE got unknown argument: " + output_type + " allowed: both, err, out"); const out = __split_trim_join(testutil.fetchCapturedStdout(false)); const err = __split_trim_join(testutil.fetchCapturedStderr(false)); var for_match = []; if (typeof (input_text) === "object") { for_match = input_text; } else if (typeof (input_text) === "string") { for_match = [input_text]; } else { testutil.fail("EXPECT_CONTAINS_MULTILINE got unknown argument: " + input_text + " allowed: string or array(string)"); } var found = false; for (i in for_match) { const text = __split_trim_join(for_match[i]); var out_result = { "matches": false, "diff": "" }; var err_result = { "matches": false, "diff": "" }; if (output_type === "both") { out_result = __check_multiline_expect(text.array, out.array); err_result = __check_multiline_expect(text.array, err.array); } else if (output_type === "err") { err_result = __check_multiline_expect(text.array, err.array); } else if (output_type === "out") { out_result = __check_multiline_expect(text.array, out.array); } found = out_result.matches || err_result.matches; if (found) return { "found": found, "out": out, "err": err, "out_result": out_result, "err_result": err_result }; } return { "found": found, "out": out, "err": err, "out_result": { "matches": false, "diff": "" }, "err_result": { "matches": false, "diff": "" } }; } function EXPECT_OUTPUT_CONTAINS_MULTILINE_ONE_OF(t) { var ret = __check_output_contains_multiline(t, "both"); if (!ret.found) { var context = "<b>Context:</b> " + __test_context + "\n<red>Missing output:</red>\n"; if (typeof (t) === "string") { context += text.str; } else { for (i in t) { context += t[i] + "\n"; } } context += "\n<yellow>Actual stdout:</yellow> " + ret.out.str + "\n<yellow>Actual stderr:</yellow> " + ret.err.str + "\n<yellow>Diff with stdout:</yellow>\n" + ret.out_result.diff + "\n<yellow>Diff with stderr:</yellow>\n" + ret.err_result.diff; testutil.fail(context); } } function EXPECT_STDOUT_CONTAINS_MULTILINE_ONE_OF(t) { var ret = __check_output_contains_multiline(t, "out"); if (!ret.found) { var context = "<b>Context:</b> " + __test_context + "\n<red>Missing output:</red>\n"; if (typeof (t) === "string") { context += t; } else { for (i in t) { context += t[i] + "\n"; } } context += "\n<yellow>Actual stdout:</yellow> " + ret.out.str + "\n<yellow>Actual stderr:</yellow> " + ret.err.str + "\n<yellow>Diff with stdout:</yellow>\n" + ret.out_result.diff; testutil.fail(context); } } function EXPECT_STDERR_CONTAINS_MULTILINE_ONE_OF(t) { var ret = __check_output_contains_multiline(t, "err"); if (!ret.found) { var context = "<b>Context:</b> " + __test_context + "\n<red>Missing output:</red>\n"; if (typeof (t) === "string") { context += text.str; } else { for (i in t) { context += t[i] + "\n"; } } context += "\n<yellow>Actual stdout:</yellow> " + ret.out.str + "\n<yellow>Actual stderr:</yellow> " + ret.err.str + "\n<yellow>Diff with stderr:</yellow>\n" + ret.err_result.diff; testutil.fail(context); } } function EXPECT_OUTPUT_CONTAINS_MULTILINE(t) { const out = __split_trim_join(testutil.fetchCapturedStdout(false)); const err = __split_trim_join(testutil.fetchCapturedStderr(false)); const text = __split_trim_join(t); const out_result = __check_multiline_expect(text.array, out.array); const err_result = __check_multiline_expect(text.array, err.array); if (!out_result.matches && !err_result.matches) { const context = "<b>Context:</b> " + __test_context + "\n<red>Missing output:</red> " + text.str + "\n<yellow>Actual stdout:</yellow> " + out.str + "\n<yellow>Actual stderr:</yellow> " + err.str + "\n<yellow>Diff with stdout:</yellow>\n" + out_result.diff + "\n<yellow>Diff with stderr:</yellow>\n" + err_result.diff; testutil.fail(context); } } function EXPECT_STDOUT_CONTAINS_MULTILINE(t) { const out = __split_trim_join(testutil.fetchCapturedStdout(false)); const err = __split_trim_join(testutil.fetchCapturedStderr(false)); const text = __split_trim_join(t); const out_result = __check_multiline_expect(text.array, out.array); if (!out_result.matches) { const context = "<b>Context:</b> " + __test_context + "\n<red>Missing output:</red> " + text.str + "\n<yellow>Actual stdout:</yellow> " + out.str + "\n<yellow>Actual stderr:</yellow> " + err.str + "\n<yellow>Diff with stdout:</yellow>\n" + out_result.diff; testutil.fail(context); } } function EXPECT_STDERR_CONTAINS_MULTILINE(t) { const out = __split_trim_join(testutil.fetchCapturedStdout(false)); const err = __split_trim_join(testutil.fetchCapturedStderr(false)); const text = __split_trim_join(t); const err_result = __check_multiline_expect(text.array, err.array); if (!err_result.matches) { const context = "<b>Context:</b> " + __test_context + "\n<red>Missing output:</red> " + text.str + "\n<yellow>Actual stdout:</yellow> " + out.str + "\n<yellow>Actual stderr:</yellow> " + err.str + "\n<yellow>Diff with stderr:</yellow>\n" + err_result.diff; testutil.fail(context); } } function EXPECT_OUTPUT_MATCHES(re) { var out = testutil.fetchCapturedStdout(false); var err = testutil.fetchCapturedStderr(false); if (!re.test(out) && !re.test(err)) { var context = "<b>Context:</b> " + __test_context + "\n<red>Missing match for:</red> " + re.source + "\n<yellow>Actual stdout:</yellow> " + out + "\n<yellow>Actual stderr:</yellow> " + err; testutil.fail(context); } } function EXPECT_STDOUT_MATCHES(re) { var out = testutil.fetchCapturedStdout(false); var err = testutil.fetchCapturedStderr(false); if (!re.test(out)) { var context = "<b>Context:</b> " + __test_context + "\n<red>Missing match for:</red> " + re.source + "\n<yellow>Actual stdout:</yellow> " + out + "\n<yellow>Actual stderr:</yellow> " + err; testutil.fail(context); } } function EXPECT_STDERR_MATCHES(re) { var out = testutil.fetchCapturedStdout(false); var err = testutil.fetchCapturedStderr(false); if (!re.test(err)) { var context = "<b>Context:</b> " + __test_context + "\n<red>Missing match for:</red> " + re.source + "\n<yellow>Actual stdout:</yellow> " + out + "\n<yellow>Actual stderr:</yellow> " + err; testutil.fail(context); } } function EXPECT_SHELL_LOG_CONTAINS(text) { var log_path = testutil.getShellLogPath(); var match_list = testutil.grepFile(log_path, text); if (match_list.length === 0) { var log_out = testutil.catFile(log_path); var context = "<b>Context:</b> " + __test_context + "\n<red>Missing log output:</red> " + text + "\n<yellow>Actual log output:</yellow> " + log_out; testutil.fail(context); } } function EXPECT_SHELL_LOG_CONTAINS_COUNT(text, count) { if (!(typeof count === 'number')) { throw Error('"count" argument must be a number.'); } var log_path = testutil.getShellLogPath(); var match_list = testutil.grepFile(log_path, text); if (match_list.length !== count) { var log_out = testutil.catFile(log_path); var context = "<b>Context:</b> " + __test_context + "\n<red>Missing log output:</red> " + text + "\n<yellow>Actual log output:</yellow> " + log_out; testutil.fail(context); } } function EXPECT_SHELL_LOG_NOT_CONTAINS(text) { var log_path = testutil.getShellLogPath(); var match_list = testutil.grepFile(log_path, text); if (match_list.length !== 0) { var log_out = testutil.catFile(log_path); var context = "<b>Context:</b> " + __test_context + "\n<red>Unexpected log output:</red> " + text + "\n<yellow>Full log output:</yellow> " + log_out; testutil.fail(context); } } function EXPECT_SHELL_LOG_MATCHES(re) { const log_path = testutil.getShellLogPath(); const log_out = testutil.catFile(log_path); if (!re.test(log_out)) { var context = "<b>Context:</b> " + __test_context + "\n<red>Missing match for:</red> " + re.source + "\n<yellow>Full log output:</yellow> " + log_out; testutil.fail(context); } } function WIPE_SHELL_LOG() { var log_path = testutil.getShellLogPath(); testutil.wipeFileContents(log_path); } function EXPECT_STDOUT_EMPTY() { var out = testutil.fetchCapturedStdout(false); if (out != "") { testutil.fail("<b>Context:</b> " + __test_context + "\n<red>Stdout expected to be empty but contains</red>: " + out); } } function EXPECT_STDERR_EMPTY() { var err = testutil.fetchCapturedStderr(false); if (err != "") { testutil.fail("<b>Context:</b> " + __test_context + "\n<red>Stderr expected to be empty but contains</red>: " + err); } } function EXPECT_OUTPUT_NOT_CONTAINS(text) { var out = testutil.fetchCapturedStdout(false); var err = testutil.fetchCapturedStderr(false); if (out.indexOf(text) >= 0 || err.indexOf(text) >= 0) { var context = "<b>Context:</b> " + __test_context + "\n<red>Unexpected output:</red> " + text + "\n<yellow>Actual stdout:</yellow> " + out + "\n<yellow>Actual stderr:</yellow> " + err; testutil.fail(context); } } function EXPECT_STDOUT_NOT_CONTAINS(text) { var out = testutil.fetchCapturedStdout(false); var err = testutil.fetchCapturedStderr(false); if (out.indexOf(text) >= 0) { var context = "<b>Context:</b> " + __test_context + "\n<red>Unexpected output:</red> " + text + "\n<yellow>Actual stdout:</yellow> " + out + "\n<yellow>Actual stderr:</yellow> " + err; testutil.fail(context); } } function EXPECT_STDERR_NOT_CONTAINS(text) { var out = testutil.fetchCapturedStdout(false); var err = testutil.fetchCapturedStderr(false); if (err.indexOf(text) >= 0) { var context = "<b>Context:</b> " + __test_context + "\n<red>Unexpected output:</red> " + text + "\n<yellow>Actual stdout:</yellow> " + out + "\n<yellow>Actual stderr:</yellow> " + err; testutil.fail(context); } } function EXPECT_NEXT_OUTPUT_EXACT(text) { var line = testutil.fetchCapturedStdout(true); EXPECT_CONTAINS(text, line); } function EXPECT_NEXT_OUTPUT(text) { var line = testutil.fetchCapturedStdout(true); // allow empty lines before the expected text while (line == "\n") line = testutil.fetchCapturedStdout(true); EXPECT_CONTAINS(text, line); } function WIPE_STDOUT() { var line; while ((line = testutil.fetchCapturedStdout(true)) !== ""); } function WIPE_STDERR() { var line; while ((line = testutil.fetchCapturedStderr(true)) !== ""); } function WIPE_OUTPUT() { WIPE_STDOUT(); WIPE_STDERR(); } // -------- InnoDB Cluster Scenarios // Various test scenarios for InnoDB cluster, both normal cases and // common error conditions // ** Non-cluster/non-group standalone instances function StandaloneScenario(ports) { for (i in ports) { testutil.deploySandbox(ports[i], "root", { report_host: hostname }); } // Always connect to the 1st port this.session = shell.connect("mysql://root:root@localhost:" + ports[0]); this.destroy = function () { this.session.close(); for (i in ports) { testutil.destroySandbox(ports[i]); } } return this; } // ** Cluster based scenarios function ClusterScenario(ports, create_cluster_options, sandboxConfiguration) { for (i in ports) { testutil.deploySandbox(ports[i], "root", { report_host: hostname }); if (sandboxConfiguration) { var uri = `root:root@localhost:${ports[i]}`; dba.configureInstance(uri, sandboxConfiguration); } if (testutil.versionCheck(__version, "<", "8.0.0")) { testutil.snapshotSandboxConf(ports[i]); } } this.session = shell.connect("mysql://root:root@localhost:" + ports[0]); if (create_cluster_options) { if (!create_cluster_options.hasOwnProperty("gtidSetIsComplete")) { create_cluster_options["gtidSetIsComplete"] = true; } if (testutil.versionCheck(__version, ">=", "8.0.27") && !create_cluster_options.hasOwnProperty("communicationStack")) { create_cluster_options["communicationStack"] = "XCOM"; } } else { if (testutil.versionCheck(__version, ">=", "8.0.27")) { create_cluster_options = { gtidSetIsComplete: true, communicationStack: "XCOM" }; } else { create_cluster_options = { gtidSetIsComplete: true }; } } this.cluster = dba.createCluster("cluster", create_cluster_options); var allowList = create_cluster_options["ipAllowlist"]; for (i in ports) { if (i > 0) { var uri = "mysql://root:root@localhost:" + ports[i]; if (allowList === undefined) { this.cluster.addInstance(uri); } else { this.cluster.addInstance(uri, { ipAllowlist: allowList }); } testutil.waitMemberState(ports[i], "ONLINE"); if (testutil.versionCheck(__version, "<", "8.0.0")) { dba.configureLocalInstance(uri, { mycnfPath: testutil.getSandboxConfPath(ports[i]) }); } } } this.destroy = function () { this.session.close(); this.cluster.disconnect(); for (i in ports) { testutil.destroySandbox(ports[i]); } } // The following make_* methods will setup a specific scenario from the // cluster. They should be considered mutually exclusive and can be called // only once. // Make this an unmanaged/non-InnoDB cluster GR group // by dropping the metadata this.make_unmanaged = function () { this.session.runSql("DROP SCHEMA mysql_innodb_cluster_metadata"); } // Make the cluster quorumless by killing all but given members and // wait them to become UNREACHABLE this.make_no_quorum = function (survivor_ports) { for (i in ports) { if (survivor_ports.indexOf(ports[i]) < 0) { testutil.killSandbox(ports[i]); var state = testutil.waitMemberState(ports[i], "UNREACHABLE,(MISSING)"); if (state != "UNREACHABLE" && state != "(MISSING)") testutil.fail("Member " + ports[i] + " got into state that was not supposed to: " + state); } } } return this; } function validate_crud_functions(crud, expected) { var actual = dir(crud); // Ensures expected functions are on the actual list var missing = []; for (exp_funct of expected) { var pos = actual.indexOf(exp_funct); if (pos == -1) { missing.push(exp_funct); } else { actual.splice(pos, 1); } } if (missing.length == 0) { print("All expected functions are available\n"); } else { print("Missing Functions:", missing); } // help is ignored cuz it's always available if (actual.length == 0 || (actual.length == 1 && actual[0] == 'help')) { print("No additional functions are available\n") } else { print("Extra Functions:", actual); } } function validateMembers(obj, expected) { var actual = dir(obj); // Ensures expected members are on the actual list var missing = []; for (exp of expected) { var pos = actual.indexOf(exp); if (pos == -1) { missing.push(exp); } else { actual.splice(pos, 1); } } var errors = []; if (missing.length) { errors.push("Missing Members: " + missing.join(", ")); } // help is ignored cuz it's always available if (actual.length > 1 || (actual.length == 1 && actual[0] != 'help')) { errors.push("Extra Members: " + actual.join(", ")); } if (errors.length) { testutil.fail(errors.join("\n")); } } function WARNING_SKIPPED_TEST(reason) { return false; } function get_mysqlx_uris(uri) { var u = shell.parseUri(uri); delete u.scheme; u.port *= 10; u = shell.unparseUri(u); return ["mysqlx://" + u, u]; } function get_mysqlx_endpoint(uri) { const u = shell.parseUri(uri); return shell.unparseUri({ 'host': u.host, 'port': u.port * 10 }); } var protocol_error_msg = "The provided URI uses the X protocol, which is not supported by this command."; function EXPECT_THROWS_ERROR(msg, f, ...args) { EXPECT_THROWS(function () { f(...args); }, msg); } function CHECK_MYSQLX_EXPECT_THROWS_ERROR(msg, f, classic, ...args) { for (const uri of get_mysqlx_uris(classic)) { EXPECT_THROWS_ERROR(msg, f, uri, ...args); } } function EXPECT_DBA_THROWS_PROTOCOL_ERROR(context, f, classic, ...args) { CHECK_MYSQLX_EXPECT_THROWS_ERROR(`${context}: ${protocol_error_msg}`, f, classic, ...args); } function EXPECT_CLUSTER_THROWS_PROTOCOL_ERROR(context, f, classic, ...args) { for (const uri of get_mysqlx_uris(classic)) { const endpoint = get_mysqlx_endpoint(classic); WIPE_OUTPUT(); EXPECT_THROWS_ERROR(`${context}: Could not open connection to '${endpoint}': ${protocol_error_msg}`, f, uri, ...args); EXPECT_STDOUT_CONTAINS(`Unable to connect to the target instance '${endpoint}'. Please verify the connection settings, make sure the instance is available and try again.`); } } function wait(timeout, wait_interval, condition) { waiting = 0; res = condition(); while (!res && waiting < timeout) { os.sleep(wait_interval); waiting = waiting + 1; res = condition(); } return res; } // Starting 8.0.24 the client lib started reporting connection error using // host:port format, previous versions used just the host. // // This function is used to format the host description accordingly. function libmysql_host_description(hostname, port) { if (testutil.versionCheck(__libmysql_version_id, ">", "8.0.23")) { return hostname + ":" + port; } return hostname; } function clone_installed(session) { var row = session.runSql("select plugin_status from information_schema.plugins where plugin_name='clone'").fetchOne(); if (row) return row[0] == "ACTIVE"; return false; } function snapshot_tables(session, tables) { snapshot = {}; for (tbl of tables) { res = session.runSql(`select * from ${tbl} use index (primary)`).fetchAll(); snapshot[tbl] = res; } return snapshot; } function CHECK_TABLE_SNAPSHOTS(session, snapshot) { for (tbl in snapshot) { before = snapshot[tbl]; after = session.runSql(`select * from ${tbl} use index (primary)`).fetchAll(); if (before.length == after.length) { for (i in before) { EXPECT_EQ(before[i], after[i], `${tbl}:${i}`) } } else { EXPECT_EQ(before, after, tbl); } } }