// PERFORMANCE OF THIS SOFTWARE.
#include <util/hooks/callout_handle.h>
+#include <util/hooks/library_handle.h>
-#include <algorithm>
-#include <functional>
#include <string>
+#include <utility>
#include <vector>
using namespace std;
namespace isc {
namespace util {
-// return the name of all context items.
+// Return the name of all argument items.
vector<string>
CalloutHandle::getArgumentNames() const {
vector<string> names;
- ArgumentCollection::const_iterator i;
- for (i = arguments_.begin(); i != arguments_.end(); ++i) {
+ for (ElementCollection::const_iterator i = arguments_.begin();
+ i != arguments_.end(); ++i) {
+ names.push_back(i->first);
+ }
+
+ return (names);
+}
+
+// Return the "current" library handle.
+
+LibraryHandle&
+CalloutHandle::getLibraryHandle() const {
+ boost::shared_ptr<LibraryHandle> handle =
+ library_collection_->getLibraryHandle();
+
+ // Return refernce to this library handle. This remains valid even
+ // after this method returns, because this object maintains a shared
+ // pointer to the LibraryHandleCollection, which in turn maintains
+ // a shared pointer to the LibraryHandle in question.
+
+ return (*handle);
+}
+
+// Check the current library index.
+
+int
+CalloutHandle::getLibraryIndex() const {
+ int curidx = library_collection_->getLibraryIndex();
+ if (curidx < 0) {
+ isc_throw(InvalidIndex, "current library handle index is not valid");
+ }
+
+ return (curidx);
+}
+
+// Return the context for the currently pointed-to library. This version is
+// used by the "setContext()" method and creates a context for the current
+// library if it does not exist.
+
+CalloutHandle::ElementCollection&
+CalloutHandle::getContextForLibrary() {
+
+ int libindex = getLibraryIndex();
+
+ // Access a reference to the element collection for the given index,
+ // creating a new element collection if necessary, and return it.
+ return (context_collection_[libindex]);
+}
+
+// The "const" version of the above, used by the "getContext()" method. If
+// the context for the current library doesn't exist, throw a
+// "NoSuchCalloutContext" exception.
+
+const CalloutHandle::ElementCollection&
+CalloutHandle::getContextForLibrary() const {
+
+ int libindex = getLibraryIndex();
+ ContextCollection::const_iterator libcontext =
+ context_collection_.find(libindex);
+ if (libcontext == context_collection_.end()) {
+ isc_throw(NoSuchCalloutContext, "unable to find callout context "
+ "associated with the current library handle");
+ }
+
+ // Return a reference to the context's element collection.
+ return (libcontext->second);
+}
+
+// Return the name of all items in the context associated with the current]
+// library.
+
+vector<string>
+CalloutHandle::getContextNames() const {
+
+ vector<string> names;
+
+ const ElementCollection& elements = getContextForLibrary();
+ for (ElementCollection::const_iterator i = elements.begin();
+ i != elements.end(); ++i) {
names.push_back(i->first);
}
isc::Exception(file, line, what) {}
};
-// Forward declaration of the handle collection class.
+/// @brief No such callout context item
+///
+/// Thrown if an attempt is made to get an item of data from this callout's
+/// context.
+class NoSuchCalloutContext : public Exception {
+public:
+ NoSuchCalloutContext(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+//
+
+// Forward declaration of the library handle and related collection classes.
+class LibraryHandle;
class LibraryHandleCollection;
-/// @brief Per-Packet callout handle
+/// @brief Per-packet callout handle
///
/// An object of this class is associated with every packet (or request)
/// processed by the server. It forms the principle means of passing
/// data between the server and the user-library callouts.
+///
+/// The class allows access to the following information:
+///
+/// - Arguments. When the callouts associated with a hook are called, they
+/// are passed information by the server (and can return information to it)
+/// through name/value pairs. Each of these pairs is an argument and the
+/// information is accessed through the {get,get}Argument() methods.
+///
+/// - Per-packet context. Each packet has a context associated with it, this
+/// context being a per-library context. In other words, As a packet passes
+/// through the callouts associated with a given library, the callouts can
+/// associate and retrieve information with the packet. The per-library
+/// nature of the context means that the callouts within a given library can
+/// pass packet-specific information between one another, they cannot pass
+/// information to callous within another library. Typically such context
+/// is created in the "context_create" callout and destroyed in the
+/// "context_destroy" callout. The information is accessed through the
+/// {get,set}Context() methods.
+///
+/// - Per-library context. Each library has a context associated with it that
+/// is independent of the packet. All callouts in the library share the
+/// information in the context, regardless of the packet being processed.
+/// The context is typically createed in the library's "load()" method and
+/// destroyed in its "unload()" method. The information is available by
+/// obtaining the handle to the library through this class's
+/// getLibraryHandle() method, then calling {get,set}Context() on the
+/// object returned.
class CalloutHandle {
-private:
- /// Typedef to allow abbreviation of iterator specification in methods
- typedef std::map<std::string, boost::any> ArgumentCollection;
-
public:
+ /// Typedef to allow abbreviation of iterator specification in methods.
+ /// The std::string is the argument name and the "boost::any" is the
+ /// corresponding value associated with it.
+ typedef std::map<std::string, boost::any> ElementCollection;
+
+ /// Typedef to allow abbreviations in specifications when accessing
+ /// context. The ElementCollection is the name/value collection for
+ /// a particular context. The "int" corresponds to the index of an
+ /// associated library handle - there is a 1:1 correspondence between
+ /// library handles and a name.value collection.
+ ///
+ /// The collection of contexts is stored in a map, as not every library
+ /// will require creating a context associated with each packet. In
+ /// addition, the structure is more flexible in that the size does not
+ /// need to be set when the CalloutHandle is constructed.
+ typedef std::map<int, ElementCollection> ContextCollection;
+
+
/// @brief Constructor
///
/// @param manager Pointer to the collection of library handles.
CalloutHandle(boost::shared_ptr<LibraryHandleCollection>& collection)
- : arguments_(), collection_(collection), skip_(false)
+ : arguments_(), context_collection_(), library_collection_(collection),
+ skip_(false)
{}
/// @brief Set argument
/// but the type of the element is not that expected
template <typename T>
void getArgument(const std::string& name, T& value) const {
- ArgumentCollection::const_iterator element_ptr = arguments_.find(name);
+ ElementCollection::const_iterator element_ptr = arguments_.find(name);
if (element_ptr == arguments_.end()) {
isc_throw(NoSuchArgument, "unable to find argument with name " <<
name);
/// Deletes an argument of the given name. If an argument of that name
/// does not exist, the method is a no-op.
///
- /// N.B. If the element is a raw pointer, the pointed-to data is
- /// NOT deleted by this.
+ /// N.B. If the element is a raw pointer, the pointed-to data is NOT deleted
+ /// by this method.
///
/// @param name Name of the element in the argument list to set.
void deleteArgument(const std::string& name) {
/// @brief Delete all arguments
///
/// Deletes all arguments associated with this context.
+ ///
+ /// N.B. If any elements are raw pointers, the pointed-to data is NOT
+ /// deleted by this method.
void deleteAllArguments() {
arguments_.clear();
}
/// @brief Set skip flag
///
/// Sets the "skip" variable in the callout handle. This variable is
- /// interrogated by the server to see if the remaining callouts should be
- /// bypassed.
+ /// interrogated by the server to see if the remaining callouts associated
+ /// with the current hook should be bypassed.
///
/// @param skip New value of the "skip" flag.
void setSkip(bool skip) {
return (skip_);
}
+ /// @brief Access current library handle
+ ///
+ /// Returns a reference to the current library handle. This function is
+ /// only available when called by a callout (which in turn is called
+ /// through the "callCallouts" method), as it is only then that the current
+ /// library index is valid. A callout would use this method to get to
+ /// the context associated with the library in which it resides.
+ ///
+ /// @return Reference to the current library handle
+ ///
+ /// @throw InvalidIndex thrown if this method is called outside of the
+ /// "callCallouts() method. (Exception is so-named because the
+ /// index used to access the library handle in the collection
+ /// is not valid at that point.)
+ LibraryHandle& getLibraryHandle() const;
+
+ /// @brief Set context
+ ///
+ /// Sets an element in the context associated with the current library. If
+ /// an element of the name is already present, it is replaced.
+ ///
+ /// @param name Name of the element in the context to set
+ /// @param value Value to set
+ template <typename T>
+ void setContext(const std::string& name, T value) {
+ getContextForLibrary()[name] = value;
+ }
+
+ /// @brief Get context
+ ///
+ /// Gets an element from the context associated with the current library.
+ ///
+ /// @param name Name of the element in the context to get.
+ /// @param value [out] Value to set. The type of "value" is important:
+ /// it must match the type of the value set.
+ ///
+ /// @throw NoSuchCalloutContext Thrown if no context element with the name
+ /// "name" is present.
+ /// @throw boost::bad_any_cast Thrown if the context element is present,
+ /// but the type of the element is not that expected
+ template <typename T>
+ void getContext(const std::string& name, T& value) const {
+ const ElementCollection& lib_context = getContextForLibrary();
+
+ ElementCollection::const_iterator element_ptr = lib_context.find(name);
+ if (element_ptr == lib_context.end()) {
+ isc_throw(NoSuchCalloutContext, "unable to find callout context "
+ "item " << name << " in the context associated with "
+ "current library handle");
+ }
+
+ value = boost::any_cast<T>(element_ptr->second);
+ }
+
+ /// @brief Get context names
+ ///
+ /// Returns a vector holding the names of items in the context associated
+ /// with the current library.
+ ///
+ /// @return Vector of strings reflecting argument names
+ std::vector<std::string> getContextNames() const;
+
+ /// @brief Delete context element
+ ///
+ /// Deletes an item of the given name from the context associated with the
+ /// current library. If an item of that name does not exist, the method is
+ /// a no-op.
+ ///
+ /// N.B. If the element is a raw pointer, the pointed-to data is NOT deleted
+ /// by this.
+ ///
+ /// @param name Name of the element in the argument list to set.
+ void deleteContext(const std::string& name) {
+ static_cast<void>(getContextForLibrary().erase(name));
+ }
+
+ /// @brief Delete all context items
+ ///
+ /// Deletes all items from the context associated with the current library.
+ ///
+ /// N.B. If any elements are raw pointers, the pointed-to data is NOT
+ /// deleted by this.
+ void deleteAllContext() {
+ getContextForLibrary().clear();
+ }
+
private:
+ /// @brief Check index
+ ///
+ /// Gets the current library index, throwing an exception if it is not set
+ /// or is invalid for the current library collection.
+ ///
+ /// @return Current library index, valid for this library collection.
+ int getLibraryIndex() const;
+
+ /// @brief Return reference to context for current library
+ ///
+ /// Called by all context-accessing functions, this checks if the current
+ /// library index is valid (throwing an exception if not). If it is, it
+ /// returns a reference to the appropriate context, creating one if it does
+ /// not exist. (The last task allows the list of library handles to grow
+ /// dynamically - although only if handles are appended to the end of the
+ /// library handle collection.)
+ ///
+ /// @return Reference to the collection of name/value pairs associated
+ /// with the current library.
+ ElementCollection& getContextForLibrary();
+
+ /// @brief Return reference to context for current library (const version)
+ ///
+ /// Called by all context-accessing functions, this checks if the current
+ /// library index is valid (throwing an exception if not). If it is, it
+ /// returns a reference to the appropriate context, creating one if it does
+ /// not exist. (The last task allows the list of library handles to grow
+ /// dynamically - although only if handles are appended to the end of the
+ /// library handle collection.)
+ ///
+ /// @return Reference to the collection of name/value pairs associated
+ /// with the current library.
+ ///
+ /// @throw NoSuchCalloutContext Thrown if there is no ElementCollection
+ /// associated with the current library.
+ const ElementCollection& getContextForLibrary() const;
+
+ // Member variables
+
/// Collection of arguments passed to the callouts
- ArgumentCollection arguments_;
+ ElementCollection arguments_;
+
+ /// Context collection - there is one entry per library context.
+ ContextCollection context_collection_;
/// Library handle collection, used to obtain the correct library handle
/// during a call to a callout.
- boost::shared_ptr<LibraryHandleCollection> collection_;
+ boost::shared_ptr<LibraryHandleCollection> library_collection_;
/// "Skip" flag, indicating if the caller should bypass remaining callouts.
bool skip_;
// LibraryHandleCollection methods.
+// Return pointer to the current library handle.
+
+boost::shared_ptr<LibraryHandle>
+LibraryHandleCollection::getLibraryHandle() const {
+ if ((curidx_ < 0) || (curidx_ >= handles_.size())) {
+ isc_throw(InvalidIndex, "current library handle index of (" <<
+ curidx_ << ") is not valid for the library handle vector "
+ "of size " << handles_.size());
+ }
+
+ return (handles_[curidx_]);
+}
+
// Check if a callout is present on a hook in any of the libraries.
bool
isc::Exception(file, line, what) {}
};
+/// @brief Invalid index
+///
+/// Thrown if an attempt is made to obtain a library handle but the current
+/// library handle index is invalid. This will occur if the method
+/// LibraryHandleCollection::getLibraryHandle() is called outside of a callout.
+
+class InvalidIndex : public Exception {
+public:
+ InvalidIndex(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
// Forward declaration for CalloutHandle
class CalloutHandle;
}
/// @brief Get context
+
///
/// Sets an element in the library context. If the name does not exist,
/// a "NoSuchContext" exception is thrown.
void getContext(const std::string& name, T& value) const {
ContextCollection::const_iterator element_ptr = context_.find(name);
if (element_ptr == context_.end()) {
- isc_throw(NoSuchContext, "unable to find library context datum " <<
+ isc_throw(NoSuchContext, "unable to find library context item " <<
name << " in library handle");
}
/// @throw NoSuchHook Thrown if the hook name is unrecognised.
void deregisterAll(const std::string& name);
-
/// @brief Checks if callouts are present
///
/// @param index Hook index for which callouts are checked.
handles_.push_back(handle);
}
+ /// @brief Return current library index
+ ///
+ /// Returns the value of the "current library index". Although a callout
+ /// callout can retrieve this information, it is of limited use: the
+ /// value is intended for use by the CalloutHandle object in order to
+ /// access the per-library context.
+ ///
+ /// @return Current library index value
+ int getLibraryIndex() const {
+ return (curidx_);
+ }
+
/// @brief Get current library handle
///
/// Returns a pointer to the current library handle. This method can
///
/// @return Pointer to current library handle. This is the handle for
/// which a callout is being called.
- boost::shared_ptr<LibraryHandle> getLibraryHandle() const {
- return (boost::shared_ptr<LibraryHandle>());
- /// @todo Return the appropriate handle
- }
+ boost::shared_ptr<LibraryHandle> getLibraryHandle() const;
/// @brief Checks if callouts are present
///
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 += io_utilities_unittest.cc
run_unittests_SOURCES += library_handle_unittest.cc
run_unittests_SOURCES += library_handle_collection_unittest.cc
--- /dev/null
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <util/hooks/callout_handle.h>
+#include <util/hooks/library_handle.h>
+#include <util/hooks/server_hooks.h>
+
+#include <boost/lexical_cast.hpp>
+#include <gtest/gtest.h>
+
+#include <string>
+
+/// @file
+/// CalloutHandle/LibraryCalloutHandle interaction tests
+///
+/// This file holds unit tests checking the interaction between the
+/// CalloutHandle and LibraryCalloutHandle[Collection] classes. In particular,
+/// they check that:
+///
+/// - A CalloutHandle's context is shared between callouts from the same
+/// library, but is separate for each library.
+/// - The LibraryHandle retrieved by the CalloutHandle is the same for each
+/// callout in the library, but different for different libraries.
+///
+/// Some minor interactions between the two classes were checked in the unit
+/// tests for each class (mainly the use of the "skip" flag).
+
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+// This test checks the many-faced nature of the context for both the
+// CalloutContext and the LibraryContext.
+//
+// Three libraries are assumed, and each supplies four callouts. All callouts
+// manipulate four context elements - two in the CalloutHandle and two in the
+// LibraryHandle, the elements being called "string" and "uint64_t" (which
+// describe the type of data manipulated).
+//
+// As with other tests, each callout shifts data to the left and inserts its
+// own data. Data is a string of the form "nmwc", where "n" is the number of
+// the library, "m" is the callout number and "w" is an indication of what
+// is being altered (library context ["x"] or callout context ["c"]) and "y" is
+// the indication of what callout was passed as an argument ("x" or "b"). ("x"
+// is used instead of "l" to indicate that library context is being altered,
+// since in the results, these single characters will be mixed with digits and
+// "l" looks too much like "1".) Hence we have:
+//
+// - "xa" if library context is being altered from a callout made with the
+// first callout handle passed as argument.
+// - "xb" if library context is being altered from a callout made with the
+// second callout handle passed as argument.
+// - "ca" if the first callout handle's context is being manipulated.
+// - "cb" if the second callout handle's context is being manipulated.
+//
+//
+// For simplicity, and to cut down the number of functions actually written,
+// the ollout indicator ("a" or "b") ) 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.
+//
+// Finally, the fourth callout copies the data into variables accessible
+// to the test program.
+
+// Values to be inspected at the end of the test.
+
+// Values set in the LibraryHandle context. There are three libraries, so
+// there are three sets of library context. To avoid a static initialization
+// fiasco, encapsulate these in a function.
+
+std::string& resultLibrary(int i) {
+ static std::string result_library[3];
+ return (result_library[i]);
+}
+
+// Values set in the CalloutHandle context. There are three libraries, so
+// there are three contexts for the callout, one for each library.
+
+std::string& resultCallout(int i) {
+ static std::string result_callout[3];
+ return (result_callout[i]);
+}
+
+// A simple function to zero the results.
+
+static void zero_results() {
+ for (int i = 0; i < 3; ++i) {
+ resultLibrary(i) = "";
+ resultCallout(i) = "";
+ }
+}
+
+
+// Library callouts.
+
+// Common code for setting the callout and library context values.
+
+int
+execute(CalloutHandle& callout_handle, int library_num, int callout_num) {
+
+ // Obtain the callout handle indicator.
+ string indicator;
+ callout_handle.getArgument("string", indicator);
+
+ // Create the basic data to be appended to the context value.
+ string data = boost::lexical_cast<string>(10 * library_num + callout_num);
+
+ // Get the library context data. As the context will not exist on the
+ // first call, catch the exception and create it. (In real life, the context
+ // should have been created by the libraries' "load()" function.)
+ string string_value = "";
+ try {
+ callout_handle.getLibraryHandle().getContext("string", string_value);
+ } catch (const NoSuchContext&) {
+ string_value = "";
+ }
+
+ // Update the context value with the library/callout indication (and the
+ // suffix "l" to denote library) and set it.
+ string_value += (data + string("x") + indicator);
+ callout_handle.getLibraryHandle().setContext("string", string_value);
+
+ // Get the context data. As before, this will not exist for the first
+ // callout called. (In real life, the library should create it when the
+ // "context_create" hook gets called before any packet processing takes
+ // place.)
+ try {
+ callout_handle.getContext("string", string_value);
+ } catch (const NoSuchCalloutContext&) {
+ string_value = "";
+ }
+
+ // Update the value and set it.
+ string_value += (data + string("c") + indicator);
+ callout_handle.setContext("string", string_value);
+
+ return (0);
+}
+
+// The following functions are the actual callouts - ther name is of the
+// form "callout_<library number>_<callout number>"
+
+int
+callout_1_1(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 1, 1));
+}
+
+int
+callout_1_2(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 1, 2));
+}
+
+int
+callout_1_3(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 1, 3));
+}
+
+int
+callout_2_1(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 2, 1));
+}
+
+int
+callout_2_2(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 2, 2));
+}
+
+int
+callout_2_3(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 2, 3));
+}
+
+int
+callout_3_1(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 3, 1));
+}
+
+int
+callout_3_2(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 3, 2));
+}
+
+int
+callout_3_3(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 3, 3));
+}
+
+// Common callout code for the fourth hook (which makes the data available for
+// checking). It copies the library and callout context data to the global
+// variables.
+
+int print_execute(CalloutHandle& callout_handle, int library_num) {
+
+ // Print per-library context values.
+ string result;
+ callout_handle.getLibraryHandle()
+ .getContext("string", resultLibrary(library_num - 1));
+
+
+ // Print callout context.
+ callout_handle.getContext("string", resultCallout(library_num - 1));
+
+ return (0);
+}
+
+// These are the actual callouts.
+
+int
+print_1(CalloutHandle& callout_handle) {
+ return (print_execute(callout_handle, 1));
+}
+
+int
+print_2(CalloutHandle& callout_handle) {
+ return (print_execute(callout_handle, 2));
+}
+
+int
+print_3(CalloutHandle& callout_handle) {
+ return (print_execute(callout_handle, 3));
+}
+
+// The test begin here
+
+TEST(HandlesTest, ContextCheck) {
+ // Create the LibraryHandleCollection with a set of four callouts
+ // (the test does not use the ContextCreate and ContextDestroy 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", callout_1_1);
+ handle->registerCallout("two", callout_1_2);
+ handle->registerCallout("three", callout_1_3);
+ handle->registerCallout("four", print_1);
+
+ collection->addLibraryHandle(handle);
+
+
+ handle.reset(new LibraryHandle(server_hooks));
+ handle->registerCallout("one", callout_2_1);
+ handle->registerCallout("two", callout_2_2);
+ handle->registerCallout("three", callout_2_3);
+ handle->registerCallout("four", print_2);
+ collection->addLibraryHandle(handle);
+
+ handle.reset(new LibraryHandle(server_hooks));
+ handle->registerCallout("one", callout_3_1);
+ handle->registerCallout("two", callout_3_2);
+ handle->registerCallout("three", callout_3_3);
+ handle->registerCallout("four", print_3);
+ collection->addLibraryHandle(handle);
+
+ // Create the callout handles and distinguish them by setting the "long"
+ // argument.
+ CalloutHandle callout_handle_a(collection);
+ callout_handle_a.setArgument("string", string("a"));
+
+ CalloutHandle callout_handle_b(collection);
+ callout_handle_b.setArgument("string", string("b"));
+
+ // 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_a);
+ collection->callCallouts(one_index, callout_handle_b);
+ collection->callCallouts(two_index, callout_handle_a);
+ collection->callCallouts(two_index, callout_handle_b);
+ collection->callCallouts(three_index, callout_handle_a);
+ collection->callCallouts(three_index, callout_handle_b);
+
+ // 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_a);
+
+ // To explain the expected library context results:
+ //
+ // The first callCallouts() call above calls the callouts for hook "one"
+ // with callout handle "a". This calls the callouts attached to hook "one"
+ // from library 1, then those attached to hook "one" from library 2, then
+ // from library 3. The callout in library 1 appends "11xa" to the first
+ // library's context. The callout in library 2 appends "21xa" to that
+ // library's context. Finally, the third library's context gets "31xa"
+ // appended to it.
+ //
+ // The next callCallouts() call repeats the calls to the callouts attached
+ // to hook "one", which result in "11xb", "21xb", "31xb" being appended to
+ // the context of libraries 1, 2, and 3 respectively.
+ //
+ // The process is then repeated for hooks "two" and "three", leading to
+ // the expected context values listed below.
+
+ EXPECT_EQ("11xa11xb12xa12xb13xa13xb", resultLibrary(0));
+ EXPECT_EQ("21xa21xb22xa22xb23xa23xb", resultLibrary(1));
+ EXPECT_EQ("31xa31xb32xa32xb33xa33xb", resultLibrary(2));
+
+ // To explain the expected callout context results.
+ //
+ // The callout handle maintains a separate context for each library. When
+ // the first call to callCallouts() is made, "11ca" gets appended to
+ // the context for library 1 maintained by by the callout handle, "21ca"
+ // gets appended to the context maintained for library 2, and "31ca" to
+ // the context maintained for library 3.
+ //
+ // The next call to callCallouts() calls the same callouts but for a
+ // different callout handle. It also maintains three contexts (one for
+ // each library) and they will get "11cb", "21cb", "31cb" appended to
+ // them. These don't affect the contexts maintained by callout handle a.
+ //
+ // The process is then repeated for hooks "two" and "three", which append
+ // "12ca", "22ca" and "32ca" for hook "two" and "31ca", "32ca" and "33ca"
+ // for hook "three".
+ //
+ // At this point, we have only called the "print" function for callout
+ // handle "a", so the following results are checking the context values
+ // maintained in that callout handle.
+
+ EXPECT_EQ("11ca12ca13ca", resultCallout(0));
+ EXPECT_EQ("21ca22ca23ca", resultCallout(1));
+ EXPECT_EQ("31ca32ca33ca", resultCallout(2));
+
+ // Repeat the checks for callout b. The library handle context values
+ // should not change, but the context maintained by the callout handle
+ // should.
+
+ zero_results();
+ collection->callCallouts(four_index, callout_handle_b);
+
+ EXPECT_EQ("11xa11xb12xa12xb13xa13xb", resultLibrary(0));
+ EXPECT_EQ("21xa21xb22xa22xb23xa23xb", resultLibrary(1));
+ EXPECT_EQ("31xa31xb32xa32xb33xa33xb", resultLibrary(2));
+
+ EXPECT_EQ("11cb12cb13cb", resultCallout(0));
+ EXPECT_EQ("21cb22cb23cb", resultCallout(1));
+ EXPECT_EQ("31cb32cb33cb", resultCallout(2));
+}
+
+} // Anonymous namespace
+