Skip to content

Commit

Permalink
Extract process execution helper from e2e tests
Browse files Browse the repository at this point in the history
  • Loading branch information
greimela-si committed Oct 11, 2018
1 parent 1b321a3 commit 98e4be7
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 114 deletions.
152 changes: 152 additions & 0 deletions rosbag2_tests/test/rosbag2_tests/process_execution_helpers.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// Copyright 2018, Bosch Software Innovations GmbH.
//
// 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 ROSBAG2_TESTS__PROCESS_EXECUTION_HELPERS_HPP_
#define ROSBAG2_TESTS__PROCESS_EXECUTION_HELPERS_HPP_

#include <gmock/gmock.h>

#include <signal.h>
#include <stdlib.h>

#include <chrono>
#include <cstdlib>
#include <string>

using namespace ::testing; // NOLINT

#ifdef _WIN32
# include <direct.h>
# include <Windows.h>
#endif

#ifdef _WIN32
struct Process
{
PROCESS_INFORMATION process_info;
HANDLE job_handle;
};
using ProcessHandle = Process;
#else
using ProcessHandle = int;
#endif

#ifdef _WIN32
PROCESS_INFORMATION create_process(TCHAR * command, const char * path = nullptr)
{
STARTUPINFO start_up_info{};
PROCESS_INFORMATION process_info{};

CreateProcess(
nullptr,
command,
nullptr,
nullptr,
false,
0,
nullptr,
path,
&start_up_info,
&process_info);

return process_info;
}

void close_process_handles(const PROCESS_INFORMATION & process)
{
CloseHandle(process.hProcess);
CloseHandle(process.hThread);
}

void const_char_to_tchar(const char * source, TCHAR * destination)
{
size_t length = strlen(source);
memcpy(destination, source, length + 1);
}
#endif

int execute_and_wait_until_completion(const std::string & command, const std::string & path)
{
#ifdef _WIN32
TCHAR * command_char = new TCHAR[strlen(command.c_str()) + 1];
const_char_to_tchar(command.c_str(), command_char);

auto process = create_process(command_char, path.c_str());
DWORD exit_code = 259; // 259 is the code one gets if the proces is still active.
while (exit_code == 259) {
GetExitCodeProcess(process.hProcess, &exit_code);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
close_process_handles(process);

delete[] command_char;
return static_cast<int>(exit_code);
#else
chdir(path.c_str());
auto exitcode = std::system(command.c_str());
return WEXITSTATUS(exitcode);
#endif
}

ProcessHandle start_execution(const std::string & command)
{
#ifdef _WIN32
auto h_job = CreateJobObject(nullptr, nullptr);
JOBOBJECT_EXTENDED_LIMIT_INFORMATION info{};
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
SetInformationJobObject(h_job, JobObjectExtendedLimitInformation, &info, sizeof(info));

TCHAR * command_char = new TCHAR[strlen(command.c_str()) + 1];
const_char_to_tchar(command.c_str(), command_char);

auto process_info = create_process(command_char);

AssignProcessToJobObject(h_job, process_info.hProcess);
Process process;
process.process_info = process_info;
process.job_handle = h_job;

delete[] command_char;
return process;
#else
auto process_id = fork();
if (process_id == 0) {
setpgid(getpid(), getpid());
int return_code = system(command.c_str());

// this call will make sure that the process does execute without issues before it is killed by
// the user in the test or, in case it runs until completion, that it has correctly executed.
EXPECT_THAT(return_code, Eq(0));
}
return process_id;
#endif
}

void stop_execution(const ProcessHandle & handle)
{
#ifdef _WIN32
DWORD exit_code;
GetExitCodeProcess(handle.process_info.hProcess, &exit_code);
// 259 indicates that the process is still active: we want to make sure that the process is
// still running properly before killing it.
EXPECT_THAT(exit_code, Eq(259));

close_process_handles(handle.process_info);
CloseHandle(handle.job_handle);
#else
kill(-handle, SIGTERM);
#endif
}

#endif // ROSBAG2_TESTS__PROCESS_EXECUTION_HELPERS_HPP_
84 changes: 10 additions & 74 deletions rosbag2_tests/test/rosbag2_tests/record_fixture.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,12 @@
#include <string>
#include <vector>

#include "rclcpp/rclcpp.hpp" // rclcpp must be included before the Windows specific includes.

#ifdef _WIN32
# include <direct.h>
# include <Windows.h>
#else
# include <unistd.h>
#endif
#include "rclcpp/rclcpp.hpp"

#include "test_msgs/msg/primitives.hpp"
#include "test_msgs/msg/static_array_primitives.hpp"
#include "test_msgs/message_fixtures.hpp"
#include "rosbag2_storage/filesystem_helper.hpp"
#include "rosbag2_storage_default_plugins/sqlite/sqlite_storage.hpp"
#include "rosbag2_test_common/temporary_directory_fixture.hpp"
#include "rosbag2_test_common/publisher_manager.hpp"
Expand All @@ -43,72 +37,24 @@ using namespace ::testing; // NOLINT
using namespace std::chrono_literals; // NOLINT
using namespace rosbag2_test_common; // NOLINT

#ifdef _WIN32
using ProcessHandle = HANDLE;
#else
using ProcessHandle = int;
#endif

class RecordFixture : public TemporaryDirectoryFixture
{
public:
RecordFixture()
{
std::string system_separator = "/";
#ifdef _WIN32
system_separator = "\\";
#endif
bag_path_ = temporary_dir_path_ + system_separator + "test";
database_path_ = bag_path_ + system_separator + "test.db3";
bag_path_ = rosbag2_storage::FilesystemHelper::concat({temporary_dir_path_, "bag"});
database_path_ = rosbag2_storage::FilesystemHelper::concat({bag_path_, "bag.db3"});
std::cout << "Database " << database_path_ << " in " << temporary_dir_path_ << std::endl;
rclcpp::init(0, nullptr);
}

~RecordFixture() override
static void SetUpTestCase()
{
rclcpp::shutdown();
rclcpp::init(0, nullptr);
}

ProcessHandle start_execution(const std::string & command)
static void TearDownTestCase()
{
#ifdef _WIN32
auto h_job = CreateJobObject(nullptr, nullptr);
JOBOBJECT_EXTENDED_LIMIT_INFORMATION info{};
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
SetInformationJobObject(h_job, JobObjectExtendedLimitInformation, &info, sizeof(info));

STARTUPINFO start_up_info{};
PROCESS_INFORMATION process_info{};

size_t length = strlen(command.c_str());
TCHAR * command_char = new TCHAR[length + 1];
memcpy(command_char, command.c_str(), length + 1);

CreateProcess(
nullptr,
command_char,
nullptr,
nullptr,
false,
0,
nullptr,
temporary_dir_path_.c_str(),
&start_up_info,
&process_info);

AssignProcessToJobObject(h_job, process_info.hProcess);
CloseHandle(process_info.hProcess);
CloseHandle(process_info.hThread);
delete[] command_char;
return h_job;
#else
auto process_id = fork();
if (process_id == 0) {
setpgid(getpid(), getpid());
system(command.c_str());
}
return process_id;
#endif
rclcpp::shutdown();
}

void wait_for_db()
Expand All @@ -125,17 +71,8 @@ class RecordFixture : public TemporaryDirectoryFixture
}
}

void stop_execution(ProcessHandle handle)
{
#ifdef _WIN32
CloseHandle(handle);
#else
kill(-handle, SIGTERM);
#endif
}

size_t
count_stored_messages(rosbag2_storage_plugins::SqliteWrapper & db, const std::string & topic_name)
size_t count_stored_messages(
rosbag2_storage_plugins::SqliteWrapper & db, const std::string & topic_name)
{
// protect against concurrent writes (from recording) that may make the count query throw.
while (true) {
Expand Down Expand Up @@ -197,5 +134,4 @@ class RecordFixture : public TemporaryDirectoryFixture
MemoryManagement memory_management_;
};


#endif // ROSBAG2_TESTS__RECORD_FIXTURE_HPP_
64 changes: 24 additions & 40 deletions rosbag2_tests/test/rosbag2_tests/test_rosbag2_play_end_to_end.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,17 @@

#include <gmock/gmock.h>

#include <cstdlib>
#include <future>
#include <iostream>
#include <map>
#include <memory>
#include <string>
#include <vector>

#include "rclcpp/rclcpp.hpp" // rclcpp must be included before the Windows specific includes.

#ifdef _WIN32
# include <direct.h>
# include <Windows.h>
#endif
// rclcpp must be included before process_execution_helpers.hpp
#include "rclcpp/rclcpp.hpp"
#include "process_execution_helpers.hpp"

#include "test_msgs/msg/primitives.hpp"
#include "test_msgs/msg/static_array_primitives.hpp"
Expand All @@ -35,71 +33,47 @@
#include "rosbag2_test_common/subscription_manager.hpp"

using namespace ::testing; // NOLINT
using namespace std::chrono_literals; // NOLINT
using namespace rosbag2_test_common; // NOLINT

class EndToEndTestFixture : public Test
class PlayEndToEndTestFixture : public Test
{
public:
EndToEndTestFixture()
PlayEndToEndTestFixture()
{
database_path_ = _SRC_RESOURCES_DIR_PATH; // variable defined in CMakeLists.txt
rclcpp::init(0, nullptr);
sub_ = std::make_unique<SubscriptionManager>();
}

~EndToEndTestFixture() override
static void SetUpTestCase()
{
rclcpp::shutdown();
rclcpp::init(0, nullptr);
}

void execute(const std::string & command)
static void TearDownTestCase()
{
#ifdef _WIN32
size_t length = strlen(command.c_str());
TCHAR * command_char = new TCHAR[length + 1];
memcpy(command_char, command.c_str(), length + 1);

STARTUPINFO start_up_info{};
PROCESS_INFORMATION process_info{};
CreateProcess(
nullptr,
command_char,
nullptr,
nullptr,
false,
0,
nullptr,
database_path_.c_str(),
&start_up_info,
&process_info);
CloseHandle(process_info.hProcess);
CloseHandle(process_info.hThread);
delete[] command_char;
#else
chdir(database_path_.c_str());
system(command.c_str());
#endif
rclcpp::shutdown();
}

std::string database_path_;
std::unique_ptr<SubscriptionManager> sub_;
};

TEST_F(EndToEndTestFixture, play_end_to_end_test) {
TEST_F(PlayEndToEndTestFixture, play_end_to_end_test) {
sub_->add_subscription<test_msgs::msg::StaticArrayPrimitives>("/array_topic", 2);
sub_->add_subscription<test_msgs::msg::Primitives>("/test_topic", 3);

auto subscription_future = sub_->spin_subscriptions();

execute("ros2 bag play test");
auto exit_code = execute_and_wait_until_completion("ros2 bag play test", database_path_);

subscription_future.get();

auto primitive_messages = sub_->get_received_messages<test_msgs::msg::Primitives>("/test_topic");
auto array_messages = sub_->get_received_messages<test_msgs::msg::StaticArrayPrimitives>(
"/array_topic");

EXPECT_THAT(exit_code, Eq(EXIT_SUCCESS));

EXPECT_THAT(primitive_messages, SizeIs(Ge(3u)));
EXPECT_THAT(primitive_messages,
Each(Pointee(Field(&test_msgs::msg::Primitives::string_value, "test"))));
Expand All @@ -112,3 +86,13 @@ TEST_F(EndToEndTestFixture, play_end_to_end_test) {
Each(Pointee(Field(&test_msgs::msg::StaticArrayPrimitives::string_values,
ElementsAre("Complex Hello1", "Complex Hello2", "Complex Hello3")))));
}

TEST_F(PlayEndToEndTestFixture, play_fails_gracefully_if_bag_does_not_exist) {
internal::CaptureStderr();
auto exit_code =
execute_and_wait_until_completion("ros2 bag play does_not_exist", database_path_);
auto error_output = internal::GetCapturedStderr();

EXPECT_THAT(exit_code, Eq(EXIT_FAILURE));
EXPECT_THAT(error_output, HasSubstr("'does_not_exist' does not exist"));
}
Loading

0 comments on commit 98e4be7

Please sign in to comment.