Add new commands to mysqltest - to make it easier to write portable test scripts

parent 98e3552a
......@@ -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 \
......
......@@ -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));
......@@ -1505,6 +1613,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
......@@ -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;
......
......@@ -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
......@@ -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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment