diff --git a/config/ac-macros/plugins.m4 b/config/ac-macros/plugins.m4 index cfc5f8dbcbe..791405f8fbd 100644 --- a/config/ac-macros/plugins.m4 +++ b/config/ac-macros/plugins.m4 @@ -280,6 +280,7 @@ AC_DEFUN([MYSQL_CONFIGURE_PLUGINS],[ _MYSQL_EMIT_PLUGIN_ACTIONS(m4_bpatsubst(__mysql_plugin_list__, :, [,])) AC_SUBST([mysql_se_dirs]) AC_SUBST([mysql_pg_dirs]) + AC_SUBST([mysql_se_unittest_dirs]) ]) ]) ]) @@ -315,6 +316,7 @@ AC_DEFUN([__MYSQL_EMIT_CHECK_PLUGIN],[ ]) AC_MSG_CHECKING([whether to use ]$3) mysql_use_plugin_dir="" + mysql_use_plugin_unittest_dir="" m4_ifdef([$10],[ if test "X[$mysql_plugin_]$2" = Xyes -a \ "X[$with_plugin_]$2" != Xno -o \ @@ -407,10 +409,24 @@ dnl Although this is "pretty", it breaks libmysqld build m4_syscmd(test -f "$6/configure") ifelse(m4_sysval, 0, [AC_CONFIG_SUBDIRS($6)], - [AC_CONFIG_FILES($6/Makefile)] + [ + AC_CONFIG_FILES($6/Makefile) + m4_syscmd(test -d "unittest/$6") + ifelse(m4_sysval, 0, + [ + mysql_use_plugin_unittest_dir="$6" + AC_CONFIG_FILES(unittest/$6/Makefile) + ], []) + ] ) ifelse(m4_substr($6, 0, 8), [storage/], - [mysql_se_dirs="$mysql_se_dirs ]m4_substr($6, 8)", + [ + [mysql_se_name="]m4_substr($6, 8)" + mysql_se_dirs="$mysql_se_dirs $mysql_se_name" + if test -n "$mysql_use_plugin_unittest_dir" ; then + mysql_se_unittest_dirs="$mysql_se_unitest_dirs $mysql_se_name" + fi + ], m4_substr($6, 0, 7), [plugin/], [mysql_pg_dirs="$mysql_pg_dirs ]m4_substr($6, 7)", [AC_FATAL([don't know how to handle plugin dir ]$6)]) diff --git a/configure.in b/configure.in index f0cdcde1abb..61c2deb5b5e 100644 --- a/configure.in +++ b/configure.in @@ -2482,6 +2482,7 @@ AC_SUBST(MAKE_BINARY_DISTRIBUTION_OPTIONS) AC_CONFIG_FILES(Makefile extra/Makefile mysys/Makefile dnl unittest/Makefile unittest/mytap/Makefile unittest/mytap/t/Makefile dnl unittest/mysys/Makefile unittest/examples/Makefile dnl + unittest/storage/Makefile dnl strings/Makefile regex/Makefile storage/Makefile dnl man/Makefile BUILD/Makefile vio/Makefile dnl libmysql/Makefile client/Makefile dnl diff --git a/storage/maria/Makefile.am b/storage/maria/Makefile.am index f5678a55266..10204226742 100644 --- a/storage/maria/Makefile.am +++ b/storage/maria/Makefile.am @@ -47,7 +47,7 @@ maria_pack_LDADD= @CLIENT_EXTRA_LDFLAGS@ libmaria.a \ $(top_builddir)/mysys/libmysys.a \ $(top_builddir)/dbug/libdbug.a \ $(top_builddir)/strings/libmystrings.a @ZLIB_LIBS@ -noinst_PROGRAMS = ma_test1 ma_test2 ma_test3 ma_rt_test ma_sp_test ma_control_file_test +noinst_PROGRAMS = ma_test1 ma_test2 ma_test3 ma_rt_test ma_sp_test noinst_HEADERS = maria_def.h ma_rt_index.h ma_rt_key.h ma_rt_mbr.h \ ma_sp_defs.h ma_fulltext.h ma_ftdefs.h ma_ft_test1.h ma_ft_eval.h \ ma_control_file.h ha_maria.h @@ -89,12 +89,6 @@ ma_sp_test_LDADD= @CLIENT_EXTRA_LDFLAGS@ libmaria.a \ $(top_builddir)/mysys/libmysys.a \ $(top_builddir)/dbug/libdbug.a \ $(top_builddir)/strings/libmystrings.a @ZLIB_LIBS@ -ma_control_file_test_DEPENDENCIES= $(LIBRARIES) -ma_control_file_test_LDADD= @CLIENT_EXTRA_LDFLAGS@ libmaria.a \ - $(top_builddir)/storage/myisam/libmyisam.a \ - $(top_builddir)/mysys/libmysys.a \ - $(top_builddir)/dbug/libdbug.a \ - $(top_builddir)/strings/libmystrings.a @ZLIB_LIBS@ libmaria_a_SOURCES = ma_init.c ma_open.c ma_extra.c ma_info.c ma_rkey.c \ ma_rnext.c ma_rnext_same.c \ ma_search.c ma_page.c ma_key.c ma_locking.c \ @@ -113,7 +107,7 @@ libmaria_a_SOURCES = ma_init.c ma_open.c ma_extra.c ma_info.c ma_rkey.c \ ha_maria.cc \ ma_rt_index.c ma_rt_key.c ma_rt_mbr.c ma_rt_split.c \ ma_sp_key.c ma_control_file.c -CLEANFILES = test?.MA? FT?.MA? isam.log ma_test_all ma_rt_test.MA? sp_test.MA? maria_control +CLEANFILES = test?.MA? FT?.MA? isam.log ma_test_all ma_rt_test.MA? sp_test.MA? SUFFIXES = .sh diff --git a/storage/maria/ma_control_file.c b/storage/maria/ma_control_file.c index 5861246baf9..5fbb0a084df 100644 --- a/storage/maria/ma_control_file.c +++ b/storage/maria/ma_control_file.c @@ -41,7 +41,7 @@ uint32 last_logno; Control file is less then 512 bytes (a disk sector), to be as atomic as possible */ -static int control_file_fd; +static int control_file_fd= -1; static void lsn8store(char *buffer, const LSN *lsn) { @@ -87,15 +87,16 @@ static char simple_checksum(char *buffer, uint size) RETURN 0 - OK - 1 - Error + 1 - Error (in which case the file is left closed) */ -int ma_control_file_create_or_open() +CONTROL_FILE_ERROR ma_control_file_create_or_open() { char buffer[CONTROL_FILE_SIZE]; char name[FN_REFLEN]; MY_STAT stat_buff; my_bool create_file; int open_flags= O_BINARY | /*O_DIRECT |*/ O_RDWR; + int error= CONTROL_FILE_UNKNOWN_ERROR; DBUG_ENTER("ma_control_file_create_or_open"); /* @@ -106,16 +107,19 @@ int ma_control_file_create_or_open() DBUG_ASSERT(CONTROL_FILE_LSN_SIZE == (4+4)); DBUG_ASSERT(CONTROL_FILE_FILENO_SIZE == 4); - /* name is concatenation of Maria's home dir and "control" */ - if (fn_format(name, "control", maria_data_root, "", MYF(MY_WME)) == NullS) - DBUG_RETURN(1); + if (control_file_fd >= 0) /* already open */ + DBUG_RETURN(0); + + if (fn_format(name, CONTROL_FILE_BASE_NAME, + maria_data_root, "", MYF(MY_WME)) == NullS) + DBUG_RETURN(CONTROL_FILE_UNKNOWN_ERROR); create_file= test(my_access(name,F_OK)); if (create_file) { if ((control_file_fd= my_create(name, 0, open_flags, MYF(0))) < 0) - DBUG_RETURN(1); + DBUG_RETURN(CONTROL_FILE_UNKNOWN_ERROR); /* TODO: from "man fsync" on Linux: "fsync does not necessarily ensure that the entry in the directory @@ -127,10 +131,10 @@ int ma_control_file_create_or_open() To be safer we should make sure that there are no logs or data/index files around (indeed it could be that the control file alone was deleted or not restored, and we should not go on with life at this point). - + TODO: For now we trust (this is alpha version), but for beta if would be great to verify. - + We could have a tool which can rebuild the control file, by reading the directory of logs, finding the newest log, reading it to find last checkpoint... Slow but can save your db. @@ -138,7 +142,7 @@ int ma_control_file_create_or_open() LSN imposs_lsn= CONTROL_FILE_IMPOSSIBLE_LSN; uint32 imposs_logno= CONTROL_FILE_IMPOSSIBLE_FILENO; - + /* init the file with these "undefined" values */ DBUG_RETURN(ma_control_file_write_and_force(&imposs_lsn, imposs_logno, CONTROL_FILE_UPDATE_ALL)); @@ -147,12 +151,12 @@ int ma_control_file_create_or_open() /* Otherwise, file exists */ if ((control_file_fd= my_open(name, open_flags, MYF(MY_WME))) < 0) - DBUG_RETURN(1); - - if (my_stat(name, &stat_buff, MYF(MY_WME)) == NULL) - DBUG_RETURN(1); + goto err; - if ((uint)stat_buff.st_size != CONTROL_FILE_SIZE) + if (my_stat(name, &stat_buff, MYF(MY_WME)) == NULL) + goto err; + + if ((uint)stat_buff.st_size < CONTROL_FILE_SIZE) { /* Given that normally we write only a sector and it's atomic, the only @@ -165,31 +169,43 @@ int ma_control_file_create_or_open() disk/filesystem has a problem. So let's be rigid. */ - my_message(0, "wrong file size", MYF(0)); /* TODO: improve errors */ - my_error(HA_ERR_CRASHED, MYF(0), name); - DBUG_RETURN(1); + my_message(0, "too small file", MYF(0)); /* TODO: improve errors */ + error= CONTROL_FILE_TOO_SMALL; + goto err; + } + + if ((uint)stat_buff.st_size > CONTROL_FILE_SIZE) + { + my_message(0, "too big file", MYF(0)); /* TODO: improve errors */ + error= CONTROL_FILE_TOO_BIG; + goto err; } if (my_read(control_file_fd, buffer, CONTROL_FILE_SIZE, MYF(MY_FNABP | MY_WME))) - DBUG_RETURN(1); + goto err; if (memcmp(buffer + CONTROL_FILE_MAGIC_STRING_OFFSET, CONTROL_FILE_MAGIC_STRING, CONTROL_FILE_MAGIC_STRING_SIZE)) { my_message(0, "bad magic string", MYF(0)); - DBUG_RETURN(1); + error= CONTROL_FILE_BAD_MAGIC_STRING; + goto err; } if (simple_checksum(buffer + CONTROL_FILE_LSN_OFFSET, CONTROL_FILE_SIZE - CONTROL_FILE_LSN_OFFSET) != buffer[CONTROL_FILE_CHECKSUM_OFFSET]) { my_message(0, "checksum mismatch", MYF(0)); - DBUG_RETURN(1); + error= CONTROL_FILE_BAD_CHECKSUM; + goto err; } last_checkpoint_lsn= lsn8korr(buffer + CONTROL_FILE_LSN_OFFSET); last_logno= uint4korr(buffer + CONTROL_FILE_FILENO_OFFSET); DBUG_RETURN(0); +err: + ma_control_file_end(); + DBUG_RETURN(error); } @@ -227,6 +243,8 @@ int ma_control_file_write_and_force(const LSN *checkpoint_lsn, uint32 logno, my_bool update_checkpoint_lsn= FALSE, update_logno= FALSE; DBUG_ENTER("ma_control_file_write_and_force"); + DBUG_ASSERT(control_file_fd >= 0); /* must be open */ + memcpy(buffer + CONTROL_FILE_MAGIC_STRING_OFFSET, CONTROL_FILE_MAGIC_STRING, CONTROL_FILE_MAGIC_STRING_SIZE); @@ -259,7 +277,7 @@ int ma_control_file_write_and_force(const LSN *checkpoint_lsn, uint32 logno, 0, MYF(MY_FNABP | MY_WME)) || my_sync(control_file_fd, MYF(MY_WME))) DBUG_RETURN(1); - + /* TODO: you need some protection to be able to write last_* global vars */ if (update_checkpoint_lsn) last_checkpoint_lsn= *checkpoint_lsn; @@ -277,15 +295,26 @@ int ma_control_file_write_and_force(const LSN *checkpoint_lsn, uint32 logno, ma_control_file_end() */ -void ma_control_file_end() +int ma_control_file_end() { + int close_error; DBUG_ENTER("ma_control_file_end"); - my_close(control_file_fd, MYF(MY_WME)); + + if (control_file_fd < 0) /* already closed */ + DBUG_RETURN(0); + + close_error= my_close(control_file_fd, MYF(MY_WME)); + /* + As my_close() frees structures even if close() fails, we do the same, + i.e. we mark the file as closed in all cases. + */ + control_file_fd= -1; /* As this module owns these variables, closing the module forbids access to them (just a safety): */ last_checkpoint_lsn= CONTROL_FILE_IMPOSSIBLE_LSN; last_logno= CONTROL_FILE_IMPOSSIBLE_FILENO; - DBUG_VOID_RETURN; + + DBUG_RETURN(close_error); } diff --git a/storage/maria/ma_control_file.h b/storage/maria/ma_control_file.h index 5f5581137b7..5ac6f158183 100644 --- a/storage/maria/ma_control_file.h +++ b/storage/maria/ma_control_file.h @@ -24,6 +24,7 @@ typedef struct st_lsn { #define maria_data_root "." #endif +#define CONTROL_FILE_BASE_NAME "maria_control" /* indicate absence of the log file number; first log is always number 1, 0 is impossible. @@ -55,7 +56,15 @@ extern uint32 last_logno; If present, read it to find out last checkpoint's LSN and last log. Called at engine's start. */ -int ma_control_file_create_or_open(); +typedef enum enum_control_file_error { + CONTROL_FILE_OK= 0, + CONTROL_FILE_TOO_SMALL, + CONTROL_FILE_TOO_BIG, + CONTROL_FILE_BAD_MAGIC_STRING, + CONTROL_FILE_BAD_CHECKSUM, + CONTROL_FILE_UNKNOWN_ERROR /* any other error */ +} CONTROL_FILE_ERROR; +CONTROL_FILE_ERROR ma_control_file_create_or_open(); /* Write information durably to the control file. @@ -66,10 +75,10 @@ int ma_control_file_create_or_open(); #define CONTROL_FILE_UPDATE_ONLY_LSN 1 #define CONTROL_FILE_UPDATE_ONLY_LOGNO 2 int ma_control_file_write_and_force(const LSN *checkpoint_lsn, uint32 logno, - uint objs_to_write); + uint objs_to_write); /* Free resources taken by control file subsystem */ -void ma_control_file_end(); +int ma_control_file_end(); #endif diff --git a/storage/maria/ma_control_file_test.c b/storage/maria/ma_control_file_test.c deleted file mode 100644 index b99c61da4fb..00000000000 --- a/storage/maria/ma_control_file_test.c +++ /dev/null @@ -1,312 +0,0 @@ -/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult AB - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - -/* Unit test of the control file module of the Maria engine */ - -/* TODO: make it fit the mytap framework */ - -/* - Note that it is not possible to test the durability of the write (can't - pull the plug programmatically :) -*/ - -#include "maria.h" -#include "ma_control_file.h" -#include - -char file_name[FN_REFLEN]; -int fd= -1; - -static void clean_files(); -static void run_test_normal(); -static void run_test_abnormal(); -static void usage(); -static void get_options(int argc, char *argv[]); - -int main(int argc,char *argv[]) -{ - MY_INIT(argv[0]); - - get_options(argc,argv); - - clean_files(); - run_test_normal(); - run_test_abnormal(); - - fprintf(stderr, "All tests succeeded\n"); - exit(0); /* all ok, if some test failed, we will have aborted */ -} - -/* - Abort unless given expression is non-zero. - - SYNOPSIS - DIE_UNLESS(expr) - - DESCRIPTION - We can't use any kind of system assert as we need to - preserve tested invariants in release builds as well. - - NOTE - This is infamous copy-paste from mysql_client_test.c; - we should instead put it in some include in one single place. -*/ - -#define DIE_UNLESS(expr) \ - ((void) ((expr) ? 0 : (die(__FILE__, __LINE__, #expr), 0))) -#define DIE_IF(expr) \ - ((void) (!(expr) ? 0 : (die(__FILE__, __LINE__, #expr), 0))) -#define DIE(expr) \ - die(__FILE__, __LINE__, #expr) - -void die(const char *file, int line, const char *expr) -{ - fprintf(stderr, "%s:%d: check failed: '%s'\n", file, line, expr); - abort(); -} - - -static void clean_files() -{ - DIE_IF(fn_format(file_name, "control", maria_data_root, "", MYF(MY_WME)) == - NullS); - my_delete(file_name, MYF(0)); /* maybe file does not exist, ignore error */ -} - - -static void run_test_normal() -{ - LSN checkpoint_lsn; - uint32 logno; - uint objs_to_write; - uint i; - char buffer[17]; - - /* TEST0: Instance starts from scratch (control file does not exist) */ - DIE_UNLESS(ma_control_file_create_or_open() == 0); - /* Check that the module reports no information */ - DIE_UNLESS(last_logno == CONTROL_FILE_IMPOSSIBLE_FILENO); - DIE_UNLESS(last_checkpoint_lsn.file_no == CONTROL_FILE_IMPOSSIBLE_FILENO); - DIE_UNLESS(last_checkpoint_lsn.rec_offset == CONTROL_FILE_IMPOSSIBLE_LOG_OFFSET); - - /* TEST1: Simulate creation of one log */ - - objs_to_write= CONTROL_FILE_UPDATE_ONLY_LOGNO; - logno= 123; - DIE_UNLESS(ma_control_file_write_and_force(NULL, logno, - objs_to_write) == 0); - /* Check that last_logno was updated */ - DIE_UNLESS(last_logno == logno); - /* Simulate shutdown */ - ma_control_file_end(); - /* Verify amnesia */ - DIE_UNLESS(last_logno == CONTROL_FILE_IMPOSSIBLE_FILENO); - DIE_UNLESS(last_checkpoint_lsn.file_no == CONTROL_FILE_IMPOSSIBLE_FILENO); - DIE_UNLESS(last_checkpoint_lsn.rec_offset == CONTROL_FILE_IMPOSSIBLE_LOG_OFFSET); - /* And restart */ - DIE_UNLESS(ma_control_file_create_or_open() == 0); - DIE_UNLESS(last_logno == logno); - - /* TEST2: Simulate creation of 5 logs */ - - objs_to_write= CONTROL_FILE_UPDATE_ONLY_LOGNO; - logno= 100; - for (i= 0; i<5; i++) - { - logno*= 3; - DIE_UNLESS(ma_control_file_write_and_force(NULL, logno, - objs_to_write) == 0); - } - ma_control_file_end(); - DIE_UNLESS(last_logno == CONTROL_FILE_IMPOSSIBLE_FILENO); - DIE_UNLESS(last_checkpoint_lsn.file_no == CONTROL_FILE_IMPOSSIBLE_FILENO); - DIE_UNLESS(last_checkpoint_lsn.rec_offset == CONTROL_FILE_IMPOSSIBLE_LOG_OFFSET); - DIE_UNLESS(ma_control_file_create_or_open() == 0); - DIE_UNLESS(last_logno == logno); - - /* - TEST3: Simulate one checkpoint, one log creation, two checkpoints, one - log creation. - */ - - objs_to_write= CONTROL_FILE_UPDATE_ONLY_LSN; - checkpoint_lsn= (LSN){5, 10000}; - logno= 10; - DIE_UNLESS(ma_control_file_write_and_force(&checkpoint_lsn, logno, - objs_to_write) == 0); - /* check that last_logno was not updated */ - DIE_UNLESS(last_logno != logno); - /* Check that last_checkpoint_lsn was updated */ - DIE_UNLESS(last_checkpoint_lsn.file_no == checkpoint_lsn.file_no); - DIE_UNLESS(last_checkpoint_lsn.rec_offset == checkpoint_lsn.rec_offset); - - objs_to_write= CONTROL_FILE_UPDATE_ONLY_LOGNO; - checkpoint_lsn= (LSN){5, 20000}; - logno= 17; - DIE_UNLESS(ma_control_file_write_and_force(&checkpoint_lsn, logno, - objs_to_write) == 0); - /* Check that checkpoint LSN was not updated */ - DIE_UNLESS(last_checkpoint_lsn.rec_offset != checkpoint_lsn.rec_offset); - objs_to_write= CONTROL_FILE_UPDATE_ONLY_LSN; - checkpoint_lsn= (LSN){17, 20000}; - DIE_UNLESS(ma_control_file_write_and_force(&checkpoint_lsn, logno, - objs_to_write) == 0); - objs_to_write= CONTROL_FILE_UPDATE_ONLY_LSN; - checkpoint_lsn= (LSN){17, 45000}; - DIE_UNLESS(ma_control_file_write_and_force(&checkpoint_lsn, logno, - objs_to_write) == 0); - objs_to_write= CONTROL_FILE_UPDATE_ONLY_LOGNO; - logno= 19; - DIE_UNLESS(ma_control_file_write_and_force(&checkpoint_lsn, logno, - objs_to_write) == 0); - - ma_control_file_end(); - DIE_UNLESS(last_logno == CONTROL_FILE_IMPOSSIBLE_FILENO); - DIE_UNLESS(last_checkpoint_lsn.file_no == CONTROL_FILE_IMPOSSIBLE_FILENO); - DIE_UNLESS(last_checkpoint_lsn.rec_offset == CONTROL_FILE_IMPOSSIBLE_LOG_OFFSET); - DIE_UNLESS(ma_control_file_create_or_open() == 0); - DIE_UNLESS(last_logno == logno); - DIE_UNLESS(last_checkpoint_lsn.file_no == checkpoint_lsn.file_no); - DIE_UNLESS(last_checkpoint_lsn.rec_offset == checkpoint_lsn.rec_offset); - - /* - TEST4: actually check by ourselves the content of the file. - Note that constants (offsets) are hard-coded here, precisely to prevent - someone from changing them in the control file module and breaking - backward-compatibility. - TODO: when we reach the format-freeze state, we may even just do a - comparison with a raw binary string, to not depend on any uint4korr - future change/breakage. - */ - - DIE_IF((fd= my_open(file_name, - O_BINARY | O_RDWR, - MYF(MY_WME))) < 0); - DIE_IF(my_read(fd, buffer, 17, MYF(MY_FNABP | MY_WME)) != 0); - DIE_IF(my_close(fd, MYF(MY_WME)) != 0); - i= uint4korr(buffer+5); - DIE_UNLESS(i == last_checkpoint_lsn.file_no); - i= uint4korr(buffer+9); - DIE_UNLESS(i == last_checkpoint_lsn.rec_offset); - i= uint4korr(buffer+13); - DIE_UNLESS(i == last_logno); - - - /* TEST5: Simulate stop/start/nothing/stop/start */ - - ma_control_file_end(); - DIE_UNLESS(last_logno == CONTROL_FILE_IMPOSSIBLE_FILENO); - DIE_UNLESS(ma_control_file_create_or_open() == 0); - ma_control_file_end(); - DIE_UNLESS(last_logno == CONTROL_FILE_IMPOSSIBLE_FILENO); - DIE_UNLESS(ma_control_file_create_or_open() == 0); - DIE_UNLESS(last_logno == logno); - DIE_UNLESS(last_checkpoint_lsn.file_no == checkpoint_lsn.file_no); - DIE_UNLESS(last_checkpoint_lsn.rec_offset == checkpoint_lsn.rec_offset); - -} - -static void run_test_abnormal() -{ - char buffer[4]; - /* Corrupt the control file */ - DIE_IF((fd= my_open(file_name, - O_BINARY | O_RDWR, - MYF(MY_WME))) < 0); - DIE_IF(my_pread(fd, buffer, 4, 0, MYF(MY_FNABP | MY_WME)) != 0); - DIE_IF(my_pwrite(fd, "papa", 4, 0, MYF(MY_FNABP | MY_WME)) != 0); - DIE_IF(my_close(fd, MYF(MY_WME)) != 0); - - /* Check that control file module sees the problem */ - DIE_IF(ma_control_file_create_or_open() == 0); - - /* Restore it and corrupt it differently */ - DIE_IF((fd= my_open(file_name, - O_BINARY | O_RDWR, - MYF(MY_WME))) < 0); - /* Restore magic string */ - DIE_IF(my_pwrite(fd, buffer, 4, 0, MYF(MY_FNABP | MY_WME)) != 0); - DIE_IF(my_pread(fd, buffer, 1, 4, MYF(MY_FNABP | MY_WME)) != 0); - buffer[1]= buffer[0]+3; /* mangle checksum */ - DIE_IF(my_pwrite(fd, buffer+1, 1, 4, MYF(MY_FNABP | MY_WME)) != 0); - DIE_IF(my_close(fd, MYF(MY_WME)) != 0); - - /* Check that control file module sees the problem */ - DIE_IF(ma_control_file_create_or_open() == 0); - - /* Note that control file is left corrupted at this point */ -} - - -static struct my_option my_long_options[] = -{ -#ifndef DBUG_OFF - {"debug", '#', "Debug log.", - 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, -#endif - {"help", '?', "Display help and exit", - 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, - {"version", 'V', "Print version number and exit", - 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, - { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} -}; - - -static void version() -{ - printf("ma_control_file_test: unit test for the control file " - "module of the Maria storage engine. Ver 1.0 \n"); -} - -static my_bool -get_one_option(int optid, const struct my_option *opt __attribute__((unused)), - char *argument) -{ - switch(optid) { - case 'V': - version(); - exit(0); - case '#': - DBUG_PUSH (argument); - break; - case '?': - version(); - usage(); - exit(0); - } - return 0; -} - - -/* Read options */ - -static void get_options(int argc, char *argv[]) -{ - int ho_error; - - if ((ho_error=handle_options(&argc, &argv, my_long_options, get_one_option))) - exit(ho_error); - - return; -} /* get options */ - - -static void usage() -{ - printf("Usage: %s [options]\n\n", my_progname); - my_print_help(my_long_options); - my_print_variables(my_long_options); -} diff --git a/unittest/Makefile.am b/unittest/Makefile.am index ca3291efde0..176ac020bea 100644 --- a/unittest/Makefile.am +++ b/unittest/Makefile.am @@ -1,10 +1,10 @@ -SUBDIRS = mytap . mysys examples +SUBDIRS = mytap . mysys storage examples noinst_SCRIPTS = unit EXTRA_DIST = unit.pl CLEANFILES = unit -unittests = mytap mysys +unittests = mytap mysys storage test: unit ./unit run $(unittests) diff --git a/unittest/storage/Makefile.am b/unittest/storage/Makefile.am new file mode 100644 index 00000000000..21de2de75fc --- /dev/null +++ b/unittest/storage/Makefile.am @@ -0,0 +1,27 @@ +# Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# Process this file with automake to create Makefile.in + +AUTOMAKE_OPTIONS = foreign + +# These are built from source in the Docs directory +EXTRA_DIST = +# Cannot use @mysql_se_dirs@ as not all engines have unit tests here +SUBDIRS = @mysql_se_unittest_dirs@ + +# Don't update the files from bitkeeper +%::SCCS/s.% diff --git a/unittest/storage/maria/Makefile.am b/unittest/storage/maria/Makefile.am new file mode 100644 index 00000000000..eae2990aea9 --- /dev/null +++ b/unittest/storage/maria/Makefile.am @@ -0,0 +1,29 @@ +# Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +AM_CPPFLAGS = @ZLIB_INCLUDES@ -I$(top_builddir)/include +AM_CPPFLAGS += -I$(top_srcdir)/include -I$(top_srcdir)/unittest/mytap + +# Only reason to link with libmyisam.a here is that it's where some fulltext +# pieces are (but soon we'll remove fulltext dependencies from Maria). +LDADD= $(top_builddir)/unittest/mytap/libmytap.a \ + $(top_builddir)/storage/maria/libmaria.a \ + $(top_builddir)/storage/myisam/libmyisam.a \ + $(top_builddir)/mysys/libmysys.a \ + $(top_builddir)/dbug/libdbug.a \ + $(top_builddir)/strings/libmystrings.a @ZLIB_LIBS@ +noinst_PROGRAMS = ma_control_file-t +CLEANFILES = maria_control diff --git a/unittest/storage/maria/ma_control_file-t.c b/unittest/storage/maria/ma_control_file-t.c new file mode 100644 index 00000000000..3ea6932c754 --- /dev/null +++ b/unittest/storage/maria/ma_control_file-t.c @@ -0,0 +1,448 @@ +/* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* Unit test of the control file module of the Maria engine WL#3234 */ + +/* + Note that it is not possible to test the durability of the write (can't + pull the plug programmatically :) +*/ + +#include +#include +#include + +#ifndef WITH_MARIA_STORAGE_ENGINE +/* + If Maria is not compiled in, normally we don't come to building this test. +*/ +#error "Maria engine is not compiled in, test cannot be built" +#endif + +#include "maria.h" +#include "../../../storage/maria/ma_control_file.h" +#include + +char file_name[FN_REFLEN]; + +/* The values we'll set and expect the control file module to return */ +LSN expect_checkpoint_lsn; +uint32 expect_logno; + +static int delete_file(); +/* + Those are test-specific wrappers around the module's API functions: after + calling the module's API functions they perform checks on the result. +*/ +static int close_file(); /* wraps ma_control_file_end */ +static int create_or_open_file(); /* wraps ma_control_file_open_or_create */ +static int write_file(); /* wraps ma_control_file_write_and_force */ + +/* Tests */ +static int test_one_log(); +static int test_five_logs(); +static int test_3_checkpoints_and_2_logs(); +static int test_binary_content(); +static int test_start_stop(); +static int test_2_open_and_2_close(); +static int test_bad_magic_string(); +static int test_bad_checksum(); +static int test_bad_size(); + +/* Utility */ +static int verify_module_values_match_expected(); +static int verify_module_values_are_impossible(); +static void usage(); +static void get_options(int argc, char *argv[]); + +/* + If "expr" is FALSE, this macro will make the function print a diagnostic + message and immediately return 1. + This is inspired from assert() but does not crash the binary (sometimes we + may want to see how other tests go even if one fails). + RET_ERR means "return error". +*/ + +#define RET_ERR_UNLESS(expr) \ + {if (!(expr)) {diag("line %d: failure: '%s'", __LINE__, #expr); return 1;}} + + +int main(int argc,char *argv[]) +{ + MY_INIT(argv[0]); + + plan(9); + + diag("Unit tests for control file"); + + get_options(argc,argv); + + diag("Deleting control file at startup, if there is an old one"); + RET_ERR_UNLESS(0 == delete_file()); /* if fails, can't continue */ + + diag("Tests of normal conditions"); + ok(0 == test_one_log(), "test of creating one log"); + ok(0 == test_five_logs(), "test of creating five logs"); + ok(0 == test_3_checkpoints_and_2_logs(), + "test of creating three checkpoints and two logs"); + ok(0 == test_binary_content(), "test of the binary content of the file"); + ok(0 == test_start_stop(), "test of multiple starts and stops"); + diag("Tests of abnormal conditions"); + ok(0 == test_2_open_and_2_close(), + "test of two open and two close (strange call sequence)"); + ok(0 == test_bad_magic_string(), "test of bad magic string"); + ok(0 == test_bad_checksum(), "test of bad checksum"); + ok(0 == test_bad_size(), "test of too small/big file"); + + return exit_status(); +} + + +static int delete_file() +{ + RET_ERR_UNLESS(fn_format(file_name, CONTROL_FILE_BASE_NAME, + maria_data_root, "", MYF(MY_WME)) != NullS); + /* + Maybe file does not exist, ignore error. + The error will however be printed on stderr. + */ + my_delete(file_name, MYF(MY_WME)); + expect_checkpoint_lsn= CONTROL_FILE_IMPOSSIBLE_LSN; + expect_logno= CONTROL_FILE_IMPOSSIBLE_FILENO; + + return 0; +} + +/* + Verifies that global values last_checkpoint_lsn and last_logno (belonging + to the module) match what we expect. +*/ +static int verify_module_values_match_expected() +{ + RET_ERR_UNLESS(last_logno == expect_logno); + RET_ERR_UNLESS(last_checkpoint_lsn.file_no == + expect_checkpoint_lsn.file_no); + RET_ERR_UNLESS(last_checkpoint_lsn.rec_offset == + expect_checkpoint_lsn.rec_offset); + return 0; +} + + +/* + Verifies that global values last_checkpoint_lsn and last_logno (belonging + to the module) are impossible (this is used when the file has been closed). +*/ +static int verify_module_values_are_impossible() +{ + RET_ERR_UNLESS(last_logno == CONTROL_FILE_IMPOSSIBLE_FILENO); + RET_ERR_UNLESS(last_checkpoint_lsn.file_no == + CONTROL_FILE_IMPOSSIBLE_FILENO); + RET_ERR_UNLESS(last_checkpoint_lsn.rec_offset == + CONTROL_FILE_IMPOSSIBLE_LOG_OFFSET); + return 0; +} + + +static int close_file() +{ + /* Simulate shutdown */ + ma_control_file_end(); + /* Verify amnesia */ + RET_ERR_UNLESS(verify_module_values_are_impossible() == 0); + return 0; +} + +static int create_or_open_file() +{ + RET_ERR_UNLESS(ma_control_file_create_or_open() == CONTROL_FILE_OK); + /* Check that the module reports expected information */ + RET_ERR_UNLESS(verify_module_values_match_expected() == 0); + return 0; +} + +static int write_file(const LSN *checkpoint_lsn, + uint32 logno, + uint objs_to_write) +{ + RET_ERR_UNLESS(ma_control_file_write_and_force(checkpoint_lsn, logno, + objs_to_write) == 0); + /* Check that the module reports expected information */ + RET_ERR_UNLESS(verify_module_values_match_expected() == 0); + return 0; +} + +static int test_one_log() +{ + uint objs_to_write; + + RET_ERR_UNLESS(create_or_open_file() == CONTROL_FILE_OK); + objs_to_write= CONTROL_FILE_UPDATE_ONLY_LOGNO; + expect_logno= 123; + RET_ERR_UNLESS(write_file(NULL, expect_logno, + objs_to_write) == 0); + RET_ERR_UNLESS(close_file() == 0); + return 0; +} + +static int test_five_logs() +{ + uint objs_to_write; + uint i; + + RET_ERR_UNLESS(create_or_open_file() == CONTROL_FILE_OK); + objs_to_write= CONTROL_FILE_UPDATE_ONLY_LOGNO; + expect_logno= 100; + for (i= 0; i<5; i++) + { + expect_logno*= 3; + RET_ERR_UNLESS(write_file(NULL, expect_logno, + objs_to_write) == 0); + } + RET_ERR_UNLESS(close_file() == 0); + return 0; +} + +static int test_3_checkpoints_and_2_logs() +{ + uint objs_to_write; + /* + Simulate one checkpoint, one log creation, two checkpoints, one + log creation. + */ + RET_ERR_UNLESS(create_or_open_file() == CONTROL_FILE_OK); + objs_to_write= CONTROL_FILE_UPDATE_ONLY_LSN; + expect_checkpoint_lsn= (LSN){5, 10000}; + RET_ERR_UNLESS(write_file(&expect_checkpoint_lsn, + expect_logno, objs_to_write) == 0); + + objs_to_write= CONTROL_FILE_UPDATE_ONLY_LOGNO; + expect_logno= 17; + RET_ERR_UNLESS(write_file(&expect_checkpoint_lsn, + expect_logno, objs_to_write) == 0); + + objs_to_write= CONTROL_FILE_UPDATE_ONLY_LSN; + expect_checkpoint_lsn= (LSN){17, 20000}; + RET_ERR_UNLESS(write_file(&expect_checkpoint_lsn, + expect_logno, objs_to_write) == 0); + + objs_to_write= CONTROL_FILE_UPDATE_ONLY_LSN; + expect_checkpoint_lsn= (LSN){17, 45000}; + RET_ERR_UNLESS(write_file(&expect_checkpoint_lsn, + expect_logno, objs_to_write) == 0); + + objs_to_write= CONTROL_FILE_UPDATE_ONLY_LOGNO; + expect_logno= 19; + RET_ERR_UNLESS(write_file(&expect_checkpoint_lsn, + expect_logno, objs_to_write) == 0); + RET_ERR_UNLESS(close_file() == 0); + return 0; +} + +static int test_binary_content() +{ + uint i; + int fd; + + /* + TEST4: actually check by ourselves the content of the file. + Note that constants (offsets) are hard-coded here, precisely to prevent + someone from changing them in the control file module and breaking + backward-compatibility. + TODO: when we reach the format-freeze state, we may even just do a + comparison with a raw binary string, to not depend on any uint4korr + future change/breakage. + */ + + char buffer[17]; + RET_ERR_UNLESS((fd= my_open(file_name, + O_BINARY | O_RDWR, + MYF(MY_WME))) >= 0); + RET_ERR_UNLESS(my_read(fd, buffer, 17, MYF(MY_FNABP | MY_WME)) == 0); + RET_ERR_UNLESS(my_close(fd, MYF(MY_WME)) == 0); + RET_ERR_UNLESS(create_or_open_file() == CONTROL_FILE_OK); + i= uint4korr(buffer+5); + RET_ERR_UNLESS(i == last_checkpoint_lsn.file_no); + i= uint4korr(buffer+9); + RET_ERR_UNLESS(i == last_checkpoint_lsn.rec_offset); + i= uint4korr(buffer+13); + RET_ERR_UNLESS(i == last_logno); + RET_ERR_UNLESS(close_file() == 0); + return 0; +} + +static int test_start_stop() +{ + /* TEST5: Simulate start/nothing/stop/start/nothing/stop/start */ + + RET_ERR_UNLESS(create_or_open_file() == CONTROL_FILE_OK); + RET_ERR_UNLESS(close_file() == 0); + RET_ERR_UNLESS(create_or_open_file() == CONTROL_FILE_OK); + RET_ERR_UNLESS(close_file() == 0); + RET_ERR_UNLESS(create_or_open_file() == CONTROL_FILE_OK); + RET_ERR_UNLESS(close_file() == 0); + return 0; +} + +static int test_2_open_and_2_close() +{ + RET_ERR_UNLESS(create_or_open_file() == CONTROL_FILE_OK); + RET_ERR_UNLESS(create_or_open_file() == CONTROL_FILE_OK); + RET_ERR_UNLESS(close_file() == 0); + RET_ERR_UNLESS(close_file() == 0); + return 0; +} + + +static int test_bad_magic_string() +{ + char buffer[4]; + int fd; + + RET_ERR_UNLESS(create_or_open_file() == CONTROL_FILE_OK); + RET_ERR_UNLESS(close_file() == 0); + + /* Corrupt magic string */ + RET_ERR_UNLESS((fd= my_open(file_name, + O_BINARY | O_RDWR, + MYF(MY_WME))) >= 0); + RET_ERR_UNLESS(my_pread(fd, buffer, 4, 0, MYF(MY_FNABP | MY_WME)) == 0); + RET_ERR_UNLESS(my_pwrite(fd, "papa", 4, 0, MYF(MY_FNABP | MY_WME)) == 0); + + /* Check that control file module sees the problem */ + RET_ERR_UNLESS(ma_control_file_create_or_open() == + CONTROL_FILE_BAD_MAGIC_STRING); + /* Restore magic string */ + RET_ERR_UNLESS(my_pwrite(fd, buffer, 4, 0, MYF(MY_FNABP | MY_WME)) == 0); + RET_ERR_UNLESS(my_close(fd, MYF(MY_WME)) == 0); + RET_ERR_UNLESS(create_or_open_file() == CONTROL_FILE_OK); + RET_ERR_UNLESS(close_file() == 0); + return 0; +} + +static int test_bad_checksum() +{ + char buffer[4]; + int fd; + + RET_ERR_UNLESS(create_or_open_file() == CONTROL_FILE_OK); + RET_ERR_UNLESS(close_file() == 0); + + /* Corrupt checksum */ + RET_ERR_UNLESS((fd= my_open(file_name, + O_BINARY | O_RDWR, + MYF(MY_WME))) >= 0); + RET_ERR_UNLESS(my_pread(fd, buffer, 1, 4, MYF(MY_FNABP | MY_WME)) == 0); + buffer[0]+= 3; /* mangle checksum */ + RET_ERR_UNLESS(my_pwrite(fd, buffer+1, 1, 4, MYF(MY_FNABP | MY_WME)) == 0); + /* Check that control file module sees the problem */ + RET_ERR_UNLESS(ma_control_file_create_or_open() == + CONTROL_FILE_BAD_CHECKSUM); + /* Restore checksum */ + buffer[0]-= 3; + RET_ERR_UNLESS(my_pwrite(fd, buffer+1, 1, 4, MYF(MY_FNABP | MY_WME)) == 0); + RET_ERR_UNLESS(my_close(fd, MYF(MY_WME)) == 0); + + return 0; +} + + +static int test_bad_size() +{ + char buffer[]="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + int fd; + + /* A too short file */ + RET_ERR_UNLESS(delete_file() == 0); + RET_ERR_UNLESS((fd= my_open(file_name, + O_BINARY | O_RDWR | O_CREAT, + MYF(MY_WME))) >= 0); + RET_ERR_UNLESS(my_write(fd, buffer, 10, MYF(MY_FNABP | MY_WME)) == 0); + /* Check that control file module sees the problem */ + RET_ERR_UNLESS(ma_control_file_create_or_open() == CONTROL_FILE_TOO_SMALL); + RET_ERR_UNLESS(my_write(fd, buffer, 30, MYF(MY_FNABP | MY_WME)) == 0); + /* Check that control file module sees the problem */ + RET_ERR_UNLESS(ma_control_file_create_or_open() == CONTROL_FILE_TOO_BIG); + RET_ERR_UNLESS(my_close(fd, MYF(MY_WME)) == 0); + + /* Leave a correct control file */ + RET_ERR_UNLESS(delete_file() == 0); + RET_ERR_UNLESS(create_or_open_file() == CONTROL_FILE_OK); + RET_ERR_UNLESS(close_file() == 0); + + return 0; +} + + +static struct my_option my_long_options[] = +{ +#ifndef DBUG_OFF + {"debug", '#', "Debug log.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"help", '?', "Display help and exit", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"version", 'V', "Print version number and exit", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} +}; + + +static void version() +{ + printf("ma_control_file_test: unit test for the control file " + "module of the Maria storage engine. Ver 1.0 \n"); +} + +static my_bool +get_one_option(int optid, const struct my_option *opt __attribute__((unused)), + char *argument) +{ + switch(optid) { + case 'V': + version(); + exit(0); + case '#': + DBUG_PUSH (argument); + break; + case '?': + version(); + usage(); + exit(0); + } + return 0; +} + + +/* Read options */ + +static void get_options(int argc, char *argv[]) +{ + int ho_error; + + if ((ho_error=handle_options(&argc, &argv, my_long_options, + get_one_option))) + exit(ho_error); + + return; +} /* get options */ + + +static void usage() +{ + printf("Usage: %s [options]\n\n", my_progname); + my_print_help(my_long_options); + my_print_variables(my_long_options); +}