diff options
-rw-r--r-- | client/Makefile.am | 3 | ||||
-rw-r--r-- | client/mysqltest.c | 467 | ||||
-rw-r--r-- | mysql-test/r/mysqltest.result | 11 | ||||
-rw-r--r-- | mysql-test/t/mysqltest.test | 75 |
4 files changed, 500 insertions, 56 deletions
diff --git a/client/Makefile.am b/client/Makefile.am index ff97243815a..94174614d30 100644 --- a/client/Makefile.am +++ b/client/Makefile.am @@ -46,7 +46,8 @@ mysqladmin_SOURCES = mysqladmin.cc mysql_LDADD = @readline_link@ @TERMCAP_LIB@ $(LDADD) $(CXXLDFLAGS) mysqltest_SOURCES= mysqltest.c $(top_srcdir)/mysys/my_getsystime.c \ $(yassl_dummy_link_fix) -mysqltest_LDADD = $(top_builddir)/regex/libregex.a $(LDADD) +mysqltest_LDADD = $(top_builddir)/regex/libregex.a $(LDADD) \ + $(top_builddir)/mysys/libmysys.a mysqlbinlog_SOURCES = mysqlbinlog.cc $(top_srcdir)/mysys/mf_tempdir.c \ $(top_srcdir)/mysys/my_new.cc \ $(top_srcdir)/mysys/my_bit.c \ diff --git a/client/mysqltest.c b/client/mysqltest.c index c3db4cf9659..56c06a10f5b 100644 --- a/client/mysqltest.c +++ b/client/mysqltest.c @@ -24,7 +24,7 @@ * Jani **/ -#define MTEST_VERSION "2.6" +#define MTEST_VERSION "2.7" #include <my_global.h> #include <mysql_embed.h> @@ -333,7 +333,8 @@ Q_EXIT, Q_DISABLE_RECONNECT, Q_ENABLE_RECONNECT, Q_IF, Q_DISABLE_PARSING, Q_ENABLE_PARSING, -Q_REPLACE_REGEX, +Q_REPLACE_REGEX, Q_REMOVE_FILE, Q_FILE_EXIST, +Q_WRITE_FILE, Q_COPY_FILE, Q_PERL, Q_UNKNOWN, /* Unknown command. */ Q_COMMENT, /* Comments, ignored. */ @@ -423,6 +424,11 @@ const char *command_names[]= "disable_parsing", "enable_parsing", "replace_regex", + "remove_file", + "file_exists", + "write_file", + "copy_file", + "perl", 0 }; @@ -468,7 +474,7 @@ static void replace_strings_append(struct st_replace *rep, DYNAMIC_STRING* ds, const char *from, int len); void free_pointer_array(POINTER_ARRAY *pa); static void do_eval(DYNAMIC_STRING *query_eval, const char *query, - my_bool pass_through_escape_chars); + const char* query_end, my_bool pass_through_escape_chars); static void str_to_file(const char *fname, char *str, int size); #ifdef __WIN__ @@ -501,8 +507,8 @@ static void handle_error(const char *query, struct st_query *q, const char *err_sqlstate, DYNAMIC_STRING *ds); static void handle_no_error(struct st_query *q); -static void do_eval(DYNAMIC_STRING* query_eval, const char *query, - my_bool pass_through_escape_chars) +static void do_eval(DYNAMIC_STRING *query_eval, const char *query, + const char *query_end, my_bool pass_through_escape_chars) { const char *p; register char c, next_c; @@ -510,7 +516,7 @@ static void do_eval(DYNAMIC_STRING* query_eval, const char *query, VAR* v; DBUG_ENTER("do_eval"); - for (p= query; (c = *p); ++p) + for (p= query; (c = *p) && p < query_end; ++p) { switch(c) { case '$': @@ -533,9 +539,9 @@ static void do_eval(DYNAMIC_STRING* query_eval, const char *query, escaped = 0; dynstr_append_mem(query_eval, p, 1); } - else if (next_c == '\\' || next_c == '$') + else if (next_c == '\\' || next_c == '$' || next_c == '"') { - /* Set escaped only if next char is \ or $ */ + /* Set escaped only if next char is \, " or $ */ escaped = 1; if (pass_through_escape_chars) @@ -556,6 +562,108 @@ static void do_eval(DYNAMIC_STRING* query_eval, const char *query, } +enum arg_type +{ + ARG_STRING, + ARG_REST +}; + +struct command_arg { + const char* argname; /* Name of argument */ + enum arg_type type; /* Type of argument */ + my_bool required; /* Argument required */ + DYNAMIC_STRING *ds; /* Storage for string argument */ + const char *description; /* Description of the argument */ +}; + +static void check_command_args(struct st_query *command, const char *arguments, + const struct command_arg *args, int num_args) +{ + int i; + const char *ptr= arguments; + const char *start; + + DBUG_ENTER("check_command_args"); + DBUG_PRINT("enter", ("num_args: %d", num_args)); + for (i= 0; i < num_args; i++) + { + const struct command_arg *arg= &args[i]; + + switch (arg->type) + { + /* A string surrounded by spaces */ + case ARG_STRING: + start= ptr; + /* Find end of arg */ + while (*ptr && !my_isspace(charset_info, *ptr)) + ptr++; + init_dynamic_string(arg->ds, 0, 256, 256); + do_eval(arg->ds, start, ptr, FALSE); + command->last_argument= (char*)ptr; + if (*ptr) + ptr++; + break; + + /* Rest of line */ + case ARG_REST: + start= ptr; + init_dynamic_string(arg->ds, 0, command->query_len, 256); + do_eval(arg->ds, start, command->end, FALSE); + command->last_argument= command->end; + break; + + default: + DBUG_ASSERT("Unknown argument type"); + break; + } + + /* Check required arg */ + if (arg->ds->length == 0 && arg->required) + die("Missing required argument '%s' to command '%.*s'", arg->argname, + command->first_word_len, command->query); + + } + DBUG_VOID_RETURN; +} + + +static void handle_command_error(struct st_query *command, uint error) +{ + DBUG_ENTER("handle_command_error"); + DBUG_PRINT("enter", ("error: %d", error)); + if (error != 0) + { + uint i; + + if (command->abort_on_error) + die("command \"%.*s\" failed", command->first_word_len, command->query); + for (i= 0; i < command->expected_errors; i++) + { + DBUG_PRINT("info", ("expected error: %d", + command->expected_errno[i].code.errnum)); + if ((command->expected_errno[i].type == ERR_ERRNO) && + (command->expected_errno[i].code.errnum == error)) + { + DBUG_PRINT("info", ("command \"%.*s\" failed with expected error: %d", + command->first_word_len, command->query, error)); + DBUG_VOID_RETURN; + } + } + die("command \"%.*s\" failed with wrong error: %d", + command->first_word_len, command->query, error); + } + else if (command->expected_errno[0].type == ERR_ERRNO && + command->expected_errno[0].code.errnum != 0) + { + /* Error code we wanted was != 0, i.e. not an expected success */ + die("command \"%.*s\" succeeded - should have failed with errno %d...", + command->first_word_len, command->query, + command->expected_errno[0].code.errnum); + } + DBUG_VOID_RETURN; +} + + static void close_cons() { DBUG_ENTER("close_cons"); @@ -743,7 +851,7 @@ static int dyn_string_cmp(DYNAMIC_STRING* ds, const char *fname) init_dynamic_string(&res_ds, "", stat_info.st_size+256, 256); if (eval_result) { - do_eval(&res_ds, tmp, FALSE); + do_eval(&res_ds, tmp, tmp + stat_info.st_size, FALSE); res_ptr= res_ds.str; res_len= res_ds.length; if (res_len != ds->length) @@ -1168,7 +1276,7 @@ static void do_exec(struct st_query *query) init_dynamic_string(&ds_cmd, 0, query->query_len+256, 256); /* Eval the command, thus replacing all environment variables */ - do_eval(&ds_cmd, cmd, TRUE); + do_eval(&ds_cmd, cmd, query->end, TRUE); cmd= ds_cmd.str; DBUG_PRINT("info", ("Executing '%s' as '%s'", @@ -1484,7 +1592,7 @@ void do_system(struct st_query *command) init_dynamic_string(&ds_cmd, 0, command->query_len + 64, 256); /* Eval the system command, thus replacing all environment variables */ - do_eval(&ds_cmd, command->first_argument, TRUE); + do_eval(&ds_cmd, command->first_argument, command->end, TRUE); DBUG_PRINT("info", ("running system command '%s' as '%s'", command->first_argument, ds_cmd.str)); @@ -1506,6 +1614,291 @@ void do_system(struct st_query *command) /* + SYNOPSIS + do_remove_file + command called command + + DESCRIPTION + remove_file <file_name> + Remove the file <file_name> +*/ + +static void do_remove_file(struct st_query *command) +{ + int error; + DYNAMIC_STRING ds_filename; + const struct command_arg rm_args[] = { + "filename", ARG_STRING, TRUE, &ds_filename, "File to delete" + }; + DBUG_ENTER("do_remove_file"); + + check_command_args(command, command->first_argument, + rm_args, sizeof(rm_args)/sizeof(struct command_arg)); + + DBUG_PRINT("info", ("removing file: %s", ds_filename.str)); + error= my_delete(ds_filename.str, MYF(0)) != 0; + handle_command_error(command, error); + dynstr_free(&ds_filename); + DBUG_VOID_RETURN; +} + + +/* + SYNOPSIS + do_copy_file + command command handle + + DESCRIPTION + copy_file <from_file> <to_file> + Copy <from_file> to <to_file> + + NOTE! Will fail if <to_file> exists +*/ + +static void do_copy_file(struct st_query *command) +{ + int error; + DYNAMIC_STRING ds_from_file; + DYNAMIC_STRING ds_to_file; + const struct command_arg copy_file_args[] = { + "from_file", ARG_STRING, TRUE, &ds_from_file, "Filename to copy from", + "to_file", ARG_STRING, TRUE, &ds_to_file, "Filename to copy to" + }; + DBUG_ENTER("do_copy_file"); + + check_command_args(command, command->first_argument, + copy_file_args, sizeof(copy_file_args)/sizeof(struct command_arg)); + + DBUG_PRINT("info", ("Copy %s to %s", ds_from_file.str, ds_to_file.str)); + error= (my_copy(ds_from_file.str, ds_to_file.str, + MYF(MY_DONT_OVERWRITE_FILE)) != 0); + handle_command_error(command, error); + dynstr_free(&ds_from_file); + dynstr_free(&ds_to_file); + DBUG_VOID_RETURN; +} + + +/* + SYNOPSIS + do_file_exists + command called command + + DESCRIPTION + fiile_exist <file_name> + Check if file <file_name> exists +*/ + +static void do_file_exist(struct st_query *command) +{ + int error; + DYNAMIC_STRING ds_filename; + const struct command_arg file_exist_args[] = { + "filename", ARG_STRING, TRUE, &ds_filename, "File to check if it exist" + }; + DBUG_ENTER("do_file_exist"); + + check_command_args(command, command->first_argument, + file_exist_args, sizeof(file_exist_args)/sizeof(struct command_arg)); + + DBUG_PRINT("info", ("Checking for existence of file: %s", ds_filename.str)); + error= (access(ds_filename.str, F_OK) != 0); + handle_command_error(command, error); + dynstr_free(&ds_filename); + DBUG_VOID_RETURN; +} + + +/* + Read characters from line buffer or file. This is needed to allow + my_ungetc() to buffer MAX_DELIMITER characters for a file + + NOTE: + This works as long as one doesn't change files (with 'source file_name') + when there is things pushed into the buffer. This should however not + happen for any tests in the test suite. +*/ + +static int my_getc(FILE *file) +{ + if (line_buffer_pos == line_buffer) + return fgetc(file); + return *--line_buffer_pos; +} + + +static void my_ungetc(int c) +{ + *line_buffer_pos++= (char) c; +} + + +static my_bool match_delimiter(int c, const char* delim, uint length) +{ + uint i; + char tmp[MAX_DELIMITER]; + + if (c != *delim) + return 0; + + for (i= 1; i < length && + (c= my_getc(cur_file->file)) == *(delim + i); + i++) + tmp[i]= c; + + if (i == length) + return 1; /* Found delimiter */ + + /* didn't find delimiter, push back things that we read */ + my_ungetc(c); + while (i > 1) + my_ungetc(tmp[--i]); + return 0; +} + + +static void read_until_EOF(DYNAMIC_STRING* ds) +{ + int c; + DBUG_ENTER("read_until_EOF"); + + /* Read from file until delimiter EOF is found */ + while (1) + { + c= my_getc(cur_file->file); + + if (feof(cur_file->file)) + die("End of file encountered before 'EOF' delimiter was found"); + + if (match_delimiter(c, "EOF", 3)) + { + DBUG_PRINT("exit", ("Found EOF")); + break; + } + dynstr_append_mem(ds, (const char*)&c, 1); + } + DBUG_PRINT("exit", ("ds: %s", ds->str)); + DBUG_VOID_RETURN; +} + + +/* + SYNOPSIS + do_write_file + command called command + + DESCRIPTION + write_file <file_name>; + <what to write line 1> + <...> + < what to write line n> + EOF + + --write_file <file_name>; + <what to write line 1> + <...> + < what to write line n> + EOF + + Write everything between the "write_file" command and EOF to "file_name" + + NOTE! Overwrites existing file + +*/ + +static void do_write_file(struct st_query *command) +{ + DYNAMIC_STRING ds_content; + DYNAMIC_STRING ds_filename; + const struct command_arg write_file_args[] = { + "filename", ARG_STRING, TRUE, &ds_filename, "File to write to", + }; + DBUG_ENTER("do_write_file"); + + check_command_args(command, + command->first_argument, + write_file_args, + sizeof(write_file_args)/sizeof(struct command_arg)); + + init_dynamic_string(&ds_content, "", 1024, 1024); + read_until_EOF(&ds_content); + DBUG_PRINT("info", ("Writing to file: %s", ds_filename.str)); + str_to_file(ds_filename.str, ds_content.str, ds_content.length); + dynstr_free(&ds_content); + dynstr_free(&ds_filename); + DBUG_VOID_RETURN; +} + + +/* + SYNOPSIS + do_perl + command command handle + + DESCRIPTION + perl; + <perlscript line 1> + <...> + <perlscript line n> + EOF + + Execute everything after "perl" until EOF as perl. + Useful for doing more advanced things + but still being able to execute it on all platforms. + + The function sets delimiter to EOF and remembers that this + is a perl command by setting "perl mode". The following lines + will then be parsed as any normal query, but when searching + for command in get_query_type, this function will be called + again since "perl mode" is on and the perl script can be + executed. +*/ + +static void do_perl(struct st_query *command) +{ + int error; + char buf[FN_REFLEN]; + FILE *res_file; + DYNAMIC_STRING ds_script; + DBUG_ENTER("do_perl"); + + init_dynamic_string(&ds_script, "", 1024, 1024); + read_until_EOF(&ds_script); + + DBUG_PRINT("info", ("Executing perl: %s", ds_script.str)); + + /* Format a name for a tmp .pl file that is unique for this process */ + my_snprintf(buf, sizeof(buf), "%s/tmp/tmp_%d.pl", + getenv("MYSQLTEST_VARDIR"), getpid()); + str_to_file(buf, ds_script.str, ds_script.length); + + /* Format the perl <filename> command */ + my_snprintf(buf, sizeof(buf), "perl %s/tmp/tmp_%d.pl", + getenv("MYSQLTEST_VARDIR"), getpid()); + + if (!(res_file= popen(buf, "r")) && command->abort_on_error) + die("popen(\"%s\", \"r\") failed", buf); + + while (fgets(buf, sizeof(buf), res_file)) + { + if (disable_result_log) + { + buf[strlen(buf)-1]=0; + DBUG_PRINT("exec_result",("%s", buf)); + } + else + { + replace_dynstr_append(&ds_res, buf); + } + } + error= pclose(res_file); + handle_command_error(command, WEXITSTATUS(error)); + dynstr_free(&ds_script); + DBUG_VOID_RETURN; +} + + +/* Print the content between echo and <delimiter> to result file. Evaluate all variables in the string before printing, allow for variable names to be escaped using \ @@ -1534,7 +1927,7 @@ int do_echo(struct st_query *command) DYNAMIC_STRING ds_echo; init_dynamic_string(&ds_echo, "", command->query_len, 256); - do_eval(&ds_echo, command->first_argument, FALSE); + do_eval(&ds_echo, command->first_argument, command->end, FALSE); dynstr_append_mem(&ds_res, ds_echo.str, ds_echo.length); dynstr_append_mem(&ds_res, "\n", 1); dynstr_free(&ds_echo); @@ -2900,50 +3293,9 @@ void do_block(enum block_cmd cmd, struct st_query* q) } -/* - Read characters from line buffer or file. This is needed to allow - my_ungetc() to buffer MAX_DELIMITER characters for a file - - NOTE: - This works as long as one doesn't change files (with 'source file_name') - when there is things pushed into the buffer. This should however not - happen for any tests in the test suite. -*/ - -int my_getc(FILE *file) -{ - if (line_buffer_pos == line_buffer) - return fgetc(file); - return *--line_buffer_pos; -} - -void my_ungetc(int c) -{ - *line_buffer_pos++= (char) c; -} - - my_bool end_of_query(int c) { - uint i; - char tmp[MAX_DELIMITER]; - - if (c != *delimiter) - return 0; - - for (i= 1; i < delimiter_length && - (c= my_getc(cur_file->file)) == *(delimiter + i); - i++) - tmp[i]= c; - - if (i == delimiter_length) - return 1; /* Found delimiter */ - - /* didn't find delimiter, push back things that we read */ - my_ungetc(c); - while (i > 1) - my_ungetc(tmp[--i]); - return 0; + return match_delimiter(c, delimiter, delimiter_length); } @@ -4786,7 +5138,7 @@ static void run_query(MYSQL *mysql, struct st_query *command, int flags) if (command->type == Q_EVAL) { init_dynamic_string(&eval_query, "", command->query_len+256, 1024); - do_eval(&eval_query, command->query, FALSE); + do_eval(&eval_query, command->query, command->end, FALSE); query = eval_query.str; query_len = eval_query.length; } @@ -5390,6 +5742,11 @@ int main(int argc, char **argv) case Q_DEC: do_modify_var(q, DO_DEC); break; case Q_ECHO: do_echo(q); query_executed= 1; break; case Q_SYSTEM: do_system(q); break; + case Q_REMOVE_FILE: do_remove_file(q); break; + case Q_FILE_EXIST: do_file_exist(q); break; + case Q_WRITE_FILE: do_write_file(q); break; + case Q_COPY_FILE: do_copy_file(q); break; + case Q_PERL: do_perl(q); break; case Q_DELIMITER: do_delimiter(q); break; diff --git a/mysql-test/r/mysqltest.result b/mysql-test/r/mysqltest.result index 4c2695e5caf..fdc43e8852b 100644 --- a/mysql-test/r/mysqltest.result +++ b/mysql-test/r/mysqltest.result @@ -467,3 +467,14 @@ a D 1 1 1 4 drop table t1; +mysqltest: At line 1: Missing required argument 'filename' to command 'remove_file' +mysqltest: At line 1: Missing required argument 'filename' to command 'write_file' +mysqltest: At line 1: End of file encountered before 'EOF' delimiter was found +mysqltest: At line 1: End of line junk detected: "write_file filename "; +" +mysqltest: At line 1: Missing required argument 'filename' to command 'file_exists' +mysqltest: At line 1: Missing required argument 'from_file' to command 'copy_file' +mysqltest: At line 1: Missing required argument 'to_file' to command 'copy_file' +hello +hello +hello diff --git a/mysql-test/t/mysqltest.test b/mysql-test/t/mysqltest.test index 86cfd66ae2b..8aa85937c17 100644 --- a/mysql-test/t/mysqltest.test +++ b/mysql-test/t/mysqltest.test @@ -1159,3 +1159,78 @@ insert into t1 values (2,4); --replace_regex /A/C/ /B/D/i /3/2/ /2/1/ select * from t1; drop table t1; + +# ---------------------------------------------------------------------------- +# test for remove_file +# ---------------------------------------------------------------------------- + +--error 1 +--exec echo "remove_file ;" | $MYSQL_TEST 2>&1 + +--error 1 +remove_file non_existing_file; + +# ---------------------------------------------------------------------------- +# test for write_file +# ---------------------------------------------------------------------------- +--error 1 +--exec echo "write_file ;" | $MYSQL_TEST 2>&1 + +--error 1 +--exec echo "write_file filename ;" | $MYSQL_TEST 2>&1 + +--error 1 +--exec echo "write_file filename \";" | $MYSQL_TEST 2>&1 + +# ---------------------------------------------------------------------------- +# test for file_exist +# ---------------------------------------------------------------------------- +--error 1 +--exec echo "file_exists ;" | $MYSQL_TEST 2>&1 + +--error 0,1 +remove_file $MYSQLTEST_VARDIR/tmp/test_file1.tmp; +--error 1 +file_exists $MYSQLTEST_VARDIR/tmp/test_file1.tmp; +write_file $MYSQLTEST_VARDIR/tmp/test_file1.tmp; +Content for test_file1 +EOF +file_exists $MYSQLTEST_VARDIR/tmp/test_file1.tmp; +remove_file $MYSQLTEST_VARDIR/tmp/test_file1.tmp; +--error 1 +file_exists $MYSQLTEST_VARDIR/tmp/test_file1.tmp; + + +# ---------------------------------------------------------------------------- +# test for copy_file +# ---------------------------------------------------------------------------- +--write_file $MYSQLTEST_VARDIR/tmp/file1.tmp +file1 +EOF + +copy_file $MYSQLTEST_VARDIR/tmp/file1.tmp $MYSQLTEST_VARDIR/tmp/file2.tmp; +file_exists $MYSQLTEST_VARDIR/tmp/file2.tmp; +remove_file $MYSQLTEST_VARDIR/tmp/file1.tmp; +remove_file $MYSQLTEST_VARDIR/tmp/file2.tmp; + +--error 1 +--exec echo "copy_file ;" | $MYSQL_TEST 2>&1 + +--error 1 +--exec echo "copy_file from_file;" | $MYSQL_TEST 2>&1 + +# ---------------------------------------------------------------------------- +# test for perl +# ---------------------------------------------------------------------------- +--perl +print "hello\n"; +EOF + +perl; +print "hello\n"; +EOF + +perl; + # Print "hello" + print "hello\n"; +EOF |