Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions rcl_logging_spdlog/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,11 @@ if(BUILD_TESTING)
ament_lint_auto_find_test_dependencies()

find_package(ament_cmake_gtest REQUIRED)
find_package(rcpputils REQUIRED)
ament_add_gtest(test_logging_interface test/test_logging_interface.cpp)
if(TARGET test_logging_interface)
target_link_libraries(test_logging_interface ${PROJECT_NAME})
ament_target_dependencies(test_logging_interface rcpputils)
endif()
endif()

Expand Down
1 change: 1 addition & 0 deletions rcl_logging_spdlog/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<test_depend>rcpputils</test_depend>

<member_of_group>rcl_logging_packages</member_of_group>

Expand Down
124 changes: 124 additions & 0 deletions rcl_logging_spdlog/test/fixtures.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Copyright 2020 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.

#ifndef FIXTURES_HPP_
#define FIXTURES_HPP_

#include <rcpputils/filesystem_helper.hpp>
#include <rcutils/allocator.h>
#include <rcutils/error_handling.h>
#include <rcutils/get_env.h>
#include <rcutils/process.h>
#include <rcutils/types/string_array.h>

#include <string>

#include "gtest/gtest.h"

#ifdef _WIN32
#define popen _popen
#define pclose _pclose
#define DIR_CMD "dir /B"
#else
#define DIR_CMD "ls -d"
#endif

namespace fs = rcpputils::fs;

class AllocatorTest : public ::testing::Test
{
public:
AllocatorTest()
: allocator(rcutils_get_default_allocator()),
bad_allocator(get_bad_allocator()),
invalid_allocator(rcutils_get_zero_initialized_allocator())
{
}

rcutils_allocator_t allocator;
rcutils_allocator_t bad_allocator;
rcutils_allocator_t invalid_allocator;

private:
static rcutils_allocator_t get_bad_allocator()
{
rcutils_allocator_t bad_allocator = rcutils_get_default_allocator();
bad_allocator.allocate = AllocatorTest::bad_malloc;
bad_allocator.reallocate = AllocatorTest::bad_realloc;
return bad_allocator;
}

static void * bad_malloc(size_t, void *)
{
return nullptr;
}

static void * bad_realloc(void *, size_t, void *)
{
return nullptr;
}
};

class LoggingTest : public AllocatorTest
{
public:
LoggingTest()
: AllocatorTest()
{
}

fs::path find_single_log()
{
fs::path log_dir = get_log_dir();
std::stringstream dir_command;
dir_command << DIR_CMD << " " << (log_dir / get_expected_log_prefix()).string() << "*";

FILE * fp = popen(dir_command.str().c_str(), "r");
if (nullptr == fp) {
throw std::runtime_error("Failed to glob for log files");
}

char raw_line[2048];
char * ret = fgets(raw_line, sizeof(raw_line), fp);
pclose(fp);
if (nullptr == ret) {
throw std::runtime_error("No log files were found");
}

std::string line(raw_line);
fs::path line_path(line.substr(0, line.find_last_not_of(" \t\r\n") + 1));
// This should be changed once ros2/rcpputils#68 is resolved
return line_path.is_absolute() ? line_path : log_dir / line_path;
}

private:
std::string get_expected_log_prefix()
{
char * exe_name = rcutils_get_executable_name(allocator);
if (nullptr == exe_name) {
throw std::runtime_error("Failed to determine executable name");
}
std::stringstream prefix;
prefix << exe_name << "_" << rcutils_get_pid() << "_";
allocator.deallocate(exe_name, allocator.state);
return prefix.str();
}

fs::path get_log_dir()
{
return fs::path(rcutils_get_home_dir()) / ".ros" / "log";
}
};

#endif // FIXTURES_HPP_
141 changes: 128 additions & 13 deletions rcl_logging_spdlog/test/test_logging_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,80 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#include <rcpputils/filesystem_helper.hpp>
#include <rcpputils/get_env.hpp>
#include <rcutils/allocator.h>
#include <rcutils/env.h>
#include <rcutils/error_handling.h>
#include <rcutils/logging.h>
#include "rcl_logging_spdlog/logging_interface.h"

#include <fstream>
#include <string>

#include "fixtures.hpp"
#include "gtest/gtest.h"
#include "rcl_logging_spdlog/logging_interface.h"

static void * bad_malloc(size_t, void *)
namespace fs = rcpputils::fs;

const int logger_levels[] =
{
return nullptr;
}
RCUTILS_LOG_SEVERITY_UNSET,
RCUTILS_LOG_SEVERITY_DEBUG,
RCUTILS_LOG_SEVERITY_INFO,
RCUTILS_LOG_SEVERITY_WARN,
RCUTILS_LOG_SEVERITY_ERROR,
RCUTILS_LOG_SEVERITY_FATAL,
};

// This is a helper class that resets an environment
// variable when leaving scope
class RestoreEnvVar
{
public:
explicit RestoreEnvVar(const std::string & name)
: name_(name),
value_(rcpputils::get_env_var(name.c_str()))
{
}

~RestoreEnvVar()
{
if (!rcutils_set_env(name_.c_str(), value_.c_str())) {
std::cerr << "Failed to restore value of environment variable: " << name_ << std::endl;
}
}

TEST(logging_interface, init_invalid)
private:
const std::string name_;
const std::string value_;
};

// TODO(cottsay): Remove when ros2/rcpputils#63 is resolved
static fs::path current_path()
{
rcutils_allocator_t allocator = rcutils_get_default_allocator();
rcutils_allocator_t bad_allocator = rcutils_get_default_allocator();
rcutils_allocator_t invalid_allocator = rcutils_get_zero_initialized_allocator();
bad_allocator.allocate = bad_malloc;
char * cwd;
#ifdef _WIN32
#ifdef UNICODE
#error "rcpputils::fs does not support Unicode paths"
#endif
cwd = _getcwd(NULL, 0);
#else
cwd = getcwd(NULL, 0);
#endif
if (nullptr == cwd) {
std::error_code ec{errno, std::system_category()};
errno = 0;
throw std::system_error{ec, "cannot get current working directory"};
}

std::string ret(cwd);
free(cwd);
return fs::path(ret);
}

TEST_F(LoggingTest, init_invalid)
{
// Config files are not supported by spdlog
EXPECT_EQ(2, rcl_logging_external_initialize("anything", allocator));
rcutils_reset_error();
Expand All @@ -39,12 +95,71 @@ TEST(logging_interface, init_invalid)
rcutils_reset_error();
}

TEST(logging_interface, full_cycle)
TEST_F(LoggingTest, init_failure)
{
RestoreEnvVar home_var("HOME");
RestoreEnvVar userprofile_var("USERPROFILE");

// No home directory to write log to
ASSERT_EQ(true, rcutils_set_env("HOME", nullptr));
ASSERT_EQ(true, rcutils_set_env("USERPROFILE", nullptr));
EXPECT_EQ(2, rcl_logging_external_initialize(nullptr, allocator));
rcutils_reset_error();

// Force failure to create directories
fs::path fake_home = current_path() / "fake_home_dir";
ASSERT_TRUE(fs::create_directories(fake_home));
ASSERT_EQ(true, rcutils_set_env("HOME", fake_home.string().c_str()));

// ...fail to create .ros dir
fs::path ros_dir = fake_home / ".ros";
std::fstream(ros_dir.string(), std::ios_base::out).close();
EXPECT_EQ(2, rcl_logging_external_initialize(nullptr, allocator));
ASSERT_TRUE(fs::remove(ros_dir));

// ...fail to create .ros/log dir
ASSERT_TRUE(fs::create_directories(ros_dir));
fs::path ros_log_dir = ros_dir / "log";
std::fstream(ros_log_dir.string(), std::ios_base::out).close();
EXPECT_EQ(2, rcl_logging_external_initialize(nullptr, allocator));
ASSERT_TRUE(fs::remove(ros_log_dir));
ASSERT_TRUE(fs::remove(ros_dir));

ASSERT_TRUE(fs::remove(fake_home));
}

TEST_F(LoggingTest, full_cycle)
{
rcutils_allocator_t allocator = rcutils_get_default_allocator();
ASSERT_EQ(0, rcl_logging_external_initialize(nullptr, allocator));

// Make sure we can call initialize more than once
ASSERT_EQ(0, rcl_logging_external_initialize(nullptr, allocator));
EXPECT_EQ(0, rcl_logging_external_set_logger_level(nullptr, RCUTILS_LOG_SEVERITY_INFO));
rcl_logging_external_log(RCUTILS_LOG_SEVERITY_INFO, nullptr, "Log Message");

std::stringstream expected_log;
for (int level : logger_levels) {
EXPECT_EQ(0, rcl_logging_external_set_logger_level(nullptr, level));

for (int severity : logger_levels) {
std::stringstream ss;
ss << "Message of severity " << severity << " at level " << level;
rcl_logging_external_log(severity, nullptr, ss.str().c_str());

if (severity >= level) {
expected_log << ss.str() << std::endl;
} else if (severity == 0 && level == 10) {
// This is a special case - not sure what the right behavior is
expected_log << ss.str() << std::endl;
Comment on lines +148 to +150
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I called this out on Slack - not sure what the expected behavior should be here, so for now I'm just asserting the current behavior.

}
}
}

EXPECT_EQ(0, rcl_logging_external_shutdown());

std::string log_file_path = find_single_log().string();
std::ifstream log_file(log_file_path);
std::stringstream actual_log;
actual_log << log_file.rdbuf();
EXPECT_EQ(
expected_log.str(),
actual_log.str()) << "Unexpected log contents in " << log_file_path;
}