List:Commits« Previous MessageNext Message »
From:Marc Alff Date:June 29 2010 10:17pm
Subject:bzr push into mysql-next-mr-wl5291 branch (marc.alff:3153 to 3154)
View as plain text  
 3154 Marc Alff	2010-06-29 [merge]
      Merge mysql-next-mr --> mysql-next-mr-wl5291

    added:
      config/ac-macros/gtest.m4
      sql/sql_alloc_error_handler.cc
      sql/thr_malloc.h.moved
      unittest/CMakeLists.txt
      unittest/gunit/
      unittest/gunit/CMakeLists.txt
      unittest/gunit/FindGTest.cmake
      unittest/gunit/Makefile.am
      unittest/gunit/gunit_test_main.cc
      unittest/gunit/mdl-t.cc
      unittest/gunit/mdl_mytap-t.cc
      unittest/gunit/sql_list-t.cc
      unittest/gunit/tap_event_listener.cc
      unittest/gunit/thread_utils-t.cc
      unittest/gunit/thread_utils.cc
      unittest/gunit/thread_utils.h
      unittest/mytap/t/CMakeLists.txt
    modified:
      .bzrignore
      CMakeLists.txt
      Makefile.am
      configure.in
      libmysqld/CMakeLists.txt
      libmysqld/Makefile.am
      sql/CMakeLists.txt
      sql/Makefile.am
      sql/thr_malloc.cc
      unittest/Makefile.am
      unittest/unit.pl
 3153 Marc Alff	2010-06-28 [merge]
      Merge mysql-next-mr --> mysql-next-mr-wl5291

    removed:
      mysql-test/suite/manual/
      mysql-test/suite/manual/r/
      mysql-test/suite/manual/r/rpl_replication_delay.result
      mysql-test/suite/manual/t/
      mysql-test/suite/manual/t/rpl_replication_delay-slave.opt
      mysql-test/suite/manual/t/rpl_replication_delay.test
      sql/sql_repl.cc
      sql/sql_repl.h
    added:
      mysql-test/collections/mysql-next-mr-rpl-merge.push
      mysql-test/extra/rpl_tests/delayed_slave_wait_on_query.inc
      mysql-test/include/save_master_pos.inc
      mysql-test/include/show_delayed_slave_state.inc
      mysql-test/include/show_slave_hosts.inc
      mysql-test/include/sync_with_master.inc
      mysql-test/r/server_uuid.result
      mysql-test/std_data/old-format-relay-log-win.info
      mysql-test/std_data/old-format-relay-log.info
      mysql-test/suite/rpl/extension/
      mysql-test/suite/rpl/extension/README
      mysql-test/suite/rpl/extension/README.bhs
      mysql-test/suite/rpl/extension/bhs/
      mysql-test/suite/rpl/extension/bhs.pl
      mysql-test/suite/rpl/extension/bhs/default.rules
      mysql-test/suite/rpl/extension/bhs/disabled.def
      mysql-test/suite/rpl/extension/bhs/master-slave-bhs.inc
      mysql-test/suite/rpl/extension/bhs/master-slave.inc
      mysql-test/suite/rpl/extension/bhs/my.cnf
      mysql-test/suite/rpl/extension/bhs/rpl_1slave_base.cnf
      mysql-test/suite/rpl/extension/bhs/update_test_cases
      mysql-test/suite/rpl/r/rpl_delayed_slave.result
      mysql-test/suite/rpl/r/rpl_read_old_relay_log_info.result
      mysql-test/suite/rpl/r/rpl_row_event_max_size.result
      mysql-test/suite/rpl/r/rpl_seconds_behind_master.result
      mysql-test/suite/rpl/r/rpl_server_uuid.result
      mysql-test/suite/rpl/t/rpl_delayed_slave.test
      mysql-test/suite/rpl/t/rpl_read_old_relay_log_info.test
      mysql-test/suite/rpl/t/rpl_row_event_max_size-master.opt
      mysql-test/suite/rpl/t/rpl_row_event_max_size-slave.opt
      mysql-test/suite/rpl/t/rpl_row_event_max_size.test
      mysql-test/suite/rpl/t/rpl_seconds_behind_master.test
      mysql-test/suite/rpl/t/rpl_server_uuid.cnf
      mysql-test/suite/rpl/t/rpl_server_uuid.test
      mysql-test/suite/sys_vars/r/server_uuid_basic.result
      mysql-test/suite/sys_vars/t/server_uuid_basic.test
      mysql-test/t/server_uuid.test
      sql/binlog.cc
      sql/binlog.h
      sql/rpl_master.cc
      sql/rpl_master.h
    renamed:
      sql/slave.cc => sql/rpl_slave.cc
      sql/slave.h => sql/rpl_slave.h
    modified:
      client/client_priv.h
      client/mysqlbinlog.cc
      client/mysqltest.cc
      cmake/ssl.cmake
      libmysqld/CMakeLists.txt
      libmysqld/Makefile.am
      mysql-test/collections/default.experimental
      mysql-test/include/mtr_warnings.sql
      mysql-test/include/sync_slave_io_with_master.inc
      mysql-test/include/wait_for_slave_sql_error.inc
      mysql-test/mysql-test-run.pl
      mysql-test/suite/binlog/r/binlog_unsafe.result
      mysql-test/suite/engines/funcs/r/rpl_000015.result
      mysql-test/suite/engines/funcs/t/rpl_000015.test
      mysql-test/suite/rpl/r/rpl_get_master_version_and_clock.result
      mysql-test/suite/rpl/r/rpl_mixed_ddl_dml.result
      mysql-test/suite/rpl/r/rpl_show_slave_hosts.result
      mysql-test/suite/rpl/t/rpl_get_master_version_and_clock.test
      mysql-test/suite/rpl/t/rpl_mixed_ddl_dml.test
      mysql-test/suite/rpl/t/rpl_show_slave_hosts.test
      mysql-test/suite/rpl/t/rpl_show_slave_running.test
      mysql-test/suite/rpl/t/rpl_slave_load_remove_tmpfile.test
      mysql-test/t/sp_trans_log.test
      mysys/default.c
      sql/CMakeLists.txt
      sql/Makefile.am
      sql/field.cc
      sql/ha_ndbcluster_binlog.cc
      sql/ha_partition.cc
      sql/item_func.cc
      sql/item_strfunc.h
      sql/lex.h
      sql/log.cc
      sql/log.h
      sql/log_event.cc
      sql/log_event.h
      sql/log_event_old.cc
      sql/mysqld.cc
      sql/mysqld.h
      sql/repl_failsafe.cc
      sql/repl_failsafe.h
      sql/rpl_handler.cc
      sql/rpl_injector.cc
      sql/rpl_mi.cc
      sql/rpl_mi.h
      sql/rpl_record.cc
      sql/rpl_rli.cc
      sql/rpl_rli.h
      sql/set_var.h
      sql/share/errmsg-utf8.txt
      sql/sql_binlog.cc
      sql/sql_class.cc
      sql/sql_class.h
      sql/sql_db.cc
      sql/sql_insert.cc
      sql/sql_lex.cc
      sql/sql_lex.h
      sql/sql_load.cc
      sql/sql_parse.cc
      sql/sql_parse.h
      sql/sql_servers.h
      sql/sql_yacc.yy
      sql/structs.h
      sql/sys_vars.cc
      sql/rpl_slave.cc
      sql/rpl_slave.h
=== modified file '.bzrignore'
--- a/.bzrignore	2010-06-17 16:58:30 +0000
+++ b/.bzrignore	2010-06-23 11:01:12 +0000
@@ -1,4 +1,5 @@
 *-t
+*_test
 *.Plo
 *.Po
 *.a
@@ -1162,6 +1163,7 @@ libmysqld/sp_pcontext.cc
 libmysqld/sp_rcontext.cc
 libmysqld/spatial.cc
 libmysqld/sql_acl.cc
+libmysqld/sql_alloc_error_handler.cc
 libmysqld/sql_analyse.cc
 libmysqld/sql_base.cc
 libmysqld/sql_builtin.cc

=== modified file 'CMakeLists.txt'
--- a/CMakeLists.txt	2010-06-21 13:20:18 +0000
+++ b/CMakeLists.txt	2010-06-23 11:01:12 +0000
@@ -246,9 +246,12 @@ IF(WITH_UNIT_TESTS)
  ENABLE_TESTING()
 ENDIF()
 IF(WITH_UNIT_TESTS)
+  ADD_SUBDIRECTORY(unittest)
   ADD_SUBDIRECTORY(unittest/examples)
   ADD_SUBDIRECTORY(unittest/mytap)
+  ADD_SUBDIRECTORY(unittest/mytap/t)
   ADD_SUBDIRECTORY(unittest/mysys)
+  ADD_SUBDIRECTORY(unittest/gunit)
 ENDIF()
 
 ADD_SUBDIRECTORY(extra)

=== modified file 'Makefile.am'
--- a/Makefile.am	2010-05-24 19:01:36 +0000
+++ b/Makefile.am	2010-06-01 11:42:26 +0000
@@ -25,8 +25,9 @@ EXTRA_DIST =		INSTALL-SOURCE INSTALL-WIN
 SUBDIRS =		. include @docs_dirs@ @zlib_dir@ \
 			@readline_topdir@ sql-common scripts \
 			@pstack_dir@ libservices \
-			@sql_union_dirs@ unittest \
-			@sql_server@ @man_dirs@ tests \
+			@sql_union_dirs@ \
+			unittest/mytap \
+			@sql_server@ unittest @man_dirs@ tests \
 			netware @libmysqld_dirs@ \
 			mysql-test support-files sql-bench \
 			win \

=== added file 'config/ac-macros/gtest.m4'
--- a/config/ac-macros/gtest.m4	1970-01-01 00:00:00 +0000
+++ b/config/ac-macros/gtest.m4	2010-04-09 09:29:33 +0000
@@ -0,0 +1,151 @@
+dnl
+dnl Autoconf macros for detecting the Google C++ Testing Framework
+dnl
+
+dnl MY_PUSH_VAR(variable, value)
+AC_DEFUN([MY_PUSH_VAR],
+[
+  my_save_$1="$[$1]" 
+  [$1]="$[$1] [$2]" 
+])
+
+dnl MY_POP_VAR(variable)
+AC_DEFUN([MY_POP_VAR],
+[
+  [$1]="$[my_save_$1]" 
+])
+
+
+dnl MY_SET_GTEST_COMMAND(commandline-var, path-to-gtest-config)
+dnl On some platforms we need to override /bin/sh in gtest-config,
+dnl because it uses some posix features.
+AC_DEFUN([MY_SET_GTEST_COMMAND],
+[
+  case "$target_os" in
+    *solaris*)
+       $1="/bin/bash $2" ;;
+    *)
+       $1="$2" ;;
+  esac
+])
+
+
+dnl MYSQL_LINK_GTEST(gtest-config-command,
+dnl                  [action-if-found],[action-if-not-found])
+dnl
+dnl Given a command to execute gtest-config, try to compile a gtest program.
+dnl Export variables which contain the compilation flags required by gtest.
+dnl The exported variables are: 
+dnl   GTEST_CPPFLAGS, GTEST_CXXFLAGS, GTEST_LDFLAGS, GTEST_LIBS.
+dnl
+AC_DEFUN([MYSQL_LINK_GTEST], [
+  dnl Retrieve compilation flags
+  GTEST_CPPFLAGS=`$1 --cppflags`
+  GTEST_CXXFLAGS=`$1  --cxxflags`
+  GTEST_LDFLAGS=`$1  --ldflags`
+  GTEST_LIBS=`$1  --libs`
+
+  AC_SUBST(GTEST_CPPFLAGS)
+  AC_SUBST(GTEST_CXXFLAGS)
+  AC_SUBST(GTEST_LDFLAGS)
+  AC_SUBST(GTEST_LIBS)
+
+  MY_PUSH_VAR([CPPFLAGS], [${GTEST_CPPFLAGS}])
+  MY_PUSH_VAR([CXXFLAGS], [${GTEST_CXXFLAGS} ${CXXFLAGS_OVERRIDE}])
+  MY_PUSH_VAR([LDFLAGS], [${GTEST_LDFLAGS}])
+  MY_PUSH_VAR([LIBS], [${GTEST_LIBS}])
+
+  AC_LANG_PUSH([C++])
+  AC_MSG_CHECKING([for ::testing::InitGoogleTest])
+
+dnl Note that we depend on an installed version of gtest for this to work,
+dnl see comments in the gtest-config script which comes with gtest.
+dnl 'gtest-config --libs' will return the name of the libtool file if we
+dnl are using a non-installed version. For that to work, we would have
+dnl to do this linkage test with libtools.
+  AC_LINK_IFELSE([
+    AC_LANG_PROGRAM([[
+      #include <gtest/gtest.h>
+      TEST(foo, bar) {}
+    ]], [[
+      int c;
+      char **v= NULL;
+      ::testing::InitGoogleTest(&c, v);
+    ]])],
+    [
+      AC_MSG_RESULT([yes])
+      $2
+    ],
+    [
+      AC_MSG_RESULT([no])
+      $3
+    ])
+
+  MY_POP_VAR([CPPFLAGS])
+  MY_POP_VAR([CXXFLAGS])
+  MY_POP_VAR([LDFLAGS])
+  MY_POP_VAR([LIBS])
+
+  AC_LANG_POP([C++])
+])
+
+dnl MYSQL_CHECK_GTEST_CONFIG([minimum version])
+dnl
+dnl Look for gtest-config and verify the minimum required version.
+dnl If we find it, then try to compile/link a simple unit test application.
+AC_DEFUN([MYSQL_CHECK_GTEST_CONFIG], [
+  AC_ARG_WITH([gtest],
+    [AS_HELP_STRING([--with-gtest[[=/path/to/gtest]]],
+                    [Enable unit tests using the Google C++ Testing Framework.])
+    ],
+    [],
+    [with_gtest=check]
+  )
+
+  AC_MSG_CHECKING([whether to use the Google C++ Testing Framework])
+  if test "x${with_gtest}" = xno; then
+    AC_MSG_RESULT([no])
+  else
+    dnl If a path is supplied, it must exist.
+    if test "x$with_gtest" != xcheck; then
+      GTEST_CONFIG_PATH="${with_gtest}/bin"
+    elif test "x$GTEST_PREFIX" != x; then
+      GTEST_CONFIG_PATH="${GTEST_PREFIX}/bin"
+    else
+      GTEST_CONFIG_PATH=$PATH
+    fi
+
+    AC_MSG_RESULT([yes])
+
+    dnl Look for the absolute name of gtest-config.
+    AC_PATH_PROG([GTEST_CONFIG], [gtest-config], [], [$GTEST_CONFIG_PATH])
+
+    if test "x$GTEST_CONFIG" = x; then
+      dnl Fail if gtest was requested but gtest-config couldn't be found.
+      if test "x$with_gtest" != xcheck; then
+        AC_MSG_ERROR([Could not find gtest-config in $GTEST_CONFIG_PATH])
+      else
+        AC_MSG_WARN([Could not find gtest-config in $PATH])
+      fi
+    else
+      MY_SET_GTEST_COMMAND(GTEST_CONFIG_CMD, $GTEST_CONFIG)
+      AC_MSG_CHECKING([whether gtest-config is at least version $1])
+      if $GTEST_CONFIG_CMD --min-version=$1 >/dev/null 2>&1
+      then
+        AC_MSG_RESULT([yes])
+        MYSQL_LINK_GTEST($GTEST_CONFIG_CMD, [have_gtest=yes], [have_gtest=no])
+      else
+        AC_MSG_RESULT([no])
+        AC_MSG_ERROR(
+          [The installed version of the Google Testing Framework is too old.])
+      fi
+    fi
+  fi
+  AM_CONDITIONAL([HAVE_GTEST], [test "x$have_gtest" = "xyes"])
+])
+
+dnl MYSQL_CHECK_GTEST([minimum version])
+dnl
+AC_DEFUN([MYSQL_CHECK_GTEST], [
+  MYSQL_CHECK_GTEST_CONFIG([$1])
+])

=== modified file 'configure.in'
--- a/configure.in	2010-06-26 07:39:21 +0000
+++ b/configure.in	2010-06-26 07:56:33 +0000
@@ -85,6 +85,7 @@ sinclude(config/ac-macros/character_sets
 sinclude(config/ac-macros/compiler_flag.m4)
 sinclude(config/ac-macros/plugins.m4)
 sinclude(config/ac-macros/dtrace.m4)
+sinclude(config/ac-macros/gtest.m4)
 sinclude(config/ac-macros/ha_ndbcluster.m4)
 sinclude(config/ac-macros/large_file.m4)
 sinclude(config/ac-macros/misc.m4)
@@ -407,6 +408,7 @@ AC_SUBST(CC)
 AC_SUBST(CFLAGS)
 AC_SUBST(CXX)
 AC_SUBST(CXXFLAGS)
+AC_SUBST(CXXFLAGS_OVERRIDE)
 AC_SUBST(ASFLAGS)
 AC_SUBST(LD)
 AC_SUBST(INSTALL_SCRIPT)
@@ -422,6 +424,7 @@ then
   # regarding offset() usage in C++ which are done in a safe manner in the
   # server
   CXXFLAGS="$CXXFLAGS -fno-implicit-templates -fno-exceptions -fno-rtti"
+  CXXFLAGS_OVERRIDE="-fimplicit-templates -fexceptions -frtti"
   AC_DEFINE([HAVE_EXPLICIT_TEMPLATE_INSTANTIATION],
     [1], [Defined by configure. Use explicit template instantiation.])
 fi
@@ -2776,6 +2779,7 @@ MYSQL_CHECK_BIG_TABLES
 MYSQL_CHECK_MAX_INDEXES
 MYSQL_CHECK_VIO
 MYSQL_CHECK_SSL
+MYSQL_CHECK_GTEST([1.5.0])
 
 #--------------------------------------------------------------------
 # Declare our plugin modules
@@ -3213,6 +3217,7 @@ if test -d "$srcdir/cmd-line-utils/readl
 fi
 
 AC_CONFIG_FILES(Makefile extra/Makefile mysys/Makefile dnl
+ unittest/gunit/Makefile dnl
  unittest/Makefile unittest/mytap/Makefile unittest/mytap/t/Makefile dnl
  unittest/mysys/Makefile unittest/examples/Makefile dnl
  strings/Makefile regex/Makefile storage/Makefile dnl

=== modified file 'libmysqld/CMakeLists.txt'
--- a/libmysqld/CMakeLists.txt	2010-06-29 03:15:28 +0000
+++ b/libmysqld/CMakeLists.txt	2010-06-29 22:16:07 +0000
@@ -59,6 +59,7 @@ SET(SQL_EMBEDDED_SOURCES emb_qcache.cc l
            ../sql/rpl_injector.cc ../sql/set_var.cc ../sql/spatial.cc 
            ../sql/sp_cache.cc ../sql/sp.cc ../sql/sp_head.cc 
            ../sql/sp_pcontext.cc ../sql/sp_rcontext.cc ../sql/sql_acl.cc 
+           ../sql/sql_alloc_error_handler.cc
            ../sql/sql_analyse.cc ../sql/sql_base.cc ../sql/sql_cache.cc 
            ../sql/sql_class.cc ../sql/sql_crypt.cc ../sql/sql_cursor.cc 
            ../sql/sql_db.cc ../sql/sql_delete.cc ../sql/sql_derived.cc 

=== modified file 'libmysqld/Makefile.am'
--- a/libmysqld/Makefile.am	2010-06-29 03:15:28 +0000
+++ b/libmysqld/Makefile.am	2010-06-29 22:16:07 +0000
@@ -71,6 +71,7 @@ sqlsources = derror.cc field.cc field_co
 	sql_prepare.cc sql_derived.cc sql_rename.cc \
 	sql_select.cc sql_do.cc sql_show.cc set_var.cc sys_vars.cc \
 	sql_string.cc sql_table.cc sql_test.cc sql_udf.cc \
+	sql_alloc_error_handler.cc \
 	sql_update.cc sql_yacc.cc table.cc thr_malloc.cc sql_time.cc \
 	unireg.cc uniques.cc sql_union.cc hash_filo.cc \
 	spatial.cc gstream.cc sql_help.cc tztime.cc sql_cursor.cc \

=== modified file 'sql/CMakeLists.txt'
--- a/sql/CMakeLists.txt	2010-06-29 03:15:28 +0000
+++ b/sql/CMakeLists.txt	2010-06-29 22:16:07 +0000
@@ -74,6 +74,7 @@ SET (SQL_SOURCE
                sql_profile.cc event_parse_data.cc
                sql_bootstrap.cc
                sql_signal.cc mdl.cc
+               sql_alloc_error_handler.cc
                transaction.cc sys_vars.cc  rpl_handler.cc sql_truncate.cc datadict.cc
                ${GEN_SOURCES}
                ${MYSYS_LIBWRAP_SOURCE})

=== modified file 'sql/Makefile.am'
--- a/sql/Makefile.am	2010-06-29 03:15:28 +0000
+++ b/sql/Makefile.am	2010-06-29 22:16:07 +0000
@@ -93,6 +93,7 @@ noinst_HEADERS =	item.h item_func.h item
 			item_strfunc.h item_timefunc.h \
 			item_xmlfunc.h sql_plugin_services.h \
 			item_create.h item_subselect.h item_row.h \
+			thr_malloc.h \
 			sql_priv.h item_geofunc.h sql_bitmap.h \
 			procedure.h sql_class.h sql_lex.h sql_list.h \
 			sql_map.h sql_string.h unireg.h \
@@ -136,6 +137,7 @@ noinst_HEADERS =	item.h item_func.h item
 mysqld_SOURCES =	sql_lex.cc sql_handler.cc sql_partition.cc \
 			item.cc item_sum.cc item_buff.cc item_func.cc \
 			item_cmpfunc.cc item_strfunc.cc item_timefunc.cc \
+			sql_alloc_error_handler.cc \
 			thr_malloc.cc item_create.cc item_subselect.cc \
 			item_row.cc item_geofunc.cc item_xmlfunc.cc \
 			field.cc strfunc.cc key.cc sql_class.cc sql_list.cc \

=== added file 'sql/sql_alloc_error_handler.cc'
--- a/sql/sql_alloc_error_handler.cc	1970-01-01 00:00:00 +0000
+++ b/sql/sql_alloc_error_handler.cc	2010-06-21 07:13:21 +0000
@@ -0,0 +1,51 @@
+/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. 
+
+   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; version 2 of the License.
+
+   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 */
+
+#include "log.h"
+#include "sql_class.h"
+#include "mysqld.h"
+
+extern "C" void sql_alloc_error_handler(void)
+{
+  THD *thd= current_thd;
+  if (thd && !thd->is_error())
+  {
+    /*
+      This thread is Out Of Memory.
+      An OOM condition is a fatal error.
+      It should not be caught by error handlers in stored procedures.
+      Also, recording that SQL condition in the condition area could
+      cause more memory allocations, which in turn could raise more
+      OOM conditions, causing recursion in the error handling code itself.
+      As a result, my_error() should not be invoked, and the
+      thread diagnostics area is set to an error status directly.
+      Note that Diagnostics_area::set_error_status() is safe,
+      since it does not call any memory allocation routines.
+      The visible result for a client application will be:
+      - a query fails with an ER_OUT_OF_RESOURCES error,
+      returned in the error packet.
+      - SHOW ERROR/SHOW WARNINGS may be empty.
+    */
+    thd->stmt_da->set_error_status(thd,
+                                   ER_OUT_OF_RESOURCES,
+                                   ER(ER_OUT_OF_RESOURCES),
+                                   NULL);
+  }
+
+  /* Skip writing to the error log to avoid mtr complaints */
+  DBUG_EXECUTE_IF("simulate_out_of_memory", return;);
+
+  sql_print_error("%s", ER(ER_OUT_OF_RESOURCES));
+}

=== modified file 'sql/thr_malloc.cc'
--- a/sql/thr_malloc.cc	2010-06-11 13:48:24 +0000
+++ b/sql/thr_malloc.cc	2010-06-21 07:13:21 +0000
@@ -21,44 +21,7 @@
 #include "thr_malloc.h"
 #include "sql_class.h"
 
-extern "C" {
-  void sql_alloc_error_handler(void)
-  {
-    THD *thd= current_thd;
-    if (thd)
-    {
-      if (! thd->is_error())
-      {
-        /*
-          This thread is Out Of Memory.
-          An OOM condition is a fatal error.
-          It should not be caught by error handlers in stored procedures.
-          Also, recording that SQL condition in the condition area could
-          cause more memory allocations, which in turn could raise more
-          OOM conditions, causing recursion in the error handling code itself.
-          As a result, my_error() should not be invoked, and the
-          thread diagnostics area is set to an error status directly.
-          Note that Diagnostics_area::set_error_status() is safe,
-          since it does not call any memory allocation routines.
-          The visible result for a client application will be:
-          - a query fails with an ER_OUT_OF_RESOURCES error,
-          returned in the error packet.
-          - SHOW ERROR/SHOW WARNINGS may be empty.
-        */
-        thd->stmt_da->set_error_status(thd,
-                                       ER_OUT_OF_RESOURCES,
-                                       ER(ER_OUT_OF_RESOURCES),
-                                       NULL);
-      }
-    }
-
-    /* Skip writing to the error log to avoid mtr complaints */
-    DBUG_EXECUTE_IF("simulate_out_of_memory", return;);
-
-    sql_print_error("%s", ER(ER_OUT_OF_RESOURCES));
-
-  }
-}
+extern "C" void sql_alloc_error_handler(void);
 
 void init_sql_alloc(MEM_ROOT *mem_root, uint block_size, uint pre_alloc)
 {

=== added file 'sql/thr_malloc.h.moved'
--- a/sql/thr_malloc.h.moved	1970-01-01 00:00:00 +0000
+++ b/sql/thr_malloc.h.moved	2010-04-12 10:50:39 +0000
@@ -0,0 +1,34 @@
+/* Copyright (C) 2009 Sun Microsystems, Inc.
+
+   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; version 2 of the License.
+
+   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 */
+
+#ifndef SQL_THR_MALLOC_INCLUDED
+#define SQL_THR_MALLOC_INCLUDED
+
+#include <my_global.h>
+#include <my_alloc.h>
+#include <m_ctype.h>
+
+void init_sql_alloc(MEM_ROOT *root, uint block_size, uint pre_alloc_size);
+void *sql_alloc(size_t);
+void *sql_calloc(size_t);
+char *sql_strdup(const char *str);
+char *sql_strmake(const char *str, size_t len);
+void *sql_memdup(const void * ptr, size_t size);
+char *sql_strmake_with_convert(const char *str, size_t arg_length,
+			       CHARSET_INFO *from_cs,
+			       size_t max_res_length,
+			       CHARSET_INFO *to_cs, size_t *result_length);
+
+#endif  // SQL_THR_MALLOC_INCLUDED

=== added file 'unittest/CMakeLists.txt'
--- a/unittest/CMakeLists.txt	1970-01-01 00:00:00 +0000
+++ b/unittest/CMakeLists.txt	2010-05-27 12:03:17 +0000
@@ -0,0 +1,20 @@
+# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. 
+# 
+# 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; version 2 of the License.
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
+
+ADD_CUSTOM_TARGET(
+  test-unit
+  COMMAND perl ${CMAKE_CURRENT_SOURCE_DIR}/unit.pl run .
+  WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+)

=== modified file 'unittest/Makefile.am'
--- a/unittest/Makefile.am	2010-05-19 13:00:23 +0000
+++ b/unittest/Makefile.am	2010-05-27 12:03:17 +0000
@@ -13,15 +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 gunit
 
 EXTRA_DIST = unit.pl
 CLEANFILES = unit
 
-unittests = mytap mysys @mysql_se_unittest_dirs@  @mysql_pg_unittest_dirs@
-
 test:
-	perl unit.pl run $(unittests)
+	perl unit.pl run .
 
 test-verbose:
-	HARNESS_VERBOSE=1 perl unit.pl run $(unittests)
+	HARNESS_VERBOSE=1 perl unit.pl run .

=== added directory 'unittest/gunit'
=== added file 'unittest/gunit/CMakeLists.txt'
--- a/unittest/gunit/CMakeLists.txt	1970-01-01 00:00:00 +0000
+++ b/unittest/gunit/CMakeLists.txt	2010-06-18 09:09:40 +0000
@@ -0,0 +1,217 @@
+# Copyright (C) 2009 Sun Microsystems,Inc
+# 
+# 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; version 2 of the License.
+# 
+# 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+# In case we need to override the choice of compiler, see below.
+INCLUDE(CMakeForceCompiler)
+
+# Builtin GTest support is only available with CMake 2.8. For 2.6, we use 
+# own copy of the FindGTest.cmake module, located in current source directory.
+IF("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS "2.8")
+  SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}")  
+ELSE()
+  SET(HAVE_CMAKE_2_8 1)
+ENDIF() 
+
+IF(CMAKE_COMPILER_IS_GNUCXX)
+  # Remove -fno-implicit-templates, gunit sources cannot be compiled with it.
+  STRING(REPLACE "-fno-implicit-templates" "" CMAKE_CXX_FLAGS
+    ${CMAKE_CXX_FLAGS})
+
+  # MySQL is often compiled/linked with gcc rather than g++
+  # (to avoid dependencies on libstdc++.so)
+  # This does not work for googletest binaries.
+  # If we are building with ccache, we need to change arg1.
+  IF(CMAKE_CXX_COMPILER_ARG1)
+    IF(${CMAKE_CXX_COMPILER_ARG1} MATCHES "gcc")
+      SET(CMAKE_CXX_COMPILER_ARG1 "g++")
+    ENDIF()
+  # If we are building with gcc, substitute gcc with g++
+  ELSEIF(${CMAKE_CXX_COMPILER} MATCHES ".*/gcc")
+    STRING(REGEX REPLACE "/gcc$" "/g++" PATH_TO_GCC ${CMAKE_CXX_COMPILER})
+    CMAKE_FORCE_CXX_COMPILER(${PATH_TO_GCC} "GNU g++")
+  ENDIF()
+ENDIF()
+
+IF(MSVC)
+  # Restore exception handling flags to avoid tons of warnings when using STL.
+  ADD_DEFINITIONS(/EHcs)
+ENDIF()
+
+# Where to download and build gtest if not found.
+IF(NOT DOWNLOAD_ROOT)
+  SET(DOWNLOAD_ROOT ${CMAKE_SOURCE_DIR}/source_downloads)
+ENDIF()
+IF(NOT EXISTS DOWNLOAD_ROOT)
+  MAKE_DIRECTORY(${DOWNLOAD_ROOT})
+ENDIF()
+SET(GTEST_PACKAGE_NAME "gtest-1.5.0")
+SET(GTEST_SOURCE_DIR ${DOWNLOAD_ROOT}/${GTEST_PACKAGE_NAME})
+
+
+# Hint for FindGTest: With MSVC, we compile with static CRT (compile flags
+# /MT or /MTd) and need compatible gtest.lib
+SET(GTEST_MSVC_SEARCH MT) 
+
+# Hint for where to look: pushbuild uses GTEST_ROOT env.var.
+IF (DEFINED ENV{GTEST_PREFIX} AND NOT DEFINED ENV{GTEST_ROOT})
+  SET(ENV{GTEST_ROOT} $ENV{GTEST_PREFIX})
+ENDIF()
+
+# We may have downloaded gtest already, building in a different directory.
+IF(NOT DEFINED ENV{GTEST_ROOT} AND EXISTS ${GTEST_SOURCE_DIR})
+  MESSAGE(STATUS "GTEST_SOURCE_DIR:${GTEST_SOURCE_DIR}")
+  SET(GTEST_DOWNLOADED 1 CACHE INTERNAL "")
+  SET(GTEST_FOUND 1 CACHE INTERNAL "")
+ENDIF()
+
+
+IF(NOT GTEST_DOWNLOADED)
+  # Hint for find_library(): reverse list ".so;.a" to prefer static linking.
+  LIST(REVERSE CMAKE_FIND_LIBRARY_SUFFIXES)
+
+  # Now look for gtest.
+  FIND_PACKAGE(GTest)
+
+  # Revert suffix list back to what it was before.
+  LIST(REVERSE CMAKE_FIND_LIBRARY_SUFFIXES)
+ENDIF()
+
+
+OPTION(ENABLE_DOWNLOADS
+  "Download and build 3rd party source code components, e.g google test"
+  OFF)
+
+# While experimenting, use local URL rather than google.
+SET(GTEST_TARBALL "${GTEST_PACKAGE_NAME}.tar.gz")
+SET(GTEST_DOWNLOAD_URL 
+  "http://googletest.googlecode.com/files/${GTEST_TARBALL}"
+# "https://clustra.norway.sun.com/~td136807/${GTEST_TARBALL}"
+  )
+  
+IF(NOT GTEST_FOUND)
+  IF(NOT ENABLE_DOWNLOADS)
+    # Give one-time warning
+    IF(NOT ONETIME_GTEST_WARNING)
+      MESSAGE(STATUS 
+        "Googletest was not found. gtest-based unit tests will be disabled. "
+        "You can run cmake . -DENABLE_DOWNLOADS=1 to automatically download and "
+        "build required components from source.")
+      SET(ONETIME_GTEST_WARNING 1 CACHE INTERNAL "")
+    ENDIF()
+    RETURN()
+  ENDIF()
+  
+  # Download gtest source
+  IF(NOT EXISTS ${GTEST_SOURCE_DIR})
+    IF(NOT EXISTS ${DOWNLOAD_ROOT}/${GTEST_TARBALL})
+      # Download the tarball
+      IF(NOT HAVE_CMAKE_2_8)
+        # In versions earlier than 2.8, try wget for downloading
+        FIND_PROGRAM(WGET_EXECUTABLE wget)
+        MARK_AS_ADVANCED(WGET_EXECUTABLE)
+        IF(WGET_EXECUTABLE)
+          EXECUTE_PROCESS(COMMAND  ${WGET_EXECUTABLE} -T 30 ${GTEST_DOWNLOAD_URL}
+            WORKING_DIRECTORY ${DOWNLOAD_ROOT} RESULT_VARIABLE ERR)
+          IF(ERR EQUAL 0)
+            SET(DOWNLOAD_SUCCEEDED 1)
+          ENDIF()
+        ENDIF()
+      ELSE()
+        # Use CMake builtin download capabilities
+        FILE(DOWNLOAD ${GTEST_DOWNLOAD_URL}
+          ${DOWNLOAD_ROOT}/${GTEST_TARBALL} 30 STATUS ERR)
+        IF(ERR EQUAL 0)
+          SET(DOWNLOAD_SUCCEEDED 1)
+        ENDIF()
+      ENDIF()
+      
+      IF (DOWNLOAD_SUCCEEDED)
+        MESSAGE(STATUS 
+          "Successfully downloaded ${GTEST_DOWNLOAD_URL} to ${DOWNLOAD_ROOT}")
+      ELSE()
+        MESSAGE(STATUS 
+          "To enable google test, please download ${GTEST_DOWNLOAD_URL} "
+          "to the directory ${DOWNLOAD_ROOT}")
+        RETURN()
+      ENDIF()
+    ENDIF()
+    
+    # Unpack tarball
+    EXECUTE_PROCESS(
+      COMMAND ${CMAKE_COMMAND} -E tar xfz  "${DOWNLOAD_ROOT}/${GTEST_TARBALL}"
+        WORKING_DIRECTORY "${DOWNLOAD_ROOT}"
+        OUTPUT_QUIET
+        ERROR_QUIET
+      )
+  ENDIF()
+  SET(GTEST_DOWNLOADED 1 CACHE INTERNAL "")
+  SET(GTEST_FOUND 1 CACHE INTERNAL "")
+ENDIF()
+
+IF(NOT GTEST_FOUND)
+  RETURN()
+ENDIF()
+
+IF(GTEST_DOWNLOADED)
+  # Build gtest library
+  INCLUDE_DIRECTORIES(
+    ${GTEST_SOURCE_DIR} 
+    ${GTEST_SOURCE_DIR}/include
+  )
+  ADD_LIBRARY(gtest STATIC ${GTEST_SOURCE_DIR}/src/gtest-all.cc)
+  
+  # Set CMake variables to make FindPackage(GTest) happy next time.
+  SET(GTEST_FOUND 1 CACHE INTERNAL "")
+  SET(GTEST_LIBRARY gtest CACHE INTERNAL "")
+  SET(GTEST_LIBRARIES gtest CACHE INTERNAL "")
+  SET(GTEST_MAIN_LIBRARY no_gtest_main_library CACHE INTERNAL "")
+  SET(GTEST_INCLUDE_DIRS ${GTEST_SOURCE_DIR}/include CACHE INTERNAL "")
+  SET(GTEST_INCLUDE_DIR "${GTEST_SOURCE_DIR}/include" CACHE INTERNAL "")
+ENDIF()
+
+
+INCLUDE_DIRECTORIES(
+  ${GTEST_INCLUDE_DIRS}
+  ${CMAKE_SOURCE_DIR}/include
+  ${CMAKE_SOURCE_DIR}/sql
+  ${CMAKE_SOURCE_DIR}/unittest/mytap
+)
+
+# main-wrapper library (with tap-compatible option).
+ADD_LIBRARY(gunit STATIC
+  gunit_test_main.cc tap_event_listener.cc thread_utils.cc)
+TARGET_LINK_LIBRARIES(gunit mysys mytap dbug strings ${GTEST_LIBRARIES})
+MESSAGE(STATUS "GTEST_LIBRARIES:${GTEST_LIBRARIES}")
+
+# Add some defines.
+ADD_DEFINITIONS(-DMYSQL_SERVER)
+# We use -fno-rtti with gcc, so disable RTTI in gtest as well.
+ADD_DEFINITIONS(-DGTEST_HAS_RTTI=0)
+# /opt/studio12/SUNWspro/bin/CC complains about
+#   iterator_traits is not a member of std
+IF (CMAKE_CXX_COMPILER_ID STREQUAL "SunPro")
+  ADD_DEFINITIONS("-library=stlport4")
+ENDIF()
+
+# Add tests (link them with sql library) 
+SET(TESTS sql_list mdl mdl_mytap thread_utils)
+FOREACH(test ${TESTS})
+  ADD_EXECUTABLE(${test}-t ${test}-t.cc)
+  TARGET_LINK_LIBRARIES(${test}-t gunit sql)
+  IF (CMAKE_CXX_COMPILER_ID STREQUAL "SunPro")
+    SET_TARGET_PROPERTIES(${test}-t  PROPERTIES LINK_FLAGS "-library=stlport4")
+  ENDIF()
+  ADD_TEST(${test} ${test}-t)
+ENDFOREACH()

=== added file 'unittest/gunit/FindGTest.cmake'
--- a/unittest/gunit/FindGTest.cmake	1970-01-01 00:00:00 +0000
+++ b/unittest/gunit/FindGTest.cmake	2010-03-18 12:52:56 +0000
@@ -0,0 +1,204 @@
+# Locate the Google C++ Testing Framework.
+#
+# Defines the following variables:
+#
+#   GTEST_FOUND - Found the Google Testing framework
+#   GTEST_INCLUDE_DIRS - Include directories
+#
+# Also defines the library variables below as normal
+# variables.  These contain debug/optimized keywords when
+# a debugging library is found.
+#
+#   GTEST_BOTH_LIBRARIES - Both libgtest & libgtest-main
+#   GTEST_LIBRARIES - libgtest
+#   GTEST_MAIN_LIBRARIES - libgtest-main
+#
+# Accepts the following variables as input:
+#
+#   GTEST_ROOT - (as CMake or env. variable)
+#                The root directory of the gtest install prefix
+#
+#   GTEST_MSVC_SEARCH - If on MSVC, enables searching the build tree of
+#                       GTest if set to MD or MT (defaults: MD)
+#
+#-----------------------
+# Example Usage:
+#
+#    enable_testing(true)
+#    find_package(GTest REQUIRED)
+#    include_directories(${GTEST_INCLUDE_DIRS})
+#
+#    add_executable(foo foo.cc)
+#    target_link_libraries(foo ${GTEST_BOTH_LIBRARIES})
+#
+#    add_test(AllTestsInFoo foo)
+#
+#-----------------------
+#
+# If you would like each Google test to show up in CTest as
+# a test you may use the following macro.  NOTE: It WILL slow
+# down your tests, so be warned.
+#
+# GTEST_ADD_TESTS(executable extra_args ARGN)
+#    executable = The path to the test executable
+#    extra_args = Pass a list of extra arguments to be passed to
+#                 executable enclosed in quotes (or "" for none)
+#    ARGN =       A list of source files to search for tests & test
+#                 fixtures.
+#
+#  Example:
+#     set(FooTestArgs --foo 1 --bar 2)
+#     add_executable(FooTest FooUnitTest.cc)
+#     GTEST_ADD_TESTS(FooTest "${FooTestArgs}" FooUnitTest.cc)
+
+#=============================================================================
+# Copyright 2009 Kitware, Inc.
+# Copyright 2009 Philip Lowman <philip@stripped>
+# Copyright 2009 Daniel Blezek <blezek@stripped>
+#
+# Distributed under the OSI-approved BSD License (the "License");
+# CMake - Cross Platform Makefile Generator
+# Copyright 2000-2009 Kitware, Inc., Insight Software Consortium
+# All rights reserved.
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# * Redistributions of source code must retain the above copyright
+#  notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+#  notice, this list of conditions and the following disclaimer in the
+#  documentation and/or other materials provided with the distribution.
+#
+# * Neither the names of Kitware, Inc., the Insight Software Consortium,
+#  nor the names of their contributors may be used to endorse or promote
+#  products derived from this software without specific prior written
+#  permission.
+
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# ------------------------------------------------------------------------------
+# 
+# The above copyright and license notice applies to distributions of
+# CMake in source and binary form.  Some source files contain additional
+# notices of original copyright by their contributors; see each source
+# for details.  Third-party software packages supplied with CMake under
+# compatible licenses provide their own copyright notices documented in
+# corresponding subdirectories.
+#----------------------------------------------------------------------------
+
+# CMake was initially developed by Kitware with the following sponsorship:
+#
+# * National Library of Medicine at the National Institutes of Health
+#   as part of the Insight Segmentation and Registration Toolkit (ITK).
+#
+# * US National Labs (Los Alamos, Livermore, Sandia) ASC Parallel
+#   Visualization Initiative.
+#
+# * National Alliance for Medical Image Computing (NAMIC) is funded by the
+#   National Institutes of Health through the NIH Roadmap for Medical Research,
+#   Grant U54 EB005149.
+#
+# * Kitware, Inc.
+#
+# This software is distributed WITHOUT ANY WARRANTY; without even the
+# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the License for more information.
+#=============================================================================
+#
+# Thanks to Daniel Blezek <blezek@stripped> for the GTEST_ADD_TESTS code
+
+function(GTEST_ADD_TESTS executable extra_args)
+    if(NOT ARGN)
+        message(FATAL_ERROR "Missing ARGN: Read the documentation for GTEST_ADD_TESTS")
+    endif()
+    foreach(source ${ARGN})
+        file(READ "${source}" contents)
+        string(REGEX MATCHALL "TEST_?F?\\(([A-Za-z_0-9 ,]+)\\)" found_tests ${contents})
+        foreach(hit ${found_tests})
+            string(REGEX REPLACE ".*\\(([A-Za-z_0-9]+)[, ]*([A-Za-z_0-9]+)\\).*" "\\1.\\2" test_name ${hit})
+            add_test(${test_name} ${executable} --gtest_filter=${test_name} ${extra_args})
+        endforeach()
+    endforeach()
+endfunction()
+
+function(_gtest_append_debugs _endvar _library)
+    if(${_library} AND ${_library}_DEBUG)
+        set(_output optimized ${${_library}} debug ${${_library}_DEBUG})
+    else()
+        set(_output ${${_library}})
+    endif()
+    set(${_endvar} ${_output} PARENT_SCOPE)
+endfunction()
+
+function(_gtest_find_library _name)
+    find_library(${_name}
+        NAMES ${ARGN}
+        HINTS
+            $ENV{GTEST_ROOT}
+            ${GTEST_ROOT}
+        PATH_SUFFIXES ${_gtest_libpath_suffixes}
+    )
+    mark_as_advanced(${_name})
+endfunction()
+
+#
+
+if(NOT DEFINED GTEST_MSVC_SEARCH)
+    set(GTEST_MSVC_SEARCH MD)
+endif()
+
+set(_gtest_libpath_suffixes lib)
+if(MSVC)
+    if(GTEST_MSVC_SEARCH STREQUAL "MD")
+        list(APPEND _gtest_libpath_suffixes
+            msvc/gtest-md/Debug
+            msvc/gtest-md/Release)
+    elseif(GTEST_MSVC_SEARCH STREQUAL "MT")
+        list(APPEND _gtest_libpath_suffixes
+            msvc/gtest/Debug
+            msvc/gtest/Release)
+    endif()
+endif()
+
+
+find_path(GTEST_INCLUDE_DIR gtest/gtest.h
+    HINTS
+        $ENV{GTEST_ROOT}/include
+        ${GTEST_ROOT}/include
+)
+mark_as_advanced(GTEST_INCLUDE_DIR)
+
+if(MSVC AND GTEST_MSVC_SEARCH STREQUAL "MD")
+    # The provided /MD project files for Google Test add -md suffixes to the
+    # library names.
+    _gtest_find_library(GTEST_LIBRARY            gtest-md  gtest)
+    _gtest_find_library(GTEST_LIBRARY_DEBUG      gtest-mdd gtestd)
+    _gtest_find_library(GTEST_MAIN_LIBRARY       gtest_main-md  gtest_main)
+    _gtest_find_library(GTEST_MAIN_LIBRARY_DEBUG gtest_main-mdd gtest_maind)
+else()
+    _gtest_find_library(GTEST_LIBRARY            gtest)
+    _gtest_find_library(GTEST_LIBRARY_DEBUG      gtestd)
+    _gtest_find_library(GTEST_MAIN_LIBRARY       gtest_main)
+    _gtest_find_library(GTEST_MAIN_LIBRARY_DEBUG gtest_maind)
+endif()
+
+include(FindPackageHandleStandardArgs)
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(GTest DEFAULT_MSG GTEST_LIBRARY GTEST_INCLUDE_DIR GTEST_MAIN_LIBRARY)
+
+if(GTEST_FOUND)
+    set(GTEST_INCLUDE_DIRS ${GTEST_INCLUDE_DIR})
+    _gtest_append_debugs(GTEST_LIBRARIES      GTEST_LIBRARY)
+    _gtest_append_debugs(GTEST_MAIN_LIBRARIES GTEST_MAIN_LIBRARY)
+    set(GTEST_BOTH_LIBRARIES ${GTEST_LIBRARIES} ${GTEST_MAIN_LIBRARIES})
+endif()
+

=== added file 'unittest/gunit/Makefile.am'
--- a/unittest/gunit/Makefile.am	1970-01-01 00:00:00 +0000
+++ b/unittest/gunit/Makefile.am	2010-06-17 06:12:36 +0000
@@ -0,0 +1,67 @@
+# Copyright (C) 2009 Sun Microsystems, Inc.
+#
+# 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; version 2 of the License.
+#
+# 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 = -I$(top_srcdir)/include -I$(top_srcdir)/sql $(GTEST_CPPFLAGS)
+AM_CXXFLAGS = $(GTEST_CXXFLAGS)
+AM_LDFLAGS  = $(GTEST_LDFLAGS)
+AM_LIBS     = $(GTEST_LIBS)
+DEFS        = -DMYSQL_SERVER @DEFS@
+EXTRA_DIST  = CMakeLists.txt FindGTest.cmake
+
+# Allow implicit templates when compiling gtest.h
+override CXXFLAGS += @CXXFLAGS_OVERRIDE@
+
+UNIT_TEST_LIBS= $(top_builddir)/mysys/.libs/libmysys.a \
+		$(top_builddir)/dbug/.libs/libdbug.a \
+		$(top_builddir)/strings/.libs/libmystrings.a
+
+UNIT_TEST_OBJS= $(top_builddir)/sql/sql_state.o \
+		$(top_builddir)/sql/sql_string.o \
+		$(top_builddir)/sql/thr_malloc.o
+
+LDADD = libgunit.la $(UNIT_TEST_OBJS) $(UNIT_TEST_LIBS) $(AM_LIBS)
+
+if HAVE_GTEST
+
+noinst_HEADERS      = thread_utils.h
+
+noinst_LTLIBRARIES  = libgunit.la
+
+libgunit_la_SOURCES = \
+	gunit_test_main.cc \
+	tap_event_listener.cc \
+	thread_utils.cc
+
+noinst_PROGRAMS = \
+	mdl-t$(EXEEXT) \
+	mdl_mytap-t$(EXEEXT) \
+	sql_list-t$(EXEEXT) \
+	thread_utils-t$(EXEEXT)
+
+sql_list_t_LDADD       = $(top_builddir)/sql/sql_list.o $(LDADD)
+sql_list_t_SOURCES     = sql_list-t.cc
+
+mdl_t_LDADD            = $(top_builddir)/sql/mdl.o $(LDADD)
+mdl_t_SOURCES          = mdl-t.cc
+
+mdl_mytap_t_CPPFLAGS   = -I$(top_srcdir)/unittest/mytap $(AM_CPPFLAGS)
+mdl_mytap_t_LDADD      = $(top_builddir)/sql/mdl.o \
+			 $(top_builddir)/unittest/mytap/tap.o \
+			 $(LDADD)
+mdl_mytap_t_SOURCES    = mdl_mytap-t.cc
+
+thread_utils_t_SOURCES = thread_utils-t.cc
+
+endif

=== added file 'unittest/gunit/gunit_test_main.cc'
--- a/unittest/gunit/gunit_test_main.cc	1970-01-01 00:00:00 +0000
+++ b/unittest/gunit/gunit_test_main.cc	2010-03-18 10:09:22 +0000
@@ -0,0 +1,69 @@
+/* Copyright (C) 2009 Sun Microsystems, Inc.
+
+   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; version 2 of the License.
+
+   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 */
+
+#include <gtest/gtest.h>
+
+#include "mdl.h"                                // SHOULD BE my_sys.h
+#include "my_getopt.h"
+
+#include <stdlib.h>
+
+namespace {
+
+my_bool opt_use_tap= true;
+my_bool opt_help= false;
+
+struct my_option unittest_options[] =
+{
+  { "tap-output", 1, "TAP (default) or gunit output.",
+    (uchar**) &opt_use_tap, (uchar**) &opt_use_tap, NULL,
+    GET_BOOL, OPT_ARG,
+    opt_use_tap, 0, 1, 0,
+    0, NULL
+  },
+  { "help", 2, "Help.",
+    (uchar**) &opt_help, (uchar**) &opt_help, NULL,
+    GET_BOOL, NO_ARG,
+    opt_help, 0, 1, 0,
+    0, NULL
+  },
+  {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
+};
+
+
+extern "C" my_bool get_one_option(int, const struct my_option *, char *)
+{
+  return FALSE;
+}
+
+}  // namespace
+
+
+extern void install_tap_listener();
+
+int main(int argc, char **argv)
+{
+  ::testing::InitGoogleTest(&argc, argv);
+  MY_INIT(argv[0]);
+
+  if (handle_options(&argc, &argv, unittest_options, get_one_option))
+    return EXIT_FAILURE;
+  if (opt_use_tap)
+    install_tap_listener();
+  if (opt_help)
+    printf("\n\nTest options: [--[disable-]tap-output]\n");
+
+  return RUN_ALL_TESTS();
+}

=== added file 'unittest/gunit/mdl-t.cc'
--- a/unittest/gunit/mdl-t.cc	1970-01-01 00:00:00 +0000
+++ b/unittest/gunit/mdl-t.cc	2010-06-23 11:01:12 +0000
@@ -0,0 +1,680 @@
+/* Copyright (C) 2009 Sun Microsystems, Inc.
+
+   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; version 2 of the License.
+
+   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 */
+
+/**
+   This is a unit test for the 'meta data locking' classes.
+   It is written to illustrate how we can use googletest for unit testing
+   of MySQL code.
+   For documentation on googletest, see http://code.google.com/p/googletest/
+   and the contained wiki pages GoogleTestPrimer and GoogleTestAdvancedGuide.
+   The code below should hopefully be (mostly) self-explanatory.
+ */
+
+// Must include gtest first, since MySQL source has macros for min() etc ....
+#include <gtest/gtest.h>
+
+#include "mdl.h"
+#include <mysqld_error.h>
+
+#include "thr_malloc.h"
+#include "thread_utils.h"
+
+pthread_key(MEM_ROOT**,THR_MALLOC);
+pthread_key(THD*, THR_THD);
+mysql_mutex_t LOCK_open;
+uint    opt_debug_sync_timeout= 0;
+
+/*
+  A mock error handler.
+*/
+static uint expected_error= 0;
+extern "C" void test_error_handler_hook(uint err, const char *str, myf MyFlags)
+{
+  EXPECT_EQ(expected_error, err) << str;
+}
+
+/*
+  A mock out-of-memory handler.
+  We do not expect this to be called during testing.
+*/
+extern "C" void sql_alloc_error_handler(void)
+{
+  ADD_FAILURE();
+}
+
+namespace {
+bool notify_thread(THD*);
+}
+
+/*
+  We need to mock away this global function, because the real version
+  pulls in a lot of dependencies.
+  (The @note for the real version of this function indicates that the
+  coupling between THD and MDL is too tight.)
+   @retval  TRUE  if the thread was woken up
+   @retval  FALSE otherwise.
+*/
+bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use,
+                                            bool needs_thr_lock_abort)
+{
+  if (in_use != NULL)
+    return notify_thread(in_use);
+  return FALSE;
+}
+
+/*
+  Mock away this function as well, with an empty function.
+  @todo didrik: Consider verifying that the MDL module actually calls
+  this with correct arguments.
+*/
+void mysql_ha_flush(THD *)
+{
+  DBUG_PRINT("mysql_ha_flush", ("mock version"));
+}
+
+/*
+  We need to mock away this global function, the real version pulls in
+  too many dependencies.
+ */
+extern "C" const char *set_thd_proc_info(void *thd, const char *info,
+                                         const char *calling_function,
+                                         const char *calling_file,
+                                         const unsigned int calling_line)
+{
+  DBUG_PRINT("proc_info", ("%s:%d  %s", calling_file, calling_line,
+                           (info != NULL) ? info : "(null)"));
+  return info;
+}
+
+/*
+  Mock away this global function.
+  We don't need DEBUG_SYNC functionality in a unit test.
+ */
+void debug_sync(THD *thd, const char *sync_point_name, size_t name_len)
+{
+  DBUG_PRINT("debug_sync_point", ("hit: '%s'", sync_point_name));
+  FAIL() << "Not yet implemented.";
+}
+
+/*
+  Putting everything in an unnamed namespace prevents any (unintentional)
+  name clashes with the code under test.
+*/
+namespace {
+
+using thread::Notification;
+using thread::Thread;
+
+const char db_name[]= "some_database";
+const char table_name1[]= "some_table1";
+const char table_name2[]= "some_table2";
+const char table_name3[]= "some_table3";
+const char table_name4[]= "some_table4";
+const ulong zero_timeout= 0;
+const ulong long_timeout= (ulong) 3600L*24L*365L;
+
+
+class MDL_test : public ::testing::Test
+{
+protected:
+  MDL_test()
+  : m_thd(NULL),
+    m_null_ticket(NULL),
+    m_null_request(NULL)
+  {
+  }
+
+  static void SetUpTestCase()
+  {
+    error_handler_hook= test_error_handler_hook;
+  }
+
+  void SetUp()
+  {
+    expected_error= 0;
+    mdl_init();
+    m_mdl_context.init(m_thd);
+    EXPECT_FALSE(m_mdl_context.has_locks());
+    m_global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE);
+  }
+
+  void TearDown()
+  {
+    m_mdl_context.destroy();
+    mdl_destroy();
+  }
+
+  // A utility member for testing single lock requests.
+  void test_one_simple_shared_lock(enum_mdl_type lock_type);
+
+  THD               *m_thd;
+  const MDL_ticket  *m_null_ticket;
+  const MDL_request *m_null_request;
+  MDL_context        m_mdl_context;
+  MDL_request        m_request;
+  MDL_request        m_global_request;
+  MDL_request_list   m_request_list;
+private:
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(MDL_test);
+};
+
+
+/*
+  Will grab a lock on table_name of given type in the run() function.
+  The two notifications are for synchronizing with the main thread.
+  Does *not* take ownership of the notifications.
+*/
+class MDL_thread : public Thread
+{
+public:
+  MDL_thread(const char   *table_name,
+             enum_mdl_type mdl_type,
+             Notification *lock_grabbed,
+             Notification *release_locks)
+  : m_table_name(table_name),
+    m_mdl_type(mdl_type),
+    m_lock_grabbed(lock_grabbed),
+    m_release_locks(release_locks),
+    m_ignore_notify(false)
+  {
+    m_thd= reinterpret_cast<THD*>(this);    // See notify_thread below.
+    m_mdl_context.init(m_thd);
+  }
+
+  ~MDL_thread()
+  {
+    m_mdl_context.destroy();
+  }
+
+  virtual void run();
+  void ignore_notify() { m_ignore_notify= true; }
+
+  bool notify()
+  {
+    if (m_ignore_notify)
+      return false;
+    m_release_locks->notify();
+    return true;
+  }
+
+private:
+  const char    *m_table_name;
+  enum_mdl_type  m_mdl_type;
+  Notification  *m_lock_grabbed;
+  Notification  *m_release_locks;
+  bool           m_ignore_notify;
+  THD           *m_thd;
+  MDL_context    m_mdl_context;
+};
+
+
+// Admittedly an ugly hack, to avoid pulling in the THD in this unit test.
+bool notify_thread(THD *thd)
+{
+  MDL_thread *thread = (MDL_thread*) thd;
+  return thread->notify();
+}
+
+
+void MDL_thread::run()
+{
+  MDL_request request;
+  MDL_request global_request;
+  MDL_request_list request_list;
+  global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE);
+  request.init(MDL_key::TABLE, db_name, m_table_name, m_mdl_type);
+
+  request_list.push_front(&request);
+  if (m_mdl_type >= MDL_SHARED_NO_WRITE)
+    request_list.push_front(&global_request);
+
+  EXPECT_FALSE(m_mdl_context.acquire_locks(&request_list, long_timeout));
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE, db_name, m_table_name, m_mdl_type));
+
+  // Tell the main thread that we have grabbed our locks.
+  m_lock_grabbed->notify();
+  // Hold on to locks until we are told to release them
+  m_release_locks->wait_for_notification();
+
+  m_mdl_context.rollback_to_savepoint(NULL);
+}
+
+// googletest recommends DeathTest suffix for classes use in death tests.
+typedef MDL_test MDL_DeathTest;
+
+
+/*
+  Verifies that we die with a DBUG_ASSERT if we destry a non-empty MDL_context.
+ */
+#if GTEST_HAS_DEATH_TEST && !defined(DBUG_OFF)
+TEST_F(MDL_DeathTest, die_when_m_tickets_nonempty)
+{
+  ::testing::FLAGS_gtest_death_test_style = "threadsafe";
+  m_request.init(MDL_key::TABLE, db_name, table_name1, MDL_SHARED);
+
+  EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
+  EXPECT_DEATH(m_mdl_context.destroy(), ".*Assertion .*m_tickets.is_empty.*");
+  m_mdl_context.release_all_locks_for_name(m_request.ticket);
+}
+#endif  // GTEST_HAS_DEATH_TEST && !defined(DBUG_OFF)
+
+
+
+/*
+  The most basic test: just construct and destruct our test fixture.
+ */
+TEST_F(MDL_test, construct_and_destruct)
+{
+}
+
+
+/*
+  Verifies that we can create requests with the factory function
+  MDL_request::create().
+ */
+TEST_F(MDL_test, factory_function)
+{
+  MEM_ROOT mem_root;
+  init_sql_alloc(&mem_root, 1024, 0);
+  // This request should not be destroyed in the normal C++ fashion.
+  MDL_request *request=
+    MDL_request::create(MDL_key::TABLE,
+                        db_name, table_name1, MDL_SHARED, &mem_root);
+  ASSERT_NE(m_null_request, request);
+  EXPECT_EQ(m_null_ticket, request->ticket);
+  free_root(&mem_root, MYF(0));
+}
+
+
+void MDL_test::test_one_simple_shared_lock(enum_mdl_type lock_type)
+{
+  m_request.init(MDL_key::TABLE, db_name, table_name1, lock_type);
+
+  EXPECT_EQ(lock_type, m_request.type);
+  EXPECT_EQ(m_null_ticket, m_request.ticket);
+
+  EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
+  EXPECT_NE(m_null_ticket, m_request.ticket);
+  EXPECT_TRUE(m_mdl_context.has_locks());
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE, db_name, table_name1, lock_type));
+
+  MDL_request request_2;
+  request_2.init(&m_request.key, lock_type);
+  EXPECT_FALSE(m_mdl_context.try_acquire_lock(&request_2));
+  EXPECT_EQ(m_request.ticket, request_2.ticket);
+
+  m_mdl_context.release_all_locks_for_name(m_request.ticket);
+  EXPECT_FALSE(m_mdl_context.has_locks());
+}
+
+
+/*
+  Acquires one lock of type MDL_SHARED.
+ */
+TEST_F(MDL_test, one_shared)
+{
+  test_one_simple_shared_lock(MDL_SHARED);
+}
+
+
+/*
+  Acquires one lock of type MDL_SHARED_HIGH_PRIO.
+ */
+TEST_F(MDL_test, one_shared_high_prio)
+{
+  test_one_simple_shared_lock(MDL_SHARED_HIGH_PRIO);
+}
+
+
+/*
+  Acquires one lock of type MDL_SHARED_READ.
+ */
+TEST_F(MDL_test, one_shared_read)
+{
+  test_one_simple_shared_lock(MDL_SHARED_READ);
+}
+
+
+/*
+  Acquires one lock of type MDL_SHARED_WRITE.
+ */
+TEST_F(MDL_test, one_shared_write)
+{
+  test_one_simple_shared_lock(MDL_SHARED_WRITE);
+}
+
+
+/*
+  Acquires one lock of type MDL_EXCLUSIVE.  
+ */
+TEST_F(MDL_test, one_exclusive)
+{
+  const enum_mdl_type lock_type= MDL_EXCLUSIVE;
+  m_request.init(MDL_key::TABLE, db_name, table_name1, lock_type);
+  EXPECT_EQ(m_null_ticket, m_request.ticket);
+
+  m_request_list.push_front(&m_request);
+  m_request_list.push_front(&m_global_request);
+
+  EXPECT_FALSE(m_mdl_context.acquire_locks(&m_request_list, long_timeout));
+
+  EXPECT_NE(m_null_ticket, m_request.ticket);
+  EXPECT_NE(m_null_ticket, m_global_request.ticket);
+  EXPECT_TRUE(m_mdl_context.has_locks());
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE, db_name, table_name1, lock_type));
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE));
+  EXPECT_TRUE(m_request.ticket->is_upgradable_or_exclusive());
+
+  m_mdl_context.release_all_locks_for_name(m_request.ticket);
+  m_mdl_context.release_lock(m_global_request.ticket);
+  EXPECT_FALSE(m_mdl_context.has_locks());
+}
+
+
+/*
+  Acquires two locks, on different tables, of type MDL_SHARED.
+  Verifies that they are independent.
+ */
+TEST_F(MDL_test, two_shared)
+{
+  MDL_request request_2;
+  m_request.init(MDL_key::TABLE, db_name, table_name1, MDL_SHARED);
+  request_2.init(MDL_key::TABLE, db_name, table_name2, MDL_SHARED);
+
+  EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
+  EXPECT_FALSE(m_mdl_context.try_acquire_lock(&request_2));
+  EXPECT_TRUE(m_mdl_context.has_locks());
+  ASSERT_NE(m_null_ticket, m_request.ticket);
+  ASSERT_NE(m_null_ticket, request_2.ticket);
+
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE, db_name, table_name1, MDL_SHARED));
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE, db_name, table_name2, MDL_SHARED));
+  EXPECT_FALSE(m_mdl_context.
+               is_lock_owner(MDL_key::TABLE, db_name, table_name3, MDL_SHARED));
+
+  m_mdl_context.release_lock(m_request.ticket);
+  EXPECT_FALSE(m_mdl_context.
+               is_lock_owner(MDL_key::TABLE, db_name, table_name1, MDL_SHARED));
+  EXPECT_TRUE(m_mdl_context.has_locks());
+
+  m_mdl_context.release_lock(request_2.ticket);
+  EXPECT_FALSE(m_mdl_context.
+               is_lock_owner(MDL_key::TABLE, db_name, table_name2, MDL_SHARED));
+  EXPECT_FALSE(m_mdl_context.has_locks());
+}
+
+
+/*
+  Verifies that two different contexts can acquire a shared lock
+  on the same table.
+ */
+TEST_F(MDL_test, shared_locks_between_contexts)
+{
+  THD         *thd2= (THD*) this;
+  MDL_context  mdl_context2;
+  mdl_context2.init(thd2);
+  MDL_request request_2;
+  m_request.init(MDL_key::TABLE, db_name, table_name1, MDL_SHARED);
+  request_2.init(MDL_key::TABLE, db_name, table_name1, MDL_SHARED);
+  
+  EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
+  EXPECT_FALSE(mdl_context2.try_acquire_lock(&request_2));
+
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE, db_name, table_name1, MDL_SHARED));
+  EXPECT_TRUE(mdl_context2.
+              is_lock_owner(MDL_key::TABLE, db_name, table_name1, MDL_SHARED));
+
+  m_mdl_context.release_all_locks_for_name(m_request.ticket);
+  mdl_context2.release_all_locks_for_name(request_2.ticket);
+}
+
+
+/*
+  Verifies that we can upgrade a shared lock to exclusive.
+ */
+TEST_F(MDL_test, upgrade_shared_upgradable)
+{
+  m_request.init(MDL_key::TABLE, db_name, table_name1, MDL_SHARED_NO_WRITE);
+
+  m_request_list.push_front(&m_request);
+  m_request_list.push_front(&m_global_request);
+
+  EXPECT_FALSE(m_mdl_context.acquire_locks(&m_request_list, long_timeout));
+  EXPECT_FALSE(m_mdl_context.
+               upgrade_shared_lock_to_exclusive(m_request.ticket, long_timeout));
+  EXPECT_EQ(MDL_EXCLUSIVE, m_request.ticket->get_type());
+
+  // Another upgrade should be a no-op.
+  EXPECT_FALSE(m_mdl_context.
+               upgrade_shared_lock_to_exclusive(m_request.ticket, long_timeout));
+  EXPECT_EQ(MDL_EXCLUSIVE, m_request.ticket->get_type());
+
+  m_mdl_context.release_all_locks_for_name(m_request.ticket);
+  m_mdl_context.release_lock(m_global_request.ticket);
+}
+
+
+/*
+  Verifies that only upgradable locks can be upgraded to exclusive.
+ */
+TEST_F(MDL_DeathTest, die_upgrade_shared)
+{
+  MDL_request request_2;
+  m_request.init(MDL_key::TABLE, db_name, table_name1, MDL_SHARED);
+  request_2.init(MDL_key::TABLE, db_name, table_name2, MDL_SHARED_NO_READ_WRITE);
+
+  m_request_list.push_front(&m_request);
+  m_request_list.push_front(&request_2);
+  m_request_list.push_front(&m_global_request);
+  
+  EXPECT_FALSE(m_mdl_context.acquire_locks(&m_request_list, long_timeout));
+
+#if GTEST_HAS_DEATH_TEST && !defined(DBUG_OFF)
+  ::testing::FLAGS_gtest_death_test_style = "threadsafe";
+  EXPECT_DEATH_IF_SUPPORTED(m_mdl_context.
+                            upgrade_shared_lock_to_exclusive(m_request.ticket,
+                                                             long_timeout),
+                            ".*MDL_SHARED_NO_.*");
+#endif
+  EXPECT_FALSE(m_mdl_context.
+               upgrade_shared_lock_to_exclusive(request_2.ticket, long_timeout));
+  m_mdl_context.rollback_to_savepoint(NULL);
+}
+
+
+/*
+  Verfies that locks are released when we roll back to a savepoint.
+ */
+TEST_F(MDL_test, savepoint)
+{
+  MDL_request request_2;
+  MDL_request request_3;
+  MDL_request request_4;
+  m_request.init(MDL_key::TABLE, db_name, table_name1, MDL_SHARED);
+  request_2.init(MDL_key::TABLE, db_name, table_name2, MDL_SHARED);
+  request_3.init(MDL_key::TABLE, db_name, table_name3, MDL_SHARED);
+  request_4.init(MDL_key::TABLE, db_name, table_name4, MDL_SHARED);
+
+  EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
+  EXPECT_FALSE(m_mdl_context.try_acquire_lock(&request_2));
+  MDL_ticket *savepoint= m_mdl_context.mdl_savepoint();
+  EXPECT_FALSE(m_mdl_context.try_acquire_lock(&request_3));
+  EXPECT_FALSE(m_mdl_context.try_acquire_lock(&request_4));
+
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE, db_name, table_name1, MDL_SHARED));
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE, db_name, table_name2, MDL_SHARED));
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE, db_name, table_name3, MDL_SHARED));
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE, db_name, table_name4, MDL_SHARED));
+
+  m_mdl_context.rollback_to_savepoint(savepoint);
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE, db_name, table_name1, MDL_SHARED));
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE, db_name, table_name2, MDL_SHARED));
+  EXPECT_FALSE(m_mdl_context.
+               is_lock_owner(MDL_key::TABLE, db_name, table_name3, MDL_SHARED));
+  EXPECT_FALSE(m_mdl_context.
+               is_lock_owner(MDL_key::TABLE, db_name, table_name4, MDL_SHARED));
+
+  m_mdl_context.rollback_to_savepoint(NULL);
+  EXPECT_FALSE(m_mdl_context.
+               is_lock_owner(MDL_key::TABLE, db_name, table_name1, MDL_SHARED));
+  EXPECT_FALSE(m_mdl_context.
+               is_lock_owner(MDL_key::TABLE, db_name, table_name2, MDL_SHARED));
+}
+
+
+/*
+  Verifies that we can grab shared locks concurrently, in different threads.
+ */
+TEST_F(MDL_test, concurrent_shared)
+{
+  Notification lock_grabbed;
+  Notification release_locks;
+  MDL_thread mdl_thread(table_name1, MDL_SHARED, &lock_grabbed, &release_locks);
+  mdl_thread.start();
+  lock_grabbed.wait_for_notification();
+
+  m_request.init(MDL_key::TABLE, db_name, table_name1, MDL_SHARED);
+
+  EXPECT_FALSE(m_mdl_context.acquire_lock(&m_request, long_timeout));
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE, db_name, table_name1, MDL_SHARED));
+
+  release_locks.notify();
+  mdl_thread.join();
+
+  m_mdl_context.release_all_locks_for_name(m_request.ticket);
+}
+
+
+/*
+  Verifies that we cannot grab an exclusive lock on something which
+  is locked with a shared lock in a different thread.
+ */
+TEST_F(MDL_test, concurrent_shared_exclusive)
+{
+  expected_error= ER_LOCK_WAIT_TIMEOUT;
+
+  Notification lock_grabbed;
+  Notification release_locks;
+  MDL_thread mdl_thread(table_name1, MDL_SHARED, &lock_grabbed, &release_locks);
+  mdl_thread.ignore_notify();
+  mdl_thread.start();
+  lock_grabbed.wait_for_notification();
+
+  m_request.init(MDL_key::TABLE, db_name, table_name1, MDL_EXCLUSIVE);
+
+  m_request_list.push_front(&m_request);
+  m_request_list.push_front(&m_global_request);
+
+  // We should *not* be able to grab the lock here.
+  EXPECT_TRUE(m_mdl_context.acquire_locks(&m_request_list, zero_timeout));
+  EXPECT_FALSE(m_mdl_context.
+               is_lock_owner(MDL_key::TABLE,
+                             db_name, table_name1, MDL_EXCLUSIVE));
+
+  release_locks.notify();
+  mdl_thread.join();
+
+  // Now we should be able to grab the lock.
+  EXPECT_FALSE(m_mdl_context.acquire_locks(&m_request_list, zero_timeout));
+  EXPECT_NE(m_null_ticket, m_request.ticket);
+
+  m_mdl_context.release_all_locks_for_name(m_request.ticket);
+  m_mdl_context.release_lock(m_global_request.ticket);
+}
+
+
+/*
+  Verifies that we cannot we cannot grab a shared lock on something which
+  is locked exlusively in a different thread.
+ */
+TEST_F(MDL_test, concurrent_exclusive_shared)
+{
+  Notification lock_grabbed;
+  Notification release_locks;
+  MDL_thread mdl_thread(table_name1, MDL_EXCLUSIVE,
+                        &lock_grabbed, &release_locks);
+  mdl_thread.start();
+  lock_grabbed.wait_for_notification();
+
+  m_request.init(MDL_key::TABLE, db_name, table_name1, MDL_SHARED);
+
+  // We should *not* be able to grab the lock here.
+  EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
+  EXPECT_EQ(m_null_ticket, m_request.ticket);
+
+  release_locks.notify();
+
+  // The other thread should eventually release its locks.
+  EXPECT_FALSE(m_mdl_context.acquire_lock(&m_request, long_timeout));
+  EXPECT_NE(m_null_ticket, m_request.ticket);
+
+  mdl_thread.join();
+  m_mdl_context.release_all_locks_for_name(m_request.ticket);
+}
+
+
+/*
+  Verifies the following scenario:
+  Thread 1: grabs a shared upgradable lock.
+  Thread 2: grabs a shared lock.
+  Thread 1: asks for an upgrade to exclusive (needs to wait for thread 2)
+  Thread 2: gets notified, and releases lock.
+  Thread 1: gets the exclusive lock.
+ */
+TEST_F(MDL_test, concurrent_upgrade)
+{
+  m_request.init(MDL_key::TABLE, db_name, table_name1, MDL_SHARED_NO_WRITE);
+  m_request_list.push_front(&m_request);
+  m_request_list.push_front(&m_global_request);
+
+  EXPECT_FALSE(m_mdl_context.acquire_locks(&m_request_list, long_timeout));
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE,
+                            db_name, table_name1, MDL_SHARED_NO_WRITE));
+  EXPECT_FALSE(m_mdl_context.
+               is_lock_owner(MDL_key::TABLE,
+                             db_name, table_name1, MDL_EXCLUSIVE));
+
+  Notification lock_grabbed;
+  Notification release_locks;
+  MDL_thread mdl_thread(table_name1, MDL_SHARED, &lock_grabbed, &release_locks);
+  mdl_thread.start();
+  lock_grabbed.wait_for_notification();
+
+  EXPECT_FALSE(m_mdl_context.
+               upgrade_shared_lock_to_exclusive(m_request.ticket, long_timeout));
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE,
+                            db_name, table_name1, MDL_EXCLUSIVE));
+
+  mdl_thread.join();
+  m_mdl_context.rollback_to_savepoint(NULL);
+}
+
+}  // namespace

=== added file 'unittest/gunit/mdl_mytap-t.cc'
--- a/unittest/gunit/mdl_mytap-t.cc	1970-01-01 00:00:00 +0000
+++ b/unittest/gunit/mdl_mytap-t.cc	2010-06-23 11:01:12 +0000
@@ -0,0 +1,796 @@
+/* Copyright (C) 2009 Sun Microsystems, Inc.
+
+   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; version 2 of the License.
+
+   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 */
+
+/**
+   This is a port of the corresponding mdl_test.cc (written for googletest)
+   to mytap. Do a 'tkdiff mdl-t.cc mdl_mytap-t.cc' to see the differences.
+   In order to illustrate (some of) the features of googletest, I have
+   added some extensions below, notably support for reporting of line
+   numbers in case of failures.
+ */
+
+#include <string>
+#include <iostream>
+#include <stdio.h>
+
+#include <tap.h>
+
+#include "mdl.h"
+#include <mysqld_error.h>
+
+#include "thr_malloc.h"
+#include "thread_utils.h"
+
+pthread_key(MEM_ROOT**,THR_MALLOC);
+pthread_key(THD*, THR_THD);
+mysql_mutex_t LOCK_open;
+uint    opt_debug_sync_timeout= 0;
+
+// Reimplemented some macros from googletest, so that the tests below
+// could be kept unchanged.  No support for streaming of user messages
+// in this simplified version.
+void print_message(const char* file, int line, const char* message)
+{
+  std::cout << "# " << file << ":" << line << " " << message << "\n";
+}
+
+// Some macro tricks to generate names like result123 and result456
+#define CONCAT_TOKEN_(foo, bar) CONCAT_TOKEN_IMPL_(foo, bar)
+#define CONCAT_TOKEN_IMPL_(foo, bar) foo ## bar
+#define BOOL_VAR_ CONCAT_TOKEN_(result, __LINE__)
+
+#define MESSAGE_(message) \
+  print_message(__FILE__, __LINE__, message)
+
+// This is where we call the ok() function from mytap!!!
+#define TEST_BOOLEAN_(boolexpr, booltext, actual, expected, fail) \
+do { \
+  const bool BOOL_VAR_ = boolexpr; \
+  ok(BOOL_VAR_, BOOL_VAR_ ? "" : booltext); \
+  if (!BOOL_VAR_) \
+    fail("\n# Value of: " booltext    \
+         "\n#   Actual: " #actual     \
+         "\n# Expected: " #expected); \
+} while(0)
+
+// Boolean assertions.
+#define EXPECT_TRUE(condition) \
+  TEST_BOOLEAN_(condition, #condition, false, true, MESSAGE_)
+#define EXPECT_FALSE(condition) \
+  TEST_BOOLEAN_(!(condition), #condition, true, false, MESSAGE_)
+
+
+// Some (very) simplified versions of comparison predicates.
+// There is no distinction between ASSERT and EXPECT in mytap.
+#define ASSERT_NE(val1, val2) \
+  EXPECT_NE(val1, val2)
+
+// This version will not print expected or actual values for arguments.
+#define EXPECT_NE(val1, val2) \
+  EXPECT_TRUE(val1 != val2)
+
+// This version will not print expected or actual values for arguments.
+#define EXPECT_EQ(val1, val2) \
+  EXPECT_TRUE(val1 == val2)
+
+#define FAIL()                                  \
+  EXPECT_TRUE(1 == 0)
+
+
+
+/*
+  A mock error handler.
+*/
+static uint expected_error= 0;
+extern "C" void test_error_handler_hook(uint err, const char *str, myf MyFlags)
+{
+  EXPECT_EQ(expected_error, err);
+}
+
+/*
+  A mock out-of-memory handler.
+  We do not expect this to be called during testing.
+*/
+extern "C" void sql_alloc_error_handler(void)
+{
+  FAIL();
+}
+
+namespace {
+bool notify_thread(THD*);
+}
+
+/*
+  We need to mock away this global function, because the real version
+  pulls in a lot of dependencies.
+  (The @note for the real version of this function indicates that the
+  coupling between THD and MDL is too tight.)
+   @retval  TRUE  if the thread was woken up
+   @retval  FALSE otherwise.
+*/
+bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use,
+                                            bool needs_thr_lock_abort)
+{
+  if (in_use != NULL)
+    return notify_thread(in_use);
+  return FALSE;
+}
+
+/*
+  Mock away this function as well, with an empty function.
+  @todo didrik: Consider verifying that the MDL module actually calls
+  this with correct arguments.
+*/
+void mysql_ha_flush(THD *)
+{
+  DBUG_PRINT("mysql_ha_flush", ("mock version"));
+}
+
+/*
+  We need to mock away this global function, the real version pulls in
+  too many dependencies.
+ */
+extern "C" const char *set_thd_proc_info(void *thd, const char *info,
+                                         const char *calling_function,
+                                         const char *calling_file,
+                                         const unsigned int calling_line)
+{
+  DBUG_PRINT("proc_info", ("%s:%d  %s", calling_file, calling_line,
+                           (info != NULL) ? info : "(null)"));
+  return info;
+}
+
+/*
+  Mock away this global function.
+  We don't need DEBUG_SYNC functionality in a unit test.
+ */
+void debug_sync(THD *thd, const char *sync_point_name, size_t name_len)
+{
+  DBUG_PRINT("debug_sync_point", ("hit: '%s'", sync_point_name));
+  FAIL();
+}
+
+/*
+  Putting everything in an unnamed namespace prevents any (unintentional)
+  name clashes with the code under test.
+*/
+namespace {
+
+using thread::Notification;
+using thread::Thread;
+
+const char db_name[]= "some_database";
+const char table_name1[]= "some_table1";
+const char table_name2[]= "some_table2";
+const char table_name3[]= "some_table3";
+const char table_name4[]= "some_table4";
+const ulong zero_timeout= 0;
+const ulong long_timeout= (ulong) 3600L*24L*365L;
+
+
+class MDL_test
+{
+public:
+  // Utility function to run one test case.
+  typedef void (MDL_test::* Pmdl_mem)();
+  static void run_one_test(Pmdl_mem member_function);
+
+  // Utility function to run all the test cases.
+  static int RUN_ALL_TESTS();
+
+protected:
+  MDL_test()
+  : m_thd(NULL),
+    m_null_ticket(NULL),
+    m_null_request(NULL)
+  {
+  }
+
+  static void SetUpTestCase()
+  {
+    error_handler_hook= test_error_handler_hook;
+  }
+
+  void SetUp()
+  {
+    expected_error= 0;
+    mdl_init();
+    m_mdl_context.init(m_thd);
+    EXPECT_FALSE(m_mdl_context.has_locks());
+    m_global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE);
+  }
+
+  void TearDown()
+  {
+    m_mdl_context.destroy();
+    mdl_destroy();
+  }
+
+  // A utility member for testing single lock requests.
+  void test_one_simple_shared_lock(enum_mdl_type lock_type);
+
+  // We must list all the individual tests here.
+  void die_when_m_tickets_nonempty();
+  void die_when_holding_global_shared_lock();
+  void construct_and_destruct();
+  void factory_function();
+  void one_shared();
+  void one_shared_high_prio();
+  void one_shared_read();
+  void one_shared_write();
+  void one_exclusive();
+  void two_shared();
+  void shared_locks_between_contexts();
+  void upgrade_shared_upgradable();
+  void die_upgrade_shared();
+  void savepoint();
+  void concurrent_shared();
+  void concurrent_shared_exclusive();
+  void concurrent_exclusive_shared();
+  void concurrent_upgrade();
+
+  THD               *m_thd;
+  const MDL_ticket  *m_null_ticket;
+  const MDL_request *m_null_request;
+  MDL_context        m_mdl_context;
+  MDL_request        m_request;
+  MDL_request        m_global_request;
+  MDL_request_list   m_request_list;
+private:
+  // GTEST_DISALLOW_COPY_AND_ASSIGN_(MDL_test);
+};
+
+
+/*
+  Will grab a lock on table_name of given type in the run() function.
+  The two notifications are for synchronizing with the main thread.
+  Does *not* take ownership of the notifications.
+*/
+class MDL_thread : public Thread
+{
+public:
+  MDL_thread(const char   *table_name,
+             enum_mdl_type mdl_type,
+             Notification *lock_grabbed,
+             Notification *release_locks)
+  : m_table_name(table_name),
+    m_mdl_type(mdl_type),
+    m_lock_grabbed(lock_grabbed),
+    m_release_locks(release_locks),
+    m_ignore_notify(false)
+  {
+    m_thd= reinterpret_cast<THD*>(this);    // See notify_thread below.
+    m_mdl_context.init(m_thd);
+  }
+
+  ~MDL_thread()
+  {
+    m_mdl_context.destroy();
+  }
+
+  virtual void run();
+  void ignore_notify() { m_ignore_notify= true; }
+
+  bool notify()
+  {
+    if (m_ignore_notify)
+      return false;
+    m_release_locks->notify();
+    return true;
+  }
+
+private:
+  const char    *m_table_name;
+  enum_mdl_type  m_mdl_type;
+  Notification  *m_lock_grabbed;
+  Notification  *m_release_locks;
+  bool           m_ignore_notify;
+  THD           *m_thd;
+  MDL_context    m_mdl_context;
+};
+
+
+// Admittedly an ugly hack, to avoid pulling in the THD in this unit test.
+bool notify_thread(THD *thd)
+{
+  MDL_thread *thread = (MDL_thread*) thd;
+  return thread->notify();
+}
+
+
+void MDL_thread::run()
+{
+  MDL_request request;
+  MDL_request global_request;
+  MDL_request_list request_list;
+  global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE);
+  request.init(MDL_key::TABLE, db_name, m_table_name, m_mdl_type);
+
+  request_list.push_front(&request);
+  if (m_mdl_type >= MDL_SHARED_NO_WRITE)
+    request_list.push_front(&global_request);
+
+  EXPECT_FALSE(m_mdl_context.acquire_locks(&request_list, long_timeout));
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE, db_name, m_table_name, m_mdl_type));
+
+  // Tell the main thread that we have grabbed our locks.
+  m_lock_grabbed->notify();
+  // Hold on to locks until we are told to release them
+  m_release_locks->wait_for_notification();
+
+  m_mdl_context.rollback_to_savepoint(NULL);
+}
+
+// googletest recommends DeathTest suffix for classes use in death tests.
+typedef MDL_test MDL_DeathTest;
+
+// Our own (simplified) version of the TEST_F macro.
+#define TEST_F(Fixture_class, function_name) \
+  void Fixture_class::function_name()
+
+
+/*
+  The most basic test: just construct and destruct our test fixture.
+ */
+TEST_F(MDL_test, construct_and_destruct)
+{
+}
+
+
+/*
+  Verifies that we can create requests with the factory function
+  MDL_request::create().
+ */
+TEST_F(MDL_test, factory_function)
+{
+  MEM_ROOT mem_root;
+  init_sql_alloc(&mem_root, 1024, 0);
+  // This request should not be destroyed in the normal C++ fashion.
+  MDL_request *request=
+    MDL_request::create(MDL_key::TABLE,
+                        db_name, table_name1, MDL_SHARED, &mem_root);
+  ASSERT_NE(m_null_request, request);
+  EXPECT_EQ(m_null_ticket, request->ticket);
+  free_root(&mem_root, MYF(0));
+}
+
+
+void MDL_test::test_one_simple_shared_lock(enum_mdl_type lock_type)
+{
+  m_request.init(MDL_key::TABLE, db_name, table_name1, lock_type);
+
+  EXPECT_EQ(lock_type, m_request.type);
+  EXPECT_EQ(m_null_ticket, m_request.ticket);
+
+  EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
+  EXPECT_NE(m_null_ticket, m_request.ticket);
+  EXPECT_TRUE(m_mdl_context.has_locks());
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE, db_name, table_name1, lock_type));
+
+  MDL_request request_2;
+  request_2.init(&m_request.key, lock_type);
+  EXPECT_FALSE(m_mdl_context.try_acquire_lock(&request_2));
+  EXPECT_EQ(m_request.ticket, request_2.ticket);
+
+  m_mdl_context.release_all_locks_for_name(m_request.ticket);
+  EXPECT_FALSE(m_mdl_context.has_locks());
+}
+
+
+/*
+  Acquires one lock of type MDL_SHARED.
+ */
+TEST_F(MDL_test, one_shared)
+{
+  test_one_simple_shared_lock(MDL_SHARED);
+}
+
+
+/*
+  Acquires one lock of type MDL_SHARED_HIGH_PRIO.
+ */
+TEST_F(MDL_test, one_shared_high_prio)
+{
+  test_one_simple_shared_lock(MDL_SHARED_HIGH_PRIO);
+}
+
+
+/*
+  Acquires one lock of type MDL_SHARED_READ.
+ */
+TEST_F(MDL_test, one_shared_read)
+{
+  test_one_simple_shared_lock(MDL_SHARED_READ);
+}
+
+
+/*
+  Acquires one lock of type MDL_SHARED_WRITE.
+ */
+TEST_F(MDL_test, one_shared_write)
+{
+  test_one_simple_shared_lock(MDL_SHARED_WRITE);
+}
+
+
+/*
+  Acquires one lock of type MDL_EXCLUSIVE.  
+ */
+TEST_F(MDL_test, one_exclusive)
+{
+  const enum_mdl_type lock_type= MDL_EXCLUSIVE;
+  m_request.init(MDL_key::TABLE, db_name, table_name1, lock_type);
+  EXPECT_EQ(m_null_ticket, m_request.ticket);
+
+  m_request_list.push_front(&m_request);
+  m_request_list.push_front(&m_global_request);
+
+  EXPECT_FALSE(m_mdl_context.acquire_locks(&m_request_list, long_timeout));
+
+  EXPECT_NE(m_null_ticket, m_request.ticket);
+  EXPECT_NE(m_null_ticket, m_global_request.ticket);
+  EXPECT_TRUE(m_mdl_context.has_locks());
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE, db_name, table_name1, lock_type));
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE));
+  EXPECT_TRUE(m_request.ticket->is_upgradable_or_exclusive());
+
+  m_mdl_context.release_all_locks_for_name(m_request.ticket);
+  m_mdl_context.release_lock(m_global_request.ticket);
+  EXPECT_FALSE(m_mdl_context.has_locks());
+}
+
+
+/*
+  Acquires two locks, on different tables, of type MDL_SHARED.
+  Verifies that they are independent.
+ */
+TEST_F(MDL_test, two_shared)
+{
+  MDL_request request_2;
+  m_request.init(MDL_key::TABLE, db_name, table_name1, MDL_SHARED);
+  request_2.init(MDL_key::TABLE, db_name, table_name2, MDL_SHARED);
+
+  EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
+  EXPECT_FALSE(m_mdl_context.try_acquire_lock(&request_2));
+  EXPECT_TRUE(m_mdl_context.has_locks());
+  ASSERT_NE(m_null_ticket, m_request.ticket);
+  ASSERT_NE(m_null_ticket, request_2.ticket);
+
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE, db_name, table_name1, MDL_SHARED));
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE, db_name, table_name2, MDL_SHARED));
+  EXPECT_FALSE(m_mdl_context.
+               is_lock_owner(MDL_key::TABLE, db_name, table_name3, MDL_SHARED));
+
+  m_mdl_context.release_lock(m_request.ticket);
+  EXPECT_FALSE(m_mdl_context.
+               is_lock_owner(MDL_key::TABLE, db_name, table_name1, MDL_SHARED));
+  EXPECT_TRUE(m_mdl_context.has_locks());
+
+  m_mdl_context.release_lock(request_2.ticket);
+  EXPECT_FALSE(m_mdl_context.
+               is_lock_owner(MDL_key::TABLE, db_name, table_name2, MDL_SHARED));
+  EXPECT_FALSE(m_mdl_context.has_locks());
+}
+
+
+/*
+  Verifies that two different contexts can acquire a shared lock
+  on the same table.
+ */
+TEST_F(MDL_test, shared_locks_between_contexts)
+{
+  THD         *thd2= (THD*) this;
+  MDL_context  mdl_context2;
+  mdl_context2.init(thd2);
+  MDL_request request_2;
+  m_request.init(MDL_key::TABLE, db_name, table_name1, MDL_SHARED);
+  request_2.init(MDL_key::TABLE, db_name, table_name1, MDL_SHARED);
+  
+  EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
+  EXPECT_FALSE(mdl_context2.try_acquire_lock(&request_2));
+
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE, db_name, table_name1, MDL_SHARED));
+  EXPECT_TRUE(mdl_context2.
+              is_lock_owner(MDL_key::TABLE, db_name, table_name1, MDL_SHARED));
+
+  m_mdl_context.release_all_locks_for_name(m_request.ticket);
+  mdl_context2.release_all_locks_for_name(request_2.ticket);
+}
+
+
+/*
+  Verifies that we can upgrade a shared lock to exclusive.
+ */
+TEST_F(MDL_test, upgrade_shared_upgradable)
+{
+  m_request.init(MDL_key::TABLE, db_name, table_name1, MDL_SHARED_NO_WRITE);
+
+  m_request_list.push_front(&m_request);
+  m_request_list.push_front(&m_global_request);
+
+  EXPECT_FALSE(m_mdl_context.acquire_locks(&m_request_list, long_timeout));
+  EXPECT_FALSE(m_mdl_context.
+               upgrade_shared_lock_to_exclusive(m_request.ticket, long_timeout));
+  EXPECT_EQ(MDL_EXCLUSIVE, m_request.ticket->get_type());
+
+  // Another upgrade should be a no-op.
+  EXPECT_FALSE(m_mdl_context.
+               upgrade_shared_lock_to_exclusive(m_request.ticket, long_timeout));
+  EXPECT_EQ(MDL_EXCLUSIVE, m_request.ticket->get_type());
+
+  m_mdl_context.release_all_locks_for_name(m_request.ticket);
+  m_mdl_context.release_lock(m_global_request.ticket);
+}
+
+
+/*
+  Verifies that only upgradable locks can be upgraded to exclusive.
+ */
+TEST_F(MDL_DeathTest, die_upgrade_shared)
+{
+  MDL_request request_2;
+  m_request.init(MDL_key::TABLE, db_name, table_name1, MDL_SHARED);
+  request_2.init(MDL_key::TABLE, db_name, table_name2, MDL_SHARED_NO_READ_WRITE);
+
+  m_request_list.push_front(&m_request);
+  m_request_list.push_front(&request_2);
+  m_request_list.push_front(&m_global_request);
+  
+  EXPECT_FALSE(m_mdl_context.acquire_locks(&m_request_list, long_timeout));
+
+#if GTEST_HAS_DEATH_TEST && !defined(DBUG_OFF)
+  ::testing::FLAGS_gtest_death_test_style = "threadsafe";
+  EXPECT_DEATH_IF_SUPPORTED(m_mdl_context.
+                            upgrade_shared_lock_to_exclusive(m_request.ticket,
+                                                             long_timeout),
+                            ".*MDL_SHARED_NO_.*");
+#endif
+  EXPECT_FALSE(m_mdl_context.
+               upgrade_shared_lock_to_exclusive(request_2.ticket, long_timeout));
+  m_mdl_context.rollback_to_savepoint(NULL);
+}
+
+
+/*
+  Verfies that locks are released when we roll back to a savepoint.
+ */
+TEST_F(MDL_test, savepoint)
+{
+  MDL_request request_2;
+  MDL_request request_3;
+  MDL_request request_4;
+  m_request.init(MDL_key::TABLE, db_name, table_name1, MDL_SHARED);
+  request_2.init(MDL_key::TABLE, db_name, table_name2, MDL_SHARED);
+  request_3.init(MDL_key::TABLE, db_name, table_name3, MDL_SHARED);
+  request_4.init(MDL_key::TABLE, db_name, table_name4, MDL_SHARED);
+
+  EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
+  EXPECT_FALSE(m_mdl_context.try_acquire_lock(&request_2));
+  MDL_ticket *savepoint= m_mdl_context.mdl_savepoint();
+  EXPECT_FALSE(m_mdl_context.try_acquire_lock(&request_3));
+  EXPECT_FALSE(m_mdl_context.try_acquire_lock(&request_4));
+
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE, db_name, table_name1, MDL_SHARED));
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE, db_name, table_name2, MDL_SHARED));
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE, db_name, table_name3, MDL_SHARED));
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE, db_name, table_name4, MDL_SHARED));
+
+  m_mdl_context.rollback_to_savepoint(savepoint);
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE, db_name, table_name1, MDL_SHARED));
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE, db_name, table_name2, MDL_SHARED));
+  EXPECT_FALSE(m_mdl_context.
+               is_lock_owner(MDL_key::TABLE, db_name, table_name3, MDL_SHARED));
+  EXPECT_FALSE(m_mdl_context.
+               is_lock_owner(MDL_key::TABLE, db_name, table_name4, MDL_SHARED));
+
+  m_mdl_context.rollback_to_savepoint(NULL);
+  EXPECT_FALSE(m_mdl_context.
+               is_lock_owner(MDL_key::TABLE, db_name, table_name1, MDL_SHARED));
+  EXPECT_FALSE(m_mdl_context.
+               is_lock_owner(MDL_key::TABLE, db_name, table_name2, MDL_SHARED));
+}
+
+
+/*
+  Verifies that we can grab shared locks concurrently, in different threads.
+ */
+TEST_F(MDL_test, concurrent_shared)
+{
+  Notification lock_grabbed;
+  Notification release_locks;
+  MDL_thread mdl_thread(table_name1, MDL_SHARED, &lock_grabbed, &release_locks);
+  mdl_thread.start();
+  lock_grabbed.wait_for_notification();
+
+  m_request.init(MDL_key::TABLE, db_name, table_name1, MDL_SHARED);
+
+  EXPECT_FALSE(m_mdl_context.acquire_lock(&m_request, long_timeout));
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE, db_name, table_name1, MDL_SHARED));
+
+  release_locks.notify();
+  mdl_thread.join();
+
+  m_mdl_context.release_all_locks_for_name(m_request.ticket);
+}
+
+
+/*
+  Verifies that we cannot grab an exclusive lock on something which
+  is locked with a shared lock in a different thread.
+ */
+TEST_F(MDL_test, concurrent_shared_exclusive)
+{
+  expected_error= ER_LOCK_WAIT_TIMEOUT;
+
+  Notification lock_grabbed;
+  Notification release_locks;
+  MDL_thread mdl_thread(table_name1, MDL_SHARED, &lock_grabbed, &release_locks);
+  mdl_thread.ignore_notify();
+  mdl_thread.start();
+  lock_grabbed.wait_for_notification();
+
+  m_request.init(MDL_key::TABLE, db_name, table_name1, MDL_EXCLUSIVE);
+
+  m_request_list.push_front(&m_request);
+  m_request_list.push_front(&m_global_request);
+
+  // We should *not* be able to grab the lock here.
+  EXPECT_TRUE(m_mdl_context.acquire_locks(&m_request_list, zero_timeout));
+  EXPECT_FALSE(m_mdl_context.
+               is_lock_owner(MDL_key::TABLE,
+                             db_name, table_name1, MDL_EXCLUSIVE));
+
+  release_locks.notify();
+  mdl_thread.join();
+
+  // Now we should be able to grab the lock.
+  EXPECT_FALSE(m_mdl_context.acquire_locks(&m_request_list, zero_timeout));
+  EXPECT_NE(m_null_ticket, m_request.ticket);
+
+  m_mdl_context.release_all_locks_for_name(m_request.ticket);
+  m_mdl_context.release_lock(m_global_request.ticket);
+}
+
+
+/*
+  Verifies that we cannot we cannot grab a shared lock on something which
+  is locked exlusively in a different thread.
+ */
+TEST_F(MDL_test, concurrent_exclusive_shared)
+{
+  Notification lock_grabbed;
+  Notification release_locks;
+  MDL_thread mdl_thread(table_name1, MDL_EXCLUSIVE,
+                        &lock_grabbed, &release_locks);
+  mdl_thread.start();
+  lock_grabbed.wait_for_notification();
+
+  m_request.init(MDL_key::TABLE, db_name, table_name1, MDL_SHARED);
+
+  // We should *not* be able to grab the lock here.
+  EXPECT_FALSE(m_mdl_context.try_acquire_lock(&m_request));
+  EXPECT_EQ(m_null_ticket, m_request.ticket);
+
+  release_locks.notify();
+
+  // The other thread should eventually release its locks.
+  EXPECT_FALSE(m_mdl_context.acquire_lock(&m_request, long_timeout));
+  EXPECT_NE(m_null_ticket, m_request.ticket);
+
+  mdl_thread.join();
+  m_mdl_context.release_all_locks_for_name(m_request.ticket);
+}
+
+
+/*
+  Verifies the following scenario:
+  Thread 1: grabs a shared upgradable lock.
+  Thread 2: grabs a shared lock.
+  Thread 1: asks for an upgrade to exclusive (needs to wait for thread 2)
+  Thread 2: gets notified, and releases lock.
+  Thread 1: gets the exclusive lock.
+ */
+TEST_F(MDL_test, concurrent_upgrade)
+{
+  m_request.init(MDL_key::TABLE, db_name, table_name1, MDL_SHARED_NO_WRITE);
+  m_request_list.push_front(&m_request);
+  m_request_list.push_front(&m_global_request);
+
+  EXPECT_FALSE(m_mdl_context.acquire_locks(&m_request_list, long_timeout));
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE,
+                            db_name, table_name1, MDL_SHARED_NO_WRITE));
+  EXPECT_FALSE(m_mdl_context.
+               is_lock_owner(MDL_key::TABLE,
+                             db_name, table_name1, MDL_EXCLUSIVE));
+
+  Notification lock_grabbed;
+  Notification release_locks;
+  MDL_thread mdl_thread(table_name1, MDL_SHARED, &lock_grabbed, &release_locks);
+  mdl_thread.start();
+  lock_grabbed.wait_for_notification();
+
+  EXPECT_FALSE(m_mdl_context.
+               upgrade_shared_lock_to_exclusive(m_request.ticket, long_timeout));
+  EXPECT_TRUE(m_mdl_context.
+              is_lock_owner(MDL_key::TABLE,
+                            db_name, table_name1, MDL_EXCLUSIVE));
+
+  mdl_thread.join();
+  m_mdl_context.rollback_to_savepoint(NULL);
+}
+
+}  // namespace
+
+
+// Creates a new fixture object for each test case.
+void MDL_test::run_one_test(Pmdl_mem member_function)
+{
+  MDL_test *test_object = new MDL_test;
+  test_object->SetUp();
+  (test_object->*member_function)();
+  test_object->TearDown();
+  delete test_object;
+}
+
+
+// We have to invoke each test explicitly here, since we don't have
+// the auto-registration support from the TEST and TEST_F macros.
+int MDL_test::RUN_ALL_TESTS()
+{
+  MDL_test::SetUpTestCase();
+
+  run_one_test(&MDL_test::construct_and_destruct);
+  run_one_test(&MDL_test::factory_function);
+  run_one_test(&MDL_test::one_shared);
+  run_one_test(&MDL_test::one_shared_high_prio);
+  run_one_test(&MDL_test::one_shared_read);
+  run_one_test(&MDL_test::one_shared_write);
+  run_one_test(&MDL_test::one_exclusive);
+  run_one_test(&MDL_test::two_shared);
+  run_one_test(&MDL_test::shared_locks_between_contexts);
+  run_one_test(&MDL_test::upgrade_shared_upgradable);
+  run_one_test(&MDL_test::die_upgrade_shared);
+  run_one_test(&MDL_test::savepoint);
+  run_one_test(&MDL_test::concurrent_shared);
+  run_one_test(&MDL_test::concurrent_shared_exclusive);
+  run_one_test(&MDL_test::concurrent_exclusive_shared);
+  run_one_test(&MDL_test::concurrent_upgrade);
+
+  // Execute MDL_test::TearDownTestCase() here, if it is defined.
+  return exit_status();
+}
+
+
+int main(int argc, char **argv) {
+  // ::testing::InitGoogleTest(&argc, argv);
+  MY_INIT(argv[0]);
+  plan(NO_PLAN);
+  return MDL_test::RUN_ALL_TESTS();
+}

=== added file 'unittest/gunit/sql_list-t.cc'
--- a/unittest/gunit/sql_list-t.cc	1970-01-01 00:00:00 +0000
+++ b/unittest/gunit/sql_list-t.cc	2010-04-21 07:26:14 +0000
@@ -0,0 +1,217 @@
+/* Copyright (C) 2009 Sun Microsystems, Inc.
+
+   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; version 2 of the License.
+
+   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 */
+
+/*
+  This is a simple example of how to use the google unit test framework.
+
+  For an introduction to the constructs used below, see:
+  http://code.google.com/p/googletest/wiki/GoogleTestPrimer
+*/
+
+// Must include gtest first, since MySQL source has macros for min() etc ....
+#include <gtest/gtest.h>
+
+#include "sql_list.h"
+
+#include "thr_malloc.h"
+#include "sql_string.h"
+#include "sql_error.h"
+#include <my_pthread.h>
+
+pthread_key(MEM_ROOT**, THR_MALLOC);
+pthread_key(THD*, THR_THD);
+
+extern "C" void sql_alloc_error_handler(void)
+{
+  ADD_FAILURE();
+}
+
+namespace {
+
+// A simple helper function to determine array size.
+template <class T, int size>
+int array_size(const T (&)[size])
+{
+  return size;
+}
+
+// A simple helper function to insert values into a List.
+template <class T, int size>
+void insert_values(T (&array)[size], List<T> *list)
+{
+  for (int ix= 0; ix < size; ++ix)
+  {
+    EXPECT_FALSE(list->push_back(&array[ix]));
+  }
+}
+
+/*
+  The fixture for testing the MySQL List and List_iterator classes.
+  A fresh instance of this class will be created for each of the
+  TEST_F functions below.
+  The functions SetUp(), TearDown(), SetUpTestCase(), TearDownTestCase() are
+  inherited from ::testing::Test (google naming style differs from MySQL).
+*/
+class Sql_list_test : public ::testing::Test
+{
+protected:
+  Sql_list_test()
+    : m_mem_root_p(&m_mem_root), m_int_list(), m_int_list_iter(m_int_list)
+  {
+  }
+
+  virtual void SetUp()
+  {
+    init_sql_alloc(&m_mem_root, 1024, 0);
+    ASSERT_EQ(0, my_pthread_setspecific_ptr(THR_MALLOC, &m_mem_root_p));
+    MEM_ROOT *root= *my_pthread_getspecific_ptr(MEM_ROOT**, THR_MALLOC);
+    ASSERT_EQ(root, m_mem_root_p);
+  }
+
+  virtual void TearDown()
+  {
+    free_root(&m_mem_root, MYF(0));
+  }
+
+  static void SetUpTestCase()
+  {
+    ASSERT_EQ(0, pthread_key_create(&THR_THD, NULL));
+    ASSERT_EQ(0, pthread_key_create(&THR_MALLOC, NULL));
+  }
+
+  static void TearDownTestCase()
+  {
+    pthread_key_delete(THR_THD);
+    pthread_key_delete(THR_MALLOC);
+  }
+
+  MEM_ROOT m_mem_root;
+  MEM_ROOT *m_mem_root_p;
+  List<int> m_int_list;
+  List_iterator<int> m_int_list_iter;
+
+private:
+  // Declares (but does not define) copy constructor and assignment operator.
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(Sql_list_test);
+};
+
+
+// Tests that we can construct and destruct lists.
+TEST_F(Sql_list_test, construct_and_destruct)
+{
+  EXPECT_TRUE(m_int_list.is_empty());
+  List<int> *p_int_list= new List<int>;
+  EXPECT_TRUE(p_int_list->is_empty());
+  delete p_int_list;
+}
+
+
+// Tests basic operations push and pop.
+TEST_F(Sql_list_test, basic_operations)
+{
+  int i1= 1;
+  int i2= 2;
+  EXPECT_FALSE(m_int_list.push_front(&i1));
+  EXPECT_FALSE(m_int_list.push_back(&i2));
+  EXPECT_FALSE(m_int_list.is_empty());
+  EXPECT_EQ(2U, m_int_list.elements);
+
+  EXPECT_EQ(&i1, m_int_list.head());
+  EXPECT_EQ(&i1, m_int_list.pop());
+  EXPECT_EQ(&i2, m_int_list.head());
+  EXPECT_EQ(&i2, m_int_list.pop());
+  EXPECT_TRUE(m_int_list.is_empty()) << "The list should be empty now!";
+}
+
+
+// Tests list copying.
+TEST_F(Sql_list_test, deep_copy)
+{
+  int values[] = {11, 22, 33, 42, 5};
+  insert_values(values, &m_int_list);
+  MEM_ROOT *mem_root= (MEM_ROOT*) malloc(1 << 20);
+  init_alloc_root(mem_root, 4096, 4096);
+  List<int> list_copy(m_int_list, mem_root);
+  EXPECT_EQ(list_copy.elements, m_int_list.elements);
+  while (!list_copy.is_empty())
+  {
+    EXPECT_EQ(*m_int_list.pop(), *list_copy.pop());
+  }
+  EXPECT_TRUE(m_int_list.is_empty());
+  free(mem_root);
+}
+
+
+// Tests that we can iterate over values.
+TEST_F(Sql_list_test, iterate)
+{
+  int values[] = {3, 2, 1};
+  insert_values(values, &m_int_list);
+  for (int ix= 0; ix < array_size(values); ++ix)
+  {
+    EXPECT_EQ(values[ix], *m_int_list_iter++);
+  }
+  m_int_list_iter.init(m_int_list);
+  int *value;
+  int value_number= 0;
+  while ((value= m_int_list_iter++))
+  {
+    EXPECT_EQ(values[value_number++], *value);
+  }
+}
+
+
+// A simple helper class for testing intrusive lists.
+class Linked_node : public ilink
+{
+public:
+  Linked_node(int val) : m_value(val) {}
+  int get_value() const { return m_value; }
+private:
+  int m_value;
+};
+
+
+// An example of a test without any fixture.
+TEST(Sql_ilist_test, construct_and_destruct)
+{
+  I_List<Linked_node> i_list;
+  I_List_iterator<Linked_node> i_list_iter(i_list);
+  EXPECT_TRUE(i_list.is_empty());
+  const Linked_node *null_node= NULL;
+  EXPECT_EQ(null_node, i_list_iter++);
+}
+
+
+// Tests iteration over intrusive lists.
+TEST(Sql_ilist_test, iterate)
+{
+  I_List<Linked_node> i_list;
+  I_List_iterator<Linked_node> i_list_iter(i_list);
+  int values[] = {11, 22, 33, 42, 5};
+  for (int ix= 0; ix < array_size(values); ++ix)
+  {
+    i_list.push_back(new Linked_node(values[ix]));
+  }
+
+  Linked_node *node;
+  int value_number= 0;
+  while ((node= i_list_iter++))
+  {
+    EXPECT_EQ(values[value_number++], node->get_value());
+  }
+}
+
+}  // namespace

=== added file 'unittest/gunit/tap_event_listener.cc'
--- a/unittest/gunit/tap_event_listener.cc	1970-01-01 00:00:00 +0000
+++ b/unittest/gunit/tap_event_listener.cc	2010-03-19 07:48:37 +0000
@@ -0,0 +1,258 @@
+/* Copyright (C) 2009 Sun Microsystems, Inc.
+
+   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; version 2 of the License.
+
+   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 */
+
+#include <gtest/gtest.h>
+#include <stdarg.h>
+#include <string>
+#include <sstream>
+
+using testing::TestEventListeners;
+using testing::TestCase;
+using testing::TestEventListener;
+using testing::TestInfo;
+using testing::TestPartResult;
+using testing::UnitTest;
+using testing::UnitTest;
+
+
+/**
+   Receives events from googletest, and outputs interesting events
+   in TAP compliant format.
+   Implementation is inspired by PrettyUnitTestResultPrinter.
+   See documentation for base class.
+ */
+class TapEventListener : public TestEventListener
+{
+public:
+  TapEventListener() : m_test_number(0) {}
+  virtual ~TapEventListener() {}
+
+  virtual void OnTestProgramStart(const UnitTest& /*unit_test*/) {}
+  virtual void OnTestIterationStart(const UnitTest& unit_test, int iteration);
+  virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test);
+  virtual void OnEnvironmentsSetUpEnd(const UnitTest& /*unit_test*/) {}
+  virtual void OnTestCaseStart(const TestCase& test_case);
+  virtual void OnTestStart(const TestInfo& test_info);
+  virtual void OnTestPartResult(const TestPartResult& test_part_result);
+  virtual void OnTestEnd(const TestInfo& test_info);
+  virtual void OnTestCaseEnd(const TestCase& /*test_case*/) {};
+  virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test);
+  virtual void OnEnvironmentsTearDownEnd(const UnitTest& /*unit_test*/) {}
+  virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration);
+  virtual void OnTestProgramEnd(const UnitTest& /*unit_test*/) {}
+private:
+  int m_test_number;
+  std::string m_test_case_name;
+};
+
+
+/**
+   Prints argument to stdout, prefixing all lines with "# ".
+ */
+static void tap_diagnostic_printf(const std::stringstream &str_stream)
+{
+  std::string message= str_stream.str();
+  size_t pos = 0;
+  while((pos = message.find("\n", pos)) != std::string::npos)
+  {
+    message.replace(pos, 1, "\n# ");
+    pos += 1;
+  }
+  printf("# %s\n", message.c_str());
+  fflush(stdout);
+}
+
+// Convenience wrapper function.
+static void tap_diagnostic_printf(const std::string &txt)
+{
+  std::stringstream str_str;
+  str_str << txt;
+  tap_diagnostic_printf(str_str);
+}
+
+// Convenience wrapper function.
+static void tap_diagnostic_printf(const char *txt)
+{
+  tap_diagnostic_printf(std::string(txt));
+}
+
+
+namespace {
+// Helper struct to simplify output of "1 test" or "n tests".
+struct num_tests
+{
+  num_tests(int num) : m_num(num) {}
+  int m_num;
+};
+
+std::ostream &operator<< (std::ostream &s, const num_tests &num)
+{
+  return s << num.m_num << (num.m_num == 1 ? " test" : " tests");
+}
+
+// Helper struct to simplify output of "1 test case" or "n test cases".
+struct num_test_cases
+{
+  num_test_cases(int num) : m_num(num) {}
+  int m_num;
+};
+
+std::ostream &operator<< (std::ostream &s, const num_test_cases &num)
+{
+  return s << num.m_num << (num.m_num == 1 ? " test case" : " test cases");
+}
+} // namespace
+
+
+/**
+   Converts a TestPartResult::Type enum to human-friendly string
+   representation.
+*/
+static std::string test_part_result_type_tostring(TestPartResult::Type type)
+{
+  switch (type)
+  {
+  case TestPartResult::kSuccess:
+    return "Success";
+
+  case TestPartResult::kNonFatalFailure:
+  case TestPartResult::kFatalFailure:
+    return "Failure";
+  }
+  return "";
+}
+
+
+/**
+   Formats a source file path and a line number as they would appear
+   in a compiler error message.
+*/
+static std::string format_file_location(const TestPartResult &test_part_result)
+{
+  const char* const file= test_part_result.file_name();
+  const char* const file_name = file == NULL ? "unknown file" : file;
+  const int line= test_part_result.line_number();
+  std::stringstream str_stream;
+  str_stream << file_name << ":";
+  if (line >= 0)
+    str_stream << line << ":";
+  return str_stream.str();
+}
+
+
+/**
+   Formats a TestPartResult as a string.
+ */
+static std::string test_part_result_tostring(const TestPartResult
+                                             &test_part_result)
+{
+  return format_file_location(test_part_result)
+    + " "
+    + test_part_result_type_tostring(test_part_result.type())
+    + test_part_result.message();
+}
+
+
+void TapEventListener::OnTestIterationStart(const UnitTest& unit_test,
+                                            int iteration)
+{
+  std::stringstream str_stream;
+  str_stream << "Running " << num_tests(unit_test.test_to_run_count())
+             << " from " << num_test_cases(unit_test.test_case_to_run_count());
+  tap_diagnostic_printf(str_stream);
+  printf("%d..%d\n", 1, unit_test.test_to_run_count());
+  fflush(stdout);
+}
+
+
+void TapEventListener::OnEnvironmentsSetUpStart(const UnitTest& unit_test)
+{
+  tap_diagnostic_printf("Global test environment set-up");
+}
+
+
+void TapEventListener::OnTestCaseStart(const TestCase& test_case)
+{
+  m_test_case_name = test_case.name();
+}
+
+
+void TapEventListener::OnTestStart(const TestInfo& test_info)
+{
+  ++m_test_number;
+  std::stringstream str_stream;
+  str_stream << "Run " << m_test_number << " "
+             << m_test_case_name << "." << test_info.name();
+  tap_diagnostic_printf(str_stream);
+}
+
+
+void TapEventListener::OnTestPartResult(const TestPartResult& test_part_result)
+{
+  if (test_part_result.passed())
+    return;
+  tap_diagnostic_printf(test_part_result_tostring(test_part_result));
+}
+
+
+void TapEventListener::OnTestEnd(const TestInfo& test_info)
+{
+  if (test_info.result()->Passed())
+    printf("ok %d\n", m_test_number);
+  else
+    printf("not ok %d\n", m_test_number);
+  fflush(stdout);
+}
+
+
+void TapEventListener::OnEnvironmentsTearDownStart(const UnitTest& unit_test)
+{
+  tap_diagnostic_printf("Global test environment tear-down");
+}
+
+
+void TapEventListener::OnTestIterationEnd(const UnitTest& unit_test,
+                                          int iteration)
+{
+  std::stringstream str_stream;
+  str_stream << "Ran " << num_tests(unit_test.test_to_run_count())
+             << " from " << num_test_cases(unit_test.test_case_to_run_count())
+             << "\n"
+             << "Passed " << num_tests(unit_test.successful_test_count());
+
+  if (!unit_test.Passed())
+    str_stream << "\n"
+               << "Failed " << num_tests(unit_test.failed_test_count());
+
+  const int num_disabled = unit_test.disabled_test_count();
+  if (num_disabled && !testing::GTEST_FLAG(also_run_disabled_tests))
+    str_stream << "\n"
+               << "YOU HAVE " << num_disabled << " DISABLED "
+               << (num_disabled == 1 ? "TEST" : "TESTS");
+
+  tap_diagnostic_printf(str_stream);
+}
+
+
+/**
+   Removes the default googletest listener (a PrettyUnitTestResultPrinter),
+   and installs our own TAP compliant pretty printer instead.
+ */
+void install_tap_listener()
+{
+  TestEventListeners& listeners = UnitTest::GetInstance()->listeners();
+  delete listeners.Release(listeners.default_result_printer());
+  listeners.Append(new TapEventListener);
+}

=== added file 'unittest/gunit/thread_utils-t.cc'
--- a/unittest/gunit/thread_utils-t.cc	1970-01-01 00:00:00 +0000
+++ b/unittest/gunit/thread_utils-t.cc	2010-03-18 13:39:53 +0000
@@ -0,0 +1,114 @@
+/* Copyright (C) 2009 Sun Microsystems, Inc.
+
+   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; version 2 of the License.
+
+   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 */
+
+// Must include gtest first, since MySQL source has macros for min() etc ....
+#include <gtest/gtest.h>
+
+#include "thread_utils.h"
+
+#include "mdl.h"
+
+pthread_key(MEM_ROOT**,THR_MALLOC);
+pthread_key(THD*, THR_THD);
+
+extern "C" void sql_alloc_error_handler(void)
+{
+  ADD_FAILURE();
+}
+
+using thread::Mutex_lock;
+using thread::Notification;
+using thread::Thread;
+
+namespace {
+
+const int counter_start_value= 42;
+
+class Notification_thread : public Thread
+{
+public:
+  Notification_thread(Notification *start_notification,
+                      Notification *end_notfication,
+                      int *counter)
+    : m_start_notification(start_notification),
+      m_end_notification(end_notfication),
+      m_counter(counter)
+  {
+  }
+
+  virtual void run()
+  {
+    // Verify counter, increment it, notify the main thread.
+    EXPECT_EQ(counter_start_value, *m_counter);
+    (*m_counter)+= 1;
+    m_start_notification->notify();
+
+    // Wait for notification from other thread.
+    m_end_notification->wait_for_notification();
+    EXPECT_EQ(counter_start_value, *m_counter);
+
+    // Set counter again before returning from thread.
+    (*m_counter)+= 1;
+  }
+
+private:
+  Notification *m_start_notification;
+  Notification *m_end_notification;
+  int          *m_counter;
+
+  Notification_thread(const Notification_thread&); // Not copyable.
+  void operator=(const Notification_thread&);      // Not assignable.
+};
+
+
+/*
+  A basic, single-threaded test of Notification.
+ */
+TEST(Notification, notify)
+{
+  Notification notification;
+  EXPECT_FALSE(notification.has_been_notified());
+  notification.notify();
+  EXPECT_TRUE(notification.has_been_notified());
+}
+
+/*
+  Starts a thread, and verifies that the notification/synchronization
+  mechanism works.
+ */
+TEST(Notification_thread, start_and_wait)
+{
+  Notification start_notification;
+  Notification end_notfication;
+  int counter= counter_start_value;
+  Notification_thread
+    notification_thread(&start_notification, &end_notfication, &counter);
+  notification_thread.start();
+
+  // Wait for the other thread to increment counter, and notify us.
+  start_notification.wait_for_notification();
+  EXPECT_EQ(counter_start_value + 1, counter);
+  EXPECT_TRUE(start_notification.has_been_notified());
+
+  // Reset counter, and notify other thread.
+  counter= counter_start_value;
+  end_notfication.notify();
+  notification_thread.join();
+
+  // We should see the final results of the thread we have joined.
+  EXPECT_EQ(counter_start_value + 1, counter);
+}
+
+}  // namespace

=== added file 'unittest/gunit/thread_utils.cc'
--- a/unittest/gunit/thread_utils.cc	1970-01-01 00:00:00 +0000
+++ b/unittest/gunit/thread_utils.cc	2010-05-21 13:02:27 +0000
@@ -0,0 +1,118 @@
+/* Copyright (C) 2009 Sun Microsystems, Inc.
+
+   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; version 2 of the License.
+
+   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 */
+
+#include <gtest/gtest.h>
+#include "thread_utils.h"
+
+namespace thread {
+
+namespace {
+void *thread_start_routine(void *arg)
+{
+  Thread *thread= (Thread*) arg;
+  Thread::run_wrapper(thread);
+  return NULL;
+}
+
+// We cannot use ASSERT_FALSE in constructors/destructors,
+// so we add a local helper routine.
+#define LOCAL_ASSERT_FALSE(arg) assert_false(arg, __LINE__)
+void assert_false(int arg, int line)
+{
+  ASSERT_FALSE(arg) << "failed with arg " << arg << " at line " << line;
+}
+
+}  // namespace
+
+Thread::~Thread()
+{
+}
+
+
+int Thread::start()
+{
+  return pthread_create(&m_thread_id, NULL, thread_start_routine, this);
+}
+
+
+void Thread::join()
+{
+  int failed= pthread_join(m_thread_id, NULL);
+  ASSERT_FALSE(failed);
+}
+
+
+void Thread::run_wrapper(Thread *thread)
+{
+  const my_bool error= my_thread_init();
+  ASSERT_FALSE(error);
+  thread->run();
+  my_thread_end();
+}
+
+
+Mutex_lock::Mutex_lock(pthread_mutex_t *mutex) : m_mutex(mutex)
+{
+  pthread_mutex_lock(m_mutex);
+}
+
+
+Mutex_lock::~Mutex_lock()
+{
+  const int failed= pthread_mutex_unlock(m_mutex);
+  LOCAL_ASSERT_FALSE(failed);
+}
+
+
+Notification::Notification() : m_notified(FALSE)
+{
+  const int failed1= pthread_cond_init(&m_cond, NULL);
+  LOCAL_ASSERT_FALSE(failed1);
+  const int failed2= pthread_mutex_init(&m_mutex, MY_MUTEX_INIT_FAST);
+  LOCAL_ASSERT_FALSE(failed2);
+}
+
+Notification::~Notification()
+{
+  pthread_mutex_destroy(&m_mutex);
+  pthread_cond_destroy(&m_cond);
+}
+
+bool Notification::has_been_notified()
+{
+  Mutex_lock lock(&m_mutex);
+  return m_notified;
+}
+
+void Notification::wait_for_notification()
+{
+  Mutex_lock lock(&m_mutex);
+  while (!m_notified)
+  {
+    const int failed= pthread_cond_wait(&m_cond, &m_mutex);
+    ASSERT_FALSE(failed);
+  }
+}
+
+void Notification::notify()
+{
+  Mutex_lock lock(&m_mutex);
+  m_notified= TRUE;
+  const int failed= pthread_cond_broadcast(&m_cond);
+  ASSERT_FALSE(failed);
+}
+
+
+}  // namespace thread

=== added file 'unittest/gunit/thread_utils.h'
--- a/unittest/gunit/thread_utils.h	1970-01-01 00:00:00 +0000
+++ b/unittest/gunit/thread_utils.h	2010-03-19 11:46:14 +0000
@@ -0,0 +1,108 @@
+/* Copyright (C) 2009 Sun Microsystems, Inc.
+
+   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; version 2 of the License.
+
+   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 */
+
+#ifndef SQL_THREAD_INCLUDED
+#define SQL_THREAD_INCLUDED
+
+#include <my_global.h>
+#include <my_pthread.h>
+
+namespace thread {
+
+/*
+  An abstract class for creating/running/joining threads.
+  Thread::start() will create a new pthread, and execute the run() function.
+*/
+class Thread
+{
+public:
+  Thread() : m_thread_id(0) {}
+  virtual ~Thread();
+
+  /*
+    Will create a new pthread, and invoke run();
+    Returns the value from pthread_create().
+  */
+  int start();
+
+  /*
+    You may invoke this to wait for the thread to finish.
+    You should probalbly join() a thread before deleting it.
+  */
+  void join();
+
+  // The id of the thread (valid only if it is actually running).
+  pthread_t thread_id() const { return m_thread_id; }
+
+  /*
+    A wrapper for the run() function.
+    Users should *not* call this function directly, they should rather
+    invoke the start() function.
+  */
+  static void run_wrapper(Thread*);
+
+protected:
+  /*
+    Define this function in derived classes.
+    Users should *not* call this function directly, they should rather
+    invoke the start() function.
+  */
+  virtual void run() = 0;
+
+private:
+  pthread_t m_thread_id;
+
+  Thread(const Thread&);                        /* Not copyable. */
+  void operator=(const Thread&);                /* Not assignable. */
+};
+
+
+// A simple wrapper around a mutex:
+// Grabs the mutex in the CTOR, releases it in the DTOR.
+class Mutex_lock
+{
+public:
+  Mutex_lock(pthread_mutex_t *mutex);
+  ~Mutex_lock();
+private:
+  pthread_mutex_t *m_mutex;
+
+  Mutex_lock(const Mutex_lock&);                /* Not copyable. */
+  void operator=(const Mutex_lock&);            /* Not assignable. */
+};
+
+
+// A barrier which can be used for one-time synchronization between threads.
+class Notification
+{
+public:
+  Notification();
+  ~Notification();
+
+  bool has_been_notified();
+  void wait_for_notification();
+  void notify();
+private:
+  bool            m_notified;
+  pthread_cond_t  m_cond;
+  pthread_mutex_t m_mutex;
+
+  Notification(const Notification&);            /* Not copyable. */
+  void operator=(const Notification&);          /* Not assignable. */
+};
+
+}  // namespace thread
+
+#endif  // SQL_THREAD_INCLUDED

=== added file 'unittest/mytap/t/CMakeLists.txt'
--- a/unittest/mytap/t/CMakeLists.txt	1970-01-01 00:00:00 +0000
+++ b/unittest/mytap/t/CMakeLists.txt	2010-05-10 12:26:05 +0000
@@ -0,0 +1,22 @@
+# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. 
+# 
+# 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; version 2 of the License.
+# 
+# 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+
+INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include
+                    ${CMAKE_SOURCE_DIR}/unittest/mytap)
+
+ADD_EXECUTABLE(basic-t basic-t.c)
+TARGET_LINK_LIBRARIES(basic-t mytap)
+ADD_TEST(basic basic-t)

=== modified file 'unittest/unit.pl'
--- a/unittest/unit.pl	2009-11-18 04:29:26 +0000
+++ b/unittest/unit.pl	2010-03-19 08:02:31 +0000
@@ -66,6 +66,7 @@ sub _find_test_files (@) {
     my @files;
     find sub { 
         $File::Find::prune = 1 if /^SCCS$/;
+        $File::Find::prune = 1 if /^.libs$/;
         push(@files, $File::Find::name) if -x _ && (/-t\z/ || /-t\.exe\z/);
     }, @dirs;
     return @files;


Attachment: [text/bzr-bundle] bzr/marc.alff@oracle.com-20100629221607-lw7sp7gkv418gs2l.bundle
Thread
bzr push into mysql-next-mr-wl5291 branch (marc.alff:3153 to 3154) Marc Alff29 Jun