2839 Rafal Somla 2009-06-30
WL#4765 - Coverage test of backup stream library.
This patch adds unittest/backup/ subdirectory containing unit tests
for backup stream library, aimed at getting full code coverage.
Currently, together with the backup test suite, these tests cover
97% of backup library code
(files sql/backup/{stream_v1.c,stream_v1_transport.c}).
These tests link against libbackupstream library residing in sql/backup/.
To build them, they must be compiled after the server. This patch changes
the building order accordingly (i.e., unittest/ will be built after sql/).
The patch also adds invocation of these unit tests for pushbuild testing.
However, I'm not able to test it without actually pushing this change.
HOW TO CHECK COVERAGE
---------------------
1. Build tree with gcov configuration. I use BUILD/compile-pentium-gcov.
2. Clean coverage data: in mysql-test/ dir: mysql-test-lcov.pl -p
3. Run backup test suite: mtr --suite=backup
4. Run backup unit tests: in unittest/ dir: perl unit.pl run backup
5. Generate coverage report: in mysql-test/ dir: mysql-test-lcov.pl
6. Load mysql-test/mysql-test-lcov/index.html into your browser.
Alternative to 5,6 is as follows:
- cd sql/backup
- run: gcov stream_v1*
- look at stream_v1*.gcov files.
Note: the lcov and gcov tools do not understand purecov annotations. On
the WL page you can find attached scripts for processing .gcov files to
hide annotated lines.
@ Makefile.am
Change order in which unittests are built - build them after the server,
so that libraries such as libbstream can be used in them.
@ configure.in
Add unittest/backup/Makefile.
@ mysql-test/collections/mysql-6.0-backup.push
I think this can make PB2 run the unit tests on every push. But it must
be tested and it can be tested only by pushing this patch.
@ unittest/Makefile.am
Add backup subdirectory to unittest/.
@ unittest/backup/bstr_callback_errors-t.c
Test which triggers errors in bstream callback functions.
@ unittest/backup/bstr_data_chunks-t.c
Test writing table data chunks of various sizes.
@ unittest/backup/bstr_eos-t.c
Test which simulates early end of stream when reading from backup image.
@ unittest/backup/bstr_extra-t.c
Various tests to cover more exotic code branches.
@ unittest/backup/bstr_extra1-t.c
Test errors when reading basic datatypes (only the ones which are not
already covered elsewhere)
@ unittest/backup/bstr_io_errors-t.c
Test triggering errors in low-level I/O functions.
@ unittest/backup/bstr_random_chunks-t.c
Test writing and reading a pseudo-random sequence of data chunks.
@ unittest/backup/catalog.c
Callback functions implementing backup catalogue.
@ unittest/backup/no_catalog.c
Trivial implementation of catalogue callbacks for tests which do not
need a catalogue.
@ unittest/backup/stream_test.h
Common header for unit tests which want to test backup stream library.
@ unittest/backup/test_main.c
Main function for unit tests.
@ unittest/backup/test_stream.c
Implementation of low-level I/O operations for backup stream used
in tests.
added:
unittest/backup/
unittest/backup/Makefile.am
unittest/backup/bstr_callback_errors-t.c
unittest/backup/bstr_data_chunks-t.c
unittest/backup/bstr_eos-t.c
unittest/backup/bstr_extra-t.c
unittest/backup/bstr_extra1-t.c
unittest/backup/bstr_io_errors-t.c
unittest/backup/bstr_random_chunks-t.c
unittest/backup/catalog.c
unittest/backup/catalog.h
unittest/backup/no_catalog.c
unittest/backup/stream_test.h
unittest/backup/test_main.c
unittest/backup/test_stream.c
modified:
Makefile.am
configure.in
mysql-test/collections/mysql-6.0-backup.push
sql/backup/stream_v1.c
sql/backup/stream_v1_transport.c
unittest/Makefile.am
unittest/README.txt
2838 Chuck Bell 2009-06-30 [merge]
merge with merge tree
modified:
mysql-test/collections/default.experimental
mysql-test/lib/mtr_cases.pm
mysql-test/r/information_schema.result
mysql-test/t/disabled.def
=== modified file 'Makefile.am'
--- a/Makefile.am 2009-06-08 14:58:33 +0000
+++ b/Makefile.am 2009-06-30 20:37:35 +0000
@@ -26,8 +26,8 @@ EXTRA_DIST = INSTALL-SOURCE INSTALL-WIN
SUBDIRS = . include @docs_dirs@ @zlib_dir@ \
@readline_topdir@ sql-common scripts \
@pstack_dir@ libservices \
- @sql_union_dirs@ unittest storage plugin \
- @sql_server@ @man_dirs@ tests \
+ @sql_union_dirs@ storage plugin \
+ @sql_server@ unittest @man_dirs@ tests \
netware @libmysqld_dirs@ \
mysql-test support-files sql-bench \
win
=== modified file 'configure.in'
--- a/configure.in 2009-06-17 07:30:19 +0000
+++ b/configure.in 2009-06-30 20:37:35 +0000
@@ -2945,7 +2945,7 @@ fi
AC_CONFIG_FILES(Makefile extra/Makefile mysys/Makefile mysys/tests/Makefile dnl
unittest/Makefile unittest/mytap/Makefile unittest/mytap/t/Makefile dnl
- unittest/mysys/Makefile unittest/examples/Makefile dnl
+ unittest/mysys/Makefile unittest/examples/Makefile unittest/backup/Makefile dnl
strings/Makefile regex/Makefile storage/Makefile dnl
man/Makefile BUILD/Makefile vio/Makefile dnl
libmysql/Makefile libmysql_r/Makefile client/Makefile dnl
=== modified file 'mysql-test/collections/mysql-6.0-backup.push'
--- a/mysql-test/collections/mysql-6.0-backup.push 2009-04-28 06:29:39 +0000
+++ b/mysql-test/collections/mysql-6.0-backup.push 2009-06-30 20:37:35 +0000
@@ -5,3 +5,4 @@ perl mysql-test-run.pl --comment=rpl_bin
perl mysql-test-run.pl --comment=funcs_1 --suite=funcs_1 --experimental=collections/default.experimental --timer --force
perl mysql-test-run.pl --comment=ps_stm_threadpool --ps-protocol --mysqld=--binlog-format=statement --mysqld=--thread-handling=pool-of-threads --suite=main,backup,backup_engines,backup_ptr,binlog,federated,rpl,maria --experimental=collections/default.experimental --timer --force
perl mysql-test-run.pl --comment=falcon --suite=falcon --experimental=collections/default.experimental --timer --force
+perl ../unittest/unit.pl run ../unittest/backup
=== modified file 'sql/backup/stream_v1.c'
--- a/sql/backup/stream_v1.c 2009-06-26 06:29:50 +0000
+++ b/sql/backup/stream_v1.c 2009-06-30 20:37:35 +0000
@@ -209,6 +209,8 @@ int bstream_rd_preamble(backup_stream *s
return BSTREAM_ERROR;
CHECK_RD_OK(bstream_next_chunk(s));
+ /* In this version, we always write summary at the end of the stream. */
+ /* purecov: begin inspected */
if (hdr->flags & BSTREAM_FLAG_INLINE_SUMMARY)
{
CHECK_RD_RES(bstream_rd_summary(s,hdr));
@@ -216,6 +218,7 @@ int bstream_rd_preamble(backup_stream *s
return BSTREAM_ERROR;
CHECK_RD_OK(bstream_next_chunk(s));
}
+ /* purecov: end inspected */
CHECK_RD_RES(bstream_rd_catalogue(s,hdr));
if (ret == BSTREAM_EOS)
@@ -734,7 +737,7 @@ int bstream_wr_catalogue(backup_stream *
/* Free iterator if not already done */
if (it)
- bcat_iterator_free(cat,it);
+ bcat_iterator_free(cat,it); /* purecov: inspected */
return ret;
}
@@ -849,6 +852,13 @@ int bstream_rd_catalogue(backup_stream *
CHECK_RD_RES(bstream_rd_byte(s,&flags));
+ /*
+ In current version we never store extra data in database catalogue entry.
+ Flags field is always 0x00 (see bstream_wr_catalogue(), code which
+ writes list of databases).
+ */
+
+ /* purecov: begin inspected */
if (flags & BSTREAM_FLAG_HAS_EXTRA_DATA)
{
if (ret != BSTREAM_OK)
@@ -857,6 +867,7 @@ int bstream_rd_catalogue(backup_stream *
CHECK_RD_OK(bstream_rd_int2(s,&len));
CHECK_RD_RES(bstream_skip(s,len));
}
+ /* purecov: end inspected */
} while (ret == BSTREAM_OK);
@@ -890,7 +901,7 @@ int bstream_rd_catalogue(backup_stream *
/* Free iterator if not already done */
if (iter)
- bcat_iterator_free(cat,iter);
+ bcat_iterator_free(cat,iter); /* purecov: inspected */
return ret;
}
@@ -1329,7 +1340,7 @@ wr_error:
/* Free iterators if not already done */
if (titer)
- bcat_iterator_free(cat,titer);
+ bcat_iterator_free(cat,titer); /* purecov: inspected */
if (iter)
bcat_iterator_free(cat,iter);
@@ -1411,7 +1422,7 @@ int bstream_rd_meta_data(backup_stream *
/* Free iterator if not already done */
if (iter)
- bcat_iterator_free(cat,iter);
+ bcat_iterator_free(cat,iter); /* purecov: inspected */
return ret;
}
=== modified file 'sql/backup/stream_v1_transport.c'
--- a/sql/backup/stream_v1_transport.c 2009-06-26 06:29:50 +0000
+++ b/sql/backup/stream_v1_transport.c 2009-06-30 20:37:35 +0000
@@ -333,7 +333,7 @@ int as_write_all(struct st_abstract_stre
return ret;
}
-
+/* purecov: begin deadcode */
/** Fill blob with bytes from stream */
int as_read_all(struct st_abstract_stream *s, bstream_blob b, bstream_blob env)
{
@@ -344,7 +344,7 @@ int as_read_all(struct st_abstract_strea
return ret;
}
-
+/* purecov: end */
/*************************************************************************
OUTPUT BUFFER
@@ -1214,6 +1214,7 @@ int bstream_write_part(backup_stream *s,
return BSTREAM_OK;
}
+/* purecov: begin deadcode */
/**
Write bytes to backup stream.
@@ -1231,6 +1232,7 @@ int bstream_write(backup_stream *s, bstr
return bstream_write_part(s,b,buf);
}
+/* purecov: end */
/**
Write complete blob to backup stream.
@@ -1689,7 +1691,7 @@ int bstream_read_part(backup_stream *s,
return check_end(s);
}
-
+/* purecov: begin deadcode */
/**
Read bytes from backup stream.
@@ -1709,7 +1711,7 @@ int bstream_read(backup_stream *s, bstre
{
return bstream_read_part(s,b,*b);
}
-
+/* purecov: end */
/**
Read complete blob to backup stream.
@@ -1828,6 +1830,7 @@ int bstream_next_chunk(backup_stream *s)
return check_end(s);
}
+/* purecov: begin deadcode */
/**
Skip given amount of bytes in the stream.
@@ -1842,3 +1845,4 @@ int bstream_skip(backup_stream *s, unsig
return BSTREAM_OK;
}
+/* purecov: end deadcode */
=== modified file 'unittest/Makefile.am'
--- a/unittest/Makefile.am 2009-01-07 10:58:33 +0000
+++ b/unittest/Makefile.am 2009-06-30 20:37:35 +0000
@@ -13,12 +13,13 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-SUBDIRS = mytap mysys examples
+SUBDIRS = mytap mysys examples backup
EXTRA_DIST = unit.pl
CLEANFILES = unit
-unittests = mytap mysys @mysql_se_unittest_dirs@ @mysql_pg_unittest_dirs@ ../dbug
+unittests = mytap mysys backup
+unittests += @mysql_se_unittest_dirs@ @mysql_pg_unittest_dirs@ ../dbug
test:
perl unit.pl run $(unittests)
=== modified file 'unittest/README.txt'
--- a/unittest/README.txt 2006-10-20 20:56:03 +0000
+++ b/unittest/README.txt 2009-06-30 20:37:35 +0000
@@ -6,9 +6,11 @@ This is the current structure of the uni
will be added over time.
mytap Source for the MyTAP library
+
mysys Tests for mysys components
bitmap-t.c Unit test for MY_BITMAP
base64-t.c Unit test for base64 encoding functions
+
examples Example unit tests.
core-t.c Example of raising a signal in the middle of the test
THIS TEST WILL STOP ALL FURTHER TESTING!
@@ -18,6 +20,17 @@ examples Example unit tests
todo-t.c Example where test contain test points that are TODO
no_plan-t.c Example of a test with no plan (avoid this)
+backup Tests for mysql backup system
+ bstr_callback_errors-t Bstream library test: errors in callback functions
+ bstr_data_chunks-t Bstream library test: write/read data chunks of various
+ sizes
+ bstr_eos-t Bstream library test: insert EOS markers into the stream
+ bstr_extra-t Bstream library test: misc tests
+ bstr_extra1-t Bstream library test: more misc tests
+ bstr_io_errors-t Bstream library test: errors in low-level I/O functions
+ bstr_random_chunks-t Bstream library test: write/read pseudo-random sequence
+ of data chunks
+
Executing unit tests
--------------------
=== added directory 'unittest/backup'
=== added file 'unittest/backup/Makefile.am'
--- a/unittest/backup/Makefile.am 1970-01-01 00:00:00 +0000
+++ b/unittest/backup/Makefile.am 2009-06-30 20:37:35 +0000
@@ -0,0 +1,32 @@
+AM_CPPFLAGS= -I$(top_srcdir)/sql/backup -I$(top_srcdir)/unittest/mytap
+
+LDADD= libstreamtest.la
+
+STREAMTESTS = bstr_callback_errors-t bstr_io_errors-t bstr_eos-t
+STREAMTESTS += bstr_data_chunks-t bstr_random_chunks-t
+STREAMTESTS += bstr_extra-t bstr_extra1-t
+
+noinst_PROGRAMS = $(STREAMTESTS)
+
+bstr_callback_errors_t_SOURCES = bstr_callback_errors-t.c catalog.c
+bstr_data_chunks_t_SOURCES = bstr_data_chunks-t.c no_catalog.c
+bstr_random_chunks_t_SOURCES = bstr_random_chunks-t.c no_catalog.c
+bstr_io_errors_t_SOURCES = bstr_io_errors-t.c catalog.c
+bstr_eos_t_SOURCES = bstr_eos-t.c catalog.c
+bstr_extra_t_SOURCES = bstr_extra-t.c catalog.c
+bstr_extra1_t_SOURCES = bstr_extra1-t.c catalog.c
+
+#
+# Library with infrastructure for unit tests
+#
+
+noinst_LTLIBRARIES = libstreamtest.la
+libstreamtest_la_LDFLAGS = -static
+libstreamtest_la_SOURCES = test_stream.c test_main.c
+libstreamtest_la_LIBADD = $(top_builddir)/sql/backup/libbackupstream.la \
+ -lmytap -lmysys -ldbug -lmystrings
+libstreamtest_la_LDFLAGS+= \
+ -L$(top_builddir)/mysys \
+ -L$(top_builddir)/dbug \
+ -L$(top_builddir)/strings \
+ -L$(top_builddir)/unittest/mytap
=== added file 'unittest/backup/bstr_callback_errors-t.c'
--- a/unittest/backup/bstr_callback_errors-t.c 1970-01-01 00:00:00 +0000
+++ b/unittest/backup/bstr_callback_errors-t.c 2009-06-30 20:37:35 +0000
@@ -0,0 +1,337 @@
+/** @file
+
+ Trigger errors in callback functions which are called from bstream
+ library when writing/reading backup image.
+*/
+
+#include <tap.h>
+
+#include "stream_test.h"
+#include "catalog.h"
+
+image_header img_header;
+
+typedef enum {WR,RD} op_type;
+
+/*
+ These helper functions look at callback counters and arrange for a specified
+ callback function to report error when called for the 1-st, 2-nd, ... time.
+ This happens during writing or reading of backup image.
+*/
+
+static int fail_iterator(op_type, item_type);
+static int fail_db_iterator(op_type, uint);
+static int fail_get_meta(item_type type);
+static int fail_item(item_type type);
+static int fail_catalog();
+static int fail_alloc();
+
+void run_test()
+{
+ uint i;
+ uint planned;
+
+ init_catalog(&img_header);
+
+ diag("Testing callback failures during writing of backup image");
+
+ planned= 1 + global_count + 4 + db_count
+ + 1 + global_count + perdb_count + 1;
+
+ /*
+ Do a clean write of backup image to fill callback_counts structure holding
+ counters for callback functions.
+ */
+ reset_counts();
+ ok(!(write_image(&img_header, "test_image.bst") || check_iterators()),
+ "Counting callbacks");
+
+ /* Failures in get_iterator */
+
+ /* iterate over all global object types present in the catalogue */
+ for (i=0; i < global_count; ++i)
+ ok(fail_iterator(WR, objects_global[i].type),
+ "Errors in bcat_iterator_get() callback (for global objects of type %d)",
+ objects_global[i].type);
+
+ /* cover remaining iterator types */
+ ok(fail_iterator(WR, BSTREAM_IT_DB),
+ "Errors in bcat_iterator_get() callback (for databases)");
+ ok(fail_iterator(WR, BSTREAM_IT_GLOBAL),
+ "Errors in bcat_iterator_get() callback (for all global objects)");
+ ok(fail_iterator(WR, BSTREAM_IT_PERDB),
+ "Errors in bcat_iterator_get() callback (for per-database objects)");
+ ok(fail_iterator(WR, BSTREAM_IT_PERTABLE),
+ "Errors in bcat_iterator_get() callback (for per-table objects)");
+
+ /* test failures in database iterators (for each database in the catalogue) */
+ for (i=0; i < db_count; ++i)
+ ok(fail_db_iterator(WR, i),
+ "Errors in bcat_db_iterator_get() callback (for database #%d)",i);
+
+ /* Failures in get_item_create_query/data */
+
+ ok(fail_get_meta(BSTREAM_IT_DB),
+ "Errors in bcat_get_item_create_query/data() callback (for databases)");
+
+ /* iterate over all global object types present in the catalogue */
+ for(i=0; i < global_count; ++i)
+ ok(fail_get_meta(objects_global[i].type),
+ "Errors in bcat_get_item_create_query/data() callback"
+ " (for objects of type %d)", objects_global[i].type);
+
+ /* iterate over all per-database object types present in the catalogue */
+ for(i=0; i < perdb_count; ++i)
+ ok(fail_get_meta(objects_perdb[i].type),
+ "Errors in bcat_get_item_create_query/data() callback"
+ " (for objects of type %d)", objects_perdb[i].type);
+
+ /* test errors in memory allocation */
+ ok(fail_alloc(WR), "Errors in memory allocation");
+
+ diag("Testing callback failures during reading of backup image");
+
+ plan(planned+= 3 + global_count + perdb_count + 2);
+
+ /* Do a clean read of the backup image to set the callback counters. */
+ reset_counts();
+ ok(!(read_image(&img_header, "test_image.bst")
+ || check_iterators()),
+ "Counting callbacks");
+
+ /*
+ The only iterator used when reading backup image is the database iterator.
+ */
+ ok(fail_iterator(RD, BSTREAM_IT_DB),
+ "Errors in bcat_iterator_get() callback (for databases)");
+
+ /* Failures in bcat_add/create_item */
+
+ ok(fail_item(BSTREAM_IT_DB),
+ "Errors in bcat_add/create_item() callback (for databases)");
+
+ for(i=0; i < global_count; ++i)
+ ok(fail_item(objects_global[i].type),
+ "Errors in bcat_add/create_item() callbacks"
+ " (for objects of type %d)", objects_global[i].type);
+
+ for(i=0; i < perdb_count; ++i)
+ ok(fail_item(objects_perdb[i].type),
+ "Errors in bcat_add/create_item() callbacks"
+ " (for objects of type %d)", objects_perdb[i].type);
+
+ /* test failures in misc catalogue managing callbacks */
+ ok(fail_catalog(), "Errors in bcat_reset/close() callbacks");
+
+ /* test errors in memory allocation */
+ ok(fail_alloc(RD), "Errors in memory allocation");
+}
+
+
+/**************************************************************************
+
+ Implementation of helper functions.
+
+**************************************************************************/
+
+int fail_callback(op_type, int*, uint);
+
+/**
+ Read or write image triggering errors in bcat_iterator_get().
+
+ @param[IN] rd_or_wr tells whether image should be written or read; if
+ reading is specified, then "test_image.bst" backup
+ image should be created earlier.
+ @param[IN] type specifies which iterator should fail.
+
+ @returns True if test went as expected, that is, read/write operations
+ were aborted with error due to error in the callback call.
+
+ @see @c fail_callback().
+*/
+
+static
+int fail_iterator(op_type rd_or_wr, item_type type)
+{
+ int *ctr;
+
+ DBUG_ASSERT(type < 128);
+
+ ctr= &(callback_counts.iterator_get[type]);
+ return fail_callback(rd_or_wr, ctr, *ctr);
+}
+
+
+/**
+ Read or write image triggering errors in bcat_db_itearator_get().
+
+ @param[IN] rd_or_wr tells whether image should be written or read; if
+ reading is specified, then "test_image.bst" backup
+ image should be created earlier.
+ @param[IN] db_num specifies which databse's iterator should fail.
+
+ @returns True if test went as expected, that is, read/write operations
+ were aborted with error due to error in the callback call.
+
+ @see @c fail_callback().
+*/
+
+static
+int fail_db_iterator(op_type rd_or_wr, uint db_num)
+{
+ int *ctr;
+
+ DBUG_ASSERT(db_num < db_count);
+
+ ctr= &(callback_counts.db_iterator_get[db_num]);
+ return fail_callback(rd_or_wr, ctr, *ctr);
+}
+
+
+/**
+ Write image triggering errors in bcat_get_item_create_query/data().
+
+ @param[IN] type The type of item for which callback should fail.
+
+ @returns True if test went as expected, that is, write operations
+ were aborted with error due to error in the callback call.
+
+ @see @c fail_callback().
+*/
+
+static
+int fail_get_meta(item_type type)
+{
+ DBUG_ASSERT(type < 128);
+ int ret1, ret2;
+ int *ctr;
+
+ ctr= &(callback_counts.get_serialization[type]);
+ ret1= fail_callback(WR, ctr, *ctr);
+
+ ctr= &(callback_counts.get_metadata[type]);
+ ret2= fail_callback(WR, ctr, *ctr);
+
+ return ret1 || ret2;
+}
+
+
+/**
+ Read image triggering errors in bcat_add/create_item().
+
+ @param[IN] type The type of item for which callback should fail.
+
+ @returns True if test went as expected, that is, read operations
+ were aborted with error due to error in the callback call.
+
+ @see @c fail_callback().
+*/
+
+static
+int fail_item(item_type type)
+{
+ DBUG_ASSERT(type < 128);
+ int ret1, ret2;
+ int *ctr;
+
+ ctr= &(callback_counts.add_item[type]);
+ ret1= fail_callback(RD, ctr, *ctr);
+
+ ctr= &(callback_counts.create_item[type]);
+ ret2= fail_callback(RD, ctr, *ctr);
+
+ return ret1 || ret2;
+}
+
+
+/**
+ Read image triggering errors in bcat_reset/close().
+
+ @returns True if test went as expected, that is, read operations
+ were aborted with error due to error in the callback call.
+
+ @see @c fail_callback().
+*/
+
+static
+int fail_catalog()
+{
+ int ret1, ret2;
+ int *ctr;
+
+ ctr= &(callback_counts.cat_reset);
+ ret1= fail_callback(RD, ctr, *ctr);
+
+ ctr= &(callback_counts.cat_close);
+ ret2= fail_callback(RD, ctr, *ctr);
+
+ return ret1 || ret2;
+}
+
+/**
+ Read/write image triggering memory allocation errors.
+
+ @returns True if test went as expected, that is, read/write operations
+ were aborted with error due to error in the callback call.
+
+ @see @c fail_callback().
+*/
+
+static
+int fail_alloc(op_type rd_or_wr)
+{
+ int *ctr= &(callback_counts.alloc);
+
+ return fail_callback(rd_or_wr, ctr, *ctr);
+}
+
+
+/*
+ Read or write backup image triggering errors in specified callback function.
+
+ This function writes or reads backup image several times, each time arranging
+ for a specified callback to report error. The error will be reported at n-th
+ call to the callback function, where n ranges from @a howmuch to @a 1.
+
+ Triggering of error is arranged by assigning negative value to the
+ corresponding callback counter (see documentation of @c callback_counts in
+ catalog.h).
+
+ @param[IN] rd_or_wr tells whether image should be written or read; if
+ reading is specified, then "test_image.bst" backup
+ image should be created earlier
+ @param[IN] cntr pointer to the counter in callback_counts structure,
+ corresponding to the callback which should be tested
+ @param[IN] howmuch specifies how many calls to the callback function will
+ be tested
+
+ @returns True if the test went as expected, that is, write/read operation
+ returned error each time it was performed with failing callback function.
+*/
+
+int fail_callback(op_type rd_or_wr, int *cntr, uint howmuch)
+{
+ struct st_callback_counts saved_counts= callback_counts;
+ int ret= 1, ret1;
+
+ if (!howmuch)
+ {
+ diag("No callbacks recorded");
+ return 0;
+ }
+
+ for(; howmuch; --howmuch)
+ {
+ reset_counts();
+ *cntr= -howmuch;
+
+ switch (rd_or_wr) {
+ case WR: ret1= write_image(&img_header, "fail_callback.bst"); break;
+ case RD: ret1= read_image(&img_header, "test_image.bst"); break;
+ }
+ ret= ret && ret1 && !check_iterators();
+ }
+
+ callback_counts= saved_counts;
+ return ret;
+}
=== added file 'unittest/backup/bstr_data_chunks-t.c'
--- a/unittest/backup/bstr_data_chunks-t.c 1970-01-01 00:00:00 +0000
+++ b/unittest/backup/bstr_data_chunks-t.c 2009-06-30 20:37:35 +0000
@@ -0,0 +1,368 @@
+/** @file
+
+ Write to stream table data chunks of various sizes relative to the
+ stream block size. Repeat it for different block sizes.
+*/
+
+#include <tap.h>
+
+#include "stream_test.h"
+
+/**
+ @brief How many stream blocks to write.
+
+ When writing to stream, enough data chunks will be written to cover this
+ many stream blocks. However, no more than MAX_STREAM_LEN bytes will be
+ written.
+*/
+
+#define BLOCK_COUNT 20
+
+/**
+ @brief Limit on the size of backup stream created in this test.
+*/
+
+#define MAX_STREAM_LEN (5*1024*1024)
+
+static int chunks(size_t block_size, size_t chunk_size);
+static int write_data_chunk(backup_stream *stream, size_t chunk_size);
+static int read_data_chunk(backup_stream *stream, size_t *data_size);
+
+/**
+ Helper function which runs @c chunks() test via @c ok() call with
+ appropriate message.
+*/
+
+void ok_chunks(size_t block_size, size_t chunk_size)
+{
+ ok(!chunks(block_size, chunk_size),
+ "Chunks of size %lu over blocks of size %lu",
+ (long unsigned)chunk_size, (long unsigned)block_size);
+}
+
+
+void run_test()
+{
+ size_t block_size[] = { 20, 1024, 4*1024, 16*1024, 1024*1024, 0 };
+ size_t bsize;
+ uint i;
+
+ plan(5*6);
+
+ for(i=0; block_size[i]; ++i)
+ {
+ bsize= block_size[i];
+
+ ok_chunks(bsize, bsize/10 + 1);
+ ok_chunks(bsize, (5*bsize)/7 + 1);
+ ok_chunks(bsize, bsize );
+ ok_chunks(bsize, (7*bsize)/5 + 1);
+ ok_chunks(bsize, 3*bsize + bsize/3);
+ ok_chunks(bsize, 10*bsize);
+ }
+}
+
+
+/**
+ Counter for table data chunks.
+
+ The counter counts how many table data chunks have been
+ written/read by write/read_data_chunk() functions. It should be
+ set to 0 before writing/reading starts.
+*/
+
+unsigned long int chunk_count=0;
+byte *data_buffer= NULL; /**< buffer for storing chunk contents */
+
+
+/**
+ Write chunks of size @c chunk_size to stream using blocks of
+ size @block_size. Then read them back and check data integrity.
+*/
+
+int chunks(size_t block_size, size_t chunk_size)
+{
+ int ret= 1;
+ int alloc;
+ test_backup_stream stream;
+ size_t data_len;
+ byte *ptr;
+
+
+ stream_block_size= block_size;
+
+ /*
+ Table data chunk header takes at least 5 bytes. We also need
+ at least 1 byte of data. Thus chunk_size can't be less than 6.
+ */
+
+ if (chunk_size < 6)
+ chunk_size= 6;
+
+ /* allocate buffer for data */
+
+ byte *mem= data_buffer= (byte*)malloc(chunk_size);
+ if (!mem)
+ {
+ diag("Failed to allocate buffer for chunk data");
+ goto end;
+ }
+
+ alloc= 10;
+
+ /* Fill buffer with data */
+
+ memset(data_buffer, 0xF5, chunk_size);
+
+ /* open stream */
+
+ if (backup_stream_open_wr(&stream, "data_chunks.bst"))
+ {
+ diag("Failed to open stream for writing");
+ goto end;
+ }
+
+ alloc= 20;
+
+ /*
+ Write enough chunks to output BLOCK_COUNT stream blocks, but no more
+ than MAX_STREAM_LEN bytes.
+ */
+ for(chunk_count=0; chunk_count*chunk_size <= BLOCK_COUNT*block_size
+ && chunk_count*chunk_size < MAX_STREAM_LEN; )
+ if (write_data_chunk((backup_stream*)&stream, chunk_size))
+ goto end;
+
+ backup_stream_close(&stream);
+
+ alloc= 10;
+
+ /* now start reading */
+
+ if (backup_stream_open_rd(&stream, "data_chunks.bst"))
+ {
+ diag("Failed to open stream for reading");
+ goto end;
+ }
+
+ alloc= 20;
+
+ /*
+ Check that block size, which is detected when the stream is opened for
+ reading, is as we specified when writing the stream.
+ */
+ if (stream.base.block_size != block_size)
+ {
+ diag("Incorrect block size %lu instead of %lu.",
+ stream.base.block_size, (long unsigned)block_size);
+ goto end;
+ }
+
+ chunk_count= 0;
+
+ /* read chunks from the stream */
+
+ do
+ {
+ ret= read_data_chunk((backup_stream*)&stream, &data_len);
+
+ if (ret == BSTREAM_ERROR)
+ goto end;
+
+ /* check that all bytes read are correct */
+
+ for (ptr= data_buffer; ptr < data_buffer + data_len; ++ptr)
+ if (*ptr != 0xF5)
+ {
+ diag("Data of chunk #%lu of length %lu"
+ " corrupted at byte %d which is 0x%X.",
+ chunk_count, (long unsigned)data_len, ptr-data_buffer, *ptr);
+ ret= 1;
+ goto end;
+ };
+
+ } while (ret == BSTREAM_OK);
+
+ ret= 0;
+
+ end:
+
+ /*
+ In a moment we are going to free the buffer memory - avoid the dangling
+ pointer.
+ */
+ data_buffer= NULL;
+
+ if (alloc >= 20) backup_stream_close(&stream);
+ if (alloc >= 10) free(mem);
+
+ return ret;
+}
+
+
+/**
+ Write a table data chunk of the given size.
+
+ Data to be written is taken from the global buffer @c data_buffer which
+ should be allocated and of appropriate size. Size of the data
+ payload is calculated so that the complete chunk (data payload
+ + headers) has size @c chunk_size.
+
+ Apart from the data payload, the following information will be
+ stored in the table data chunk header, for debugging purposes.
+
+ table number - size of the data payload,
+ snapshot number - consecutive chunk number modulo 128,
+ flags - 0x0 (not used)
+
+ @returns 0 on success.
+*/
+
+int write_data_chunk(backup_stream *stream, size_t chunk_size)
+{
+ struct st_bstream_data_chunk dchunk;
+ size_t data_size= chunk_size;
+
+ memset(&dchunk, 0, sizeof(dchunk));
+
+ ++chunk_count;
+
+ dchunk.table_num= data_size;
+ dchunk.snap_num= chunk_count & 0x7F;
+ dchunk.data.begin= data_buffer;
+ dchunk.data.end= data_buffer+data_size;
+
+ if (BSTREAM_OK != bstream_wr_data_chunk(stream, &dchunk))
+ {
+ diag("Error when writing data chunk #%lu", chunk_count);
+ return 1;
+ }
+
+ return 0;
+}
+
+
+int read_data_chunk1(backup_stream *stream, size_t *len);
+int read_data_chunk2(backup_stream *stream, size_t *len);
+
+/**
+ Read next table data chunk.
+
+ Read either a complete chunk using @c read_data_chunk1() function
+ or just an initial fragment woith @c read_data_chunk2() function.
+
+ After reading the chunk, global pointer @c data_buffer is set to point
+ at the data payload. The size of the data is stored in @c data_size
+ which should not be NULL.
+
+ @retval BSTREAM_OK Data chunk has been read and there are more in the stream.
+ @retval BSTREAM_EOS Data chunk has been read and end of stream has been
+ reached.
+ @retcal BSTREAM_ERROR Error when reading data chunk.
+*/
+
+int read_data_chunk(backup_stream *stream, size_t *len)
+{
+ struct st_bstream_data_chunk dchunk;
+ memset(&dchunk, 0, sizeof(dchunk));
+
+ ++chunk_count;
+
+ /* every now and then read data chunk using different logic */
+ if (0 == chunk_count % 7)
+ return read_data_chunk2(stream, len); /* read only fragment of the chunk */
+ else
+ return read_data_chunk1(stream, len); /* read complete chunk */
+}
+
+
+/**
+ Read complete table data chunk.
+
+ Read chunk using @c bstream_rd_data_chunk() function. If the size of the
+ chunk stored as table number does not equal the actual size then error is
+ reported.
+
+ @retval BSTREAM_OK Data chunk has been read and there are more in the stream.
+ @retval BSTREAM_EOS Data chunk has been read and end of stream has been
+ reached.
+ @retcal BSTREAM_ERROR Error when reading data chunk.
+*/
+
+int read_data_chunk1(backup_stream *stream, size_t *len)
+{
+ struct st_bstream_data_chunk dchunk;
+ int ret;
+ size_t data_size;
+
+ DBUG_ASSERT(len);
+
+ memset(&dchunk, 0, sizeof(dchunk));
+
+ ret= bstream_rd_data_chunk(stream, &dchunk);
+
+ if (ret == BSTREAM_ERROR)
+ {
+ diag("Error reading data chunk #%lu.", chunk_count);
+ return BSTREAM_ERROR;
+ }
+
+ if (ret == BSTREAM_EOC)
+ {
+ diag("Invalid chunk encountered (#%lu).", chunk_count);
+ return BSTREAM_ERROR;
+ }
+
+ data_size= dchunk.data.end - dchunk.data.begin;
+
+ if (dchunk.table_num != data_size)
+ {
+ diag("Invalid data size in chunk #%lu (%u): %lu instead of %lu",
+ chunk_count, dchunk.snap_num, dchunk.table_num,
+ (long unsigned)data_size);
+ return BSTREAM_ERROR;
+ }
+
+ data_buffer= dchunk.data.begin;
+ *len= data_size;
+
+ return ret;
+}
+
+
+int bstream_read_part(backup_stream *s, blob *data, blob buf);
+
+/**
+ Read first 1024 bytes of a table data chunk and skip the rest.
+*/
+
+int read_data_chunk2(backup_stream *stream, size_t *len)
+{
+ short unsigned int snap_num, flags;
+ unsigned int seq_num;
+ long unsigned int table_num;
+ byte buf[1024];
+ blob to_read, envelope;
+ int ret= BSTREAM_OK;
+
+ /* read table data chunk header */
+ bstream_rd_byte(stream, &snap_num);
+ bstream_rd_int2(stream, &seq_num);
+ bstream_rd_byte(stream, &flags);
+ bstream_rd_num(stream, &table_num);
+
+ to_read.begin= buf;
+ to_read.end= buf+1024;
+ envelope= to_read;
+ /*
+ Read bytes until current buffer is full or end of chunk is reached.
+ */
+ while ((ret == BSTREAM_OK) && (to_read.begin < to_read.end))
+ ret= bstream_read_part(stream,&to_read,envelope);
+
+ data_buffer= buf;
+ *len= to_read.begin - buf;
+
+ /* Move to next chunk */
+ return ret==BSTREAM_ERROR ? BSTREAM_ERROR : bstream_next_chunk(stream);
+}
=== added file 'unittest/backup/bstr_eos-t.c'
--- a/unittest/backup/bstr_eos-t.c 1970-01-01 00:00:00 +0000
+++ b/unittest/backup/bstr_eos-t.c 2009-06-30 20:37:35 +0000
@@ -0,0 +1,422 @@
+/** @file
+
+ Prematurely report end of stream when reading backup image. EOS is
+ reported after reading given amount of stream chunks.
+
+ Test uses in-memory stream. When backup image is written, it is stored
+ in a memory buffer. Then the image is analysed to find chunk boundaries.
+ Finally, image is read from memory several times. Each time read is
+ interrupted by EOS signal at different stages.
+*/
+
+#include <tap.h>
+
+#include "stream_test.h"
+#include "catalog.h"
+
+#define FR_EOC 0x80 /**< bits for EOC fragment */
+#define FR_EOS 0xC0 /**< bits for EOS fragment */
+#define FR_MORE 0x00 /**< bits for MORE fragment */
+#define FR_LAST 0x40 /**< bits for LAST fragment */
+
+static int mem_write_image(image_header *hdr);
+static int mem_read_image(image_header *hdr);
+static int mem_read_lowlevel();
+static void get_chunk_boundaries();
+
+#define BSIZE 1024 /**< Size of stram i/o block */
+
+/**
+ @brief Buffer for in-memory stream.
+
+ We will store only the first block of the image, hence only that much
+ space is needed.
+*/
+
+byte image[BSIZE];
+byte *stream_pos= image; /**< current position in the buffer */
+byte *stop_at= NULL; /**< position at which reading will be interrupted */
+int early_eos= BSTREAM_EOS; /**< value to return when reding is interrupted */
+
+/**
+ @brief Array of pointers to chunk boundaries.
+
+ This is filled by @c get_chunk_boundaries().
+*/
+
+byte *chunk[128];
+
+/**
+ @brief The number of chunks found in the image.
+
+ This is set by @c get_chunk_boundaries().
+*/
+
+uint chunk_count= 0;
+
+
+void run_test()
+{
+ image_header hdr;
+ int i;
+ byte header;
+
+ init_catalog(&hdr);
+
+ ok(!mem_write_image(&hdr), "Writing backup image");
+
+ get_chunk_boundaries();
+
+ plan(1 + 2*chunk_count + 3*(chunk_count-3));
+
+ /*
+ In what follows we will read the image interrupting low-level read at each
+ chunk boundary. First we instruct low-level read to report ERROR at
+ interruption. Then, it will report EOS.
+ */
+
+ early_eos= BSTREAM_ERROR;
+
+ for (i= chunk_count-1; i >= 0; --i)
+ {
+ stop_at= chunk[i];
+ ok(BSTREAM_ERROR == mem_read_image(&hdr),
+ "ERROR at chunk %d when reading image", i);
+ }
+
+ /*
+ The low-level read subroutine does not interpret the image. It merely
+ scans all data chunks.
+ */
+ for (i= chunk_count-1; i >= 0; --i)
+ {
+ stop_at= chunk[i];
+ ok(BSTREAM_ERROR == mem_read_lowlevel(&hdr),
+ "ERROR at chunk %d when reading chunks", i);
+ }
+
+ early_eos= BSTREAM_EOS;
+
+ /*
+ There are "chunks" at the end of the stream which function as end
+ markers. The reading logic works fine if these are missing
+ due to early EOS. Hence we don't expect bstream library to report
+ errors for the few last chunks and we skip them.
+ */
+
+ for (i= chunk_count-4; i >= 0; --i)
+ {
+ stop_at= chunk[i];
+ ok(BSTREAM_ERROR == mem_read_image(&hdr),
+ "EOS at chunk %d when reading image", i);
+ }
+
+ /*
+ In the next loop we store EOS marker at the end of each chunk, so that the
+ end of stream is detected by bstream logic before it is reported by
+ low-level I/O.
+ */
+
+ for (i= chunk_count-4; i >= 0; --i)
+ {
+ header= *chunk[i];
+ *chunk[i]= FR_EOS;
+ stop_at= chunk[i]+1;
+ ok(BSTREAM_ERROR == mem_read_image(&hdr),
+ "EOS marker at chunk %d when reading image", i);
+ *chunk[i]= header;
+ }
+
+ for (i= chunk_count-4; i >= 0; --i)
+ {
+ header= *chunk[i];
+ *chunk[i]= FR_EOS;
+ stop_at= chunk[i]+1;
+ ok(BSTREAM_EOS == mem_read_lowlevel(&hdr),
+ "EOS marker at chunk %d when reading chunks", i);
+ *chunk[i]= header;
+ }
+}
+
+
+/*********************************************************************
+
+ Helper functions for reading/writing backup image.
+
+*********************************************************************/
+
+static int mem_write(void*, blob*, blob);
+static int mem_read(void*, blob*, blob);
+static int mem_forward(void*, unsigned long int*);
+
+static int mem_write_image(image_header *hdr)
+{
+ backup_stream stream;
+ int ret;
+
+ stream.stream.write= mem_write;
+ stream.stream.read= mem_read;
+ stream.stream.forward= mem_forward;
+ stream_pos= image;
+
+ reset_counts();
+
+ if (bstream_open_wr(&stream, 1024, 0))
+ return OPEN_ERROR;
+
+ ret= bstream_wr_preamble(&stream, hdr);
+ bstream_close(&stream);
+
+ return check_iterators() ? BSTREAM_ERROR : ret;
+}
+
+
+static int mem_read_image(image_header *hdr)
+{
+ backup_stream stream;
+ int ret;
+
+ stream.stream.write= mem_write;
+ stream.stream.read= mem_read;
+ stream.stream.forward= mem_forward;
+ stream_pos= image;
+
+ reset_counts();
+
+ if (bstream_open_rd(&stream, 0))
+ return OPEN_ERROR;
+
+ ret= bstream_rd_preamble(&stream, hdr);
+ bstream_close(&stream);
+
+ return check_iterators() ? BSTREAM_ERROR : ret;
+}
+
+
+static int mem_read_lowlevel()
+{
+ backup_stream stream;
+ unsigned short int x;
+ int ret;
+
+ stream.stream.write= mem_write;
+ stream.stream.read= mem_read;
+ stream.stream.forward= mem_forward;
+ stream_pos= image;
+
+ if (bstream_open_rd(&stream, 0))
+ return OPEN_ERROR;
+
+ do {
+ ret= bstream_rd_byte(&stream, &x);
+ ret= bstream_next_chunk(&stream);
+ } while (ret == BSTREAM_OK);
+
+ bstream_close(&stream);
+
+ return ret;
+}
+
+
+/*********************************************************************
+
+ Implementation of in-memory stream.
+
+*********************************************************************/
+
+static
+int mem_write(void *stream __attribute__ ((__unused__)),
+ bstream_blob *buf,
+ bstream_blob envelope __attribute__ ((__unused__)))
+{
+ int ret= BSTREAM_OK;
+
+ DBUG_ASSERT(buf);
+
+ if (!buf->begin || buf->begin == buf->end)
+ return BSTREAM_OK;
+
+ DBUG_ASSERT(buf->end);
+
+ size_t howmuch = buf->end - buf->begin;
+ if (stream_pos + howmuch >= image + 1024)
+ {
+ howmuch= image + 1024 - stream_pos;
+ ret= BSTREAM_EOS;
+ }
+
+ memmove(stream_pos, buf->begin, howmuch);
+
+ stream_pos+= howmuch;
+ buf->begin+= howmuch;
+
+ return ret;
+}
+
+
+static
+int mem_read(void *stream __attribute__ ((__unused__)),
+ bstream_blob *buf,
+ bstream_blob envelope __attribute__ ((__unused__)))
+{
+ size_t howmuch;
+ int ret= BSTREAM_OK;
+
+ DBUG_ASSERT(buf);
+
+ if (!buf->begin || buf->begin == buf->end)
+ return BSTREAM_OK;
+
+ DBUG_ASSERT(buf->end);
+
+ if (stream_pos == stop_at)
+ return early_eos;
+
+ howmuch= buf->end - buf->begin;
+ if (stream_pos + howmuch >= image + 1024)
+ {
+ howmuch= image + 1024 - stream_pos;
+ ret= BSTREAM_EOS;
+ }
+ if (stop_at && stream_pos + howmuch > stop_at)
+ {
+ howmuch= stop_at - stream_pos;
+ ret= BSTREAM_EOS;
+ }
+
+ memmove(buf->begin, stream_pos, howmuch);
+
+ stream_pos += howmuch;
+ buf->begin += howmuch;
+
+ return ret;
+}
+
+
+static
+int mem_forward(void *stream __attribute__ ((__unused__)),
+ unsigned long int *offset)
+{
+ int ret= BSTREAM_OK;
+ size_t howmuch;
+
+ DBUG_ASSERT(offset);
+
+ if (*offset == 0)
+ return BSTREAM_OK;
+
+ howmuch= *offset;
+ if (stream_pos + howmuch >= image + 1024)
+ {
+ howmuch= image + 1024 - stream_pos;
+ ret= BSTREAM_EOS;
+ }
+
+ stream_pos+= howmuch;
+ *offset-= howmuch;
+
+ return ret;
+}
+
+
+/*********************************************************************
+
+ Code for analysing chunk boundaries (taken from stream_v1_transport.c).
+
+*********************************************************************/
+
+#define FR_TYPE_MASK 0xC0 /**< type bits for mask */
+#define FR_LEN_MASK 0x3F /**< type length for mask */
+
+/** biggest size of small fragment */
+#define FR_SMALL_MAX ((size_t)FR_LEN_MASK)
+#define FR_BIG 0x80 /**< type bits for big fragment */
+#define FR_HUGE 0xC0 /**< type bits for huge fragment */
+#define FR_BIG_SHIFT 6 /**< value shift for big fragment */
+#define FR_HUGE_SHIFT 12 /**< value shift for huge fragment */
+/** header for the biggest possible chunk */
+#define FR_HUGE_MAX_HDR (FR_HUGE|FR_LEN_MASK)
+/** size of the biggest possible chunk */
+#define FR_HUGE_MAX_LEN ((size_t)FR_LEN_MASK << FR_HUGE_SHIFT)
+
+
+static
+int read_fragment_header(byte **header)
+{
+ byte hdr= *((*header)++);
+ size_t len= hdr & FR_LEN_MASK; /* 6 bit value stored in the header */
+ int last;
+
+ if (hdr == FR_EOS)
+ return BSTREAM_EOS;
+
+ if (hdr == FR_EOC)
+ return BSTREAM_EOC;
+
+ last= hdr & FR_LAST ? FR_LAST : FR_MORE; /* is it last fragment of a chunk? */
+
+ /*
+ If len == 0 then we have a fragment which extends to the end of a block,
+ thus we restore *header to signal that fact to the caller.
+ */
+ if (len == 0)
+ {
+ (*header)--;
+ return last;
+ }
+
+ /*
+ If the highest bit is set, we are looking at a big or huge fragment. Such
+ fragment is never the last fragment of a chunk.
+ */
+ if (hdr & 0x80)
+ {
+ last= FR_MORE;
+
+ if ((hdr & FR_TYPE_MASK) == FR_BIG)
+ len <<= FR_BIG_SHIFT;
+
+ if ((hdr & FR_TYPE_MASK) == FR_HUGE)
+ len <<= FR_HUGE_SHIFT;
+ }
+
+ /* move *header pointer to the first byte after the fragment */
+ *header += len;
+
+ return last;
+}
+
+
+void get_chunk_boundaries()
+{
+ byte *fhead= image+5;
+ byte *saved_fhead;
+
+ chunk_count=0;
+
+ chunk[chunk_count++]= fhead;
+
+ while (fhead < image + 1024 && chunk_count < 127)
+ {
+ saved_fhead= fhead;
+ switch (read_fragment_header(&fhead)) {
+
+ case BSTREAM_EOC:
+ case FR_LAST:
+ if (fhead > saved_fhead)
+ chunk[chunk_count++]= fhead;
+ else
+ fhead = image + 1024;
+ break;
+
+ case BSTREAM_EOS:
+ chunk[chunk_count++]= saved_fhead;
+ fhead= image + 1024;
+
+ case FR_MORE: break;
+
+ }
+ }
+
+ chunk[chunk_count]= NULL;
+}
=== added file 'unittest/backup/bstr_extra-t.c'
--- a/unittest/backup/bstr_extra-t.c 1970-01-01 00:00:00 +0000
+++ b/unittest/backup/bstr_extra-t.c 2009-06-30 20:37:35 +0000
@@ -0,0 +1,114 @@
+/** @file
+
+ Execute some "border cases" to exercise bstream library code. This includes
+ incomplete backup image preamble, incorrect snapshot/item types and
+ writing/reading image with no objects.
+*/
+#include <tap.h>
+#include "stream_test.h"
+#include "catalog.h"
+
+/* Import some low-level functions from bstream library */
+int bstream_wr_header(backup_stream*, struct st_bstream_image_header*);
+int bstream_wr_catalogue(backup_stream*, struct st_bstream_image_header*);
+int bstream_end_chunk(backup_stream*);
+
+image_header img_header;
+
+
+void run_test()
+{
+ test_backup_stream stream;
+ int ret1, ret2;
+
+ plan(5);
+
+ init_catalog(&img_header);
+
+ /* write/read only image header */
+
+ reset_counts();
+ if (backup_stream_open_wr(&stream, "extra.bst"))
+ {
+ diag("Failed to open stream for writing");
+ return;
+ };
+ ret1= bstream_wr_header((backup_stream*)&stream, &img_header);
+ backup_stream_close(&stream);
+ ret1= ret1 || check_iterators();
+
+ reset_counts();
+ if (backup_stream_open_rd(&stream, "extra.bst"))
+ {
+ diag("Failed to open stream for reading");
+ return;
+ };
+ ret2= bstream_rd_preamble((backup_stream*)&stream, &img_header);
+ backup_stream_close(&stream);
+ ret1= ret1 || check_iterators();
+
+ ok(!ret1 && ret2, "Incomplete preamble: only header");
+
+ /* write/read preamble with metadata section missing */
+
+ reset_counts();
+ if (backup_stream_open_wr(&stream, "extra.bst"))
+ {
+ diag("Failed to open stream for writing");
+ return;
+ };
+ ret1= bstream_wr_header((backup_stream*)&stream, &img_header);
+ ret1= ret1 || bstream_end_chunk((backup_stream*)&stream);
+ ret1= ret1 || bstream_wr_catalogue((backup_stream*)&stream, &img_header);
+ backup_stream_close(&stream);
+ ret1= ret1 || check_iterators();
+
+ reset_counts();
+ if (backup_stream_open_rd(&stream, "extra.bst"))
+ {
+ diag("Failed to open stream for reading");
+ return;
+ };
+ ret2= bstream_rd_preamble((backup_stream*)&stream, &img_header);
+ backup_stream_close(&stream);
+ ret1= ret1 || check_iterators();
+
+ ok(!ret1 && ret2, "Incomplete preamble: no metadata");
+
+
+ /* write/read snapshot of unknown type */
+
+ snapshots[0]= 333; /* unknown snapshot type */
+ init_catalog(&img_header);
+
+ reset_counts();
+ ret1= write_image(&img_header, "unknown_snapshot.bst") || check_iterators();
+ reset_counts();
+ ret2= read_image(&img_header, "unknown_snapshot.bst");
+ ret1= ret1 || check_iterators();
+
+ ok(!ret1 && ret2, "Image with unknown snapshot type");
+
+ /* try writing image with unknown item type (it should fail) */
+
+ objects_perdb[0].type= 333;
+ init_catalog(&img_header);
+
+ reset_counts();
+ ret1= write_image(&img_header, "unknown_item.bst");
+ ret2= check_iterators();
+
+ ok(ret1 && !ret2, "Writing image with unknown item type");
+
+ /* write/read empty backup image (also no databases) */
+ opt_empty_catalog= 1;
+ img_header.snap_count= 0;
+
+ reset_counts();
+ ret1= write_image(&img_header, "empty.bst") || check_iterators();
+ reset_counts();
+ ret2= read_image(&img_header, "empty.bst") || check_iterators();
+
+ ok(!ret1 && !ret2, "Empty image");
+
+}
=== added file 'unittest/backup/bstr_extra1-t.c'
--- a/unittest/backup/bstr_extra1-t.c 1970-01-01 00:00:00 +0000
+++ b/unittest/backup/bstr_extra1-t.c 2009-06-30 20:37:35 +0000
@@ -0,0 +1,160 @@
+/* @file
+
+ Provoke errors when reading basic data types, so that error handling branch
+ in the code is executed. This is done only for datatypes for which error
+ branches are not covered in other tests.
+*/
+
+#include <tap.h>
+
+#include "stream_test.h"
+
+/* import some low-level functions from bstream library */
+
+int bstream_rd_item_type(backup_stream*, item_type*);
+int bstream_write_blob(backup_stream*, blob);
+int bstream_end_chunk(backup_stream*);
+void bstream_free(byte*);
+
+/**
+ Dummy low-level read function which always returns error.
+*/
+
+int dummy_read(void *stream, blob *buf, blob envelope);
+
+void run_test()
+{
+ test_backup_stream stream;
+ backup_stream *bstr= (backup_stream*)&stream;
+
+ /*
+ Note: buffer size must be odd as only then expected error branch is taken.
+ */
+ byte buf[127];
+
+ blob data;
+ unsigned int num;
+ long unsigned int lnum;
+ int ret= BSTREAM_OK;
+
+ /* fill buffer with 0x01 bytes */
+ memset(buf, 0x01, sizeof(buf));
+ data.begin= buf;
+ data.end= buf + sizeof(buf);
+
+ stream_block_size= 256;
+
+ plan(3 + 3 + 3);
+
+ /* write 0x01 bytes to stream */
+
+ ret= backup_stream_open_wr(&stream, "extra1.bst");
+ if (ret)
+ {
+ diag("Failed to open stream for writing");
+ return;
+ }
+
+ ret= bstream_write_blob(bstr, data);
+ ok(BSTREAM_OK == ret, "Writing bytes to stream");
+
+ /*
+ execute flush twice to enter branch where flush is made with empty output
+ buffer
+ */
+ ret= bstream_flush(bstr);
+ ret= ret || bstream_flush(bstr);
+ ok(!ret, "Flushing flushed stream");
+
+ backup_stream_close(&stream);
+
+ /*
+ call close on already closed stream to cover corresponding branch in the
+ code
+ */
+ ret= bstream_close(bstr);
+ ok(BSTREAM_OK == ret, "Closing already closed stream");
+
+ ret= backup_stream_open_rd(&stream, "extra1.bst");
+ if (ret)
+ {
+ diag("Failed to open stream for reading");
+ return;
+ }
+
+ /*
+ Call writing functions on stream opened for reading.
+ */
+ ret= bstream_write_blob(bstr, data);
+ ok(BSTREAM_ERROR == ret, "Writing bytes to stream opened for reading");
+ ret= bstream_end_chunk(bstr);
+ ok(BSTREAM_ERROR == ret, "Closing chunk in stream opened for reading");
+ ret= bstream_flush(bstr);
+ ok(BSTREAM_ERROR == ret, "Flushing stream opened for reading");
+
+ /*
+ After setting read function to dummy_read, error will be hit by bstream
+ library next time it tries to reload input buffer.
+ */
+ stream.base.stream.read= dummy_read;
+
+ /*
+ In what follows, various basic types of data are read from the stream. This
+ is done in a loop, so that eventually the bstream input buffer will be
+ reloaded and error will be hit.
+ */
+
+ do
+ {
+ ret= bstream_rd_string(bstr, &data);
+ if (!ret)
+ bstream_free(data.begin);
+ } while (ret == BSTREAM_OK);
+
+ backup_stream_close(&stream);
+ ok(BSTREAM_ERROR == ret, "Error when reading strings");
+
+ ret= backup_stream_open_rd(&stream, "extra1.bst");
+ if (ret)
+ {
+ diag("Failed to open stream for reading");
+ return;
+ }
+ stream.base.stream.read= dummy_read;
+
+ do
+ {
+ ret= bstream_rd_int4(bstr, &lnum);
+ } while (ret == BSTREAM_OK);
+
+ backup_stream_close(&stream);
+ ok(BSTREAM_ERROR == ret, "Error when reading int4 numbers");
+
+ ret= backup_stream_open_rd(&stream, "extra1.bst");
+ if (ret)
+ {
+ diag("Failed to open stream for reading");
+ return;
+ }
+ stream.base.stream.read= dummy_read;
+
+ do
+ {
+ ret= bstream_rd_int2(bstr, &num);
+ } while (ret == BSTREAM_OK);
+
+ bstream_next_chunk(bstr);
+ bstream_next_chunk(bstr);
+
+ backup_stream_close(&stream);
+ ok(BSTREAM_ERROR == ret, "Error when reading int2 numbers");
+
+}
+
+
+int dummy_read(void *stream __attribute__ ((__unused__)),
+ blob *buf __attribute__ ((__unused__)),
+ blob envelope __attribute__ ((__unused__)))
+{
+ return BSTREAM_ERROR;
+}
=== added file 'unittest/backup/bstr_io_errors-t.c'
--- a/unittest/backup/bstr_io_errors-t.c 1970-01-01 00:00:00 +0000
+++ b/unittest/backup/bstr_io_errors-t.c 2009-06-30 20:37:35 +0000
@@ -0,0 +1,75 @@
+/** @file
+
+ Write and read backup image triggering errors during low-level write/read
+ calls.
+*/
+
+#include <tap.h>
+
+#include "stream_test.h"
+#include "catalog.h"
+
+image_header img_header;
+
+
+void run_test()
+{
+ int i;
+ int can_pass;
+ uint planned;
+
+ init_catalog(&img_header);
+
+ diag("Testing writing of backup image");
+
+ reset_io_counts();
+ ok(!write_image(&img_header, "io_errors.bst") && !check_iterators(),
+ "Counting i/o write calls");
+
+ diag("Made %lu write calls", io_counts.write);
+ diag("Wrote %lu bytes", (long unsigned)io_counts.write_bytes);
+
+ planned= 1 + 2*io_counts.write;
+
+ /*
+ Errors during last 2 write calls (which happen during closing of a stream)
+ are not reported by bstream library
+ */
+ can_pass= 2;
+
+ for(i= io_counts.write; i > 0 ; --i)
+ {
+ reset_io_counts();
+ io_counts.write= -i;
+ ok((write_image(&img_header, "write_fail.bst") && !check_iterators())
+ || can_pass, "Failing %d-th write call", i);
+
+ reset_io_counts();
+ ok((read_image(&img_header, "write_fail.bst") && !check_iterators())
+ || can_pass, "Reading resulting image");
+
+ if (can_pass)
+ --can_pass;
+ }
+
+ diag("Testing reading of backup image");
+
+ reset_io_counts();
+ ok(!read_image(&img_header, "io_errors.bst") && !check_iterators(),
+ "Counting i/o read calls");
+
+ diag("Made %lu read calls", io_counts.read);
+ diag("Read %lu bytes", (long unsigned)io_counts.read_bytes);
+
+ plan(planned+= 1 + io_counts.read);
+
+ for(i= io_counts.read; i > 0 ; --i)
+ {
+ reset_io_counts();
+ io_counts.read= -i;
+ ok(read_image(&img_header, "io_errors.bst") && !check_iterators(),
+ "Failing %d-th read call", i);
+ }
+
+}
+
=== added file 'unittest/backup/bstr_random_chunks-t.c'
--- a/unittest/backup/bstr_random_chunks-t.c 1970-01-01 00:00:00 +0000
+++ b/unittest/backup/bstr_random_chunks-t.c 2009-06-30 20:37:35 +0000
@@ -0,0 +1,244 @@
+/* @file
+
+ In order to excersise bstream library code, read and write a pseudo-random
+ sequence of chunks of varied sizes.
+*/
+#include <tap.h>
+#include "stream_test.h"
+
+/* import some low-level function from bstream library */
+
+int bstream_flush(backup_stream *s);
+int bstream_end_chunk(backup_stream *s);
+int bstream_write_blob(backup_stream *s, bstream_blob buf);
+int bstream_wr_int4(backup_stream *s, unsigned long int x);
+int bstream_read_part(backup_stream *s, bstream_blob *data, bstream_blob buf);
+
+/** Determines how big image will be created */
+#define DATA_AMOUNT 10*1024*1024
+/** Stream block size to use */
+#define BSIZE 4*1024
+
+/**
+ List of chunk sizes which will be used in the pseudo-random sequence.
+*/
+size_t chunk_size[] = { 10,
+ BSIZE/10,
+ (5*BSIZE)/7,
+ (7*BSIZE)/5,
+ 500*1024 };
+#define CSIZE_NUM 5 /**< Size of @c chunk_size[] array */
+
+
+static uint hash(uint mod, size_t key);
+static int write_chunk(backup_stream *stream, size_t chunk_size);
+static int read_chunk(backup_stream *stream, size_t *len);
+/**
+ Chunk counter increased by @c write/read_chunk() functions.
+*/
+static unsigned long int chunk_count=0;
+
+void run_test()
+{
+ test_backup_stream stream;
+ size_t byte_count;
+ size_t csize;
+ int ret;
+
+ stream_block_size= BSIZE;
+
+ diag("Writing");
+
+ ret= backup_stream_open_wr(&stream, "random.bst");
+ if (ret)
+ {
+ diag("Failed to open stream for writing");
+ return;
+ }
+
+ chunk_count= 0;
+
+ for(byte_count=0 ; byte_count < DATA_AMOUNT ; )
+ {
+ csize= chunk_size[hash(CSIZE_NUM, chunk_count)];
+
+ ok(!write_chunk((backup_stream*)&stream, csize),
+ "Wrote chunk #%lu of size %lu bytes", chunk_count, (long unsigned)csize);
+
+ /*
+ Flush stream every now and then to exercise more code in bstream library.
+ */
+ if (chunk_count % 7 == 0)
+ bstream_flush((backup_stream*)&stream);
+
+ byte_count+= csize;
+ }
+
+ backup_stream_close(&stream);
+
+ diag("Reading");
+
+ plan(2*chunk_count + 2);
+
+ ret= backup_stream_open_rd(&stream, "random.bst");
+ if (ret)
+ {
+ diag("Failed to open stream for reading");
+ return;
+ }
+
+ chunk_count= 0;
+
+ do {
+
+ ret= read_chunk((backup_stream*)&stream, &byte_count);
+ ok(BSTREAM_ERROR != ret,
+ "Reading chunk #%lu of size %lu bytes",
+ chunk_count, (long unsigned)byte_count);
+
+ if (ret == BSTREAM_ERROR)
+ goto end;
+
+ } while (ret == BSTREAM_OK);
+
+ /* try to read yet another chunk to hit EOS */
+
+ ret= read_chunk((backup_stream*)&stream, &byte_count);
+ ok(BSTREAM_ERROR == ret,"Reading another chunk after EOS was hit");
+
+ ret= bstream_next_chunk((backup_stream*)&stream);
+ ok(BSTREAM_EOS == ret,"Moving to next chunk after EOS was hit");
+
+ end:
+
+ backup_stream_close(&stream);
+}
+
+/**
+ Hash function returning number in given range.
+
+ @param[IN] mod function will return number in range 0 .. (mod-1)
+ @param[IN] key function argument
+
+ @note Function uses sdbm algorithm from http://www.cse.yorku.ca/~oz/hash.html
+*/
+uint hash(uint mod, size_t key)
+{
+ static size_t hash= 33;
+ hash= key + (hash << 6) + (hash << 16) - hash;
+ return hash % mod;
+}
+
+/**
+ Write a chunk of given size to backup stream.
+
+ First 4 bytes of the chunk contain its size. The remaining bytes are filled
+ with 0xF5 byte.
+
+ @returns Non-zero value in case of error, 0 otherwise.
+*/
+int write_chunk(backup_stream *stream, size_t chunk_size)
+{
+ byte buf[1024];
+ blob to_write;
+ int ret;
+
+ memset(buf, 0xF5, 1024);
+ ++chunk_count;
+
+ bstream_wr_int4(stream, chunk_size);
+ chunk_size-= 4;
+
+ while (chunk_size)
+ {
+ to_write.begin= buf;
+ to_write.end= buf + chunk_size;
+
+ if (to_write.end > to_write.begin + 1024)
+ to_write.end= to_write.begin + 1024;
+
+ chunk_size-= (to_write.end - to_write.begin);
+
+ ret= bstream_write_blob(stream, to_write);
+
+ if (BSTREAM_OK != ret)
+ {
+ diag("Error when writing chunk #%lu", chunk_count);
+ return ret;
+ }
+ }
+
+ return bstream_end_chunk(stream);
+}
+
+/**
+ Read chunk written by @c write_chunk.
+
+ Reads the chunk, verifies data integrity and its length.
+
+ @param[OUT] len set to the number of bytes found in the chunk.
+
+ @retval BSTREAM_OK Chunk read successfuly and integrity tests passed.
+ @retval BSTREAM_EOS End of stream has been encountered.
+ @retval BSTREAM_ERROR Error detected.
+*/
+int read_chunk(backup_stream *stream, size_t *len)
+{
+ unsigned long int chunk_size;
+ byte buf[1024];
+ byte *ptr;
+ bstream_blob to_read, envelope;
+ int ret= BSTREAM_OK;
+
+ DBUG_ASSERT(len);
+
+ ++chunk_count;
+
+ ret= bstream_rd_int4(stream, &chunk_size);
+ if (ret != BSTREAM_OK)
+ return ret;
+
+ *len= chunk_size;
+ chunk_size-= 4;
+
+ while (chunk_size && ret == BSTREAM_OK)
+ {
+ to_read.begin= buf;
+ to_read.end= buf + chunk_size;
+
+ if (to_read.end > to_read.begin + 1024)
+ to_read.end= to_read.begin + 1024;
+
+ envelope= to_read;
+ ret= bstream_read_part(stream, &to_read, envelope);
+
+ if (ret == BSTREAM_ERROR)
+ {
+ diag("Error reading data in chunk #%lu.", chunk_count);
+ return BSTREAM_ERROR;
+ }
+
+ /* check that all bytes are correct */
+
+ for (ptr= buf; ptr < to_read.begin; ++ptr)
+ if (0xF5 != *ptr)
+ {
+ diag("Incorrect byte read in chunk #%lu", chunk_count);
+ return BSTREAM_ERROR;
+ }
+
+ chunk_size-= to_read.begin - buf;
+ }
+
+ (*len)-= chunk_size;
+ ret= bstream_next_chunk(stream);
+
+ if (chunk_size)
+ {
+ diag("Not enough bytes found in chunk #%lu: %lu are missing",
+ chunk_count, chunk_size);
+ return BSTREAM_ERROR;
+ }
+
+ return ret;
+}
=== added file 'unittest/backup/catalog.c'
--- a/unittest/backup/catalog.c 1970-01-01 00:00:00 +0000
+++ b/unittest/backup/catalog.c 2009-06-30 20:37:35 +0000
@@ -0,0 +1,694 @@
+/** @file
+
+ Define backup/restore catalogue callbacks to be used for testing backup
+ stream library.
+
+ The contents of the catalogue is determined by arrays @c snapshots[],
+ @c objects_global[], @c objects_perdb[], @c objects_pertable[]. Additionally,
+ the catalogue will contain DB_COUNT databases named db1, ..., dbN and
+ TABLE_COUNT tables named t1, ..., tM. Tables are distributed among available
+ databases and snapshots.
+
+ For each object in the catalogue, the serialization string and extra metadata
+ string have form "serialization of <object name>" and
+ "metadata for <object name>", respectively.
+
+ Calls to all callback functions are counted. There are mechanisms for
+ reporting error from each callback function after a given number of calls.
+*/
+
+#include <my_global.h>
+#include <m_string.h>
+#include <stream_v1_services.h>
+
+#include "catalog.h"
+
+/**
+ List of table data snapshots reported in the catalogue.
+*/
+snapshot_type snapshots[] = {BI_NATIVE, BI_DEFAULT, BI_CS, BI_NODATA};
+const uint snap_count= sizeof(snapshots)/sizeof(snapshot_type);
+
+/**
+ List of global objects other than databases.
+
+ Databases are declared separately with DB_COUNT constant.
+*/
+item_info objects_global[]=
+{
+ {BSTREAM_IT_CHARSET, {(byte*)"charset", NULL}, 0},
+ {BSTREAM_IT_USER, {(byte*)"user", NULL}, 0},
+ {BSTREAM_IT_TABLESPACE, {(byte*)"tablespace",NULL}, 0},
+};
+const uint global_count= sizeof(objects_global)/sizeof(item_info);
+
+/*
+ List of per-database objects other than tables.
+
+ Tables are declared separately with TABLE_COUNT constant. These objects
+ are reported inside each database.
+*/
+item_info objects_perdb[]=
+{
+ {BSTREAM_IT_VIEW, {(byte*)"view",NULL}, 0},
+ {BSTREAM_IT_SPROC, {(byte*)"procedure",NULL}, 1},
+ {BSTREAM_IT_SFUNC, {(byte*)"function",NULL}, 2},
+ {BSTREAM_IT_EVENT, {(byte*)"event",NULL}, 3},
+ {BSTREAM_IT_PRIVILEGE, {(byte*)"privilege",NULL}, 4}
+};
+const uint perdb_count= sizeof(objects_perdb)/sizeof(item_info);
+
+/*
+ List of per-table objects.
+
+ These objects are distributed among all the tables present in the catalogue.
+*/
+item_info objects_pertable[]=
+{
+ {BSTREAM_IT_TRIGGER, {(byte*)"trigger",NULL}, 0}
+};
+const uint pertable_count= sizeof(objects_pertable)/sizeof(item_info);
+
+/**
+ Defines number N of databases present in the catalogue.
+
+ The databases are called db1, ... , dbN.
+*/
+#define DB_COUNT 1
+
+/**
+ Defines number M of tables present in the catalogue.
+
+ The tables are called t1, ... , tM. They are evenly distributed among the
+ databases. That is, table t1 belongs to db1, ... tN belongs to dbN, tN+1
+ belongs to db1 and so on.
+*/
+#define TABLE_COUNT 4
+
+table_info tables[TABLE_COUNT];
+db_info dbs[DB_COUNT];
+
+const uint db_count= DB_COUNT;
+const uint table_count= TABLE_COUNT;
+
+/**
+ Structure containing bstream callback counters.
+
+ Whenever one of the low-level I/O functions is called, the corresponding
+ counter will be increased.
+
+ Setting counter to a negative value will cause the corresponding function
+ to report ERROR or EOS when counter reaches the value of 0.
+*/
+
+struct st_callback_counts callback_counts;
+
+
+/**
+ Initialize data structures implementing the catalogue.
+
+ This function must be called before any callback is used.
+*/
+void init_catalog(image_header *hdr)
+{
+ uint i;
+ char *name;
+ blob *engine_name;
+ static char db_name[DB_COUNT][10];
+ static char table_name[TABLE_COUNT][10];
+
+ bzero(hdr, sizeof(image_header));
+
+ hdr->version= 1;
+ hdr->snap_count= snap_count;
+ hdr->flags= BSTREAM_FLAG_BINLOG;
+
+ /* Fill snapshot information */
+
+ for(i=0; i < snap_count; ++i)
+ {
+ hdr->snapshot[i].type= snapshots[i];
+ if (snapshots[i] == BI_NATIVE)
+ {
+ engine_name= &(hdr->snapshot[i].engine.name);
+ engine_name->begin= (byte*)"Storage_engine";
+ engine_name->end= engine_name->begin + 14;
+ }
+ }
+
+ /* Initialize database items */
+
+ for (i=0; i < db_count; ++i)
+ {
+ name= db_name[i];
+
+ snprintf(name, 10, "db%d", i+1);
+
+ dbs[i].base.type= BSTREAM_IT_DB;
+ dbs[i].base.pos= i;
+ dbs[i].base.name.begin= (byte*)name;
+ dbs[i].base.name.end= (byte*)name + strlen(name);
+ }
+
+ /* Initialize other global items */
+
+ for (i=0; i < global_count; ++i)
+ {
+ name= (char*)objects_global[i].name.begin;
+ objects_global[i].name.end= (byte*)name + strlen(name);
+ }
+
+ /* Initialize table items */
+
+ for (i=0; i < table_count; ++i)
+ {
+ name= table_name[i];
+
+ snprintf(name, 10, "t%d", i+1);
+
+ tables[i].base.base.type= BSTREAM_IT_TABLE;
+ tables[i].base.base.pos= i;
+ tables[i].base.base.name.begin= (byte*)name;
+ tables[i].base.base.name.end= (byte*)name + strlen(name);
+ tables[i].base.db= &dbs[i % db_count];
+ tables[i].snap_num= i % snap_count;
+ ++(hdr->snapshot[i % snap_count].table_count);
+ }
+
+ /* Initialize other per-db items */
+
+ for (i=0; i < perdb_count; ++i)
+ {
+ name= (char*)objects_perdb[i].name.begin;
+ objects_perdb[i].name.end= (byte*)name + strlen(name);
+ }
+
+ /* Initialize other per-table items */
+
+ for (i=0; i < pertable_count; ++i)
+ {
+ name= (char*)objects_pertable[i].name.begin;
+ objects_pertable[i].name.end= (byte*)name + strlen(name);
+ }
+
+}
+
+/** Reset all callback counters. */
+void reset_counts()
+{
+ bzero(&callback_counts, sizeof(callback_counts));
+}
+
+/**
+ Check that each iterator_get() call was matched with iterator_free().
+
+ @return 0 if call counts match, non-zero otherwise.
+*/
+int check_iterators()
+{
+ uint i;
+
+ for(i=0; i<128; ++i)
+ if (callback_counts.iterator_get[i] > 0
+ && callback_counts.iterator_get[i]
+ != callback_counts.iterator_free[i])
+ return 1;
+
+ for(i=0; i<128; ++i)
+ if (callback_counts.db_iterator_get[i] > 0
+ && callback_counts.db_iterator_get[i]
+ != callback_counts.db_iterator_free[i])
+ return 1;
+
+ return 0;
+}
+
+/**********************************************************************
+
+ Functions for iterating over contents of the catalogue.
+
+
+ Backup stream library asks for different kinds of iterators:
+
+ a) iterator over a given type of global objects (BSTREAM_IT_CHARSET,
+ BSTRERAM_IT_USER, etc).
+ b) iterator determining order in which global objects' metadata will be
+ stored (BSTREAM_IT_GLOBAL).
+ c) iterator determining order in which per-database objects' metadata will
+ be stored (BSTREAM_IT_PERDB).
+ d) iterator determining order in which per-table objects' metadata will be
+ stored (BSTREAM_IT_PERTABLE).
+
+ The iterators are represented by pointers to the corresponding @c it_*
+ counter. In case of iterators a) and b) they are both represented by a pointer
+ to @c it_global. However, for iterator a), the value of @c it_global
+ is positive and indicates the type of the object over which we should iterate.
+ For iterator b) the value if @c it_global is negative.
+
+ Note: Since always only one instance of an iterator is used, we do not bother
+ to dynamically allocate/free iterators. Instead, we always use a single,
+ static instance of each iterator represented by an appropriate pointer to one
+ of the @c it_* variables.
+
+*********************************************************************/
+
+
+int it_global;
+int it_db;
+int it_perdb;
+int it_pertable;
+
+/** Iterate over databases */
+item_info* db_iterator_next()
+{
+ DBUG_ASSERT(it_db >= 0);
+
+ if ((unsigned)it_db >= db_count)
+ return NULL;
+
+ return (item_info*)&dbs[it_db++];
+}
+
+/**
+ Iterate over global objects of type @c it_global.
+
+ This function is used for iterator represented by @c it_global when its
+ value is positive. The value of @c it_global indicates the type of global
+ objects over which we shuld iterate. A separate counter @c it_db is used to
+ keep track of iterator's position.
+*/
+item_info* other_iterator_next()
+{
+ uint pos;
+ item_info *item= NULL;
+
+ DBUG_ASSERT(it_global >= 0);
+
+ /* there are no more global objects to look at - return NULL */
+ if ((unsigned)it_db >= global_count)
+ return NULL;
+
+ /*
+ Search objects_global[] array for next global objects whose type equals
+ it_global.
+ */
+ for(pos=it_db++; pos < global_count; ++pos)
+ if (objects_global[pos].type == (item_type)it_global)
+ item= &objects_global[pos];
+
+ if (!item)
+ return NULL;
+
+ /*
+ For objects of type CHARSET and USER bstream library expects only the name
+ of the object. For other objects we should return a pointer to item_info
+ structure describing it.
+ */
+ switch (item->type) {
+ case BSTREAM_IT_CHARSET:
+ case BSTREAM_IT_USER: return (item_info*)&(item->name);
+ default: return item;
+ }
+}
+
+/**
+ Iterate over all global objecs.
+
+ This function is used for iterator represented by @c it_global when its
+ value is negative. In that case we should iterate over all global objects
+ from the catalogue. We start with databases which are enumerated using
+ @c it_db counter. Then we enumerate other global objects from
+ @c objects_global[] array using @c it_global to track iterator's position.
+*/
+item_info* global_iterator_next()
+{
+ unsigned pos= it_db;
+
+ DBUG_ASSERT(it_global < 0);
+
+ /*
+ Return next database if not all of them enumerated yet.
+ */
+ if (pos < db_count)
+ {
+ ++it_db;
+ return (item_info*)&dbs[pos];
+ }
+
+ /*
+ Otherwise iterate over all global objects from objects_global[] array
+ using the value of it_global for keeping the current position within
+ the array. Since we want it_global to be negative, we use convention that
+ values -1,-2,-3,... correspond to positions 0,1,2,...
+ */
+ pos= -it_global-1;
+
+ /*
+ Return NULL if all global objects have been already enumerated.
+ */
+ if (pos >= global_count)
+ return NULL;
+
+ --it_global;
+
+ return &objects_global[pos];
+}
+
+/**
+ Iterate over all objects belonging to a given database.
+
+ First all tables belonging to that database are returned, then other
+ per-database objects as defined by @c objects_perdb[] array. Counter
+ @c it_perdb indicates current postition of the iterator. First table_count
+ values correspond to the tables while the following values indicate position
+ within @c objects_perdb[] array.
+*/
+dbitem_info* perdb_iterator_next(struct st_bstream_db_info *db)
+{
+ uint pos;
+ static dbitem_info item_buf;
+
+ DBUG_ASSERT(it_perdb >= 0);
+
+ /* Find next table beloging to this database */
+
+ for (; (unsigned)it_perdb < table_count; ++it_perdb)
+ if (tables[it_db].base.db == db)
+ return (dbitem_info*)&(tables[it_perdb++]);
+
+ /* Otherwise return next non-table object */
+ pos= it_perdb - table_count;
+
+ if (pos >= perdb_count)
+ return NULL;
+
+ ++it_perdb;
+
+ item_buf.db= db;
+ item_buf.base= objects_perdb[pos];
+
+ return &item_buf;
+}
+
+/**
+ Iterate over all per-table objects (in all tables).
+
+ Per-table object descriptions are stored in @c objects_pertable[] array.
+ Counter @c it_pertable is used to keep position within that table.
+*/
+titem_info* pertable_iterator_next()
+{
+ static titem_info item;
+
+ DBUG_ASSERT(it_pertable >= 0);
+
+ if ((unsigned)it_pertable >= pertable_count)
+ return NULL;
+
+ item.base= objects_pertable[it_pertable++];
+ item.table= &tables[it_pertable % table_count];
+
+ return &item;
+}
+
+
+/**********************************************************************
+
+ Implementation of catalogue iterator callbacks.
+
+*********************************************************************/
+
+/**
+ Return pointer to given iterator instance.
+
+ The type of the iterator is indicated by @a type parameter:
+
+ BSTREAM_IT_CHARSET, BSTREAM_IT_USER, etc - an iterator over a given type
+ of global objects.
+
+ BSTREAM_IT_GLOBAL - an iterator determining order in which global objects'
+ metadata will be stored.
+
+ BSTREAM_IT_PERDB - an iterator determining order in which per-database
+ objects' metadata will be stored.
+
+ BSTREAM_IT_PERTABLE - an iterator determining order in which per-table
+ objects' metadata will be stored.
+
+ @return Pointer to the @c it_* counter representing given iterator or NULL
+ in case of error.
+*/
+void* bcat_iterator_get(struct st_bstream_image_header
+ *catalogue __attribute__ ((__unused__)),
+ unsigned int type)
+{
+ STEP_AND_CHECK_COUNT(callback_counts.iterator_get[type], return NULL);
+
+ switch (type) {
+
+ case BSTREAM_IT_GLOBAL:
+ it_db= 0;
+ it_global= -1;
+ return &it_global;
+
+ case BSTREAM_IT_PERDB:
+ it_db= 0;
+ it_perdb= 0;
+ return &it_perdb;
+
+ case BSTREAM_IT_DB:
+ it_db= 0;
+ return &it_db;
+
+ case BSTREAM_IT_PERTABLE:
+ it_pertable= 0;
+ return &it_pertable;
+
+ default:
+ it_global= type;
+ it_db= 0;
+ return &it_global;
+ }
+}
+
+/**
+ Free resources used by iterator.
+
+ Since we do not allocate any resources, here we only step the appropriate
+ callback counter. Later these conuters can be used to verify that the number
+ of @c bcat_iterator_free() calls matches that of @c bcat_iterator_get(). This
+ is done in @check_iterators() helper function.
+*/
+void bcat_iterator_free(struct st_bstream_image_header
+ *catalogue __attribute__ ((__unused__)),
+ void *iter)
+{
+ uint type;
+
+ if (iter == &it_global)
+ type= it_global < 0 ? BSTREAM_IT_GLOBAL : it_global;
+ else if (iter == &it_perdb)
+ type= BSTREAM_IT_PERDB;
+ else if (iter == &it_db)
+ type= BSTREAM_IT_DB;
+ else if (iter == &it_pertable)
+ type= BSTREAM_IT_PERTABLE;
+
+ ++(callback_counts.iterator_free[type]);
+}
+
+int opt_empty_catalog= 0;
+
+/**
+ Return next object from a given iterator.
+
+ Call appropriate @c *_iterator_next() function to return next object pointed
+ by the iterator.
+*/
+struct st_bstream_item_info*
+bcat_iterator_next(struct st_bstream_image_header
+ *catalogue __attribute__ ((__unused__)), void *iter)
+{
+ dbitem_info *info;
+
+ if (opt_empty_catalog)
+ return NULL;
+
+ if (iter == &it_global && it_global < 0)
+ return global_iterator_next();
+
+ if (iter == &it_global)
+ return other_iterator_next();
+
+ if (iter == &it_db)
+ return db_iterator_next();
+
+ if (iter == &it_pertable)
+ return (item_info*)pertable_iterator_next();
+
+ /*
+ If iter == &it_perdb we want to enumerate all per-database objects. We
+ iterate over all databases using it_db counter and for each database use
+ perdb_iterator_next() until it returns NULL indicating that there are
+ no more objects in that database.
+ */
+
+ if (iter == &it_perdb)
+ {
+ DBUG_ASSERT(it_db >= 0);
+
+ if ((unsigned)it_db >= db_count)
+ return NULL;
+
+ info= perdb_iterator_next(&dbs[it_db]);
+
+ if (!info)
+ {
+ ++it_db;
+ it_perdb=0;
+ return bcat_iterator_next(NULL, iter);
+ }
+
+ return (item_info*)info;
+ }
+
+ return NULL;
+}
+
+/**
+ Return iterator enumerating all objects belonging to a given database.
+
+ It is not important what pointer is returned as long as it is not NULL.
+ The enumeration is always performed by @c perdb_iterator_next() function.
+*/
+void* bcat_db_iterator_get(struct st_bstream_image_header
+ *catalogue __attribute__ ((__unused__)),
+ struct st_bstream_db_info *db)
+{
+ STEP_AND_CHECK_COUNT(callback_counts.db_iterator_get[db->base.pos],
+ return NULL);
+ it_perdb=0;
+ return &it_perdb;
+}
+
+/** Return next object from database iterator */
+struct st_bstream_dbitem_info*
+bcat_db_iterator_next(struct st_bstream_image_header
+ *catalogue __attribute__ ((__unused__)),
+ struct st_bstream_db_info *db,
+ void *iter __attribute__ ((__unused__)))
+{
+ return perdb_iterator_next(db);
+}
+
+/** Free database iterator resources */
+void bcat_db_iterator_free(struct st_bstream_image_header
+ *catalogue __attribute__ ((__unused__)),
+ struct st_bstream_db_info *db,
+ void *iter __attribute__ ((__unused__)))
+{
+ ++(callback_counts.db_iterator_free[db->base.pos]);
+}
+
+/**********************************************************************
+
+ Functions for getting object's metadata.
+
+*********************************************************************/
+
+int bcat_get_item_create_query(struct st_bstream_image_header
+ *catalogue __attribute__ ((__unused__)),
+ struct st_bstream_item_info *item,
+ bstream_blob *stmt)
+{
+ static char buf[]= "serialization of xxxxxxxxxxxxxxxx";
+ int len;
+
+ STEP_AND_CHECK_COUNT(callback_counts.get_serialization[item->type],
+ return BSTREAM_ERROR);
+
+ len= snprintf(buf+17, 16, "%s", item->name.begin);
+
+ stmt->begin= (byte*) buf;
+ stmt->end= stmt->begin + 17 + len;
+
+ return BSTREAM_OK;
+}
+
+int bcat_get_item_create_data(struct st_bstream_image_header
+ *catalogue __attribute__ ((__unused__)),
+ struct st_bstream_item_info *item,
+ bstream_blob *data)
+{
+ static char buf[]= "metadata for xxxxxxxxxxxxxxxx";
+ int len;
+
+ STEP_AND_CHECK_COUNT(callback_counts.get_metadata[item->type],
+ return BSTREAM_ERROR);
+
+ len= snprintf(buf+13, 16, "%s", item->name.begin);
+
+ data->begin= (byte*) buf;
+ data->end= data->begin + 13 + len;
+
+ return BSTREAM_OK;
+}
+
+/*********************************************************************
+
+ Other catalogue callback functions.
+
+*********************************************************************/
+
+int bcat_create_item(struct st_bstream_image_header
+ *catalogue __attribute__ ((__unused__)),
+ struct st_bstream_item_info *item,
+ bstream_blob create_stmt __attribute__ ((__unused__)),
+ bstream_blob other_meta_data __attribute__ ((__unused__)))
+{
+ STEP_AND_CHECK_COUNT(callback_counts.create_item[item->type],
+ return BSTREAM_ERROR);
+ return BSTREAM_OK;
+}
+
+int bcat_reset(struct st_bstream_image_header
+ *catalogue __attribute__ ((__unused__)))
+{
+ STEP_AND_CHECK_COUNT(callback_counts.cat_reset, return BSTREAM_ERROR);
+ return BSTREAM_OK;
+}
+
+int bcat_close(struct st_bstream_image_header
+ *catalogue __attribute__ ((__unused__)))
+{
+ STEP_AND_CHECK_COUNT(callback_counts.cat_close, return BSTREAM_ERROR);
+ return BSTREAM_OK;
+}
+
+int bcat_add_item(struct st_bstream_image_header
+ *catalogue __attribute__ ((__unused__)),
+ struct st_bstream_item_info *item)
+{
+ STEP_AND_CHECK_COUNT(callback_counts.add_item[item->type],
+ return BSTREAM_ERROR);
+ return BSTREAM_OK;
+}
+
+
+/*********************************************************************
+
+ Memory allocation callbacks.
+
+*********************************************************************/
+
+byte* bstream_alloc(unsigned long int size)
+{
+ STEP_AND_CHECK_COUNT(callback_counts.alloc, return NULL);
+ return malloc(size);
+}
+
+void bstream_free(byte *ptr)
+{
+ ++callback_counts.free;
+ free(ptr);
+}
=== added file 'unittest/backup/catalog.h'
--- a/unittest/backup/catalog.h 1970-01-01 00:00:00 +0000
+++ b/unittest/backup/catalog.h 2009-06-30 20:37:35 +0000
@@ -0,0 +1,59 @@
+#ifndef CATALOG_H_
+#define CATALOG_H_
+
+#include "stream_test.h"
+
+extern const uint snap_count;
+extern const uint global_count;
+extern const uint db_count;
+extern const uint table_count;
+extern const uint perdb_count;
+extern const uint pertable_count;
+
+extern snapshot_type snapshots[];
+extern item_info objects_global[];
+extern db_info dbs[];
+extern table_info tables[];
+extern item_info objects_perdb[];
+extern item_info objects_pertable[];
+
+/**
+ If non zero, makes the catalogue empty - no objects are reported by the
+ iterators.
+*/
+extern int opt_empty_catalog;
+
+/**
+ Structure containing bstream callback counters.
+
+ Whenever one of the callback functions is called, the corresponding
+ counter will be increased.
+
+ Setting counter to a negative value will cause the corresponding function
+ report ERROR when counter reaches value of 0.
+*/
+extern struct st_callback_counts {
+ int iterator_get[128];
+ int iterator_free[128];
+ int db_iterator_get[128];
+ int db_iterator_free[128];
+ int get_serialization[128];
+ int get_metadata[128];
+ int add_item[128];
+ int create_item[128];
+ int cat_reset;
+ int cat_close;
+ int alloc;
+ int free;
+} callback_counts;
+
+/**
+ Increase counter C, execute X if counter reaches value of 0.
+*/
+#define STEP_AND_CHECK_COUNT(C,X) if (0 == ++(C)) X
+
+void init_catalog(image_header*);
+void reset_counts();
+int check_iterators();
+
+#endif /*CATALOG_H_*/
=== added file 'unittest/backup/no_catalog.c'
--- a/unittest/backup/no_catalog.c 1970-01-01 00:00:00 +0000
+++ b/unittest/backup/no_catalog.c 2009-06-30 20:37:35 +0000
@@ -0,0 +1,96 @@
+/** @file
+
+ A trivial implementation of catalogue callbacks required by bstream library.
+*/
+
+#define BSTREAM_USE_MALLOC
+#include <stream_v1_services.h>
+
+
+int bcat_reset(struct st_bstream_image_header
+ *catalogue __attribute__ ((__unused__)))
+{ return BSTREAM_OK; }
+
+
+int bcat_close(struct st_bstream_image_header
+ *catalogue __attribute__ ((__unused__)))
+{ return BSTREAM_OK; }
+
+
+int bcat_add_item(struct st_bstream_image_header
+ *catalogue __attribute__ ((__unused__)),
+ struct st_bstream_item_info
+ *item __attribute__ ((__unused__)))
+{ return BSTREAM_OK; }
+
+
+void* bcat_iterator_get(struct st_bstream_image_header
+ *catalogue __attribute__ ((__unused__)),
+ unsigned int type __attribute__ ((__unused__)))
+{ return NULL; }
+
+
+struct st_bstream_item_info*
+bcat_iterator_next(struct st_bstream_image_header
+ *catalogue __attribute__ ((__unused__)),
+ void *iter __attribute__ ((__unused__)))
+{ return NULL; }
+
+
+void bcat_iterator_free(struct st_bstream_image_header
+ *catalogue __attribute__ ((__unused__)),
+ void *iter __attribute__ ((__unused__)))
+{}
+
+
+void* bcat_db_iterator_get(struct st_bstream_image_header
+ *catalogue __attribute__ ((__unused__)),
+ struct st_bstream_db_info
+ *db __attribute__ ((__unused__)))
+{ return NULL; }
+
+
+/** Return next item from database items iterator */
+
+struct st_bstream_dbitem_info*
+bcat_db_iterator_next(struct st_bstream_image_header
+ *catalogue __attribute__ ((__unused__)),
+ struct st_bstream_db_info
+ *db __attribute__ ((__unused__)),
+ void *iter __attribute__ ((__unused__)))
+{ return NULL; }
+
+
+/** Free database items iterator resources */
+
+void bcat_db_iterator_free(struct st_bstream_image_header
+ *catalogue __attribute__ ((__unused__)),
+ struct st_bstream_db_info
+ *db __attribute__ ((__unused__)),
+ void *iter __attribute__ ((__unused__)))
+{}
+
+
+int bcat_get_item_create_query(struct st_bstream_image_header
+ *catalogue __attribute__ ((__unused__)),
+ struct st_bstream_item_info
+ *item __attribute__ ((__unused__)),
+ bstream_blob *stmt __attribute__ ((__unused__)))
+{ return BSTREAM_OK; }
+
+
+int bcat_get_item_create_data(struct st_bstream_image_header
+ *catalogue __attribute__ ((__unused__)),
+ struct st_bstream_item_info
+ *item __attribute__ ((__unused__)),
+ bstream_blob *data __attribute__ ((__unused__)))
+{ return BSTREAM_OK; }
+
+
+int bcat_create_item(struct st_bstream_image_header
+ *catalogue __attribute__ ((__unused__)),
+ struct st_bstream_item_info
+ *item __attribute__ ((__unused__)),
+ bstream_blob create_stmt __attribute__ ((__unused__)),
+ bstream_blob other_meta_data __attribute__ ((__unused__)))
+{ return BSTREAM_OK; }
=== added file 'unittest/backup/stream_test.h'
--- a/unittest/backup/stream_test.h 1970-01-01 00:00:00 +0000
+++ b/unittest/backup/stream_test.h 2009-06-30 20:37:35 +0000
@@ -0,0 +1,49 @@
+#ifndef STREAM_TEST_H_
+#define STREAM_TEST_H_
+
+#include <m_string.h>
+#include <stream_v1.h>
+
+typedef bstream_byte byte;
+typedef bstream_blob blob;
+
+typedef enum enum_bstream_snapshot_type snapshot_type;
+typedef enum enum_bstream_item_type item_type;
+typedef struct st_bstream_image_header image_header;
+typedef struct st_bstream_item_info item_info;
+typedef struct st_bstream_db_info db_info;
+typedef struct st_bstream_dbitem_info dbitem_info;
+typedef struct st_bstream_table_info table_info;
+typedef struct st_bstream_titem_info titem_info;
+typedef struct st_bstream_data_chunk table_data_chunk;
+
+extern
+struct st_io_counts
+{
+ long read;
+ long write;
+ size_t read_bytes;
+ size_t write_bytes;
+} io_counts;
+
+void reset_io_counts();
+
+typedef struct
+{
+ backup_stream base;
+ int m_fd;
+} test_backup_stream;
+
+extern size_t stream_block_size;
+
+int backup_stream_open_rd(test_backup_stream *stream, const char *path);
+int backup_stream_open_wr(test_backup_stream *stream, const char *path);
+int backup_stream_close(test_backup_stream *stream);
+
+#define OPEN_ERROR (BSTREAM_ERROR+10)
+#define CLOSE_ERROR (BSTREAM_ERROR+11)
+
+int write_image(image_header *hdr, const char *path);
+int read_image(image_header *hdr, const char *path);
+
+#endif /*STREAM_TEST_H_*/
=== added file 'unittest/backup/test_main.c'
--- a/unittest/backup/test_main.c 1970-01-01 00:00:00 +0000
+++ b/unittest/backup/test_main.c 2009-06-30 20:37:35 +0000
@@ -0,0 +1,23 @@
+/** @file
+
+ Implements main() function for unit tests. The tests should define
+ their functionality in run_test() function.
+*/
+
+#include <my_global.h>
+#include <my_sys.h>
+#include <tap.h>
+
+extern void run_test();
+void (*test_exit_hook)() = NULL;
+
+int main()
+{
+ my_init();
+ run_test();
+
+ if (test_exit_hook)
+ test_exit_hook();
+
+ return exit_status();
+}
=== added file 'unittest/backup/test_stream.c'
--- a/unittest/backup/test_stream.c 1970-01-01 00:00:00 +0000
+++ b/unittest/backup/test_stream.c 2009-06-30 20:37:35 +0000
@@ -0,0 +1,330 @@
+/** @file
+
+ Implementation of low-level I/O file stream for testing backup stream
+ library.
+*/
+
+#include <my_global.h>
+#include <my_sys.h>
+
+#include "stream_test.h"
+
+/** I/O block size to be used when creating new stream. */
+
+size_t stream_block_size= 1024;
+
+static int stream_write(void *stream, bstream_blob *buf, bstream_blob envelope);
+static int stream_read(void *stream, bstream_blob *buf, bstream_blob);
+static int stream_forward(void *stream, unsigned long int *offset);
+
+/**
+ Structure containing I/O callback counters.
+
+ Whenever one of the low-level I/O functions is called, the corresponding
+ counter will be increased.
+
+ Setting counter to a negative value will cause the corresponding function
+ to report ERROR or EOS when counter reaches the value of 0.
+*/
+struct st_io_counts io_counts;
+
+static const char* files_to_delete[128];
+static unsigned int files_to_delete_count= 0;
+
+extern void (*test_exit_hook)();
+
+
+void delete_files_at_exit()
+{
+ unsigned i;
+
+ for (i=0; i < files_to_delete_count && i < 128; ++i)
+ {
+ DBUG_ASSERT(files_to_delete[i]);
+ my_delete(files_to_delete[i], MYF(0));
+ }
+}
+
+
+/** Reset all callback counters */
+
+void reset_io_counts()
+{
+ bzero(&io_counts, sizeof(io_counts));
+}
+
+
+/**
+ Open test stream for writing.
+
+ Data will be written to @a path file. If that file already exists, it will be
+ overwritten. The stream will use I/O blocks of size specified by the current
+ value of @c stream_block_size.
+*/
+
+int backup_stream_open_wr(test_backup_stream *stream, const char *path)
+{
+ DBUG_ASSERT(stream);
+
+ /* open file for writing */
+ int fd= my_open(path, O_WRONLY|O_CREAT|O_TRUNC, MYF(0));
+ if (fd < 0)
+ return BSTREAM_ERROR;
+
+ /* Register file path for deletion at the end of the test */
+ files_to_delete[files_to_delete_count++]= path;
+ test_exit_hook= delete_files_at_exit;
+
+ stream->m_fd= fd;
+ stream->base.stream.write= stream_write;
+ stream->base.stream.forward= stream_forward;
+
+ return bstream_open_wr(&(stream->base), stream_block_size, 0);
+}
+
+
+/**
+ Open test stream for reading.
+
+ The stream will get bytes from @a path file.
+*/
+
+int backup_stream_open_rd(test_backup_stream *stream, const char *path)
+{
+ DBUG_ASSERT(stream);
+
+ /* open file for reading */
+ int fd= my_open(path, O_RDONLY, MYF(0));
+ if (fd < 0)
+ return BSTREAM_ERROR;
+
+ stream->m_fd= fd;
+ stream->base.stream.read= stream_read;
+ stream->base.stream.forward= stream_forward;
+
+ return bstream_open_rd(&(stream->base), 0);
+}
+
+
+/** Close a test stream. */
+
+int backup_stream_close(test_backup_stream *stream)
+{
+ DBUG_ASSERT(stream);
+ int fd= stream->m_fd;
+
+ (void)bstream_close(&(stream->base));
+
+ /* close the stream */
+ if (fd >= 0)
+ my_close(fd, MYF(0));
+
+ return BSTREAM_OK;
+}
+
+
+/**
+ Helper function which writes a backup image to given file.
+
+ The caller of this function should implement the @c bcat_ callbacks which
+ will determine the set of objects to be included in the image and their
+ metadata. The image will contain no table data chunks, only preamble and
+ summary section.
+
+ @returns 0 on success, non-zero otherwise.
+*/
+
+int write_image(image_header *hdr, const char *path)
+{
+ test_backup_stream stream;
+ int ret1, ret2;
+
+ ret1= backup_stream_open_wr(&stream, path);
+ if (ret1)
+ return OPEN_ERROR;
+
+ ret1= bstream_wr_preamble((backup_stream*)&stream, hdr);
+ ret2= bstream_wr_summary((backup_stream*)&stream, hdr);
+
+ backup_stream_close(&stream);
+
+ return ret1 || ret2;
+}
+
+
+/**
+ Helper fuction which reads backup image from a file.
+
+ Any table data chunks present in the image are ignored.
+
+ @returns 0 on success, non-zero otherwise.
+*/
+
+int read_image(image_header *hdr, const char *path)
+{
+ test_backup_stream stream;
+ table_data_chunk dchunk;
+ int ret1, ret2;
+
+ ret1= backup_stream_open_rd(&stream, path);
+ if (ret1)
+ return OPEN_ERROR;
+
+ ret1= bstream_rd_preamble((backup_stream*)&stream, hdr);
+
+ if (ret1 == BSTREAM_ERROR)
+ goto end;
+
+ /* Read and skip table data chunks */
+ do {
+ ret2= bstream_rd_data_chunk((backup_stream*)&stream, &dchunk);
+ } while (ret2 == BSTREAM_OK);
+
+ /*
+ Now, there should be the summary block at the end of the image.
+ */
+ if (ret2 != BSTREAM_EOC)
+ goto end;
+
+ /* Read the summary block and check that stream ends after it */
+
+ ret2= bstream_rd_summary((backup_stream*)&stream, hdr);
+ ret2= (ret2 != BSTREAM_EOS) ? BSTREAM_ERROR : BSTREAM_OK;
+
+end:
+
+ backup_stream_close(&stream);
+
+ return ret1 || ret2;
+}
+
+
+/*******************************************************************
+
+ Implementation of low-level I/O operations.
+
+*******************************************************************/
+
+
+/** Low-level write function */
+
+static
+int stream_write(void *stream, bstream_blob *buf,
+ bstream_blob envelope __attribute__ ((__unused__)))
+{
+ int fd;
+ int res;
+ size_t howmuch;
+
+ DBUG_ASSERT(stream);
+ DBUG_ASSERT(buf);
+
+ test_backup_stream *s= stream;
+
+ fd= s->m_fd;
+
+ if (fd < 0)
+ return BSTREAM_ERROR;
+
+ if (!buf->begin || buf->begin == buf->end)
+ return BSTREAM_OK;
+
+ DBUG_ASSERT(buf->end);
+
+ howmuch = buf->end - buf->begin;
+ res= my_write(fd, buf->begin, howmuch,
+ MYF(MY_NABP) /* error if not all bytes written */ );
+
+ if (++io_counts.write == 0)
+ goto error;
+
+ if (res)
+ goto error;
+
+ buf->begin= buf->end;
+ io_counts.write_bytes += howmuch;
+
+ return BSTREAM_OK;
+
+ error:
+
+ my_close(fd, MYF(0));
+ s->m_fd= -1;
+ return BSTREAM_ERROR;
+}
+
+
+/** Low-level read function */
+static
+int stream_read(void *stream, bstream_blob *buf,
+ bstream_blob envelope __attribute__ ((__unused__)))
+{
+ int fd;
+ size_t howmuch;
+
+ DBUG_ASSERT(stream);
+ DBUG_ASSERT(buf);
+
+ test_backup_stream *s= stream;
+
+ fd= s->m_fd;
+
+ DBUG_ASSERT(fd >= 0);
+
+ if (!buf->begin || buf->begin == buf->end)
+ return BSTREAM_OK;
+
+ DBUG_ASSERT(buf->end);
+
+ howmuch= buf->end - buf->begin;
+ howmuch= my_read(fd, buf->begin, howmuch, MYF(0));
+
+ if (0 == ++io_counts.read)
+ goto error;
+
+ if (howmuch == (size_t) -1)
+ goto error;
+
+ if (howmuch == 0)
+ return BSTREAM_EOS;
+
+ buf->begin += howmuch;
+
+ io_counts.read_bytes += howmuch;
+
+ return BSTREAM_OK;
+
+ error:
+
+ my_close(fd, MYF(0));
+ s->m_fd= -1;
+ return BSTREAM_ERROR;
+}
+
+
+/** Low-level forward function */
+static
+int stream_forward(void *stream, unsigned long int *offset)
+{
+ int fd;
+ size_t howmuch;
+
+ DBUG_ASSERT(stream);
+ DBUG_ASSERT(offset);
+
+ test_backup_stream *s= stream;
+
+ fd= s->m_fd;
+
+ DBUG_ASSERT(fd >= 0);
+
+ if (*offset == 0)
+ return BSTREAM_OK;
+
+ howmuch= *offset;
+
+ lseek(fd, howmuch, SEEK_CUR);
+ *offset= 0;
+
+ return BSTREAM_OK;
+}
Attachment: [text/bzr-bundle] bzr/rafal.somla@sun.com-20090630203735-e5k93j1z6m0sb69x.bundle
| Thread |
|---|
| • bzr push into mysql-6.0-backup branch (Rafal.Somla:2838 to 2839)WL#4765 | Rafal Somla | 30 Jun |