implement tests for rcl

This commit is contained in:
William Woodall 2015-12-15 15:59:16 -08:00
parent a022299568
commit 0ae4ca735d
5 changed files with 241 additions and 8 deletions

View file

@ -46,11 +46,17 @@ extern "C"
* be ignored.
* If argc is 0 and argv is NULL no parameters will be parsed.
*
* This function allocates heap memory.
* This function is not thread-safe.
* This function is lock-free so long as the C11's stdatomic.h function
* atomic_is_lock_free() returns true for atomic_uint_least64_t.
*
* \param[in] argc number of strings in argv
* \param[in] argv command line arguments; rcl specific arguments are removed
* \param[in] allocator allocator to be used during rcl_init and rcl_shutdown
* \return RCL_RET_OK if initialization is successful, or
* RCL_RET_ALREADY_INIT if rcl_init has already been called, or
* RCL_RET_BAD_ALLOC if allocating memory failed, or
* RCL_RET_ERROR if an unspecified error occurs.
*/
RCL_PUBLIC
@ -72,21 +78,39 @@ rcl_init(int argc, char ** argv, rcl_allocator_t allocator);
* - No new work (executing callbacks) will be done in executors.
* - Currently running work in executors will be finished.
*
* \return RCL_RET_OK if shutdown is successful, otherwise RCL_RET_ERROR or
* RCL_RET_NOT_INIT if rcl_init has not yet been called
* This function does not allocate heap memory.
* This function is thread-safe, except with rcl_init().
* This function is lock-free so long as the C11's stdatomic.h function
* atomic_is_lock_free() returns true for atomic_uint_least64_t.
*
* \return RCL_RET_OK if the shutdown was completed successfully, or
* RCL_RET_NOT_INIT if rcl is not currently initialized, or
* RCL_RET_ERROR if an unspecified error occur.
*/
RCL_PUBLIC
rcl_ret_t
rcl_shutdown();
/// Returns an uint64_t number that is unique for the latest rcl_init call.
/* If called before rcl_init or after rcl_shutdown then 0 will be returned. */
/* If called before rcl_init or after rcl_shutdown then 0 will be returned.
*
* This function does not allocate memory.
* This function is thread-safe.
* This function is lock-free so long as the C11's stdatomic.h function
* atomic_is_lock_free() returns true for atomic_uint_least64_t.
*
* \return a unique id specific to this rcl instance, or 0 if not initialized.
*/
RCL_PUBLIC
uint64_t
rcl_get_instance_id();
/// Return true until rcl_shutdown is called, then false.
/* This function is thread safe. */
/// Return true if rcl is currently initialized, otherwise false.
/* This function does not allocate memory.
* This function is thread-safe.
* This function is lock-free so long as the C11's stdatomic.h function
* atomic_is_lock_free() returns true for atomic_uint_least64_t.
*/
RCL_PUBLIC
bool
rcl_ok();

View file

@ -21,6 +21,7 @@ extern "C"
#include <string.h>
#include "./common.h"
#include "./stdatomic_helper.h"
#include "rcl/error_handling.h"
@ -38,11 +39,15 @@ __clean_up_init()
size_t i;
for (i = 0; i < __rcl_argc; ++i) {
if (__rcl_argv[i]) {
// Use the old allocator.
__rcl_allocator.deallocate(__rcl_argv[i], __rcl_allocator.state);
}
}
// Use the old allocator.
__rcl_allocator.deallocate(__rcl_argv, __rcl_allocator.state);
}
__rcl_argc = 0;
__rcl_argv = NULL;
rcl_atomic_store(&__rcl_instance_id, 0);
rcl_atomic_store(&__rcl_is_initialized, false);
}
@ -50,16 +55,28 @@ __clean_up_init()
rcl_ret_t
rcl_init(int argc, char ** argv, rcl_allocator_t allocator)
{
rcl_ret_t fail_ret = RCL_RET_ERROR;
if (argc > 0) {
RCL_CHECK_ARGUMENT_FOR_NULL(argv, RCL_RET_INVALID_ARGUMENT);
}
RCL_CHECK_FOR_NULL_WITH_MSG(
allocator.allocate,
"invalid allocator, allocate not set", return RCL_RET_INVALID_ARGUMENT);
RCL_CHECK_FOR_NULL_WITH_MSG(
allocator.deallocate,
"invalid allocator, deallocate not set", return RCL_RET_INVALID_ARGUMENT);
if (rcl_atomic_exchange_bool(&__rcl_is_initialized, true)) {
RCL_SET_ERROR_MSG("rcl_init called while already initialized");
return RCL_RET_ALREADY_INIT;
}
// TODO(wjwwood): Remove rcl specific command line arguments.
// For now just copy the argc and argv.
__rcl_allocator = allocator; // Set the new allocator.
__rcl_argc = argc;
__rcl_argv = (char **)__rcl_allocator.allocate(sizeof(char *) * argc, __rcl_allocator.state);
if (!__rcl_argv) {
RCL_SET_ERROR_MSG("allocation failed");
fail_ret = RCL_RET_BAD_ALLOC;
goto fail;
}
memset(__rcl_argv, 0, sizeof(char **) * argc);
@ -78,7 +95,7 @@ rcl_init(int argc, char ** argv, rcl_allocator_t allocator)
return RCL_RET_OK;
fail:
__clean_up_init();
return RCL_RET_ERROR;
return fail_ret;
}
rcl_ret_t

View file

@ -79,4 +79,16 @@ if(AMENT_ENABLE_TESTING)
endif()
target_link_libraries(test_common ${PROJECT_NAME} ${extra_test_libraries})
endif()
ament_add_gtest(test_rcl rcl/test_rcl.cpp ENV ${extra_memory_tools_env})
if(TARGET test_rcl)
target_include_directories(test_rcl PUBLIC
${rcl_interfaces_INCLUDE_DIRS}
${rmw_INCLUDE_DIRS}
)
if(NOT WIN32)
set_target_properties(test_rcl PROPERTIES COMPILE_FLAGS "-std=c++11")
endif()
target_link_libraries(test_rcl ${PROJECT_NAME} ${extra_test_libraries})
endif()
endif()

View file

@ -63,7 +63,7 @@ custom_malloc(size_t size)
}
void * memory = malloc(size);
MALLOC_PRINTF(
"malloc expected(%s): %p %zu\n", malloc_expected ? "true " : "false", memory, size);
"malloc expected(%s): %p %llu\n", malloc_expected ? "true " : "false", memory, size);
return memory;
}
@ -103,7 +103,7 @@ custom_realloc(void * memory_in, size_t size)
}
void * memory = realloc(memory_in, size);
MALLOC_PRINTF(
"realloc expected(%s): %p %p %zu\n",
"realloc expected(%s): %p %p %llu\n",
malloc_expected ? "true " : "false", memory_in, memory, size);
return memory;
}

180
rcl/test/rcl/test_rcl.cpp Normal file
View file

@ -0,0 +1,180 @@
// Copyright 2015 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <gtest/gtest.h>
#include "rcl/rcl.h"
#include "../memory_tools.hpp"
#include "rcl/error_handling.h"
class TestRCLFixture : public::testing::Test
{
public:
void SetUp()
{
set_on_unepexcted_malloc_callback([]() {ASSERT_FALSE(true) << "UNEXPECTED MALLOC";});
set_on_unepexcted_realloc_callback([]() {ASSERT_FALSE(true) << "UNEXPECTED REALLOC";});
set_on_unepexcted_free_callback([]() {ASSERT_FALSE(true) << "UNEXPECTED FREE";});
start_memory_checking();
}
void TearDown()
{
assert_no_malloc_end();
assert_no_realloc_end();
assert_no_free_end();
stop_memory_checking();
set_on_unepexcted_malloc_callback(nullptr);
set_on_unepexcted_realloc_callback(nullptr);
set_on_unepexcted_free_callback(nullptr);
}
};
void *
failing_malloc(size_t size, void * state)
{
(void)(size);
(void)(state);
return nullptr;
}
void *
failing_realloc(void * pointer, size_t size, void * state)
{
(void)(pointer);
(void)(size);
(void)(state);
return nullptr;
}
void
failing_free(void * pointer, void * state)
{
(void)pointer;
(void)state;
return;
}
struct FakeTestArgv
{
FakeTestArgv() : argc(2)
{
this->argv = (char **)malloc(2 * sizeof(char *));
if (!this->argv) {
throw std::bad_alloc();
}
this->argv[0] = (char *)malloc(10 * sizeof(char));
sprintf(this->argv[0], "foo");
this->argv[1] = (char *)malloc(10 * sizeof(char));
sprintf(this->argv[1], "bar");
}
~FakeTestArgv()
{
if (this->argv) {
if (this->argv > 0) {
size_t unsigned_argc = this->argc;
for (size_t i = 0; i < unsigned_argc; --i) {
free(this->argv[i]);
}
}
}
free(this->argv);
}
int argc;
char ** argv;
};
/* Tests the rcl_init() and rcl_shutdown() functions.
*/
TEST_F(TestRCLFixture, test_rcl_init_and_shutdown)
{
rcl_ret_t ret;
// A shutdown before any init has been called should fail.
ret = rcl_shutdown();
EXPECT_EQ(RCL_RET_NOT_INIT, ret);
rcl_reset_error();
ASSERT_EQ(false, rcl_ok());
// If argc is not 0, but argv is, it should be an invalid argument.
ret = rcl_init(42, nullptr, rcl_get_default_allocator());
EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret);
rcl_reset_error();
ASSERT_EQ(false, rcl_ok());
// If either the allocate or deallocate function pointers are not set, it should be invalid arg.
rcl_allocator_t invalid_allocator = rcl_get_default_allocator();
invalid_allocator.allocate = nullptr;
ret = rcl_init(0, nullptr, invalid_allocator);
EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret);
rcl_reset_error();
ASSERT_EQ(false, rcl_ok());
invalid_allocator.allocate = rcl_get_default_allocator().allocate;
invalid_allocator.deallocate = nullptr;
ret = rcl_init(0, nullptr, invalid_allocator);
EXPECT_EQ(RCL_RET_INVALID_ARGUMENT, ret);
rcl_reset_error();
ASSERT_EQ(false, rcl_ok());
// If the malloc call fails (with some valid arguments to copy), it should be a bad alloc.
{
FakeTestArgv test_args;
rcl_allocator_t failing_allocator = rcl_get_default_allocator();
failing_allocator.allocate = &failing_malloc;
failing_allocator.deallocate = failing_free;
failing_allocator.reallocate = failing_realloc;
ret = rcl_init(test_args.argc, test_args.argv, failing_allocator);
EXPECT_EQ(RCL_RET_BAD_ALLOC, ret);
rcl_reset_error();
ASSERT_EQ(false, rcl_ok());
}
// If argc is 0 and argv is nullptr and the allocator is valid, it should succeed.
ret = rcl_init(0, nullptr, rcl_get_default_allocator());
EXPECT_EQ(RCL_RET_OK, ret);
ASSERT_EQ(true, rcl_ok());
// Then shutdown should work.
ret = rcl_shutdown();
EXPECT_EQ(ret, RCL_RET_OK);
ASSERT_EQ(false, rcl_ok());
// Valid argc/argv values and a valid allocator should succeed.
{
FakeTestArgv test_args;
ret = rcl_init(test_args.argc, test_args.argv, rcl_get_default_allocator());
EXPECT_EQ(RCL_RET_OK, ret);
ASSERT_EQ(true, rcl_ok());
}
// Then shutdown should work.
ret = rcl_shutdown();
EXPECT_EQ(RCL_RET_OK, ret);
ASSERT_EQ(false, rcl_ok());
// A repeat call to shutdown should not work.
ret = rcl_shutdown();
EXPECT_EQ(RCL_RET_NOT_INIT, ret);
rcl_reset_error();
ASSERT_EQ(false, rcl_ok());
// Repeat, but valid, calls to rcl_init() should fail.
{
FakeTestArgv test_args;
ret = rcl_init(test_args.argc, test_args.argv, rcl_get_default_allocator());
EXPECT_EQ(RCL_RET_OK, ret);
ASSERT_EQ(true, rcl_ok());
ret = rcl_init(test_args.argc, test_args.argv, rcl_get_default_allocator());
EXPECT_EQ(RCL_RET_ALREADY_INIT, ret);
rcl_reset_error();
ASSERT_EQ(true, rcl_ok());
}
// But shutdown should still work.
ret = rcl_shutdown();
EXPECT_EQ(ret, RCL_RET_OK);
ASSERT_EQ(false, rcl_ok());
}