From: Stephen Morris Date: Wed, 29 May 2013 17:26:34 +0000 (+0100) Subject: [2974] Added per-library packet context. X-Git-Tag: bind10-1.2.0beta1-release~402^2~16 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=e686a5339d6927d4ef7bf35a8e5d9c2ddfa98149;p=thirdparty%2Fkea.git [2974] Added per-library packet context. --- diff --git a/src/lib/util/hooks/callout_handle.cc b/src/lib/util/hooks/callout_handle.cc index 57d1b6a876..60b86d38b4 100644 --- a/src/lib/util/hooks/callout_handle.cc +++ b/src/lib/util/hooks/callout_handle.cc @@ -13,10 +13,10 @@ // PERFORMANCE OF THIS SOFTWARE. #include +#include -#include -#include #include +#include #include using namespace std; @@ -25,14 +25,91 @@ using namespace isc::util; namespace isc { namespace util { -// return the name of all context items. +// Return the name of all argument items. vector CalloutHandle::getArgumentNames() const { vector 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 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 +CalloutHandle::getContextNames() const { + + vector names; + + const ElementCollection& elements = getContextForLibrary(); + for (ElementCollection::const_iterator i = elements.begin(); + i != elements.end(); ++i) { names.push_back(i->first); } diff --git a/src/lib/util/hooks/callout_handle.h b/src/lib/util/hooks/callout_handle.h index aea5ad89e5..59df050cc5 100644 --- a/src/lib/util/hooks/callout_handle.h +++ b/src/lib/util/hooks/callout_handle.h @@ -36,27 +36,82 @@ public: 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 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 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 ContextCollection; + + /// @brief Constructor /// /// @param manager Pointer to the collection of library handles. CalloutHandle(boost::shared_ptr& collection) - : arguments_(), collection_(collection), skip_(false) + : arguments_(), context_collection_(), library_collection_(collection), + skip_(false) {} /// @brief Set argument @@ -85,7 +140,7 @@ public: /// but the type of the element is not that expected template 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); @@ -107,8 +162,8 @@ public: /// 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) { @@ -118,6 +173,9 @@ public: /// @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(); } @@ -125,8 +183,8 @@ public: /// @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) { @@ -142,14 +200,142 @@ public: 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 + 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 + 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(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 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(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 collection_; + boost::shared_ptr library_collection_; /// "Skip" flag, indicating if the caller should bypass remaining callouts. bool skip_; diff --git a/src/lib/util/hooks/library_handle.cc b/src/lib/util/hooks/library_handle.cc index 261d0f41be..6947a6f8ba 100644 --- a/src/lib/util/hooks/library_handle.cc +++ b/src/lib/util/hooks/library_handle.cc @@ -153,6 +153,19 @@ LibraryHandle::getContextNames() const { // LibraryHandleCollection methods. +// Return pointer to the current library handle. + +boost::shared_ptr +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 diff --git a/src/lib/util/hooks/library_handle.h b/src/lib/util/hooks/library_handle.h index acca0d4824..22091ad4ad 100644 --- a/src/lib/util/hooks/library_handle.h +++ b/src/lib/util/hooks/library_handle.h @@ -47,6 +47,18 @@ public: 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; @@ -97,6 +109,7 @@ public: } /// @brief Get context + /// /// Sets an element in the library context. If the name does not exist, /// a "NoSuchContext" exception is thrown. @@ -113,7 +126,7 @@ public: 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"); } @@ -187,7 +200,6 @@ public: /// @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. @@ -285,6 +297,18 @@ public: 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 @@ -294,10 +318,7 @@ public: /// /// @return Pointer to current library handle. This is the handle for /// which a callout is being called. - boost::shared_ptr getLibraryHandle() const { - return (boost::shared_ptr()); - /// @todo Return the appropriate handle - } + boost::shared_ptr getLibraryHandle() const; /// @brief Checks if callouts are present /// diff --git a/src/lib/util/tests/Makefile.am b/src/lib/util/tests/Makefile.am index 44dcb872bb..c968e566aa 100644 --- a/src/lib/util/tests/Makefile.am +++ b/src/lib/util/tests/Makefile.am @@ -30,6 +30,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 += io_utilities_unittest.cc run_unittests_SOURCES += library_handle_unittest.cc run_unittests_SOURCES += library_handle_collection_unittest.cc diff --git a/src/lib/util/tests/handles_unittest.cc b/src/lib/util/tests/handles_unittest.cc new file mode 100644 index 0000000000..d27c9df96c --- /dev/null +++ b/src/lib/util/tests/handles_unittest.cc @@ -0,0 +1,364 @@ +// 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 +#include +#include + +#include +#include + +#include + +/// @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(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__" + +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 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 + collection(new LibraryHandleCollection()); + + boost::shared_ptr 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 +