diff --git a/include/class_loader/class_loader.hpp b/include/class_loader/class_loader.hpp index 7615b687..de79c633 100644 --- a/include/class_loader/class_loader.hpp +++ b/include/class_loader/class_loader.hpp @@ -121,7 +121,20 @@ class ClassLoader * if the library is not yet loaded (which typically happens when in "On Demand Load/Unload" mode). * * @param derived_class_name The name of the class we want to create (@see getAvailableClasses()) - * @return A boost::shared_ptr to newly created plugin object + * @return A std::shared_ptr to newly created plugin object + */ + template + std::shared_ptr createSharedInstance(const std::string & derived_class_name) + { + return std::shared_ptr( + createRawInstance(derived_class_name, true), + boost::bind(&ClassLoader::onPluginDeletion, this, _1)); + } + + /** + * @brief Generates an instance of loadable classes (i.e. class_loader). + * + * Same as createSharedInstance() except it returns a boost::shared_ptr. */ template boost::shared_ptr createInstance(const std::string & derived_class_name) diff --git a/include/class_loader/multi_library_class_loader.hpp b/include/class_loader/multi_library_class_loader.hpp index e07254c3..41bf2337 100644 --- a/include/class_loader/multi_library_class_loader.hpp +++ b/include/class_loader/multi_library_class_loader.hpp @@ -72,10 +72,10 @@ class CLASS_LOADER_PUBLIC MultiLibraryClassLoader * This version does not look in a specific library for the factory, but rather the first open library that defines the classs * @param Base - polymorphic type indicating base class * @param class_name - the name of the concrete plugin class we want to instantiate - * @return A boost::shared_ptr to newly created plugin + * @return A std::shared_ptr to newly created plugin */ template - boost::shared_ptr createInstance(const std::string & class_name) + std::shared_ptr createSharedInstance(const std::string & class_name) { CONSOLE_BRIDGE_logDebug( "class_loader::MultiLibraryClassLoader: " @@ -90,7 +90,7 @@ class CLASS_LOADER_PUBLIC MultiLibraryClassLoader "was explicitly loaded through MultiLibraryClassLoader::loadLibrary()"); } - return loader->createInstance(class_name); + return loader->createSharedInstance(class_name); } /** @@ -99,7 +99,48 @@ class CLASS_LOADER_PUBLIC MultiLibraryClassLoader * @param Base - polymorphic type indicating base class * @param class_name - the name of the concrete plugin class we want to instantiate * @param library_path - the library from which we want to create the plugin - * @return A boost::shared_ptr to newly created plugin + * @return A std::shared_ptr to newly created plugin + */ + template + std::shared_ptr + createSharedInstance(const std::string & class_name, const std::string & library_path) + { + ClassLoader * loader = getClassLoaderForLibrary(library_path); + if (nullptr == loader) { + throw class_loader::NoClassLoaderExistsException( + "Could not create instance as there is no ClassLoader in " + "MultiLibraryClassLoader bound to library " + library_path + + " Ensure you called MultiLibraryClassLoader::loadLibrary()"); + } + return loader->createSharedInstance(class_name); + } + + /** + * @brief Creates an instance of an object of given class name with ancestor class Base + * Same as createSharedInstance() except it returns a boost::shared_ptr. + */ + template + boost::shared_ptr createInstance(const std::string & class_name) + { + CONSOLE_BRIDGE_logDebug( + "class_loader::MultiLibraryClassLoader: " + "Attempting to create instance of class type %s.", + class_name.c_str()); + ClassLoader * loader = getClassLoaderForClass(class_name); + if (nullptr == loader) { + throw class_loader::CreateClassException( + "MultiLibraryClassLoader: Could not create object of class type " + + class_name + + " as no factory exists for it. Make sure that the library exists and " + "was explicitly loaded through MultiLibraryClassLoader::loadLibrary()"); + } + + return loader->createInstance(class_name); + } + + /** + * @brief Creates an instance of an object of given class name with ancestor class Base + * Same as createSharedInstance() except it returns a boost::shared_ptr. */ template boost::shared_ptr @@ -117,10 +158,7 @@ class CLASS_LOADER_PUBLIC MultiLibraryClassLoader /** * @brief Creates an instance of an object of given class name with ancestor class Base - * This version does not look in a specific library for the factory, but rather the first open library that defines the classs - * @param Base - polymorphic type indicating base class - * @param class_name - the name of the concrete plugin class we want to instantiate - * @return A unique pointer to newly created plugin + * Same as createSharedInstance() except it returns a std::unique_ptr. */ template ClassLoader::UniquePtr createUniqueInstance(const std::string & class_name) @@ -141,11 +179,7 @@ class CLASS_LOADER_PUBLIC MultiLibraryClassLoader /** * @brief Creates an instance of an object of given class name with ancestor class Base - * This version takes a specific library to make explicit the factory being used - * @param Base - polymorphic type indicating base class - * @param class_name - the name of the concrete plugin class we want to instantiate - * @param library_path - the library from which we want to create the plugin - * @return A unique pointer to newly created plugin + * Same as createSharedInstance() except it returns a std::unique_ptr. */ template ClassLoader::UniquePtr diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 46e1b5f1..15bbb334 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -18,6 +18,12 @@ if(TARGET ${PROJECT_NAME}_utest) add_dependencies(${PROJECT_NAME}_utest ${PROJECT_NAME}_TestPlugins1 ${PROJECT_NAME}_TestPlugins2) endif() +catkin_add_gtest(${PROJECT_NAME}_shared_ptr_test shared_ptr_test.cpp) +if(TARGET ${PROJECT_NAME}_shared_ptr_test) + target_link_libraries(${PROJECT_NAME}_shared_ptr_test ${Boost_LIBRARIES} ${class_loader_LIBRARIES}) + add_dependencies(${PROJECT_NAME}_shared_ptr_test ${PROJECT_NAME}_TestPlugins1 ${PROJECT_NAME}_TestPlugins2) +endif() + catkin_add_gtest(${PROJECT_NAME}_unique_ptr_test unique_ptr_test.cpp) if(TARGET ${PROJECT_NAME}_unique_ptr_test) target_link_libraries(${PROJECT_NAME}_unique_ptr_test ${Boost_LIBRARIES} ${class_loader_LIBRARIES}) diff --git a/test/shared_ptr_test.cpp b/test/shared_ptr_test.cpp new file mode 100644 index 00000000..cb398f49 --- /dev/null +++ b/test/shared_ptr_test.cpp @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2012, Willow Garage, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Willow Garage, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "class_loader/class_loader.hpp" +#include "class_loader/multi_library_class_loader.hpp" + +#include "gtest/gtest.h" + +#include "./base.hpp" + +const std::string LIBRARY_1 = class_loader::systemLibraryFormat("class_loader_TestPlugins1"); // NOLINT +const std::string LIBRARY_2 = class_loader::systemLibraryFormat("class_loader_TestPlugins2"); // NOLINT + +TEST(ClassLoaderSharedPtrTest, basicLoad) { + try { + class_loader::ClassLoader loader1(LIBRARY_1, false); + loader1.createSharedInstance("Cat")->saySomething(); // See if lazy load works + } catch (class_loader::ClassLoaderException & e) { + FAIL() << "ClassLoaderException: " << e.what() << "\n"; + } + + SUCCEED(); +} + +TEST(ClassLoaderSharedPtrTest, correctNonLazyLoadUnload) { + try { + ASSERT_FALSE(class_loader::impl::isLibraryLoadedByAnybody(LIBRARY_1)); + class_loader::ClassLoader loader1(LIBRARY_1, false); + ASSERT_TRUE(class_loader::impl::isLibraryLoadedByAnybody(LIBRARY_1)); + ASSERT_TRUE(loader1.isLibraryLoaded()); + loader1.unloadLibrary(); + ASSERT_FALSE(class_loader::impl::isLibraryLoadedByAnybody(LIBRARY_1)); + ASSERT_FALSE(loader1.isLibraryLoaded()); + return; + } catch (class_loader::ClassLoaderException & e) { + FAIL() << "ClassLoaderException: " << e.what() << "\n"; + } catch (...) { + FAIL() << "Unhandled exception"; + } +} + +TEST(ClassLoaderSharedPtrTest, correctLazyLoadUnload) { + try { + ASSERT_FALSE(class_loader::impl::isLibraryLoadedByAnybody(LIBRARY_1)); + class_loader::ClassLoader loader1(LIBRARY_1, true); + ASSERT_FALSE(class_loader::impl::isLibraryLoadedByAnybody(LIBRARY_1)); + ASSERT_FALSE(loader1.isLibraryLoaded()); + + { + std::shared_ptr obj = loader1.createSharedInstance("Cat"); + ASSERT_TRUE(class_loader::impl::isLibraryLoadedByAnybody(LIBRARY_1)); + ASSERT_TRUE(loader1.isLibraryLoaded()); + } + + // The library will unload automatically when the only plugin object left is destroyed + ASSERT_FALSE(class_loader::impl::isLibraryLoadedByAnybody(LIBRARY_1)); + return; + } catch (class_loader::ClassLoaderException & e) { + FAIL() << "ClassLoaderException: " << e.what() << "\n"; + } catch (...) { + FAIL() << "Unhandled exception"; + } +} + +TEST(ClassLoaderSharedPtrTest, nonExistentPlugin) { + class_loader::ClassLoader loader1(LIBRARY_1, false); + + try { + std::shared_ptr obj = loader1.createSharedInstance("Bear"); + if (nullptr == obj) { + FAIL() << "Null object being returned instead of exception thrown."; + } + + obj->saySomething(); + } catch (const class_loader::CreateClassException &) { + SUCCEED(); + return; + } catch (...) { + FAIL() << "Unknown exception caught.\n"; + } + + FAIL() << "Did not throw exception as expected.\n"; +} + +TEST(ClassLoaderSharedPtrTest, nonExistentLibrary) { + try { + class_loader::ClassLoader loader1("libDoesNotExist.so", false); + } catch (const class_loader::LibraryLoadException &) { + SUCCEED(); + return; + } catch (...) { + FAIL() << "Unknown exception caught.\n"; + } + + FAIL() << "Did not throw exception as expected.\n"; +} + +class InvalidBase +{ +}; + +TEST(ClassLoaderSharedPtrTest, invalidBase) { + try { + class_loader::ClassLoader loader1(LIBRARY_1, false); + if (loader1.isClassAvailable("Cat")) { + FAIL() << "Cat should not be available for InvalidBase"; + } else if (loader1.isClassAvailable("Cat")) { + SUCCEED(); + return; + } else { + FAIL() << "Class not available for correct base class."; + } + } catch (const class_loader::LibraryLoadException &) { + FAIL() << "Unexpected exception"; + } catch (...) { + FAIL() << "Unexpected and unknown exception caught.\n"; + } +} + +void wait(int seconds) +{ + std::this_thread::sleep_for(std::chrono::seconds(seconds)); +} + +void run(class_loader::ClassLoader * loader) +{ + std::vector classes = loader->getAvailableClasses(); + for (auto & class_ : classes) { + loader->createSharedInstance(class_)->saySomething(); + } +} + +TEST(ClassLoaderSharedPtrTest, threadSafety) { + class_loader::ClassLoader loader1(LIBRARY_1); + ASSERT_TRUE(loader1.isLibraryLoaded()); + + // Note: Hard to test thread safety to make sure memory isn't corrupted. + // The hope is this test is hard enough that once in a while it'll segfault + // or something if there's some implementation error. + try { + std::vector client_threads; + + for (size_t c = 0; c < 1000; c++) { + client_threads.push_back(new std::thread(std::bind(&run, &loader1))); + } + + for (auto & client_thread : client_threads) { + client_thread->join(); + } + + for (auto & client_thread : client_threads) { + delete (client_thread); + } + + loader1.unloadLibrary(); + ASSERT_FALSE(loader1.isLibraryLoaded()); + } catch (const class_loader::ClassLoaderException &) { + FAIL() << "Unexpected ClassLoaderException."; + } catch (...) { + FAIL() << "Unknown exception."; + } +} + +TEST(ClassLoaderSharedPtrTest, loadRefCountingNonLazy) { + try { + class_loader::ClassLoader loader1(LIBRARY_1, false); + ASSERT_TRUE(loader1.isLibraryLoaded()); + + loader1.loadLibrary(); + loader1.loadLibrary(); + ASSERT_TRUE(loader1.isLibraryLoaded()); + + loader1.unloadLibrary(); + ASSERT_TRUE(loader1.isLibraryLoaded()); + + loader1.unloadLibrary(); + ASSERT_TRUE(loader1.isLibraryLoaded()); + + loader1.unloadLibrary(); + ASSERT_FALSE(loader1.isLibraryLoaded()); + + loader1.unloadLibrary(); + ASSERT_FALSE(loader1.isLibraryLoaded()); + + loader1.loadLibrary(); + ASSERT_TRUE(loader1.isLibraryLoaded()); + + return; + } catch (const class_loader::ClassLoaderException &) { + FAIL() << "Unexpected exception.\n"; + } catch (...) { + FAIL() << "Unknown exception caught.\n"; + } + + FAIL() << "Did not throw exception as expected.\n"; +} + +TEST(ClassLoaderSharedPtrTest, loadRefCountingLazy) { + try { + class_loader::ClassLoader loader1(LIBRARY_1, true); + ASSERT_FALSE(loader1.isLibraryLoaded()); + + { + std::shared_ptr obj = loader1.createSharedInstance("Dog"); + ASSERT_TRUE(loader1.isLibraryLoaded()); + } + + ASSERT_FALSE(loader1.isLibraryLoaded()); + + loader1.loadLibrary(); + ASSERT_TRUE(loader1.isLibraryLoaded()); + + loader1.loadLibrary(); + ASSERT_TRUE(loader1.isLibraryLoaded()); + + loader1.unloadLibrary(); + ASSERT_TRUE(loader1.isLibraryLoaded()); + + loader1.unloadLibrary(); + ASSERT_FALSE(loader1.isLibraryLoaded()); + + loader1.unloadLibrary(); + ASSERT_FALSE(loader1.isLibraryLoaded()); + + loader1.loadLibrary(); + ASSERT_TRUE(loader1.isLibraryLoaded()); + + return; + } catch (const class_loader::ClassLoaderException &) { + FAIL() << "Unexpected exception.\n"; + } catch (...) { + FAIL() << "Unknown exception caught.\n"; + } + + FAIL() << "Did not throw exception as expected.\n"; +} + +void testMultiClassLoader(bool lazy) +{ + try { + class_loader::MultiLibraryClassLoader loader(lazy); + loader.loadLibrary(LIBRARY_1); + loader.loadLibrary(LIBRARY_2); + for (int i = 0; i < 2; ++i) { + loader.createSharedInstance("Cat")->saySomething(); + loader.createSharedInstance("Dog")->saySomething(); + loader.createSharedInstance("Robot")->saySomething(); + } + } catch (class_loader::ClassLoaderException & e) { + FAIL() << "ClassLoaderException: " << e.what() << "\n"; + } + + SUCCEED(); +} + +TEST(MultiClassLoaderTest, lazyLoad) { + testMultiClassLoader(true); +} +TEST(MultiClassLoaderTest, lazyLoadSecondTime) { + testMultiClassLoader(true); +} +TEST(MultiClassLoaderTest, nonLazyLoad) { + testMultiClassLoader(false); +} +TEST(MultiClassLoaderTest, noWarningOnLazyLoad) { + try { + std::shared_ptr cat, dog, rob; + { + class_loader::MultiLibraryClassLoader loader(true); + loader.loadLibrary(LIBRARY_1); + loader.loadLibrary(LIBRARY_2); + + cat = loader.createSharedInstance("Cat"); + dog = loader.createSharedInstance("Dog"); + rob = loader.createSharedInstance("Robot"); + } + cat->saySomething(); + dog->saySomething(); + rob->saySomething(); + } catch (class_loader::ClassLoaderException & e) { + FAIL() << "ClassLoaderException: " << e.what() << "\n"; + } + + SUCCEED(); +} + +// Run all the tests that were declared with TEST() +int main(int argc, char ** argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/test/unique_ptr_test.cpp b/test/unique_ptr_test.cpp index 2d03b3a8..e0fccf95 100644 --- a/test/unique_ptr_test.cpp +++ b/test/unique_ptr_test.cpp @@ -32,12 +32,13 @@ #include #include -#include +#include #include #include #include #include +#include #include #include "./base.hpp" @@ -102,7 +103,7 @@ TEST(ClassLoaderUniquePtrTest, nonExistentPlugin) { void wait(int seconds) { - boost::this_thread::sleep(boost::posix_time::seconds(seconds)); + std::this_thread::sleep_for(std::chrono::seconds(seconds)); } void run(ClassLoader * loader) @@ -121,7 +122,7 @@ TEST(ClassLoaderUniquePtrTest, threadSafety) { // The hope is this test is hard enough that once in a while it'll segfault // or something if there's some implementation error. try { - std::vector client_threads; + std::vector client_threads; for (size_t c = 0; c < 1000; c++) { client_threads.emplace_back(std::bind(&run, &loader1)); diff --git a/test/utest.cpp b/test/utest.cpp index e41a0708..6bf9b23b 100644 --- a/test/utest.cpp +++ b/test/utest.cpp @@ -27,11 +27,12 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include -#include +#include #include +#include #include #include +#include #include #include "class_loader/class_loader.hpp" @@ -152,7 +153,7 @@ TEST(ClassLoaderTest, invalidBase) { void wait(int seconds) { - boost::this_thread::sleep(boost::posix_time::seconds(seconds)); + std::this_thread::sleep_for(std::chrono::seconds(seconds)); } void run(class_loader::ClassLoader * loader) @@ -171,10 +172,10 @@ TEST(ClassLoaderTest, threadSafety) { // The hope is this test is hard enough that once in a while it'll segfault // or something if there's some implementation error. try { - std::vector client_threads; + std::vector client_threads; for (size_t c = 0; c < 1000; c++) { - client_threads.push_back(new boost::thread(boost::bind(&run, &loader1))); + client_threads.push_back(new std::thread(std::bind(&run, &loader1))); } for (auto & client_thread : client_threads) {