Commit 26fb3606 authored by unknown's avatar unknown

WL#3234 Maria control file manager.

Fitting ma_control_file_test into the mytap unittest framework:
new directories:
- unittest/storage/ for unit tests of any storage engine
- unittest/storage/maria for ... Maria, containing ma_control_file-t.
Later, older tests like ma_test*, ma_test_all (but which is Unix
dependent in its current form) could move here too.
The plugins macro enable building of unittest/storage/X for any
enabled engine X which has such a directory.
If Falcon wants to have unit tests there too, I may have to merge
this patch into 5.x one day.


config/ac-macros/plugins.m4:
  If a storage engine has a directory in unittest/storage, build this
  directory.
configure.in:
  build storage engines' unit tests.
storage/maria/Makefile.am:
  ma_control_file_test moves to unittest/storage/maria
storage/maria/ma_control_file.c:
  more error codes when opening the control file fails.
  ma_control_file_end() may now return an error if my_close() failed.
storage/maria/ma_control_file.h:
  more error codes when opening the control file fails.
unittest/Makefile.am:
  adding unit tests for storage engines.
  Note that unit.pl simply recurses into "storage", so if a unit test for
  storage engine X has been built previously, and now you re-configure
  (without making clean) to disable this engine, then the unit test of
  X will not be rebuilt but will still be present in storage/X, so will
  be run.
unittest/storage/maria/ma_control_file-t.c:
  Making the test fit the mytap framework (return all the way up
  the stack instead of assert(); use the mytap functions plan(), ok() etc).
  Adding test of file too short/long.
unittest/storage/maria/Makefile.am:
  a_control_file-t is added to the Maria unit tests.
  Later, older tests (ma_test1 etc) could also move here.
unittest/storage/Makefile.am:
  New BitKeeper file ``unittest/storage/Makefile.am''
parent e321f8eb
......@@ -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)])
......
......@@ -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
......
......@@ -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
......
......@@ -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);
goto err;
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 ((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);
}
......@@ -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
/* 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 <my_getopt.h>
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);
}
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)
......
# 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.%
# 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
/* 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 <my_global.h>
#include <my_sys.h>
#include <tap.h>
#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 <my_getopt.h>
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);
}
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