From e47b7c482d8b09135b74589bead2ac85ff95421e Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Mon, 2 Nov 2026 17:05:34 -0600 Subject: [PATCH 1/2] Deduplicate the shared_ptr and unique_ptr tests utest.cpp and unique_ptr_test.cpp were copies of each other differing only in createInstance() vs createUniqueInstance(). Make the managed instance tests typed tests that run with both, and drop unique_ptr_test.cpp. The unmanaged instance tests move to their own executable: createUnmanagedInstance() disables on-demand unloading for the whole process, and utest.cpp's lazy-unload tests only passed because gtest happened to run them first. Fixes #94 Signed-off-by: Michael Carroll --- test/CMakeLists.txt | 22 +- test/unique_ptr_test.cpp | 340 ------------------------ test/unmanaged_test.cpp | 67 +++++ test/utest.cpp | 552 ++++++++++++++++----------------------- 4 files changed, 303 insertions(+), 678 deletions(-) delete mode 100644 test/unique_ptr_test.cpp create mode 100644 test/unmanaged_test.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d9a02a4..8c72c68 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -58,26 +58,14 @@ if(TARGET ${PROJECT_NAME}_utest) ${PROJECT_NAME}_TestGlobalPlugins) endif() -ament_add_gtest(${PROJECT_NAME}_unique_ptr_test unique_ptr_test.cpp +ament_add_gtest(${PROJECT_NAME}_unmanaged_test unmanaged_test.cpp APPEND_LIBRARY_DIRS "${append_library_dirs}" ) -if(TARGET ${PROJECT_NAME}_unique_ptr_test) - if(ament_cmake_FOUND) - target_include_directories(${PROJECT_NAME}_unique_ptr_test - PUBLIC "../include") - else() - target_include_directories(${PROJECT_NAME}_unique_ptr_test - PUBLIC "../include") - target_link_libraries(${PROJECT_NAME}_unique_ptr_test) - endif() - target_link_libraries(${PROJECT_NAME}_unique_ptr_test ${PROJECT_NAME} project_options) - - if(NOT WIN32) - target_link_libraries(${PROJECT_NAME}_unique_ptr_test pthread) - endif() - add_dependencies(${PROJECT_NAME}_unique_ptr_test +if(TARGET ${PROJECT_NAME}_unmanaged_test) + target_include_directories(${PROJECT_NAME}_unmanaged_test PUBLIC "../include") + target_link_libraries(${PROJECT_NAME}_unmanaged_test ${PROJECT_NAME} project_options) + add_dependencies(${PROJECT_NAME}_unmanaged_test ${PROJECT_NAME}_TestPlugins1 - ${PROJECT_NAME}_TestPlugins2 ${PROJECT_NAME}_TestPlugins3) endif() diff --git a/test/unique_ptr_test.cpp b/test/unique_ptr_test.cpp deleted file mode 100644 index 6910b2a..0000000 --- a/test/unique_ptr_test.cpp +++ /dev/null @@ -1,340 +0,0 @@ -/* - * Copyright (c) 2012, Willow Garage, Inc. - * Copyright (c) 2016, Delft Robotics B.V. - * 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 - -#include "console_bridge/console.h" -#include "gtest/gtest.h" - -#include "class_loader/class_loader.hpp" -#include "class_loader/multi_library_class_loader.hpp" - -#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 -const std::string LIBRARY_3 = class_loader::systemLibraryFormat("class_loader_TestPlugins3"); // NOLINT - -// Collects console_bridge warnings and errors logged while in scope. -struct WarningCapture : console_bridge::OutputHandler -{ - WarningCapture() {console_bridge::useOutputHandler(this);} - ~WarningCapture() override {console_bridge::restorePreviousOutputHandler();} - void log(const std::string & text, console_bridge::LogLevel level, const char *, int) override - { - if (level >= console_bridge::CONSOLE_BRIDGE_LOG_WARN) {warnings += text;} - } - std::string warnings; -}; - -using class_loader::ClassLoader; - -TEST(ClassLoaderUniquePtrTest, basicLoad) { - try { - ClassLoader loader1(LIBRARY_1, false); - loader1.createUniqueInstance("Cat")->saySomething(); // See if lazy load works - ASSERT_NO_THROW(class_loader::impl::printDebugInfoToScreen()); - SUCCEED(); - } catch (class_loader::ClassLoaderException & e) { - FAIL() << "ClassLoaderException: " << e.what() << "\n"; - } -} - -TEST(ClassLoaderUniquePtrTest, basicLoadTwice) { - try { - { - ClassLoader loader1(LIBRARY_1, false); - loader1.createUniqueInstance("Cat")->saySomething(); // See if lazy load works - } - ClassLoader loader1(LIBRARY_1, false); - loader1.createUniqueInstance("Cat")->saySomething(); // See if lazy load works - ASSERT_NO_THROW(class_loader::impl::printDebugInfoToScreen()); - SUCCEED(); - } catch (class_loader::ClassLoaderException & e) { - FAIL() << "ClassLoaderException: " << e.what() << "\n"; - } -} - -TEST(ClassLoaderUniquePtrTest, basicLoadTwiceSameTime) { - try { - ClassLoader loader1(LIBRARY_1, false); - loader1.createUniqueInstance("Cat")->saySomething(); // See if lazy load works - ClassLoader loader2(LIBRARY_1, false); - loader2.createUniqueInstance("Cat")->saySomething(); // See if lazy load works - ASSERT_NO_THROW(class_loader::impl::printDebugInfoToScreen()); - SUCCEED(); - } catch (class_loader::ClassLoaderException & e) { - FAIL() << "ClassLoaderException: " << e.what() << "\n"; - } -} - -TEST(ClassLoaderUniquePtrTest, constructorLoad) { - try { - ClassLoader loader1(LIBRARY_3, false); - ASSERT_EQ(loader1.createUniqueInstance("Identity", "identity 1", - std::make_unique(1))->get_number(), 1); - ASSERT_EQ(loader1.createUniqueInstance("Identity", "identity 2", - std::make_unique(10))->get_number(), - 10); - ASSERT_EQ(loader1.createUniqueInstance("Double", "double 1", - std::make_unique(1))->get_number(), 2); - ASSERT_EQ(loader1.createUniqueInstance("Double", "double 2", - std::make_unique(10))->get_number(), 20); - ASSERT_NO_THROW(class_loader::impl::printDebugInfoToScreen()); - SUCCEED(); - } catch (class_loader::ClassLoaderException & e) { - FAIL() << "ClassLoaderException: " << e.what() << "\n"; - } -} - -TEST(ClassLoaderUniquePtrTest, basicLoadFailures) { - ClassLoader loader1(LIBRARY_1, false); - ClassLoader loader2("", false); - loader2.loadLibrary(); - EXPECT_THROW( - class_loader::impl::loadLibrary("LIBRARY_1", &loader1), - class_loader::LibraryLoadException); -} - -TEST(ClassLoaderUniquePtrTest, MultiLibraryClassLoaderFailures) { - class_loader::MultiLibraryClassLoader loader(true); - loader.loadLibrary(LIBRARY_1); - EXPECT_THROW(std::ignore = loader.createUniqueInstance("Cat2"), - class_loader::ClassLoaderException); -} - -TEST(ClassLoaderUniquePtrTest, LibrariesUsedByClassLoader) { - try { - ClassLoader loader1(LIBRARY_1, false); - std::vector v = class_loader::impl::getAllLibrariesUsedByClassLoader(&loader1); - ASSERT_EQ(v.size(), 1u); - SUCCEED(); - } catch (class_loader::ClassLoaderException & e) { - FAIL() << "ClassLoaderException: " << e.what() << "\n"; - } -} - -TEST(ClassLoaderUniquePtrTest, correctLazyLoadUnload) { - try { - ASSERT_FALSE(class_loader::impl::isLibraryLoadedByAnybody(LIBRARY_1)); - ClassLoader loader1(LIBRARY_1, true); - ASSERT_FALSE(class_loader::impl::isLibraryLoadedByAnybody(LIBRARY_1)); - ASSERT_FALSE(loader1.isLibraryLoaded()); - - { - ClassLoader::UniquePtr obj = loader1.createUniqueInstance("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(ClassLoaderUniquePtrTest, nonExistentPlugin) { - ClassLoader loader1(LIBRARY_1, false); - - try { - ClassLoader::UniquePtr obj = loader1.createUniqueInstance("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"; -} - -void wait(int seconds) -{ - std::this_thread::sleep_for(std::chrono::seconds(seconds)); -} - -void run(ClassLoader * loader) -{ - std::vector classes = loader->getAvailableClasses(); - for (auto & class_ : classes) { - loader->createUniqueInstance(class_)->saySomething(); - } -} - -TEST(ClassLoaderUniquePtrTest, threadSafety) { - 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 < STRESS_TEST_NUM_THREADS; c++) { - client_threads.emplace_back(std::bind(&run, &loader1)); - } - - for (auto & client_thread : client_threads) { - client_thread.join(); - } - - loader1.unloadLibrary(); - ASSERT_FALSE(loader1.isLibraryLoaded()); - } catch (const class_loader::ClassLoaderException &) { - FAIL() << "Unexpected ClassLoaderException."; - } catch (...) { - FAIL() << "Unknown exception."; - } -} - -TEST(ClassLoaderUniquePtrTest, loadRefCountingLazy) { - try { - ClassLoader loader1(LIBRARY_1, true); - ASSERT_FALSE(loader1.isLibraryLoaded()); - - { - ClassLoader::UniquePtr obj = loader1.createUniqueInstance("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); - loader.loadLibrary(LIBRARY_3); - for (int i = 0; i < 2; ++i) { - loader.createUniqueInstance("Cat")->saySomething(); - loader.createUniqueInstance("Dog")->saySomething(); - loader.createUniqueInstance("Robot")->saySomething(); - ASSERT_EQ(loader.createUniqueInstance("Identity", "identity 1", - std::make_unique(1))->get_number(), 1); - ASSERT_EQ(loader.createUniqueInstance("Identity", "identity 2", - std::make_unique(10))->get_number(), - 10); - ASSERT_EQ(loader.createUniqueInstance("Double", "double 1", - std::make_unique(1))->get_number(), 2); - ASSERT_EQ(loader.createUniqueInstance("Double", "double 2", - std::make_unique(10))->get_number(), 20); - } - } catch (class_loader::ClassLoaderException & e) { - FAIL() << "ClassLoaderException: " << e.what() << "\n"; - } - - SUCCEED(); -} - -TEST(MultiClassLoaderUniquePtrTest, lazyLoad) { - testMultiClassLoader(true); -} -TEST(MultiClassLoaderUniquePtrTest, lazyLoadSecondTime) { - testMultiClassLoader(true); -} -TEST(MultiClassLoaderUniquePtrTest, nonLazyLoad) { - testMultiClassLoader(false); -} -TEST(MultiClassLoaderUniquePtrTest, noWarningOnLazyLoad) { - try { - WarningCapture capture; - ClassLoader::UniquePtr cat, dog, rob; - { - class_loader::MultiLibraryClassLoader loader(true); - loader.loadLibrary(LIBRARY_1); - loader.loadLibrary(LIBRARY_2); - - cat = loader.createUniqueInstance("Cat"); - dog = loader.createUniqueInstance("Dog"); - rob = loader.createUniqueInstance("Robot"); - } - cat->saySomething(); - dog->saySomething(); - rob->saySomething(); - EXPECT_EQ("", capture.warnings); - } 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/unmanaged_test.cpp b/test/unmanaged_test.cpp new file mode 100644 index 0000000..3eeb96b --- /dev/null +++ b/test/unmanaged_test.cpp @@ -0,0 +1,67 @@ +/* + * 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 "class_loader/class_loader.hpp" + +#include "gtest/gtest.h" + +#include "./base.hpp" + +// createUnmanagedInstance() disables on-demand unloading for the whole process, so these tests +// run in their own executable. + +const std::string LIBRARY_1 = class_loader::systemLibraryFormat("class_loader_TestPlugins1"); // NOLINT +const std::string LIBRARY_3 = class_loader::systemLibraryFormat("class_loader_TestPlugins3"); // NOLINT + +TEST(ClassLoaderUnmanagedTest, basicLoadUnmanaged) { + class_loader::ClassLoader loader1(LIBRARY_1, false); + Base * unmanaged_instance = loader1.createUnmanagedInstance("Dog"); + ASSERT_NE(unmanaged_instance, nullptr); + unmanaged_instance->saySomething(); + delete unmanaged_instance; +} + +TEST(ClassLoaderUnmanagedTest, constructorLoadUnmanaged) { + class_loader::ClassLoader loader1(LIBRARY_3, false); + BaseWithInterfaceCtor * unmanaged_instance = + loader1.createUnmanagedInstance( + "Identity", "identity", std::make_unique(1)); + ASSERT_NE(unmanaged_instance, nullptr); + ASSERT_EQ(unmanaged_instance->get_number(), 1); + delete unmanaged_instance; +} + +int main(int argc, char ** argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/test/utest.cpp b/test/utest.cpp index 8151a08..cfc9aea 100644 --- a/test/utest.cpp +++ b/test/utest.cpp @@ -27,18 +27,18 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include #include #ifndef _WIN32 #include #endif -#include #include #include #include #include +#include +#include #include #include "class_loader/class_loader.hpp" @@ -70,331 +70,301 @@ struct WarningCapture : console_bridge::OutputHandler std::string warnings; }; -TEST(ClassLoaderTest, basicLoad) { - try { - class_loader::ClassLoader loader1(LIBRARY_1, false); - ASSERT_NO_THROW(class_loader::impl::printDebugInfoToScreen()); - loader1.createInstance("Cat")->saySomething(); // See if lazy load works - } catch (class_loader::ClassLoaderException & e) { - FAIL() << "ClassLoaderException: " << e.what() << "\n"; - } - - SUCCEED(); -} +// Tests for managed instances run once with createInstance() (std::shared_ptr) and once with +// createUniqueInstance() (ClassLoader::UniquePtr). +struct SharedPtr +{ + template + using Ptr = std::shared_ptr; -TEST(ClassLoaderTest, basicLoadTwice) { - try { - { - class_loader::ClassLoader loader1(LIBRARY_1, false); - ASSERT_NO_THROW(class_loader::impl::printDebugInfoToScreen()); - loader1.createInstance("Cat")->saySomething(); // See if lazy load works - } - class_loader::ClassLoader loader1(LIBRARY_1, false); - ASSERT_NO_THROW(class_loader::impl::printDebugInfoToScreen()); - loader1.createInstance("Cat")->saySomething(); // See if lazy load works - } catch (class_loader::ClassLoaderException & e) { - FAIL() << "ClassLoaderException: " << e.what() << "\n"; + template + static auto create(Loader & loader, const std::string & class_name, Args &&... args) + { + return loader.template createInstance(class_name, std::forward(args)...); } +}; - SUCCEED(); -} +struct UniquePtr +{ + template + using Ptr = class_loader::ClassLoader::UniquePtr; -TEST(ClassLoaderTest, basicLoadTwiceSameTime) { - try { - class_loader::ClassLoader loader1(LIBRARY_1, false); - ASSERT_NO_THROW(class_loader::impl::printDebugInfoToScreen()); - loader1.createInstance("Cat")->saySomething(); // See if lazy load works - class_loader::ClassLoader loader2(LIBRARY_1, false); - ASSERT_NO_THROW(class_loader::impl::printDebugInfoToScreen()); - loader2.createInstance("Cat")->saySomething(); // See if lazy load works - } catch (class_loader::ClassLoaderException & e) { - FAIL() << "ClassLoaderException: " << e.what() << "\n"; + template + static auto create(Loader & loader, const std::string & class_name, Args &&... args) + { + return loader.template createUniqueInstance(class_name, std::forward(args)...); } +}; - SUCCEED(); -} - -TEST(ClassLoaderTest, constructorLoad) { - try { - class_loader::ClassLoader loader1(LIBRARY_3, false); - ASSERT_NO_THROW(class_loader::impl::printDebugInfoToScreen()); - ASSERT_EQ(loader1.createInstance("Identity", "identity 1", - std::make_unique(1))->get_number(), 1); - ASSERT_EQ(loader1.createInstance("Identity", "identity 2", - std::make_unique(10))->get_number(), 10); - ASSERT_EQ(loader1.createInstance("Double", "double 1", - std::make_unique(1))->get_number(), 2); - ASSERT_EQ(loader1.createInstance("Double", "double 2", - std::make_unique(10))->get_number(), 20); - } catch (class_loader::ClassLoaderException & e) { - FAIL() << "ClassLoaderException: " << e.what() << "\n"; +struct InstanceTypeNames +{ + template + static std::string GetName([[maybe_unused]] int index) + { + return std::is_same_v ? "SharedPtr" : "UniquePtr"; } +}; - SUCCEED(); -} +using InstanceTypes = ::testing::Types; -// Requires separate namespace so static variables are isolated -TEST(ClassLoaderUnmanagedTest, basicLoadUnmanaged) { - try { - class_loader::ClassLoader loader1(LIBRARY_1, false); - Base * unmanaged_instance = loader1.createUnmanagedInstance("Dog"); - ASSERT_NE(unmanaged_instance, nullptr); - unmanaged_instance->saySomething(); - delete unmanaged_instance; - } catch (class_loader::ClassLoaderException & e) { - FAIL() << "ClassLoaderException: " << e.what() << "\n"; - } +template +class ClassLoaderTest : public ::testing::Test {}; +TYPED_TEST_SUITE(ClassLoaderTest, InstanceTypes, InstanceTypeNames); - SUCCEED(); +template +class MultiClassLoaderTest : public ::testing::Test {}; +TYPED_TEST_SUITE(MultiClassLoaderTest, InstanceTypes, InstanceTypeNames); + +TYPED_TEST(ClassLoaderTest, basicLoad) { + class_loader::ClassLoader loader1(LIBRARY_1, false); + ASSERT_NO_THROW(class_loader::impl::printDebugInfoToScreen()); + TypeParam::template create(loader1, "Cat")->saySomething(); } -// Requires separate namespace so static variables are isolated -TEST(ClassLoaderUnmanagedTest, constructorLoadUnmanaged) { - try { - class_loader::ClassLoader loader1(LIBRARY_3, false); - BaseWithInterfaceCtor * unmanaged_instance = - loader1.createUnmanagedInstance("Identity", "identity", - std::make_unique(1)); - ASSERT_NE(unmanaged_instance, nullptr); - ASSERT_EQ(unmanaged_instance->get_number(), 1); - delete unmanaged_instance; - } catch (class_loader::ClassLoaderException & e) { - FAIL() << "ClassLoaderException: " << e.what() << "\n"; +TYPED_TEST(ClassLoaderTest, basicLoadTwice) { + { + class_loader::ClassLoader loader1(LIBRARY_1, false); + TypeParam::template create(loader1, "Cat")->saySomething(); } - - SUCCEED(); + class_loader::ClassLoader loader1(LIBRARY_1, false); + TypeParam::template create(loader1, "Cat")->saySomething(); + ASSERT_NO_THROW(class_loader::impl::printDebugInfoToScreen()); } -TEST(ClassLoaderUniquePtrTest, basicLoadFailures) { +TYPED_TEST(ClassLoaderTest, basicLoadTwiceSameTime) { class_loader::ClassLoader loader1(LIBRARY_1, false); - class_loader::ClassLoader loader2("", false); - loader2.loadLibrary(); - EXPECT_THROW( - class_loader::impl::loadLibrary("LIBRARY_1", &loader1), - class_loader::LibraryLoadException); + TypeParam::template create(loader1, "Cat")->saySomething(); + class_loader::ClassLoader loader2(LIBRARY_1, false); + TypeParam::template create(loader2, "Cat")->saySomething(); + ASSERT_NO_THROW(class_loader::impl::printDebugInfoToScreen()); } -TEST(ClassLoaderUniquePtrTest, MultiLibraryClassLoaderFailures) { - class_loader::MultiLibraryClassLoader loader(true); - loader.loadLibrary(LIBRARY_1); - EXPECT_THROW(std::ignore = loader.createUniqueInstance("Cat2"), - class_loader::ClassLoaderException); +TYPED_TEST(ClassLoaderTest, constructorLoad) { + class_loader::ClassLoader loader1(LIBRARY_3, false); + ASSERT_EQ( + TypeParam::template create( + loader1, "Identity", "identity 1", std::make_unique(1))->get_number(), 1); + ASSERT_EQ( + TypeParam::template create( + loader1, "Identity", "identity 2", std::make_unique(10))->get_number(), 10); + ASSERT_EQ( + TypeParam::template create( + loader1, "Double", "double 1", std::make_unique(1))->get_number(), 2); + ASSERT_EQ( + TypeParam::template create( + loader1, "Double", "double 2", std::make_unique(10))->get_number(), 20); + ASSERT_NO_THROW(class_loader::impl::printDebugInfoToScreen()); } -TEST(ClassLoaderTest, correctNonLazyLoadUnload) { - try { - ASSERT_FALSE(class_loader::impl::isLibraryLoadedByAnybody(LIBRARY_1)); - class_loader::ClassLoader loader1(LIBRARY_1, false); +TYPED_TEST(ClassLoaderTest, correctLazyLoadUnload) { + 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()); + + { + auto obj = TypeParam::template create(loader1, "Cat"); 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(ClassLoaderTest, 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.createInstance("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"; - } + // The library will unload automatically when the only plugin object left is destroyed + ASSERT_FALSE(class_loader::impl::isLibraryLoadedByAnybody(LIBRARY_1)); } -TEST(ClassLoaderTest, nonExistentPlugin) { +TYPED_TEST(ClassLoaderTest, nonExistentPlugin) { class_loader::ClassLoader loader1(LIBRARY_1, false); + EXPECT_THROW( + std::ignore = TypeParam::template create(loader1, "Bear"), + class_loader::CreateClassException); +} - try { - std::shared_ptr obj = loader1.createInstance("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"; +TYPED_TEST(ClassLoaderTest, 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. + std::vector client_threads; + for (size_t c = 0; c < STRESS_TEST_NUM_THREADS; ++c) { + client_threads.emplace_back( + [&loader1]() { + for (const auto & class_name : loader1.getAvailableClasses()) { + TypeParam::template create(loader1, class_name)->saySomething(); + } + }); + } + for (auto & client_thread : client_threads) { + client_thread.join(); } - FAIL() << "Did not throw exception as expected.\n"; + loader1.unloadLibrary(); + ASSERT_FALSE(loader1.isLibraryLoaded()); } -TEST(ClassLoaderTest, nonExistentLibrary) { - try { - class_loader::ClassLoader loader1("libDoesNotExist.so", false); - } catch (const class_loader::LibraryLoadException &) { - SUCCEED(); - return; - } catch (...) { - FAIL() << "Unknown exception caught.\n"; +TYPED_TEST(ClassLoaderTest, loadRefCountingLazy) { + class_loader::ClassLoader loader1(LIBRARY_1, true); + ASSERT_FALSE(loader1.isLibraryLoaded()); + + { + auto obj = TypeParam::template create(loader1, "Dog"); + ASSERT_TRUE(loader1.isLibraryLoaded()); } - FAIL() << "Did not throw exception as expected.\n"; -} + ASSERT_FALSE(loader1.isLibraryLoaded()); -class InvalidBase -{ -}; + loader1.loadLibrary(); + ASSERT_TRUE(loader1.isLibraryLoaded()); -TEST(ClassLoaderTest, 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"; - } -} + loader1.loadLibrary(); + ASSERT_TRUE(loader1.isLibraryLoaded()); -void wait(int seconds) -{ - std::this_thread::sleep_for(std::chrono::seconds(seconds)); -} + loader1.unloadLibrary(); + ASSERT_TRUE(loader1.isLibraryLoaded()); -void run(class_loader::ClassLoader * loader) -{ - std::vector classes = loader->getAvailableClasses(); - for (auto & class_ : classes) { - loader->createInstance(class_)->saySomething(); - } -} + loader1.unloadLibrary(); + ASSERT_FALSE(loader1.isLibraryLoaded()); -TEST(ClassLoaderTest, threadSafety) { - class_loader::ClassLoader loader1(LIBRARY_1); + loader1.unloadLibrary(); + ASSERT_FALSE(loader1.isLibraryLoaded()); + + loader1.loadLibrary(); 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 < STRESS_TEST_NUM_THREADS; ++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."; +template +void testMultiClassLoader(bool lazy) +{ + class_loader::MultiLibraryClassLoader loader(lazy); + loader.loadLibrary(LIBRARY_1); + loader.loadLibrary(LIBRARY_2); + loader.loadLibrary(LIBRARY_3); + for (int i = 0; i < 2; ++i) { + Instances::template create(loader, "Cat")->saySomething(); + Instances::template create(loader, "Dog")->saySomething(); + Instances::template create(loader, "Robot")->saySomething(); + ASSERT_EQ( + Instances::template create( + loader, "Identity", "identity 1", std::make_unique(1))->get_number(), 1); + ASSERT_EQ( + Instances::template create( + loader, "Identity", "identity 2", std::make_unique(10))->get_number(), 10); + ASSERT_EQ( + Instances::template create( + loader, "Double", "double 1", std::make_unique(1))->get_number(), 2); + ASSERT_EQ( + Instances::template create( + loader, "Double", "double 2", std::make_unique(10))->get_number(), 20); } } -TEST(ClassLoaderTest, loadRefCountingNonLazy) { - try { - class_loader::ClassLoader loader1(LIBRARY_1, false); - ASSERT_TRUE(loader1.isLibraryLoaded()); +TYPED_TEST(MultiClassLoaderTest, lazyLoad) { + testMultiClassLoader(true); +} - loader1.loadLibrary(); - loader1.loadLibrary(); - ASSERT_TRUE(loader1.isLibraryLoaded()); +TYPED_TEST(MultiClassLoaderTest, lazyLoadSecondTime) { + testMultiClassLoader(true); +} - loader1.unloadLibrary(); - ASSERT_TRUE(loader1.isLibraryLoaded()); +TYPED_TEST(MultiClassLoaderTest, nonLazyLoad) { + testMultiClassLoader(false); +} - loader1.unloadLibrary(); - ASSERT_TRUE(loader1.isLibraryLoaded()); +TYPED_TEST(MultiClassLoaderTest, noWarningOnLazyLoad) { + WarningCapture capture; + typename TypeParam::template Ptr cat, dog, rob; + { + class_loader::MultiLibraryClassLoader loader(true); + loader.loadLibrary(LIBRARY_1); + loader.loadLibrary(LIBRARY_2); - loader1.unloadLibrary(); - ASSERT_FALSE(loader1.isLibraryLoaded()); + cat = TypeParam::template create(loader, "Cat"); + dog = TypeParam::template create(loader, "Dog"); + rob = TypeParam::template create(loader, "Robot"); + } + cat->saySomething(); + dog->saySomething(); + rob->saySomething(); + EXPECT_EQ("", capture.warnings); +} - loader1.unloadLibrary(); - ASSERT_FALSE(loader1.isLibraryLoaded()); +TYPED_TEST(MultiClassLoaderTest, nonExistentPlugin) { + class_loader::MultiLibraryClassLoader loader(true); + loader.loadLibrary(LIBRARY_1); + EXPECT_THROW( + std::ignore = TypeParam::template create(loader, "Cat2"), + class_loader::ClassLoaderException); +} - loader1.loadLibrary(); - ASSERT_TRUE(loader1.isLibraryLoaded()); +// Tests that don't depend on how instances are managed. - return; - } catch (const class_loader::ClassLoaderException &) { - FAIL() << "Unexpected exception.\n"; - } catch (...) { - FAIL() << "Unknown exception caught.\n"; - } +TEST(ClassLoaderBasicTest, basicLoadFailures) { + class_loader::ClassLoader loader1(LIBRARY_1, false); + class_loader::ClassLoader loader2("", false); + loader2.loadLibrary(); + EXPECT_THROW( + class_loader::impl::loadLibrary("LIBRARY_1", &loader1), + class_loader::LibraryLoadException); +} - FAIL() << "Did not throw exception as expected.\n"; +TEST(ClassLoaderBasicTest, librariesUsedByClassLoader) { + class_loader::ClassLoader loader1(LIBRARY_1, false); + std::vector v = class_loader::impl::getAllLibrariesUsedByClassLoader(&loader1); + ASSERT_EQ(v.size(), 1u); } -TEST(ClassLoaderTest, loadRefCountingLazy) { - try { - class_loader::ClassLoader loader1(LIBRARY_1, true); - ASSERT_FALSE(loader1.isLibraryLoaded()); +TEST(ClassLoaderBasicTest, correctNonLazyLoadUnload) { + 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()); +} - { - std::shared_ptr obj = loader1.createInstance("Dog"); - ASSERT_TRUE(loader1.isLibraryLoaded()); - } +TEST(ClassLoaderBasicTest, nonExistentLibrary) { + EXPECT_THROW( + class_loader::ClassLoader loader1("libDoesNotExist.so", false), + class_loader::LibraryLoadException); +} - ASSERT_FALSE(loader1.isLibraryLoaded()); +class InvalidBase +{ +}; - loader1.loadLibrary(); - ASSERT_TRUE(loader1.isLibraryLoaded()); +TEST(ClassLoaderBasicTest, invalidBase) { + class_loader::ClassLoader loader1(LIBRARY_1, false); + EXPECT_FALSE(loader1.isClassAvailable("Cat")); + EXPECT_TRUE(loader1.isClassAvailable("Cat")); +} - loader1.loadLibrary(); - ASSERT_TRUE(loader1.isLibraryLoaded()); +TEST(ClassLoaderBasicTest, loadRefCountingNonLazy) { + class_loader::ClassLoader loader1(LIBRARY_1, false); + ASSERT_TRUE(loader1.isLibraryLoaded()); - loader1.unloadLibrary(); - ASSERT_TRUE(loader1.isLibraryLoaded()); + loader1.loadLibrary(); + loader1.loadLibrary(); + ASSERT_TRUE(loader1.isLibraryLoaded()); - loader1.unloadLibrary(); - ASSERT_FALSE(loader1.isLibraryLoaded()); + loader1.unloadLibrary(); + ASSERT_TRUE(loader1.isLibraryLoaded()); - loader1.unloadLibrary(); - ASSERT_FALSE(loader1.isLibraryLoaded()); + loader1.unloadLibrary(); + ASSERT_TRUE(loader1.isLibraryLoaded()); - loader1.loadLibrary(); - ASSERT_TRUE(loader1.isLibraryLoaded()); + loader1.unloadLibrary(); + ASSERT_FALSE(loader1.isLibraryLoaded()); - return; - } catch (const class_loader::ClassLoaderException &) { - FAIL() << "Unexpected exception.\n"; - } catch (...) { - FAIL() << "Unknown exception caught.\n"; - } + loader1.unloadLibrary(); + ASSERT_FALSE(loader1.isLibraryLoaded()); - FAIL() << "Did not throw exception as expected.\n"; + loader1.loadLibrary(); + ASSERT_TRUE(loader1.isLibraryLoaded()); } -TEST(ClassLoaderTest, isLibraryLoadedScopedToLoader) { +TEST(ClassLoaderBasicTest, isLibraryLoadedScopedToLoader) { class_loader::ClassLoader loader1(LIBRARY_1, false); ASSERT_TRUE(loader1.isLibraryLoaded()); @@ -414,66 +384,6 @@ TEST(ClassLoaderTest, isLibraryLoadedScopedToLoader) { ASSERT_FALSE(class_loader::impl::isLibraryLoadedByAnybody(LIBRARY_1)); } -void testMultiClassLoader(bool lazy) -{ - try { - class_loader::MultiLibraryClassLoader loader(lazy); - loader.loadLibrary(LIBRARY_1); - loader.loadLibrary(LIBRARY_2); - loader.loadLibrary(LIBRARY_3); - for (int i = 0; i < 2; ++i) { - loader.createInstance("Cat")->saySomething(); - loader.createInstance("Dog")->saySomething(); - loader.createInstance("Robot")->saySomething(); - ASSERT_EQ(loader.createInstance("Identity", "identity 1", - std::make_unique(1))->get_number(), 1); - ASSERT_EQ(loader.createInstance("Identity", "identity 2", - std::make_unique(10))->get_number(), 10); - ASSERT_EQ(loader.createInstance("Double", "double 1", - std::make_unique(1))->get_number(), 2); - ASSERT_EQ(loader.createInstance("Double", "double 2", - std::make_unique(10))->get_number(), 20); - } - } 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 { - WarningCapture capture; - std::shared_ptr cat, dog, rob; - { - class_loader::MultiLibraryClassLoader loader(true); - loader.loadLibrary(LIBRARY_1); - loader.loadLibrary(LIBRARY_2); - - cat = loader.createInstance("Cat"); - dog = loader.createInstance("Dog"); - rob = loader.createInstance("Robot"); - } - cat->saySomething(); - dog->saySomething(); - rob->saySomething(); - EXPECT_EQ("", capture.warnings); - } catch (class_loader::ClassLoaderException & e) { - FAIL() << "ClassLoaderException: " << e.what() << "\n"; - } - - SUCCEED(); -} - #ifndef _WIN32 // Not run on Windows because this tests dlopen-specific behavior From 112ee4173b0b8eaaa484d5ff85af18ce04ed3321 Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Fri, 6 Nov 2026 08:51:42 -0600 Subject: [PATCH 2/2] Avoid a ternary after a template-id in InstanceTypeNames uncrustify 0.78.1 (uncrustify_vendor, Ubuntu) and 0.80.1 (EPEL, RHEL) disagree on the spacing of '?' after 'is_same_v<...>', so no spelling of the ternary passes both. Use if constexpr instead. Signed-off-by: Michael Carroll --- test/utest.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/utest.cpp b/test/utest.cpp index cfc9aea..e9c3da1 100644 --- a/test/utest.cpp +++ b/test/utest.cpp @@ -101,7 +101,10 @@ struct InstanceTypeNames template static std::string GetName([[maybe_unused]] int index) { - return std::is_same_v ? "SharedPtr" : "UniquePtr"; + if constexpr (std::is_same_v) { + return "SharedPtr"; + } + return "UniquePtr"; } };