Skip to content

Commit c1a2c78

Browse files
brawnerahcorde
authored andcommitted
Increase coverage with a graveyard behavior test and unmanaged instance test (#159)
1 parent 376005f commit c1a2c78

File tree

4 files changed

+170
-2
lines changed

4 files changed

+170
-2
lines changed

QUALITY_DECLARATION.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,9 @@ This includes:
114114

115115
Changes are required to make a best effort to keep or increase coverage before being accepted, but decreases are allowed if properly justified and accepted by reviewers.
116116

117-
Current coverage statistics can be viewed [here](https://ci.ros2.org/job/ci_linux_coverage/85/cobertura/src_ros_class_loader_include_class_loader/) and [here](https://ci.ros2.org/job/ci_linux_coverage/85/cobertura/src_ros_class_loader_include_class_loader/). This package does not yet meet the 95% coverage guideline, but it is currently above 90%.
117+
This package has testing coverage of at least 95%.
118+
Current coverage statistics can be viewed [here](https://ci.ros2.org/job/nightly_linux_coverage/lastSuccessfulBuild/cobertura/).
119+
A description of how coverage statistics are calculated is summarized in the [ROS 2 On-boarding Guide](https://index.ros.org/doc/ros2/Contributing/ROS-2-On-boarding-Guide/#note-on-coverage-runs).
118120

119121
### Performance [4.iv]
120122

test/CMakeLists.txt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,20 @@ endif()
2626
target_link_libraries(${PROJECT_NAME}_TestPlugins2 ${PROJECT_NAME})
2727
class_loader_hide_library_symbols(${PROJECT_NAME}_TestPlugins2)
2828

29+
# These plugins are loaded using dlopen() with RTLD_GLOBAL in utest and may cause side effects
30+
# in other tests if they are used elsewhere.
31+
add_library(${PROJECT_NAME}_TestGlobalPlugins EXCLUDE_FROM_ALL SHARED global_plugins.cpp)
32+
if(ament_cmake_FOUND)
33+
target_include_directories(${PROJECT_NAME}_TestGlobalPlugins
34+
PUBLIC "../include")
35+
ament_target_dependencies(${PROJECT_NAME}_TestGlobalPlugins "console_bridge")
36+
else()
37+
target_include_directories(${PROJECT_NAME}_TestGlobalPlugins
38+
PUBLIC "../include" ${console_bridge_INCLUDE_DIRS})
39+
endif()
40+
target_link_libraries(${PROJECT_NAME}_TestGlobalPlugins ${PROJECT_NAME})
41+
class_loader_hide_library_symbols(${PROJECT_NAME}_TestGlobalPlugins)
42+
2943
if(WIN32)
3044
set(append_library_dirs "$<TARGET_FILE_DIR:${PROJECT_NAME}>;$<TARGET_FILE_DIR:${PROJECT_NAME}_TestPlugins1>")
3145
else()
@@ -52,7 +66,8 @@ if(TARGET ${PROJECT_NAME}_utest)
5266
endif()
5367
add_dependencies(${PROJECT_NAME}_utest
5468
${PROJECT_NAME}_TestPlugins1
55-
${PROJECT_NAME}_TestPlugins2)
69+
${PROJECT_NAME}_TestPlugins2
70+
${PROJECT_NAME}_TestGlobalPlugins)
5671
endif()
5772

5873
ament_add_gtest(${PROJECT_NAME}_unique_ptr_test unique_ptr_test.cpp

test/global_plugins.cpp

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright (c) 2012, Willow Garage, Inc.
3+
* All rights reserved.
4+
* Copyright (c) 2020, Open Source Robotics Foundation, Inc.
5+
*
6+
* Redistribution and use in source and binary forms, with or without
7+
* modification, are permitted provided that the following conditions are met:
8+
*
9+
* * Redistributions of source code must retain the above copyright
10+
* notice, this list of conditions and the following disclaimer.
11+
* * Redistributions in binary form must reproduce the above copyright
12+
* notice, this list of conditions and the following disclaimer in the
13+
* documentation and/or other materials provided with the distribution.
14+
* * Neither the name of the Willow Garage, Inc. nor the names of its
15+
* contributors may be used to endorse or promote products derived from
16+
* this software without specific prior written permission.
17+
*
18+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28+
* POSSIBILITY OF SUCH DAMAGE.
29+
*
30+
* 2020, Copied from plugins1.cpp and plugins2.cpp and changing class names
31+
*/
32+
33+
#include <iostream>
34+
35+
#include "class_loader/class_loader.hpp"
36+
37+
#include "./base.hpp"
38+
39+
class Kangaroo : public Base
40+
{
41+
public:
42+
void saySomething()
43+
{
44+
printf("[Angry growl]\n");
45+
}
46+
};
47+
48+
class Panda : public Base
49+
{
50+
public:
51+
void saySomething()
52+
{
53+
printf("[Excited squeaks!!!]\n");
54+
}
55+
};
56+
57+
class Hyena : public Base
58+
{
59+
public:
60+
void saySomething()
61+
{
62+
printf("[Cackling laugh]\n");
63+
}
64+
};
65+
66+
class Alpaca : public Base
67+
{
68+
public:
69+
void saySomething()
70+
{
71+
printf("hhhaaaaaaaaaa\n");
72+
}
73+
};
74+
75+
76+
CLASS_LOADER_REGISTER_CLASS(Kangaroo, Base)
77+
CLASS_LOADER_REGISTER_CLASS(Panda, Base)
78+
CLASS_LOADER_REGISTER_CLASS(Hyena, Base)
79+
CLASS_LOADER_REGISTER_CLASS(Alpaca, Base)

test/utest.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@
2929

3030
#include <chrono>
3131
#include <cstddef>
32+
33+
#ifndef _WIN32
34+
#include <dlfcn.h>
35+
#endif
36+
3237
#include <iostream>
3338
#include <memory>
3439
#include <string>
@@ -45,6 +50,11 @@
4550
const std::string LIBRARY_1 = class_loader::systemLibraryFormat("class_loader_TestPlugins1"); // NOLINT
4651
const std::string LIBRARY_2 = class_loader::systemLibraryFormat("class_loader_TestPlugins2"); // NOLINT
4752

53+
// These are loaded with dlopen() and RTLD_GLOBAL in loadUnloadLoadFromGraveyard and may cause
54+
// unexpected side-effects if used elsewhere
55+
const std::string GLOBAL_PLUGINS = // NOLINT
56+
class_loader::systemLibraryFormat("class_loader_TestGlobalPlugins");
57+
4858
TEST(ClassLoaderTest, basicLoad) {
4959
try {
5060
class_loader::ClassLoader loader1(LIBRARY_1, false);
@@ -57,6 +67,21 @@ TEST(ClassLoaderTest, basicLoad) {
5767
SUCCEED();
5868
}
5969

70+
// Requires separate namespace so static variables are isolated
71+
TEST(ClassLoaderUnmanagedTest, basicLoadUnmanaged) {
72+
try {
73+
class_loader::ClassLoader loader1(LIBRARY_1, false);
74+
Base * unmanaged_instance = loader1.createUnmanagedInstance<Base>("Dog");
75+
ASSERT_NE(unmanaged_instance, nullptr);
76+
unmanaged_instance->saySomething();
77+
delete unmanaged_instance;
78+
} catch (class_loader::ClassLoaderException & e) {
79+
FAIL() << "ClassLoaderException: " << e.what() << "\n";
80+
}
81+
82+
SUCCEED();
83+
}
84+
6085
TEST(ClassLoaderUniquePtrTest, basicLoadFailures) {
6186
class_loader::ClassLoader loader1(LIBRARY_1, false);
6287
class_loader::ClassLoader loader2("", false);
@@ -337,6 +362,53 @@ TEST(MultiClassLoaderTest, noWarningOnLazyLoad) {
337362
SUCCEED();
338363
}
339364

365+
#ifndef _WIN32
366+
// Not run on Windows because this tests dlopen-specific behavior
367+
368+
// This is a different class name so that static variables in ClassLoader are isolated
369+
TEST(ClassLoaderGraveyardTest, loadUnloadLoadFromGraveyard) {
370+
// This first load/unload adds the plugin to the graveyard
371+
try {
372+
class_loader::ClassLoader loader(GLOBAL_PLUGINS, false);
373+
loader.createInstance<Base>("Kangaroo")->saySomething();
374+
loader.unloadLibrary();
375+
} catch (class_loader::ClassLoaderException & e) {
376+
FAIL() << "ClassLoaderException: " << e.what() << "\n";
377+
}
378+
379+
// Not all platforms use RTLD_GLOBAL as a default, and rcutils doesn't explicitly choose either.
380+
// In order to invoke graveyard behavior, this needs to be loaded first for global relocation.
381+
382+
void * handle = dlopen(GLOBAL_PLUGINS.c_str(), RTLD_NOW | RTLD_GLOBAL);
383+
ASSERT_NE(handle, nullptr);
384+
385+
// This load will cause system to use globally relocatable library.
386+
// For testing purposes, this will cause ClassLoader to revive the library from the graveyard.
387+
try {
388+
class_loader::ClassLoader loader(GLOBAL_PLUGINS, false);
389+
loader.createInstance<Base>("Panda")->saySomething();
390+
loader.unloadLibrary();
391+
392+
loader.loadLibrary();
393+
loader.createInstance<Base>("Hyena")->saySomething();
394+
loader.unloadLibrary();
395+
} catch (class_loader::ClassLoaderException & e) {
396+
FAIL() << "ClassLoaderException: " << e.what() << "\n";
397+
}
398+
399+
dlclose(handle);
400+
// With all libraries closed, this should act like a normal load/unload.
401+
try {
402+
class_loader::ClassLoader loader(GLOBAL_PLUGINS, false);
403+
loader.createInstance<Base>("Alpaca")->saySomething();
404+
loader.unloadLibrary();
405+
} catch (class_loader::ClassLoaderException & e) {
406+
FAIL() << "ClassLoaderException: " << e.what() << "\n";
407+
}
408+
}
409+
410+
#endif // ifndef _WIN32
411+
340412
// Run all the tests that were declared with TEST()
341413
int main(int argc, char ** argv)
342414
{

0 commit comments

Comments
 (0)