/* * Copyright 2017 The Cartographer Authors * * 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 #include #include "cartographer/cloud/client/map_builder_stub.h" #include "cartographer/cloud/internal/map_builder_server.h" #include "cartographer/cloud/map_builder_server_options.h" #include "cartographer/io/internal/in_memory_proto_stream.h" #include "cartographer/io/internal/testing/test_helpers.h" #include "cartographer/io/proto_stream.h" #include "cartographer/io/proto_stream_deserializer.h" #include "cartographer/mapping/internal/local_slam_result_data.h" #include "cartographer/mapping/internal/testing/mock_map_builder.h" #include "cartographer/mapping/internal/testing/mock_pose_graph.h" #include "cartographer/mapping/internal/testing/mock_trajectory_builder.h" #include "cartographer/mapping/internal/testing/test_helpers.h" #include "cartographer/mapping/map_builder.h" #include "glog/logging.h" #include "gmock/gmock.h" #include "gtest/gtest.h" using ::cartographer::io::testing::ProtoReaderFromStrings; using ::cartographer::mapping::CreateMapBuilder; using ::cartographer::mapping::MapBuilderInterface; using ::cartographer::mapping::PoseGraphInterface; using ::cartographer::mapping::TrajectoryBuilderInterface; using ::cartographer::mapping::testing::MockMapBuilder; using ::cartographer::mapping::testing::MockPoseGraph; using ::cartographer::mapping::testing::MockTrajectoryBuilder; using ::testing::_; using SensorId = ::cartographer::mapping::TrajectoryBuilderInterface::SensorId; namespace cartographer { namespace cloud { namespace { constexpr char kClientId[] = "CLIENT_ID"; const SensorId kImuSensorId{SensorId::SensorType::IMU, "imu"}; const SensorId kRangeSensorId{SensorId::SensorType::RANGE, "range"}; constexpr double kDuration = 4.; // Seconds. constexpr double kTimeStep = 0.1; // Seconds. constexpr double kTravelDistance = 1.2; // Meters. constexpr char kSerializationHeaderProtoString[] = "format_version: 2"; constexpr char kPoseGraphProtoString[] = R"(pose_graph { trajectory: { trajectory_id: 0 node: {} submap: {} } })"; constexpr char kAllTrajectoryBuilderOptionsProtoString[] = R"(all_trajectory_builder_options { options_with_sensor_ids: {} })"; constexpr char kSubmapProtoString[] = "submap {}"; constexpr char kNodeProtoString[] = "node {}"; constexpr char kTrajectoryDataProtoString[] = "trajectory_data {}"; constexpr char kImuDataProtoString[] = "imu_data {}"; constexpr char kOdometryDataProtoString[] = "odometry_data {}"; constexpr char kFixedFramePoseDataProtoString[] = "fixed_frame_pose_data {}"; constexpr char kLandmarkDataProtoString[] = "landmark_data {}"; template class ClientServerTestBase : public T { protected: void SetUp() override { // TODO(cschuet): Due to the hard-coded addresses these tests will become // flaky when run in parallel. const std::string kMapBuilderServerLua = R"text( include "map_builder_server.lua" MAP_BUILDER.use_trajectory_builder_2d = true MAP_BUILDER.pose_graph.optimize_every_n_nodes = 0 MAP_BUILDER_SERVER.num_event_threads = 1 MAP_BUILDER_SERVER.num_grpc_threads = 1 MAP_BUILDER_SERVER.uplink_server_address = "" MAP_BUILDER_SERVER.server_address = "0.0.0.0:50051" return MAP_BUILDER_SERVER)text"; auto map_builder_server_parameters = mapping::testing::ResolveLuaParameters(kMapBuilderServerLua); map_builder_server_options_ = CreateMapBuilderServerOptions(map_builder_server_parameters.get()); const std::string kUploadingMapBuilderServerLua = R"text( include "map_builder_server.lua" MAP_BUILDER.use_trajectory_builder_2d = true MAP_BUILDER.pose_graph.optimize_every_n_nodes = 0 MAP_BUILDER_SERVER.num_event_threads = 1 MAP_BUILDER_SERVER.num_grpc_threads = 1 MAP_BUILDER_SERVER.uplink_server_address = "localhost:50051" MAP_BUILDER_SERVER.server_address = "0.0.0.0:50052" MAP_BUILDER_SERVER.upload_batch_size = 1 return MAP_BUILDER_SERVER)text"; auto uploading_map_builder_server_parameters = mapping::testing::ResolveLuaParameters(kUploadingMapBuilderServerLua); uploading_map_builder_server_options_ = CreateMapBuilderServerOptions( uploading_map_builder_server_parameters.get()); EXPECT_NE(map_builder_server_options_.server_address(), uploading_map_builder_server_options_.server_address()); const std::string kTrajectoryBuilderLua = R"text( include "trajectory_builder.lua" TRAJECTORY_BUILDER.trajectory_builder_2d.use_imu_data = false TRAJECTORY_BUILDER.trajectory_builder_2d.submaps.num_range_data = 4 return TRAJECTORY_BUILDER)text"; auto trajectory_builder_parameters = mapping::testing::ResolveLuaParameters(kTrajectoryBuilderLua); trajectory_builder_options_ = mapping::CreateTrajectoryBuilderOptions( trajectory_builder_parameters.get()); number_of_insertion_results_ = 0; local_slam_result_callback_ = [this](int, common::Time, transform::Rigid3d local_pose, sensor::RangeData, std::unique_ptr< const mapping::TrajectoryBuilderInterface::InsertionResult> insertion_result) { std::unique_lock lock(local_slam_result_mutex_); if (insertion_result) { ++number_of_insertion_results_; } local_slam_result_poses_.push_back(local_pose); lock.unlock(); local_slam_result_condition_.notify_all(); }; } void InitializeRealServer() { auto map_builder = CreateMapBuilder(map_builder_server_options_.map_builder_options()); server_ = absl::make_unique(map_builder_server_options_, std::move(map_builder)); EXPECT_TRUE(server_ != nullptr); } void InitializeRealUploadingServer() { auto map_builder = CreateMapBuilder( uploading_map_builder_server_options_.map_builder_options()); uploading_server_ = absl::make_unique( uploading_map_builder_server_options_, std::move(map_builder)); EXPECT_TRUE(uploading_server_ != nullptr); } void InitializeServerWithMockMapBuilder() { auto mock_map_builder = absl::make_unique(); mock_map_builder_ = mock_map_builder.get(); mock_pose_graph_ = absl::make_unique(); EXPECT_CALL(*mock_map_builder_, pose_graph()) .WillOnce(::testing::Return(mock_pose_graph_.get())); EXPECT_CALL(*mock_pose_graph_, SetGlobalSlamOptimizationCallback(_)); server_ = absl::make_unique(map_builder_server_options_, std::move(mock_map_builder)); EXPECT_TRUE(server_ != nullptr); mock_trajectory_builder_ = absl::make_unique(); } void InitializeStub() { stub_ = absl::make_unique( map_builder_server_options_.server_address(), kClientId); EXPECT_TRUE(stub_ != nullptr); } void InitializeStubForUploadingServer() { stub_for_uploading_server_ = absl::make_unique( uploading_map_builder_server_options_.server_address(), kClientId); EXPECT_TRUE(stub_for_uploading_server_ != nullptr); } void SetOptionsToTSDF2D() { trajectory_builder_options_.mutable_trajectory_builder_2d_options() ->mutable_submaps_options() ->mutable_range_data_inserter_options() ->set_range_data_inserter_type( ::cartographer::mapping::proto::RangeDataInserterOptions:: TSDF_INSERTER_2D); trajectory_builder_options_.mutable_trajectory_builder_2d_options() ->mutable_submaps_options() ->mutable_grid_options_2d() ->set_grid_type(::cartographer::mapping::proto::GridOptions2D::TSDF); trajectory_builder_options_.mutable_trajectory_builder_2d_options() ->mutable_ceres_scan_matcher_options() ->set_occupied_space_weight(10.0); map_builder_server_options_.mutable_map_builder_options() ->mutable_pose_graph_options() ->mutable_constraint_builder_options() ->mutable_ceres_scan_matcher_options() ->set_occupied_space_weight(50.0); uploading_map_builder_server_options_.mutable_map_builder_options() ->mutable_pose_graph_options() ->mutable_constraint_builder_options() ->mutable_ceres_scan_matcher_options() ->set_occupied_space_weight(50.0); } void WaitForLocalSlamResults(size_t size) { std::unique_lock lock(local_slam_result_mutex_); local_slam_result_condition_.wait( lock, [&] { return local_slam_result_poses_.size() >= size; }); } void WaitForLocalSlamResultUploads(size_t size) { while (stub_->pose_graph()->GetTrajectoryNodePoses().size() < size) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } proto::MapBuilderServerOptions map_builder_server_options_; proto::MapBuilderServerOptions uploading_map_builder_server_options_; MockMapBuilder* mock_map_builder_; std::unique_ptr mock_pose_graph_; std::unique_ptr mock_trajectory_builder_; ::cartographer::mapping::proto::TrajectoryBuilderOptions trajectory_builder_options_; std::unique_ptr server_; std::unique_ptr uploading_server_; std::unique_ptr stub_; std::unique_ptr stub_for_uploading_server_; TrajectoryBuilderInterface::LocalSlamResultCallback local_slam_result_callback_; std::condition_variable local_slam_result_condition_; std::condition_variable local_slam_result_upload_condition_; std::mutex local_slam_result_mutex_; std::mutex local_slam_result_upload_mutex_; std::vector local_slam_result_poses_; int number_of_insertion_results_; }; class ClientServerTest : public ClientServerTestBase<::testing::Test> {}; class ClientServerTestByGridType : public ClientServerTestBase< ::testing::TestWithParam<::cartographer::mapping::GridType>> {}; INSTANTIATE_TEST_CASE_P( ClientServerTestByGridType, ClientServerTestByGridType, ::testing::Values(::cartographer::mapping::GridType::PROBABILITY_GRID, ::cartographer::mapping::GridType::TSDF)); TEST_F(ClientServerTest, StartAndStopServer) { InitializeRealServer(); server_->Start(); server_->Shutdown(); } TEST_P(ClientServerTestByGridType, AddTrajectoryBuilder) { if (GetParam() == ::cartographer::mapping::GridType::TSDF) { SetOptionsToTSDF2D(); } InitializeRealServer(); server_->Start(); InitializeStub(); int trajectory_id = stub_->AddTrajectoryBuilder( {kImuSensorId}, trajectory_builder_options_, nullptr); EXPECT_FALSE(stub_->pose_graph()->IsTrajectoryFinished(trajectory_id)); stub_->FinishTrajectory(trajectory_id); EXPECT_TRUE(stub_->pose_graph()->IsTrajectoryFinished(trajectory_id)); EXPECT_FALSE(stub_->pose_graph()->IsTrajectoryFrozen(trajectory_id)); server_->Shutdown(); } TEST_P(ClientServerTestByGridType, AddTrajectoryBuilderWithMock) { if (GetParam() == ::cartographer::mapping::GridType::TSDF) { SetOptionsToTSDF2D(); } InitializeServerWithMockMapBuilder(); server_->Start(); InitializeStub(); std::set expected_sensor_ids = {kImuSensorId}; EXPECT_CALL( *mock_map_builder_, AddTrajectoryBuilder(::testing::ContainerEq(expected_sensor_ids), _, _)) .WillOnce(::testing::Return(3)); EXPECT_CALL(*mock_map_builder_, GetTrajectoryBuilder(_)) .WillRepeatedly(::testing::Return(nullptr)); int trajectory_id = stub_->AddTrajectoryBuilder( expected_sensor_ids, trajectory_builder_options_, nullptr); EXPECT_EQ(trajectory_id, 3); EXPECT_CALL(*mock_map_builder_, FinishTrajectory(trajectory_id)); stub_->FinishTrajectory(trajectory_id); server_->Shutdown(); } TEST_P(ClientServerTestByGridType, AddSensorData) { if (GetParam() == ::cartographer::mapping::GridType::TSDF) { SetOptionsToTSDF2D(); } trajectory_builder_options_.mutable_trajectory_builder_2d_options() ->set_use_imu_data(true); InitializeRealServer(); server_->Start(); InitializeStub(); int trajectory_id = stub_->AddTrajectoryBuilder( {kImuSensorId}, trajectory_builder_options_, nullptr); TrajectoryBuilderInterface* trajectory_stub = stub_->GetTrajectoryBuilder(trajectory_id); sensor::ImuData imu_data{common::FromUniversal(42), Eigen::Vector3d(0., 0., 9.8), Eigen::Vector3d::Zero()}; trajectory_stub->AddSensorData(kImuSensorId.id, imu_data); stub_->FinishTrajectory(trajectory_id); server_->Shutdown(); } TEST_P(ClientServerTestByGridType, AddSensorDataWithMock) { if (GetParam() == ::cartographer::mapping::GridType::TSDF) { SetOptionsToTSDF2D(); } InitializeServerWithMockMapBuilder(); server_->Start(); InitializeStub(); std::set expected_sensor_ids = {kImuSensorId}; EXPECT_CALL( *mock_map_builder_, AddTrajectoryBuilder(::testing::ContainerEq(expected_sensor_ids), _, _)) .WillOnce(::testing::Return(3)); int trajectory_id = stub_->AddTrajectoryBuilder( expected_sensor_ids, trajectory_builder_options_, nullptr); EXPECT_EQ(trajectory_id, 3); EXPECT_CALL(*mock_map_builder_, GetTrajectoryBuilder(_)) .WillRepeatedly(::testing::Return(mock_trajectory_builder_.get())); mapping::TrajectoryBuilderInterface* trajectory_stub = stub_->GetTrajectoryBuilder(trajectory_id); sensor::ImuData imu_data{common::FromUniversal(42), Eigen::Vector3d(0., 0., 9.8), Eigen::Vector3d::Zero()}; EXPECT_CALL(*mock_trajectory_builder_, AddSensorData(::testing::StrEq(kImuSensorId.id), ::testing::Matcher(_))) .WillOnce(::testing::Return()); trajectory_stub->AddSensorData(kImuSensorId.id, imu_data); EXPECT_CALL(*mock_map_builder_, FinishTrajectory(trajectory_id)); stub_->FinishTrajectory(trajectory_id); server_->Shutdown(); } TEST_P(ClientServerTestByGridType, LocalSlam2D) { if (GetParam() == ::cartographer::mapping::GridType::TSDF) { SetOptionsToTSDF2D(); } InitializeRealServer(); server_->Start(); InitializeStub(); EXPECT_TRUE(stub_->pose_graph()->GetTrajectoryStates().empty()); int trajectory_id = stub_->AddTrajectoryBuilder({kRangeSensorId}, trajectory_builder_options_, local_slam_result_callback_); TrajectoryBuilderInterface* trajectory_stub = stub_->GetTrajectoryBuilder(trajectory_id); const auto measurements = mapping::testing::GenerateFakeRangeMeasurements( kTravelDistance, kDuration, kTimeStep); for (const auto& measurement : measurements) { trajectory_stub->AddSensorData(kRangeSensorId.id, measurement); } WaitForLocalSlamResults(measurements.size()); EXPECT_EQ(stub_->pose_graph()->GetTrajectoryStates().at(trajectory_id), PoseGraphInterface::TrajectoryState::ACTIVE); stub_->FinishTrajectory(trajectory_id); stub_->pose_graph()->RunFinalOptimization(); EXPECT_EQ(stub_->pose_graph()->GetTrajectoryStates().at(trajectory_id), PoseGraphInterface::TrajectoryState::FINISHED); EXPECT_EQ(local_slam_result_poses_.size(), measurements.size()); EXPECT_NEAR(kTravelDistance, (local_slam_result_poses_.back().translation() - local_slam_result_poses_.front().translation()) .norm(), 0.1 * kTravelDistance); server_->Shutdown(); } TEST_P(ClientServerTestByGridType, LocalSlamAndDelete2D) { if (GetParam() == ::cartographer::mapping::GridType::TSDF) { SetOptionsToTSDF2D(); } InitializeRealServer(); server_->Start(); InitializeStub(); int trajectory_id = stub_->AddTrajectoryBuilder({kRangeSensorId}, trajectory_builder_options_, local_slam_result_callback_); TrajectoryBuilderInterface* trajectory_stub = stub_->GetTrajectoryBuilder(trajectory_id); const auto measurements = mapping::testing::GenerateFakeRangeMeasurements( kTravelDistance, kDuration, kTimeStep); for (const auto& measurement : measurements) { trajectory_stub->AddSensorData(kRangeSensorId.id, measurement); } WaitForLocalSlamResults(measurements.size()); stub_->pose_graph()->RunFinalOptimization(); EXPECT_EQ(stub_->pose_graph()->GetTrajectoryStates().at(trajectory_id), PoseGraphInterface::TrajectoryState::ACTIVE); EXPECT_GT(stub_->pose_graph()->GetAllSubmapPoses().size(), 0); EXPECT_GT(stub_->pose_graph()->GetTrajectoryNodePoses().size(), 0); stub_->FinishTrajectory(trajectory_id); stub_->pose_graph()->DeleteTrajectory(trajectory_id); stub_->pose_graph()->RunFinalOptimization(); EXPECT_EQ(stub_->pose_graph()->GetTrajectoryStates().at(trajectory_id), PoseGraphInterface::TrajectoryState::DELETED); EXPECT_EQ(stub_->pose_graph()->GetAllSubmapPoses().size(), 0); EXPECT_EQ(stub_->pose_graph()->GetTrajectoryNodePoses().size(), 0); server_->Shutdown(); } TEST_F(ClientServerTest, GlobalSlam3D) { map_builder_server_options_.mutable_map_builder_options() ->set_use_trajectory_builder_2d(false); map_builder_server_options_.mutable_map_builder_options() ->set_use_trajectory_builder_3d(true); map_builder_server_options_.mutable_map_builder_options() ->mutable_pose_graph_options() ->set_optimize_every_n_nodes(3); trajectory_builder_options_.mutable_trajectory_builder_3d_options() ->mutable_motion_filter_options() ->set_max_distance_meters(0); trajectory_builder_options_.mutable_trajectory_builder_3d_options() ->mutable_motion_filter_options() ->set_max_angle_radians(0); InitializeRealServer(); server_->Start(); InitializeStub(); int trajectory_id = stub_->AddTrajectoryBuilder( {kRangeSensorId, kImuSensorId}, trajectory_builder_options_, local_slam_result_callback_); TrajectoryBuilderInterface* trajectory_stub = stub_->GetTrajectoryBuilder(trajectory_id); const auto measurements = mapping::testing::GenerateFakeRangeMeasurements( kTravelDistance, kDuration, kTimeStep); for (const auto& measurement : measurements) { sensor::ImuData imu_data{ measurement.time - common::FromSeconds(kTimeStep / 2), Eigen::Vector3d(0., 0., 9.8), Eigen::Vector3d::Zero()}; trajectory_stub->AddSensorData(kImuSensorId.id, imu_data); trajectory_stub->AddSensorData(kRangeSensorId.id, measurement); } // First range data will not call back while initializing PoseExtrapolator, so // expect one less. WaitForLocalSlamResults(measurements.size() - 1); stub_->FinishTrajectory(trajectory_id); EXPECT_NEAR(kTravelDistance, (local_slam_result_poses_.back().translation() - local_slam_result_poses_.front().translation()) .norm(), 0.1 * kTravelDistance); server_->Shutdown(); } TEST_P(ClientServerTestByGridType, StartAndStopUploadingServerAndServer) { if (GetParam() == ::cartographer::mapping::GridType::TSDF) { SetOptionsToTSDF2D(); } InitializeRealServer(); server_->Start(); InitializeRealUploadingServer(); uploading_server_->Start(); uploading_server_->Shutdown(); server_->Shutdown(); } TEST_P(ClientServerTestByGridType, AddTrajectoryBuilderWithUploadingServer) { if (GetParam() == ::cartographer::mapping::GridType::TSDF) { SetOptionsToTSDF2D(); } InitializeRealServer(); server_->Start(); InitializeRealUploadingServer(); uploading_server_->Start(); InitializeStub(); InitializeStubForUploadingServer(); int trajectory_id = stub_for_uploading_server_->AddTrajectoryBuilder( {kImuSensorId}, trajectory_builder_options_, nullptr); EXPECT_FALSE(stub_for_uploading_server_->pose_graph()->IsTrajectoryFinished( trajectory_id)); EXPECT_FALSE(stub_->pose_graph()->IsTrajectoryFinished(trajectory_id)); stub_for_uploading_server_->FinishTrajectory(trajectory_id); EXPECT_TRUE(stub_for_uploading_server_->pose_graph()->IsTrajectoryFinished( trajectory_id)); EXPECT_TRUE(stub_->pose_graph()->IsTrajectoryFinished(trajectory_id)); EXPECT_FALSE(stub_for_uploading_server_->pose_graph()->IsTrajectoryFrozen( trajectory_id)); EXPECT_FALSE(stub_->pose_graph()->IsTrajectoryFrozen(trajectory_id)); uploading_server_->Shutdown(); server_->Shutdown(); } TEST_P(ClientServerTestByGridType, LocalSlam2DWithUploadingServer) { if (GetParam() == ::cartographer::mapping::GridType::TSDF) { SetOptionsToTSDF2D(); } InitializeRealServer(); server_->Start(); InitializeStub(); InitializeRealUploadingServer(); uploading_server_->Start(); InitializeStubForUploadingServer(); int trajectory_id = stub_for_uploading_server_->AddTrajectoryBuilder( {kRangeSensorId}, trajectory_builder_options_, local_slam_result_callback_); TrajectoryBuilderInterface* trajectory_stub = stub_for_uploading_server_->GetTrajectoryBuilder(trajectory_id); const auto measurements = mapping::testing::GenerateFakeRangeMeasurements( kTravelDistance, kDuration, kTimeStep); for (const auto& measurement : measurements) { trajectory_stub->AddSensorData(kRangeSensorId.id, measurement); } WaitForLocalSlamResults(measurements.size()); WaitForLocalSlamResultUploads(number_of_insertion_results_); std::queue> chunks; io::ForwardingProtoStreamWriter writer( [&chunks](const google::protobuf::Message* proto) -> bool { if (!proto) { return true; } std::unique_ptr p(proto->New()); p->CopyFrom(*proto); chunks.push(std::move(p)); return true; }); stub_->SerializeState(false, &writer); CHECK(writer.Close()); // Ensure it can be read. io::InMemoryProtoStreamReader reader(std::move(chunks)); io::ProtoStreamDeserializer deserializer(&reader); EXPECT_EQ(deserializer.pose_graph().trajectory_size(), 1); stub_for_uploading_server_->FinishTrajectory(trajectory_id); EXPECT_EQ(local_slam_result_poses_.size(), measurements.size()); EXPECT_NEAR(kTravelDistance, (local_slam_result_poses_.back().translation() - local_slam_result_poses_.front().translation()) .norm(), 0.1 * kTravelDistance); uploading_server_->Shutdown(); server_->Shutdown(); } TEST_P(ClientServerTestByGridType, LocalSlam2DUplinkServerRestarting) { if (GetParam() == ::cartographer::mapping::GridType::TSDF) { SetOptionsToTSDF2D(); } InitializeRealServer(); server_->Start(); InitializeStub(); InitializeRealUploadingServer(); uploading_server_->Start(); InitializeStubForUploadingServer(); int trajectory_id = stub_for_uploading_server_->AddTrajectoryBuilder( {kRangeSensorId}, trajectory_builder_options_, local_slam_result_callback_); TrajectoryBuilderInterface* trajectory_stub = stub_for_uploading_server_->GetTrajectoryBuilder(trajectory_id); const auto measurements = mapping::testing::GenerateFakeRangeMeasurements( kTravelDistance, kDuration, kTimeStep); // Insert half of the measurements. for (unsigned int i = 0; i < measurements.size() / 2; ++i) { trajectory_stub->AddSensorData(kRangeSensorId.id, measurements.at(i)); } WaitForLocalSlamResults(measurements.size() / 2); WaitForLocalSlamResultUploads(number_of_insertion_results_); // Simulate a cloud server restart. LOG(INFO) << "Simulating server restart."; constexpr int kUplinkTrajectoryId = 0; stub_->FinishTrajectory(kUplinkTrajectoryId); server_->Shutdown(); server_->WaitForShutdown(); InitializeRealServer(); server_->Start(); InitializeStub(); // Insert the second half of the measurements. for (unsigned int i = measurements.size() / 2; i < measurements.size(); ++i) { trajectory_stub->AddSensorData(kRangeSensorId.id, measurements.at(i)); } WaitForLocalSlamResults(measurements.size() / 2); WaitForLocalSlamResultUploads(2); stub_for_uploading_server_->FinishTrajectory(trajectory_id); uploading_server_->Shutdown(); uploading_server_->WaitForShutdown(); server_->Shutdown(); server_->WaitForShutdown(); } TEST_F(ClientServerTest, DelayedConnectionToUplinkServer) { InitializeRealUploadingServer(); uploading_server_->Start(); InitializeStubForUploadingServer(); int trajectory_id = stub_for_uploading_server_->AddTrajectoryBuilder( {kRangeSensorId}, trajectory_builder_options_, local_slam_result_callback_); TrajectoryBuilderInterface* trajectory_stub = stub_for_uploading_server_->GetTrajectoryBuilder(trajectory_id); const auto measurements = mapping::testing::GenerateFakeRangeMeasurements( kTravelDistance, kDuration, kTimeStep); // Insert the first measurement. trajectory_stub->AddSensorData(kRangeSensorId.id, measurements.at(0)); WaitForLocalSlamResults(1); LOG(INFO) << "Delayed start of uplink server."; InitializeRealServer(); server_->Start(); InitializeStub(); // Insert all other measurements. for (unsigned int i = 1; i < measurements.size(); ++i) { trajectory_stub->AddSensorData(kRangeSensorId.id, measurements.at(i)); } WaitForLocalSlamResults(measurements.size()); WaitForLocalSlamResultUploads(2); stub_for_uploading_server_->FinishTrajectory(trajectory_id); uploading_server_->Shutdown(); uploading_server_->WaitForShutdown(); server_->Shutdown(); server_->WaitForShutdown(); } TEST_P(ClientServerTestByGridType, LoadStateAndDelete) { if (GetParam() == ::cartographer::mapping::GridType::TSDF) { SetOptionsToTSDF2D(); } InitializeRealServer(); server_->Start(); InitializeStub(); // Load text proto into in_memory_reader. auto reader = ProtoReaderFromStrings(kSerializationHeaderProtoString, { kPoseGraphProtoString, kAllTrajectoryBuilderOptionsProtoString, kSubmapProtoString, kNodeProtoString, kImuDataProtoString, kOdometryDataProtoString, kLandmarkDataProtoString, }); auto trajectory_remapping = stub_->LoadState(reader.get(), true); int expected_trajectory_id = 0; EXPECT_EQ(trajectory_remapping.size(), 1); EXPECT_EQ(trajectory_remapping.at(0), expected_trajectory_id); stub_->pose_graph()->RunFinalOptimization(); EXPECT_TRUE(stub_->pose_graph()->IsTrajectoryFrozen(expected_trajectory_id)); EXPECT_FALSE( stub_->pose_graph()->IsTrajectoryFinished(expected_trajectory_id)); for (const auto& entry : trajectory_remapping) { int trajectory_id = entry.second; stub_->pose_graph()->DeleteTrajectory(trajectory_id); stub_->pose_graph()->RunFinalOptimization(); EXPECT_EQ(stub_->pose_graph()->GetTrajectoryStates().at(trajectory_id), PoseGraphInterface::TrajectoryState::DELETED); } server_->Shutdown(); } TEST_P(ClientServerTestByGridType, LoadUnfrozenStateAndDelete) { if (GetParam() == ::cartographer::mapping::GridType::TSDF) { SetOptionsToTSDF2D(); } InitializeRealServer(); server_->Start(); InitializeStub(); // Load text proto into in_memory_reader. auto reader = ProtoReaderFromStrings(kSerializationHeaderProtoString, { kPoseGraphProtoString, kAllTrajectoryBuilderOptionsProtoString, kSubmapProtoString, kNodeProtoString, kImuDataProtoString, kOdometryDataProtoString, kLandmarkDataProtoString, }); auto trajectory_remapping = stub_->LoadState(reader.get(), false /* load_frozen_state */); int expected_trajectory_id = 0; EXPECT_EQ(trajectory_remapping.size(), 1); EXPECT_EQ(trajectory_remapping.at(0), expected_trajectory_id); stub_->pose_graph()->RunFinalOptimization(); EXPECT_FALSE(stub_->pose_graph()->IsTrajectoryFrozen(expected_trajectory_id)); EXPECT_FALSE( stub_->pose_graph()->IsTrajectoryFinished(expected_trajectory_id)); EXPECT_EQ( stub_->pose_graph()->GetTrajectoryStates().at(expected_trajectory_id), PoseGraphInterface::TrajectoryState::ACTIVE); stub_->FinishTrajectory(expected_trajectory_id); EXPECT_EQ( stub_->pose_graph()->GetTrajectoryStates().at(expected_trajectory_id), PoseGraphInterface::TrajectoryState::FINISHED); for (const auto& entry : trajectory_remapping) { int trajectory_id = entry.second; stub_->pose_graph()->DeleteTrajectory(trajectory_id); stub_->pose_graph()->RunFinalOptimization(); EXPECT_EQ(stub_->pose_graph()->GetTrajectoryStates().at(trajectory_id), PoseGraphInterface::TrajectoryState::DELETED); } server_->Shutdown(); } // TODO(gaschler): Test-cover LoadStateFromFile. TEST_P(ClientServerTestByGridType, LocalSlam2DHandlesInvalidRequests) { if (GetParam() == ::cartographer::mapping::GridType::TSDF) { SetOptionsToTSDF2D(); } InitializeRealServer(); server_->Start(); InitializeStub(); int trajectory_id = stub_->AddTrajectoryBuilder({kRangeSensorId}, trajectory_builder_options_, local_slam_result_callback_); TrajectoryBuilderInterface* trajectory_stub = stub_->GetTrajectoryBuilder(trajectory_id); const auto measurements = mapping::testing::GenerateFakeRangeMeasurements( kTravelDistance, kDuration, kTimeStep); for (const auto& measurement : measurements) { trajectory_stub->AddSensorData(kRangeSensorId.id, measurement); } WaitForLocalSlamResults(measurements.size()); stub_->pose_graph()->RunFinalOptimization(); const int kInvalidTrajectoryId = 7; stub_->pose_graph()->DeleteTrajectory(kInvalidTrajectoryId); EXPECT_FALSE(stub_->pose_graph()->IsTrajectoryFinished(kInvalidTrajectoryId)); EXPECT_FALSE(stub_->pose_graph()->IsTrajectoryFrozen(kInvalidTrajectoryId)); EXPECT_EQ(nullptr, stub_->GetTrajectoryBuilder(kInvalidTrajectoryId)); stub_->FinishTrajectory(kInvalidTrajectoryId); const mapping::SubmapId kInvalidSubmapId0{kInvalidTrajectoryId, 0}, kInvalidSubmapId1{trajectory_id, 424242}; mapping::proto::SubmapQuery::Response submap_query_response; // Expect that it returns non-empty error string. EXPECT_NE("", stub_->SubmapToProto(kInvalidSubmapId0, &submap_query_response)); EXPECT_NE("", stub_->SubmapToProto(kInvalidSubmapId1, &submap_query_response)); EXPECT_EQ(stub_->pose_graph()->GetTrajectoryStates().at(trajectory_id), PoseGraphInterface::TrajectoryState::ACTIVE); auto submap_poses = stub_->pose_graph()->GetAllSubmapPoses(); EXPECT_GT(submap_poses.size(), 0); EXPECT_GT(stub_->pose_graph()->GetTrajectoryNodePoses().size(), 0); stub_->FinishTrajectory(trajectory_id); stub_->pose_graph()->DeleteTrajectory(trajectory_id); stub_->pose_graph()->RunFinalOptimization(); mapping::SubmapId deleted_submap_id = submap_poses.begin()->id; EXPECT_NE("", stub_->SubmapToProto(deleted_submap_id, &submap_query_response)); EXPECT_EQ(stub_->pose_graph()->GetTrajectoryStates().at(trajectory_id), PoseGraphInterface::TrajectoryState::DELETED); // Make sure optimization runs with a deleted trajectory. stub_->pose_graph()->RunFinalOptimization(); server_->Shutdown(); } } // namespace } // namespace cloud } // namespace cartographer