]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[2974] Updated CalloutHandle to use the new CalloutManager
authorStephen Morris <stephen@isc.org>
Mon, 10 Jun 2013 16:23:48 +0000 (17:23 +0100)
committerStephen Morris <stephen@isc.org>
Mon, 10 Jun 2013 16:23:48 +0000 (17:23 +0100)
src/lib/util/hooks/callout_handle.cc
src/lib/util/hooks/callout_manager.cc
src/lib/util/hooks/callout_manager.h
src/lib/util/tests/Makefile.am
src/lib/util/tests/callout_manager_unittest.cc
src/lib/util/tests/handles_unittest.cc

index 4aab9fe94caaf59a8c3f0f63cac34e168d06fd09..5b6b3b221a2cffe83ddf086791ba55ce946959f4 100644 (file)
@@ -106,7 +106,8 @@ CalloutHandle::getContextForLibrary() const {
         context_collection_.find(libindex);
     if (libcontext == context_collection_.end()) {
         isc_throw(NoSuchCalloutContext, "unable to find callout context "
-                  "associated with the current library handle");
+                  "associated with the current library index (" << libindex <<
+                  ")");
     }
 
     // Return a reference to the context's element collection.
index 0b88888449716e903f925ccb1670a9720809cfdd..7ab6131e7d61acd110d9d5a67e6afebd4553cf1a 100644 (file)
@@ -78,14 +78,32 @@ CalloutManager::callCallouts(int hook_index, CalloutHandle& callout_handle) {
     // call.
     callout_handle.setSkip(false);
 
-    // Call all the callouts, stopping if the "skip" flag is set or if a
-    // non-zero status is returned.
+    // Duplicate the callout vector for this hook and work through that.
+    // This step is needed because we allow dynamic registration and
+    // deregistration of callouts.  If a callout attached to a hook modified
+    // the list of callouts, the underlying CalloutVector would change and
+    // potentially affect the iteration through that vector.
+    CalloutVector callouts(hook_vector_[hook_index]);
+
+    // Call all the callouts, stopping if a non-zero status is returned.
     int status = 0;
-    for (CalloutVector::const_iterator i = hook_vector_[hook_index].begin();
-         i != hook_vector_[hook_index].end() && (status == 0); ++i) {
+
+    for (CalloutVector::const_iterator i = callouts.begin();
+         i != callouts.end() && (status == 0); ++i) {
+        // In case the callout tries to register or deregister a callout, set
+        // the current library index to the index associated with the callout
+        // being called.
+        current_library_ = i->first;
+
+        // Call the callout
         status = (*i->second)(callout_handle);
     }
 
+    // Reset the current library index to an invalid value to catch any
+    // programming errors.
+    current_library_ = -1;
+
+
     return (status);
 }
 
index df3f505bab5e317f7ecc8a3b7784690d0745255d..f42901ba7e950ce6b90c0b1768738eaea266ef6a 100644 (file)
@@ -168,6 +168,9 @@ public:
     /// Iterates through the libray handles and calls the callouts associated
     /// with the given hook index.
     ///
+    /// @note This method invalidates the current library index set with
+    ///       setLibraryIndex().
+    ///
     /// @param hook_index Index of the hook to call.
     /// @param callout_handle Reference to the CalloutHandle object for the
     ///        current object being processed.
index 8f9306034d7cfc7110d47b0e440c41e5cb2436bc..ceb8b95c037a31cba1c31479c0e9ad0500f15a24 100644 (file)
@@ -31,7 +31,7 @@ run_unittests_SOURCES += fd_share_tests.cc
 run_unittests_SOURCES += fd_tests.cc
 run_unittests_SOURCES += filename_unittest.cc
 run_unittests_SOURCES += hex_unittest.cc
-run_unittests_SOURCES += handles_unittest.cc
+run_unittests_SOURCES += handles_unittest.cc
 run_unittests_SOURCES += io_utilities_unittest.cc
 run_unittests_SOURCES += lru_list_unittest.cc
 run_unittests_SOURCES += memory_segment_local_unittest.cc
index 630b18bd23758cd8f77c454acb036786d6e3d146..ebd4773d9f74187965a40756a426bc538c865395 100644 (file)
@@ -506,7 +506,10 @@ TEST_F(CalloutManagerTest, DeregisterSingleCallout) {
     EXPECT_EQ(0, status);
     EXPECT_EQ(2, callout_value_);
 
-    // Remove it and check that the no callouts are present.
+    // Remove it and check that the no callouts are present.  We have to reset
+    // the current library index here as it was invalidated by the call
+    // to callCallouts().
+    getCalloutManager()->setLibraryIndex(0);
     EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
     EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", manager_two));
     EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
@@ -536,7 +539,9 @@ TEST_F(CalloutManagerTest, DeregisterSingleCalloutSameLibrary) {
     EXPECT_EQ(0, status);
     EXPECT_EQ(1234, callout_value_);
 
-    // Remove the manager_two callout.
+    // Remove the manager_two callout.  We have to reset the current library
+    // index here as it was invalidated by the call to callCallouts().
+    getCalloutManager()->setLibraryIndex(0);
     EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", manager_two));
     callout_value_ = 0;
     status = getCalloutManager()->callCallouts(alpha_index_,
@@ -545,6 +550,7 @@ TEST_F(CalloutManagerTest, DeregisterSingleCalloutSameLibrary) {
     EXPECT_EQ(134, callout_value_);
 
     // Try removing it again.
+    getCalloutManager()->setLibraryIndex(0);
     EXPECT_FALSE(getCalloutManager()->deregisterCallout("alpha", manager_two));
     callout_value_ = 0;
     status = getCalloutManager()->callCallouts(alpha_index_,
@@ -581,7 +587,9 @@ TEST_F(CalloutManagerTest, DeregisterMultipleCalloutsSameLibrary) {
     EXPECT_EQ(0, status);
     EXPECT_EQ(12123434, callout_value_);
 
-    // Remove the manager_two callouts.
+    // Remove the manager_two callouts.  We have to reset the current library
+    // index here as it was invalidated by the call to callCallouts().
+    getCalloutManager()->setLibraryIndex(0);
     EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", manager_two));
     callout_value_ = 0;
     status = getCalloutManager()->callCallouts(alpha_index_,
@@ -591,6 +599,7 @@ TEST_F(CalloutManagerTest, DeregisterMultipleCalloutsSameLibrary) {
 
     // Try removing multiple callouts that includes one at the end of the
     // list of callouts.
+    getCalloutManager()->setLibraryIndex(0);
     EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", manager_four));
     callout_value_ = 0;
     status = getCalloutManager()->callCallouts(alpha_index_,
@@ -599,6 +608,7 @@ TEST_F(CalloutManagerTest, DeregisterMultipleCalloutsSameLibrary) {
     EXPECT_EQ(1133, callout_value_);
 
     // ... and from the start.
+    getCalloutManager()->setLibraryIndex(0);
     EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", manager_one));
     callout_value_ = 0;
     status = getCalloutManager()->callCallouts(alpha_index_,
@@ -607,6 +617,7 @@ TEST_F(CalloutManagerTest, DeregisterMultipleCalloutsSameLibrary) {
     EXPECT_EQ(33, callout_value_);
 
     // ... and the remaining callouts.
+    getCalloutManager()->setLibraryIndex(0);
     EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", manager_three));
     callout_value_ = 0;
     status = getCalloutManager()->callCallouts(alpha_index_,
index 34ecc51d21c49c16880eadc1a9021df978d5c6ac..7bcc232aef9dfe9a2447394013100010df3213f7 100644 (file)
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <util/hooks/callout_handle.h>
+#include <util/hooks/callout_manager.h>
 #include <util/hooks/library_handle.h>
 #include <util/hooks/server_hooks.h>
 
 #include <boost/lexical_cast.hpp>
+#include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
 
 #include <algorithm>
 /// - The various methods manipulating the items in the CalloutHandle's context
 ///   work correctly.
 ///
-/// Some minor interactions between the two classes are checked in the unit
-/// tests for each class (mainly the use of the "skip" flag).
+/// - An active callout can only modify the registration of callouts registered
+///   by its own library.
 
 using namespace isc::util;
 using namespace std;
 
 namespace {
 
+class HandlesTest : public ::testing::Test {
+public:
+    /// @brief Constructor
+    ///
+    /// Sets up the various elements used in each test.
+    HandlesTest() : hooks_(new ServerHooks()), manager_()
+    {
+        // Set up four hooks, although through gamma
+        alpha_index_ = hooks_->registerHook("alpha");
+        beta_index_ = hooks_->registerHook("beta");
+        gamma_index_ = hooks_->registerHook("gamma");
+        delta_index_ = hooks_->registerHook("delta");
+
+        // Set up for three libraries.
+        manager_.reset(new CalloutManager(hooks_, 3));
+    }
+
+    /// @brief Return callout manager
+    boost::shared_ptr<CalloutManager> getCalloutManager() {
+        return (manager_);
+    }
+
+    /// Hook indexes - these are frequently accessed, so are accessed directly.
+    int alpha_index_;
+    int beta_index_;
+    int gamma_index_;
+    int delta_index_;
+
+private:
+    /// Server hooks 
+    boost::shared_ptr<ServerHooks> hooks_;
+
+    /// Callout manager.  Declared static so that the callout functions can
+    /// access it.
+    boost::shared_ptr<CalloutManager> manager_;
+
+};
+
 // The next set of functions define the callouts used by the tests.  They
 // manipulate the data in such a way that callouts called - and the order in
 // which they were called - can be determined.  The functions also check that
@@ -59,7 +99,7 @@ namespace {
 // identification has been set in the callout handle).
 //
 // For simplicity, and to cut down the number of functions actually written,
-// the callout indicator ("a" or "b") ) used in the in the CalloutHandle
+// the callout indicator ("1" or "2") ) used in the in the CalloutHandle
 // functions is passed via a CalloutArgument.  The argument is named "string":
 // use of a name the same as that of one of the context elements serves as a
 // check that the argument name space and argument context space are separate.
@@ -140,7 +180,7 @@ execute(CalloutHandle& callout_handle, int library_num, int callout_num) {
         string_value = "";
     }
 
-    // Update the values and set them.
+    // Update the values and set them back in the callout context.
     int_value += idata;
     callout_handle.setContext("int", int_value);
 
@@ -228,71 +268,59 @@ print3(CalloutHandle& callout_handle) {
 
 // This test checks the many-faced nature of the context for the CalloutContext.
 
-TEST(HandlesTest, ContextAccessCheck) {
-    // Create the LibraryHandleCollection with a set of four callouts (the test
-    // does not use the context_create and context_destroy callouts).
-
-    boost::shared_ptr<ServerHooks> server_hooks(new ServerHooks());
-    const int one_index = server_hooks->registerHook("one");
-    const int two_index = server_hooks->registerHook("two");
-    const int three_index = server_hooks->registerHook("three");
-    const int four_index = server_hooks->registerHook("four");
-
-    // Create the library handle collection and the library handles.
-    boost::shared_ptr<LibraryHandleCollection>
-        collection(new LibraryHandleCollection());
-
-    boost::shared_ptr<LibraryHandle> handle(new LibraryHandle(server_hooks));
-    handle->registerCallout("one", callout11);
-    handle->registerCallout("two", callout12);
-    handle->registerCallout("three", callout13);
-    handle->registerCallout("four", print1);
-    collection->addLibraryHandle(handle);
-
-    handle.reset(new LibraryHandle(server_hooks));
-    handle->registerCallout("one", callout21);
-    handle->registerCallout("two", callout22);
-    handle->registerCallout("three", callout23);
-    handle->registerCallout("four", print2);
-    collection->addLibraryHandle(handle);
-
-    handle.reset(new LibraryHandle(server_hooks));
-    handle->registerCallout("one", callout31);
-    handle->registerCallout("two", callout32);
-    handle->registerCallout("three", callout33);
-    handle->registerCallout("four", print3);
-    collection->addLibraryHandle(handle);
+TEST_F(HandlesTest, ContextAccessCheck) {
+    // Register callouts for the different libraries.
+    CalloutHandle handle(getCalloutManager());
+
+    // Library 0.
+    getCalloutManager()->setLibraryIndex(0);
+    getCalloutManager()->registerCallout("alpha", callout11);
+    getCalloutManager()->registerCallout("beta", callout12);
+    getCalloutManager()->registerCallout("gamma", callout13);
+    getCalloutManager()->registerCallout("delta", print1);
+
+    getCalloutManager()->setLibraryIndex(1);
+    getCalloutManager()->registerCallout("alpha", callout21);
+    getCalloutManager()->registerCallout("beta", callout22);
+    getCalloutManager()->registerCallout("gamma", callout23);
+    getCalloutManager()->registerCallout("delta", print2);
+
+    getCalloutManager()->setLibraryIndex(2);
+    getCalloutManager()->registerCallout("alpha", callout31);
+    getCalloutManager()->registerCallout("beta", callout32);
+    getCalloutManager()->registerCallout("gamma", callout33);
+    getCalloutManager()->registerCallout("delta", print3);
 
     // Create the callout handles and distinguish them by setting the
     // "handle_num" argument.
-    CalloutHandle callout_handle_1(collection);
+    CalloutHandle callout_handle_1(getCalloutManager());
     callout_handle_1.setArgument("handle_num", static_cast<int>(1)); 
 
-    CalloutHandle callout_handle_2(collection);
+    CalloutHandle callout_handle_2(getCalloutManager());
     callout_handle_2.setArgument("handle_num", static_cast<int>(2)); 
 
     // Now call the callouts attached to the first three hooks.  Each hook is
     // called twice (once for each callout handle) before the next hook is
     // called.
-    collection->callCallouts(one_index, callout_handle_1);
-    collection->callCallouts(one_index, callout_handle_2);
-    collection->callCallouts(two_index, callout_handle_1);
-    collection->callCallouts(two_index, callout_handle_2);
-    collection->callCallouts(three_index, callout_handle_1);
-    collection->callCallouts(three_index, callout_handle_2);
-
-    // Get the results for each callout.  Explicitly zero the variables before
-    // getting the results so we are certain that the values are the results
-    // of the callouts.
+    getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
+    getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
+    getCalloutManager()->callCallouts(beta_index_, callout_handle_1);
+    getCalloutManager()->callCallouts(beta_index_, callout_handle_2);
+    getCalloutManager()->callCallouts(gamma_index_, callout_handle_1);
+    getCalloutManager()->callCallouts(gamma_index_, callout_handle_2);
+
+    // Get the results for each callout (the callout on hook "delta" copies
+    // the context values into a location the test can access).  Explicitly
+    // zero the variables before getting the results so we are certain that
+    // the values are the results of the callouts.
 
     zero_results();
-    collection->callCallouts(four_index, callout_handle_1);
 
     // To explain the expected callout context results.
     //
-    // The callout handle maintains a separate context for each library.  When
+    // Each callout handle maintains a separate context for each library.  When
     // the first call to callCallouts() is made, "111" gets appended to
-    // the context for library 1 maintained by by the callout handle, "211"
+    // the context for library 1 maintained by the first callout handle, "211"
     // gets appended to the context maintained for library 2, and "311" to
     // the context maintained for library 3.  In each case, the first digit
     // corresponds to the library number, the second to the callout number and
@@ -307,9 +335,9 @@ TEST(HandlesTest, ContextAccessCheck) {
     // always 2.  These additions don't affect the contexts maintained by
     // callout handle 1.
     //
-    // The process is then repeated for hooks "two" and "three" which, for
-    // callout handle 1, append "121", "221" and "321" for hook "two" and "311",
-    // "321" and "331" for hook "three".
+    // The process is then repeated for hooks "beta" and "gamma" which, for
+    // callout handle 1, append "121", "221" and "321" for hook "beta" and
+    // "311", "321" and "331" for hook "gamma".
     //
     // The expected integer values can be found by summing up the values
     // corresponding to the elements of the strings.
@@ -318,6 +346,7 @@ TEST(HandlesTest, ContextAccessCheck) {
     // handle "1", so the following results are checking the context values
     // maintained in that callout handle.
 
+    getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
     EXPECT_EQ("111121131", resultCalloutString(0));
     EXPECT_EQ("211221231", resultCalloutString(1));
     EXPECT_EQ("311321331", resultCalloutString(2));
@@ -329,7 +358,7 @@ TEST(HandlesTest, ContextAccessCheck) {
     // Repeat the checks for callout 2.
 
     zero_results();
-    collection->callCallouts(four_index, callout_handle_2);
+    getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
 
     EXPECT_EQ((112 + 122 + 132), resultCalloutInt(0));
     EXPECT_EQ((212 + 222 + 232), resultCalloutInt(1));
@@ -340,7 +369,7 @@ TEST(HandlesTest, ContextAccessCheck) {
     EXPECT_EQ("312322332", resultCalloutString(2));
 }
 
-// Now repeat the test, but add a deletion callout to the list.  The "two"
+// Now repeat the test, but add a deletion callout to the list.  The "beta"
 // hook of library 2 will have an additional callout to delete the "int"
 // element: the same hook for library 3 will delete both elements.  In
 // addition, the names of context elements for the libraries at this point
@@ -395,70 +424,55 @@ printContextNames3(CalloutHandle& handle) {
 
 // Perform the test including deletion of context items.
 
-TEST(HandlesTest, ContextDeletionCheck) {
-    // Create the LibraryHandleCollection with a set of four callouts
-    // (the test does not use the context_create and context_destroy callouts.)
-
-    boost::shared_ptr<ServerHooks> server_hooks(new ServerHooks());
-    const int one_index = server_hooks->registerHook("one");
-    const int two_index = server_hooks->registerHook("two");
-    const int three_index = server_hooks->registerHook("three");
-    const int four_index = server_hooks->registerHook("four");
-
-    // Create the library handle collection and the library handles.
-    boost::shared_ptr<LibraryHandleCollection>
-        collection(new LibraryHandleCollection());
-
-    boost::shared_ptr<LibraryHandle> handle(new LibraryHandle(server_hooks));
-    handle->registerCallout("one", callout11);
-    handle->registerCallout("two", callout12);
-    handle->registerCallout("two", printContextNames1);
-    handle->registerCallout("three", callout13);
-    handle->registerCallout("four", print1);
-    collection->addLibraryHandle(handle);
-
-    handle.reset(new LibraryHandle(server_hooks));
-    handle->registerCallout("one", callout21);
-    handle->registerCallout("two", callout22);
-    handle->registerCallout("two", deleteIntContextItem);
-    handle->registerCallout("two", printContextNames2);
-    handle->registerCallout("three", callout23);
-    handle->registerCallout("four", print2);
-    collection->addLibraryHandle(handle);
-
-    handle.reset(new LibraryHandle(server_hooks));
-    handle->registerCallout("one", callout31);
-    handle->registerCallout("two", callout32);
-    handle->registerCallout("two", deleteAllContextItems);
-    handle->registerCallout("two", printContextNames3);
-    handle->registerCallout("three", callout33);
-    handle->registerCallout("four", print3);
-    collection->addLibraryHandle(handle);
+TEST_F(HandlesTest, ContextDeletionCheck) {
+
+    getCalloutManager()->setLibraryIndex(0);
+    getCalloutManager()->registerCallout("alpha", callout11);
+    getCalloutManager()->registerCallout("beta", callout12);
+    getCalloutManager()->registerCallout("beta", printContextNames1);
+    getCalloutManager()->registerCallout("gamma", callout13);
+    getCalloutManager()->registerCallout("delta", print1);
+
+    getCalloutManager()->setLibraryIndex(1);
+    getCalloutManager()->registerCallout("alpha", callout21);
+    getCalloutManager()->registerCallout("beta", callout22);
+    getCalloutManager()->registerCallout("beta", deleteIntContextItem);
+    getCalloutManager()->registerCallout("beta", printContextNames2);
+    getCalloutManager()->registerCallout("gamma", callout23);
+    getCalloutManager()->registerCallout("delta", print2);
+
+    getCalloutManager()->setLibraryIndex(2);
+    getCalloutManager()->registerCallout("alpha", callout31);
+    getCalloutManager()->registerCallout("beta", callout32);
+    getCalloutManager()->registerCallout("beta", deleteAllContextItems);
+    getCalloutManager()->registerCallout("beta", printContextNames3);
+    getCalloutManager()->registerCallout("gamma", callout33);
+    getCalloutManager()->registerCallout("delta", print3);
 
     // Create the callout handles and distinguish them by setting the "long"
     // argument.
-    CalloutHandle callout_handle_1(collection);
+    CalloutHandle callout_handle_1(getCalloutManager());
     callout_handle_1.setArgument("handle_num", static_cast<int>(1));
 
-    CalloutHandle callout_handle_2(collection);
+    CalloutHandle callout_handle_2(getCalloutManager());
     callout_handle_2.setArgument("handle_num", static_cast<int>(2));
 
     // Now call the callouts attached to the first three hooks.  Each hook is
     // called twice (once for each callout handle) before the next hook is
     // called.
-    collection->callCallouts(one_index, callout_handle_1);
-    collection->callCallouts(one_index, callout_handle_2);
-    collection->callCallouts(two_index, callout_handle_1);
-    collection->callCallouts(two_index, callout_handle_2);
-    collection->callCallouts(three_index, callout_handle_1);
-    collection->callCallouts(three_index, callout_handle_2);
+    getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
+    getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
+    getCalloutManager()->callCallouts(beta_index_, callout_handle_1);
+    getCalloutManager()->callCallouts(beta_index_, callout_handle_2);
+    getCalloutManager()->callCallouts(gamma_index_, callout_handle_1);
+    getCalloutManager()->callCallouts(gamma_index_, callout_handle_2);
 
     // Get the results for each callout.  Explicitly zero the variables before
     // getting the results so we are certain that the values are the results
     // of the callouts.
 
     zero_results();
-    collection->callCallouts(four_index, callout_handle_1);
+    getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
 
     // The logic by which the expected results are arrived at is described
     // in the ContextAccessCheck test.  The results here are different
@@ -475,7 +489,7 @@ TEST(HandlesTest, ContextDeletionCheck) {
     // Repeat the checks for callout handle 2.
 
     zero_results();
-    collection->callCallouts(four_index, callout_handle_2);
+    getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
 
     EXPECT_EQ((112 + 122 + 132), resultCalloutInt(0));
     EXPECT_EQ((            232), resultCalloutInt(1));
@@ -486,7 +500,7 @@ TEST(HandlesTest, ContextDeletionCheck) {
     EXPECT_EQ(      "332", resultCalloutString(2));
 
     // ... and check what the names of the context items are after the callouts
-    // for hook "two".  We know they are in sorted order.
+    // for hook "beta".  We know they are in sorted order.
 
     EXPECT_EQ(2, getItemNames(0).size());
     EXPECT_EQ(string("int"),    getItemNames(0)[0]);
@@ -507,29 +521,20 @@ int returnError(CalloutHandle&) {
     return (1);
 }
 
-TEST(HandlesTest, ConstructionDestructionCallouts) {
-    // Create the LibraryHandleCollection comprising two LibraryHandles.
-    // Register callouts for the context_create and context_destroy hooks.
-
-    boost::shared_ptr<ServerHooks> server_hooks(new ServerHooks());
+TEST_F(HandlesTest, ConstructionDestructionCallouts) {
 
-    // Create the library handle collection and the library handles.
-    boost::shared_ptr<LibraryHandleCollection>
-        collection(new LibraryHandleCollection());
-
-    boost::shared_ptr<LibraryHandle> handle(new LibraryHandle(server_hooks));
-    handle->registerCallout("context_create", callout11);
-    handle->registerCallout("context_create", print1);
-    handle->registerCallout("context_destroy", callout12);
-    handle->registerCallout("context_destroy", print1);
-    collection->addLibraryHandle(handle);
+    // Register context callouts.
+    getCalloutManager()->setLibraryIndex(0);
+    getCalloutManager()->registerCallout("context_create", callout11);
+    getCalloutManager()->registerCallout("context_create", print1);
+    getCalloutManager()->registerCallout("context_destroy", callout12);
+    getCalloutManager()->registerCallout("context_destroy", print1);
 
     // Create the CalloutHandle and check that the constructor callout
     // has run.
     zero_results();
-    boost::shared_ptr<CalloutHandle>
-        callout_handle(new CalloutHandle(collection));
-
+    boost::scoped_ptr<CalloutHandle>
+        callout_handle(new CalloutHandle(getCalloutManager()));
     EXPECT_EQ("110", resultCalloutString(0));
     EXPECT_EQ(110, resultCalloutInt(0));
 
@@ -544,19 +549,220 @@ TEST(HandlesTest, ConstructionDestructionCallouts) {
     EXPECT_EQ((110 + 120), resultCalloutInt(0));
 
     // Test that the destructor throws an error if the context_destroy
-    // callout returns an error.
-    handle->registerCallout("context_destroy", returnError);
-    callout_handle.reset(new CalloutHandle(collection));
+    // callout returns an error. (As the constructor and destructor will
+    // have implicitly run the CalloutManager's callCallouts method, we need
+    // to set the library index again.)
+    getCalloutManager()->setLibraryIndex(0);
+    getCalloutManager()->registerCallout("context_destroy", returnError);
+    callout_handle.reset(new CalloutHandle(getCalloutManager()));
     EXPECT_THROW(callout_handle.reset(), ContextDestroyFail);
 
     // We don't know what callout_handle is pointing to - it could be to a
     // half-destroyed object - so use a new CalloutHandle to test construction
     // failure.
-    handle->registerCallout("context_create", returnError);
-    boost::shared_ptr<CalloutHandle> callout_handle2;
-    EXPECT_THROW(callout_handle2.reset(new CalloutHandle(collection)),
+    getCalloutManager()->setLibraryIndex(0);
+    getCalloutManager()->registerCallout("context_create", returnError);
+    boost::scoped_ptr<CalloutHandle> callout_handle2;
+    EXPECT_THROW(callout_handle2.reset(new CalloutHandle(getCalloutManager())),
                  ContextCreateFail);
+}
+
+// Dynamic callout registration and deregistration.
+// The following are the dynamic registration/deregistration callouts.
+
+
+// Add callout_78_alpha - adds a callout to hook alpha that appends "78x"
+// (where "x" is the callout handle) to the current output.
 
+int
+callout78(CalloutHandle& callout_handle) {
+    return (execute(callout_handle, 7, 8));
+}
+
+int
+add_callout78_alpha(CalloutHandle& callout_handle) {
+    callout_handle.getLibraryHandle().registerCallout("alpha", callout78);
+    return (0);
+}
+
+int
+delete_callout78_alpha(CalloutHandle& callout_handle) {
+    static_cast<void>(
+        callout_handle.getLibraryHandle().deregisterCallout("alpha",
+                                                            callout78));
+    return (0);
+}
+
+// Check that a callout can register another callout on a different hook.
+
+TEST_F(HandlesTest, DynamicRegistrationAnotherHook) {
+    // Register callouts for the different libraries.
+    CalloutHandle handle(getCalloutManager());
+
+    // Set up callouts on "alpha".
+    getCalloutManager()->setLibraryIndex(0);
+    getCalloutManager()->registerCallout("alpha", callout11);
+    getCalloutManager()->registerCallout("delta", print1);
+    getCalloutManager()->setLibraryIndex(1);
+    getCalloutManager()->registerCallout("alpha", callout21);
+    getCalloutManager()->registerCallout("delta", print2);
+    getCalloutManager()->setLibraryIndex(2);
+    getCalloutManager()->registerCallout("alpha", callout31);
+    getCalloutManager()->registerCallout("delta", print3);
+
+    // ... and on "beta", set up the function to add a hook to alpha (but only
+    // for library 1).
+    getCalloutManager()->setLibraryIndex(1);
+    getCalloutManager()->registerCallout("beta", add_callout78_alpha);
+
+    // See what we get for calling the callouts on alpha first.
+    CalloutHandle callout_handle_1(getCalloutManager());
+    callout_handle_1.setArgument("handle_num", static_cast<int>(1)); 
+    getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
+
+    zero_results();
+    getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
+    EXPECT_EQ("111", resultCalloutString(0));
+    EXPECT_EQ("211", resultCalloutString(1));
+    EXPECT_EQ("311", resultCalloutString(2));
+
+    // All as expected, now call the callouts on beta.  This should add a
+    // callout to the list of callouts for alpha, which we should see when
+    // we run the test again.
+    getCalloutManager()->callCallouts(beta_index_, callout_handle_1);
+
+    // Use a new callout handle so as to get fresh callout context.
+    CalloutHandle callout_handle_2(getCalloutManager());
+    callout_handle_2.setArgument("handle_num", static_cast<int>(2)); 
+    getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
+
+    zero_results();
+    getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
+    EXPECT_EQ("112", resultCalloutString(0));
+    EXPECT_EQ("212782", resultCalloutString(1));
+    EXPECT_EQ("312", resultCalloutString(2));
+}
+
+// Check that a callout can register another callout on the same hook.
+// Note that the registration only applies to a subsequent invocation of
+// callCallouts, not to the current one. In other words, if
+//
+// * the callout list for a library is "A then B then C"
+// * when callCallouts is executed "B" adds "D" to that list,
+//
+// ... the current execution of callCallouts only executes A, B and C.  A
+// subsequent invocation will execute A, B, C then D.
+
+TEST_F(HandlesTest, DynamicRegistrationSameHook) {
+    // Register callouts for the different libraries.
+    CalloutHandle handle(getCalloutManager());
+
+    // Set up callouts on "alpha".
+    getCalloutManager()->setLibraryIndex(0);
+    getCalloutManager()->registerCallout("alpha", callout11);
+    getCalloutManager()->registerCallout("alpha", add_callout78_alpha);
+    getCalloutManager()->registerCallout("delta", print1);
+
+    // See what we get for calling the callouts on alpha first.
+    CalloutHandle callout_handle_1(getCalloutManager());
+    callout_handle_1.setArgument("handle_num", static_cast<int>(1)); 
+    getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
+    zero_results();
+    getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
+    EXPECT_EQ("111", resultCalloutString(0));
+
+    // Run it again - we should have added something to this hook.
+    CalloutHandle callout_handle_2(getCalloutManager());
+    callout_handle_2.setArgument("handle_num", static_cast<int>(2)); 
+    getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
+    zero_results();
+    getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
+    EXPECT_EQ("112782", resultCalloutString(0));
+
+    // And a third time...
+    CalloutHandle callout_handle_3(getCalloutManager());
+    callout_handle_3.setArgument("handle_num", static_cast<int>(3)); 
+    getCalloutManager()->callCallouts(alpha_index_, callout_handle_3);
+    zero_results();
+    getCalloutManager()->callCallouts(delta_index_, callout_handle_3);
+    EXPECT_EQ("113783783", resultCalloutString(0));
+}
+
+// Deregistration of a callout from a different hook
+
+TEST_F(HandlesTest, DynamicDeregistrationDifferentHook) {
+    // Register callouts for the different libraries.
+    CalloutHandle handle(getCalloutManager());
+
+    // Set up callouts on "alpha".
+    getCalloutManager()->setLibraryIndex(0);
+    getCalloutManager()->registerCallout("alpha", callout11);
+    getCalloutManager()->registerCallout("alpha", callout78);
+    getCalloutManager()->registerCallout("alpha", callout11);
+    getCalloutManager()->registerCallout("delta", print1);
+
+    getCalloutManager()->registerCallout("beta", delete_callout78_alpha);
+
+    // Call the callouts on alpha
+    CalloutHandle callout_handle_1(getCalloutManager());
+    callout_handle_1.setArgument("handle_num", static_cast<int>(1)); 
+    getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
+
+    zero_results();
+    getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
+    EXPECT_EQ("111781111", resultCalloutString(0));
+
+    // Run the callouts on hook beta to remove the callout on alpha.
+    getCalloutManager()->callCallouts(beta_index_, callout_handle_1);
+
+    // The run of the callouts should have altered the callout list on the
+    // first library for hook alpha, so call again to make sure.
+    CalloutHandle callout_handle_2(getCalloutManager());
+    callout_handle_2.setArgument("handle_num", static_cast<int>(2)); 
+    getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
+
+    zero_results();
+    getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
+    EXPECT_EQ("112112", resultCalloutString(0));
+}
+
+// Deregistration of a callout from the same hook
+
+TEST_F(HandlesTest, DynamicDeregistrationSameHook) {
+    // Register callouts for the different libraries.
+    CalloutHandle handle(getCalloutManager());
+
+    // Set up callouts on "alpha".
+    getCalloutManager()->setLibraryIndex(0);
+    getCalloutManager()->registerCallout("alpha", callout11);
+    getCalloutManager()->registerCallout("alpha", delete_callout78_alpha);
+    getCalloutManager()->registerCallout("alpha", callout78);
+    getCalloutManager()->registerCallout("delta", print1);
+    getCalloutManager()->setLibraryIndex(1);
+    getCalloutManager()->registerCallout("alpha", callout21);
+    getCalloutManager()->registerCallout("alpha", callout78);
+    getCalloutManager()->registerCallout("delta", print2);
+
+    // Call the callouts on alpha
+    CalloutHandle callout_handle_1(getCalloutManager());
+    callout_handle_1.setArgument("handle_num", static_cast<int>(1)); 
+    getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
+
+    zero_results();
+    getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
+    EXPECT_EQ("111781", resultCalloutString(0));
+    EXPECT_EQ("211781", resultCalloutString(1));
+
+    // The run of the callouts should have altered the callout list on the
+    // first library for hook alpha, so call again to make sure.
+    CalloutHandle callout_handle_2(getCalloutManager());
+    callout_handle_2.setArgument("handle_num", static_cast<int>(2)); 
+    getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
+
+    zero_results();
+    getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
+    EXPECT_EQ("112", resultCalloutString(0));
+    EXPECT_EQ("212782", resultCalloutString(1));
 }