blob: e4d4b38551bc1bab396f21112f260d880c696509 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/cobalt/bin/app/cobalt_app.h"
#include <lib/gtest/test_loop_fixture.h>
#include <lib/sys/cpp/testing/component_context_provider.h>
#include <gmock/gmock.h>
#include "sdk/lib/sys/cpp/testing/service_directory_provider.h"
#include "src/cobalt/bin/app/activity_listener_impl.h"
#include "src/cobalt/bin/app/testapp_metrics_registry.cb.h"
#include "src/cobalt/bin/testing/fake_clock.h"
#include "src/cobalt/bin/testing/fake_http_loader.h"
#include "src/lib/cobalt/cpp/metric_event_builder.h"
#include "src/lib/files/directory.h"
#include "src/lib/files/file.h"
#include "src/lib/files/path.h"
#include "third_party/abseil-cpp/absl/strings/match.h"
#include "third_party/cobalt/src/public/testing/fake_cobalt_service.h"
namespace cobalt {
const char kTestDir[] = "/tmp/cobalt_app_test";
bool WriteFile(const std::string& file, const std::string& to_write) {
return files::WriteFile(std::string(kTestDir) + std::string("/") + file, to_write.c_str(),
to_write.length());
}
class CreateCobaltConfigTest : public gtest::TestLoopFixture {
public:
CreateCobaltConfigTest()
: ::gtest::TestLoopFixture(), context_provider_(), clock_(dispatcher()) {}
protected:
void SetUp() override {
ASSERT_TRUE(files::DeletePath(kTestDir, true));
ASSERT_TRUE(files::CreateDirectory(kTestDir));
loader_ = std::make_unique<FakeHTTPLoader>(dispatcher());
service_directory_provider_.AddService(loader_->GetHandler());
}
CobaltConfig CreateCobaltConfig(
const std::string& metrics_registry_path, const FuchsiaConfigurationData& configuration_data,
std::chrono::seconds target_interval, std::chrono::seconds min_interval,
std::chrono::seconds initial_interval, float jitter, size_t event_aggregator_backfill_days,
bool use_memory_observation_store, size_t max_bytes_per_observation_store,
const std::string& product_name, const std::string& board_name, const std::string& version) {
return CobaltApp::CreateCobaltConfig(
dispatcher(), metrics_registry_path, configuration_data, &clock_,
[this] {
fuchsia::net::http::LoaderSyncPtr loader_sync;
service_directory_provider_.service_directory()->Connect(loader_sync.NewRequest());
return loader_sync;
},
UploadScheduleConfig{target_interval, min_interval, initial_interval, jitter},
event_aggregator_backfill_days, use_memory_observation_store,
max_bytes_per_observation_store, product_name, board_name, version,
std::make_unique<ActivityListenerImpl>(dispatcher(), context_provider_.context()->svc()));
}
sys::testing::ServiceDirectoryProvider service_directory_provider_;
std::unique_ptr<FakeHTTPLoader> loader_;
sys::testing::ComponentContextProvider context_provider_;
FuchsiaSystemClock clock_;
};
TEST_F(CreateCobaltConfigTest, Devel) {
ASSERT_TRUE(WriteFile("cobalt_environment", "DEVEL"));
ASSERT_TRUE(WriteFile("config.json", "{\"release_stage\": \"DEBUG\"}"));
FuchsiaConfigurationData configuration_data(kTestDir, kTestDir);
CobaltConfig config =
CreateCobaltConfig("/pkg/data/testapp_metrics_registry.pb", configuration_data,
std::chrono::seconds(1), std::chrono::seconds(2), std::chrono::seconds(3),
0, 4, true, 1048000, "core", "x64", "0.1.2");
EXPECT_EQ(config.target_pipeline->environment(), system_data::Environment::DEVEL);
EXPECT_EQ(config.release_stage, ReleaseStage::DEBUG);
}
TEST_F(CreateCobaltConfigTest, Local) {
ASSERT_TRUE(WriteFile("cobalt_environment", "LOCAL"));
ASSERT_TRUE(WriteFile("config.json", "{\"release_stage\": \"DEBUG\"}"));
FuchsiaConfigurationData configuration_data(kTestDir, kTestDir);
CobaltConfig config =
CreateCobaltConfig("/pkg/data/testapp_metrics_registry.pb", configuration_data,
std::chrono::seconds(1), std::chrono::seconds(2), std::chrono::seconds(3),
0, 4, true, 1048000, "core", "x64", "0.1.2");
EXPECT_EQ(config.target_pipeline->environment(), system_data::Environment::LOCAL);
EXPECT_EQ(config.release_stage, ReleaseStage::DEBUG);
}
TEST_F(CreateCobaltConfigTest, GA) {
ASSERT_TRUE(WriteFile("cobalt_environment", "DEVEL"));
ASSERT_TRUE(WriteFile("config.json", "{\"release_stage\": \"GA\"}"));
FuchsiaConfigurationData configuration_data(kTestDir, kTestDir);
CobaltConfig config =
CreateCobaltConfig("/pkg/data/testapp_metrics_registry.pb", configuration_data,
std::chrono::seconds(1), std::chrono::seconds(2), std::chrono::seconds(3),
0, 4, true, 1048000, "core", "x64", "0.1.2");
EXPECT_EQ(config.target_pipeline->environment(), system_data::Environment::DEVEL);
EXPECT_EQ(config.release_stage, ReleaseStage::GA);
}
TEST_F(CreateCobaltConfigTest, ConfigFields) {
ASSERT_TRUE(WriteFile("cobalt_environment", "DEVEL"));
ASSERT_TRUE(WriteFile("config.json", "{\"release_stage\": \"GA\"}"));
float test_jitter = .3;
FuchsiaConfigurationData configuration_data(kTestDir, kTestDir);
CobaltConfig config =
CreateCobaltConfig("/pkg/data/testapp_metrics_registry.pb", configuration_data,
std::chrono::seconds(1), std::chrono::seconds(2), std::chrono::seconds(3),
test_jitter, 4, true, 1048000, "core", "x64", "0.1.2");
EXPECT_EQ(config.upload_schedule_cfg.jitter, test_jitter);
}
class CobaltAppTest : public gtest::TestLoopFixture {
public:
CobaltAppTest()
: ::gtest::TestLoopFixture(),
context_provider_(),
clock_(new FakeFuchsiaSystemClock(dispatcher())),
fake_service_(new testing::FakeCobaltService()),
cobalt_app_(context_provider_.TakeContext(), dispatcher(),
std::unique_ptr<testing::FakeCobaltService>(fake_service_),
std::unique_ptr<FakeFuchsiaSystemClock>(clock_), true, false) {}
protected:
void SetUp() override {
context_provider_.ConnectToPublicService(factory_.NewRequest());
ASSERT_NE(factory_.get(), nullptr);
context_provider_.ConnectToPublicService(metric_event_logger_factory_.NewRequest());
ASSERT_NE(metric_event_logger_factory_.get(), nullptr);
}
// Call the LoggerFactory to create a Logger connection.
fuchsia::cobalt::LoggerPtr GetLogger(int project_id = testapp_registry::kProjectId) {
fuchsia::cobalt::Status status = fuchsia::cobalt::Status::INTERNAL_ERROR;
fuchsia::cobalt::LoggerPtr logger;
factory_->CreateLoggerFromProjectId(project_id, logger.NewRequest(),
[&](fuchsia::cobalt::Status status_) { status = status_; });
RunLoopUntilIdle();
EXPECT_EQ(status, fuchsia::cobalt::Status::OK);
EXPECT_NE(logger.get(), nullptr);
return logger;
}
// Call the MetricEventLoggerFactory to create a Logger connection.
fuchsia::metrics::MetricEventLoggerPtr GetMetricEventLogger(
int project_id = testapp_registry::kProjectId) {
fuchsia::metrics::Status status = fuchsia::metrics::Status::INTERNAL_ERROR;
fuchsia::metrics::ProjectSpec project;
project.set_customer_id(1);
project.set_project_id(project_id);
fuchsia::metrics::MetricEventLoggerPtr logger;
metric_event_logger_factory_->CreateMetricEventLogger(
std::move(project), logger.NewRequest(),
[&](fuchsia::metrics::Status status_) { status = status_; });
RunLoopUntilIdle();
EXPECT_EQ(status, fuchsia::metrics::Status::OK);
EXPECT_NE(logger.get(), nullptr);
return logger;
}
fuchsia::cobalt::Controller* GetCobaltController() { return cobalt_app_.controller_impl_.get(); }
sys::testing::ComponentContextProvider context_provider_;
FakeFuchsiaSystemClock* clock_;
testing::FakeCobaltService* fake_service_;
CobaltApp cobalt_app_;
fuchsia::cobalt::LoggerFactoryPtr factory_;
fuchsia::metrics::MetricEventLoggerFactoryPtr metric_event_logger_factory_;
};
TEST_F(CobaltAppTest, CreateLogger) {
logger::testing::FakeLogger* fake_logger = fake_service_->last_logger_created();
EXPECT_EQ(fake_logger, nullptr);
fuchsia::cobalt::LoggerPtr logger = GetLogger();
fake_logger = fake_service_->last_logger_created();
EXPECT_NE(fake_logger, nullptr);
EXPECT_EQ(fake_logger->call_count(), 0);
}
TEST_F(CobaltAppTest, CreateLoggerNoValidLogger) {
// Make sure that the CobaltService returns nullptr for the next call to NewLogger().
fake_service_->FailNextNewLogger();
fuchsia::cobalt::Status status = fuchsia::cobalt::Status::OK;
fuchsia::cobalt::LoggerPtr logger;
factory_->CreateLoggerFromProjectId(/*project_id=*/987654321, logger.NewRequest(),
[&](fuchsia::cobalt::Status status_) { status = status_; });
RunLoopUntilIdle();
EXPECT_EQ(status, fuchsia::cobalt::Status::INVALID_ARGUMENTS);
}
TEST_F(CobaltAppTest, CreateLoggerForNonFuchsiaCustomer) {
fuchsia::cobalt::Status status = fuchsia::cobalt::Status::INTERNAL_ERROR;
fuchsia::cobalt::LoggerPtr logger;
// Use the customer and project ID for the cobalt_internal metrics project.
factory_->CreateLoggerFromProjectSpec(/*customer_id=*/2147483647, /*project_id=*/205836624,
logger.NewRequest(),
[&](fuchsia::cobalt::Status status_) { status = status_; });
RunLoopUntilIdle();
EXPECT_EQ(status, fuchsia::cobalt::Status::OK);
EXPECT_NE(logger.get(), nullptr);
}
TEST_F(CobaltAppTest, SystemClockIsAccurate) {
bool callback_invoked = false;
GetCobaltController()->ListenForInitialized([&callback_invoked]() { callback_invoked = true; });
EXPECT_FALSE(callback_invoked);
// Give the clock time to become accurate.
RunLoopUntilIdle();
EXPECT_EQ(fake_service_->system_clock_is_accurate(), true);
EXPECT_TRUE(callback_invoked);
}
TEST_F(CobaltAppTest, LogEvent) {
fuchsia::cobalt::LoggerPtr logger = GetLogger();
logger::testing::FakeLogger* fake_logger = fake_service_->last_logger_created();
ASSERT_NE(fake_logger, nullptr);
EXPECT_EQ(fake_logger->call_count(), 0);
fuchsia::cobalt::Status status = fuchsia::cobalt::Status::INTERNAL_ERROR;
logger->LogEvent(testapp_registry::kErrorOccurredMetricId, /*event_code=*/2,
[&](fuchsia::cobalt::Status status_) { status = status_; });
RunLoopUntilIdle();
ASSERT_EQ(status, fuchsia::cobalt::Status::OK);
EXPECT_EQ(fake_logger->call_count(), 1);
auto event = fake_logger->last_event_logged();
EXPECT_EQ(event.metric_id(), testapp_registry::kErrorOccurredMetricId);
EXPECT_EQ(event.event_occurred_event().event_code(), 2);
}
TEST_F(CobaltAppTest, SetSoftwareDistributionInfo) {
fuchsia::cobalt::SystemDataUpdaterPtr system_data_updater;
context_provider_.ConnectToPublicService(system_data_updater.NewRequest());
fuchsia::cobalt::Status status = fuchsia::cobalt::Status::INTERNAL_ERROR;
fuchsia::cobalt::SoftwareDistributionInfo info;
info.set_current_channel("new-channel-name");
info.set_current_realm("new-realm-name");
system_data_updater->SetSoftwareDistributionInfo(
std::move(info), [&](fuchsia::cobalt::Status status_) { status = status_; });
RunLoopUntilIdle();
ASSERT_EQ(status, fuchsia::cobalt::Status::OK);
EXPECT_EQ(fake_service_->system_data()->channel(), "new-channel-name");
EXPECT_EQ(fake_service_->system_data()->realm(), "new-realm-name");
}
TEST_F(CobaltAppTest, CreateMetricEventLogger) {
logger::testing::FakeLogger* fake_logger = fake_service_->last_logger_created();
EXPECT_EQ(fake_logger, nullptr);
fuchsia::metrics::MetricEventLoggerPtr logger = GetMetricEventLogger();
fake_logger = fake_service_->last_logger_created();
EXPECT_NE(fake_logger, nullptr);
EXPECT_EQ(fake_logger->call_count(), 0);
}
TEST_F(CobaltAppTest, CreateMetricEventLoggerNoValidLogger) {
// Make sure that the CobaltService returns nullptr for the next call to NewLogger().
fake_service_->FailNextNewLogger();
fuchsia::metrics::Status status = fuchsia::metrics::Status::OK;
fuchsia::metrics::ProjectSpec project;
project.set_customer_id(1);
project.set_project_id(987654321);
fuchsia::metrics::MetricEventLoggerPtr logger;
metric_event_logger_factory_->CreateMetricEventLogger(
std::move(project), logger.NewRequest(),
[&](fuchsia::metrics::Status status_) { status = status_; });
RunLoopUntilIdle();
EXPECT_EQ(status, fuchsia::metrics::Status::INVALID_ARGUMENTS);
}
TEST_F(CobaltAppTest, LogOccurrence) {
fuchsia::metrics::MetricEventLoggerPtr logger = GetMetricEventLogger();
logger::testing::FakeLogger* fake_logger = fake_service_->last_logger_created();
ASSERT_NE(fake_logger, nullptr);
EXPECT_EQ(fake_logger->call_count(), 0);
fuchsia::metrics::Status status = fuchsia::metrics::Status::INTERNAL_ERROR;
logger->LogOccurrence(testapp_registry::kErrorOccurredNewMetricId, /*count=*/1,
/*event_codes=*/{2},
[&](fuchsia::metrics::Status status_) { status = status_; });
RunLoopUntilIdle();
ASSERT_EQ(status, fuchsia::metrics::Status::OK);
EXPECT_EQ(fake_logger->call_count(), 1);
auto event = fake_logger->last_event_logged();
EXPECT_EQ(event.metric_id(), testapp_registry::kErrorOccurredNewMetricId);
EXPECT_EQ(event.occurrence_event().count(), 1);
EXPECT_THAT(event.occurrence_event().event_code(), ::testing::ElementsAre(2));
}
TEST_F(CobaltAppTest, LogInteger) {
fuchsia::metrics::MetricEventLoggerPtr logger = GetMetricEventLogger();
logger::testing::FakeLogger* fake_logger = fake_service_->last_logger_created();
ASSERT_NE(fake_logger, nullptr);
EXPECT_EQ(fake_logger->call_count(), 0);
fuchsia::metrics::Status status = fuchsia::metrics::Status::INTERNAL_ERROR;
logger->LogInteger(testapp_registry::kUpdateDurationNewMetricId, /*value=*/-42,
/*event_codes=*/{3},
[&](fuchsia::metrics::Status status_) { status = status_; });
RunLoopUntilIdle();
ASSERT_EQ(status, fuchsia::metrics::Status::OK);
EXPECT_EQ(fake_logger->call_count(), 1);
auto event = fake_logger->last_event_logged();
EXPECT_EQ(event.metric_id(), testapp_registry::kUpdateDurationNewMetricId);
EXPECT_EQ(event.integer_event().value(), -42);
EXPECT_THAT(event.integer_event().event_code(), ::testing::ElementsAre(3));
}
TEST_F(CobaltAppTest, LogIntegerHistogram) {
fuchsia::metrics::MetricEventLoggerPtr logger = GetMetricEventLogger();
logger::testing::FakeLogger* fake_logger = fake_service_->last_logger_created();
ASSERT_NE(fake_logger, nullptr);
EXPECT_EQ(fake_logger->call_count(), 0);
fuchsia::metrics::Status status = fuchsia::metrics::Status::INTERNAL_ERROR;
std::vector<fuchsia::metrics::HistogramBucket> histogram;
fuchsia::metrics::HistogramBucket entry;
entry.index = 1;
entry.count = 42;
histogram.push_back(entry);
logger->LogIntegerHistogram(testapp_registry::kBandwidthUsageNewMetricId, std::move(histogram),
/*event_codes=*/{4, 5},
[&](fuchsia::metrics::Status status_) { status = status_; });
RunLoopUntilIdle();
ASSERT_EQ(status, fuchsia::metrics::Status::OK);
EXPECT_EQ(fake_logger->call_count(), 1);
auto event = fake_logger->last_event_logged();
EXPECT_EQ(event.metric_id(), testapp_registry::kBandwidthUsageNewMetricId);
EXPECT_EQ(event.integer_histogram_event().buckets().size(), 1);
EXPECT_EQ(event.integer_histogram_event().buckets(0).index(), 1);
EXPECT_EQ(event.integer_histogram_event().buckets(0).count(), 42);
EXPECT_THAT(event.integer_histogram_event().event_code(), ::testing::ElementsAre(4, 5));
}
TEST_F(CobaltAppTest, LogString) {
fuchsia::metrics::MetricEventLoggerPtr logger = GetMetricEventLogger();
logger::testing::FakeLogger* fake_logger = fake_service_->last_logger_created();
ASSERT_NE(fake_logger, nullptr);
EXPECT_EQ(fake_logger->call_count(), 0);
fuchsia::metrics::Status status = fuchsia::metrics::Status::INTERNAL_ERROR;
logger->LogString(testapp_registry::kErrorOccurredComponentsMetricId,
/*string_value=*/"component", /*event_codes=*/{3},
[&](fuchsia::metrics::Status status_) { status = status_; });
RunLoopUntilIdle();
ASSERT_EQ(status, fuchsia::metrics::Status::OK);
EXPECT_EQ(fake_logger->call_count(), 1);
auto event = fake_logger->last_event_logged();
EXPECT_EQ(event.metric_id(), testapp_registry::kErrorOccurredComponentsMetricId);
EXPECT_EQ(event.string_event().string_value(), "component");
EXPECT_THAT(event.string_event().event_code(), ::testing::ElementsAre(3));
}
TEST_F(CobaltAppTest, LogMetricEvents) {
FX_LOGS(INFO) << "A logging statement";
fuchsia::metrics::MetricEventLoggerPtr logger = GetMetricEventLogger();
logger::testing::FakeLogger* fake_logger = fake_service_->last_logger_created();
ASSERT_NE(fake_logger, nullptr);
EXPECT_EQ(fake_logger->call_count(), 0);
fuchsia::metrics::Status status = fuchsia::metrics::Status::INTERNAL_ERROR;
std::vector<fuchsia::metrics::MetricEvent> events;
events.push_back(MetricEventBuilder(testapp_registry::kErrorOccurredNewMetricId)
.with_event_code(2)
.as_occurrence(1));
events.push_back(MetricEventBuilder(testapp_registry::kErrorOccurredNewMetricId)
.with_event_code(2)
.as_occurrence(2));
events.push_back(MetricEventBuilder(testapp_registry::kErrorOccurredNewMetricId)
.with_event_code(2)
.as_occurrence(3));
events.push_back(MetricEventBuilder(testapp_registry::kErrorOccurredNewMetricId)
.with_event_code(2)
.as_occurrence(4));
events.push_back(MetricEventBuilder(testapp_registry::kErrorOccurredNewMetricId)
.with_event_code(2)
.as_occurrence(5));
logger->LogMetricEvents(std::move(events),
[&](fuchsia::metrics::Status status_) { status = status_; });
RunLoopUntilIdle();
ASSERT_EQ(status, fuchsia::metrics::Status::OK);
EXPECT_EQ(fake_logger->call_count(), 5);
auto event = fake_logger->last_event_logged();
EXPECT_EQ(event.metric_id(), testapp_registry::kErrorOccurredNewMetricId);
EXPECT_EQ(event.occurrence_event().count(), 5);
EXPECT_THAT(event.occurrence_event().event_code(), ::testing::ElementsAre(2));
}
TEST_F(CobaltAppTest, LogCustomEvent) {
FX_LOGS(INFO) << "A logging statement";
fuchsia::metrics::MetricEventLoggerPtr logger = GetMetricEventLogger();
logger::testing::FakeLogger* fake_logger = fake_service_->last_logger_created();
ASSERT_NE(fake_logger, nullptr);
EXPECT_EQ(fake_logger->call_count(), 0);
fuchsia::metrics::Status status = fuchsia::metrics::Status::INTERNAL_ERROR;
std::vector<fuchsia::metrics::CustomEventValue> parts(3);
parts.at(0).dimension_name = "query";
parts.at(0).value.set_string_value("SELECT 1;");
parts.at(1).dimension_name = "wait_time_ms";
parts.at(1).value.set_int_value(1234);
parts.at(2).dimension_name = "response_code";
parts.at(2).value.set_index_value(2);
logger->LogCustomEvent(testapp_registry::kQueryResponseMetricId, std::move(parts),
[&](fuchsia::metrics::Status status_) { status = status_; });
RunLoopUntilIdle();
ASSERT_EQ(status, fuchsia::metrics::Status::OK);
EXPECT_EQ(fake_logger->call_count(), 1);
auto event = fake_logger->last_event_logged();
EXPECT_EQ(event.metric_id(), testapp_registry::kQueryResponseMetricId);
EXPECT_EQ(event.custom_event().values().size(), 3);
EXPECT_EQ(event.custom_event().values().at("query").string_value(), "SELECT 1;");
EXPECT_EQ(event.custom_event().values().at("wait_time_ms").int_value(), 1234);
EXPECT_EQ(event.custom_event().values().at("response_code").index_value(), 2);
}
TEST_F(CobaltAppTest, ShutDown) {
EXPECT_EQ(fake_service_->is_shut_down(), false);
fuchsia::metrics::MetricEventLoggerPtr metric_logger = GetMetricEventLogger();
fuchsia::cobalt::LoggerPtr logger = GetLogger();
EXPECT_TRUE(metric_logger.is_bound());
EXPECT_TRUE(logger.is_bound());
fuchsia::process::lifecycle::LifecyclePtr process_lifecycle;
context_provider_.ConnectToPublicService(process_lifecycle.NewRequest(),
"fuchsia.process.lifecycle.Lifecycle");
process_lifecycle->Stop();
RunLoopUntilIdle();
EXPECT_EQ(fake_service_->is_shut_down(), true);
EXPECT_FALSE(metric_logger.is_bound());
EXPECT_FALSE(logger.is_bound());
EXPECT_FALSE(process_lifecycle.is_bound());
}
TEST_F(CobaltAppTest, NoNewLoggersAfterShutDown) {
fuchsia::process::lifecycle::LifecyclePtr process_lifecycle;
context_provider_.ConnectToPublicService(process_lifecycle.NewRequest(),
"fuchsia.process.lifecycle.Lifecycle");
process_lifecycle->Stop();
RunLoopUntilIdle();
fuchsia::metrics::MetricEventLoggerPtr metric_logger = nullptr;
fuchsia::cobalt::LoggerPtr logger = nullptr;
fuchsia::cobalt::Status cobalt_status = fuchsia::cobalt::Status::INTERNAL_ERROR;
fuchsia::metrics::Status metrics_status = fuchsia::metrics::Status::INTERNAL_ERROR;
fuchsia::metrics::ProjectSpec project;
project.set_customer_id(1);
project.set_project_id(testapp_registry::kProjectId);
metric_event_logger_factory_->CreateMetricEventLogger(
std::move(project), metric_logger.NewRequest(),
[&](fuchsia::metrics::Status status_) { metrics_status = status_; });
RunLoopUntilIdle();
EXPECT_EQ(metrics_status, fuchsia::metrics::Status::SHUT_DOWN);
EXPECT_FALSE(metric_logger.is_bound());
factory_->CreateLoggerFromProjectId(
testapp_registry::kProjectId, logger.NewRequest(),
[&](fuchsia::cobalt::Status status_) { cobalt_status = status_; });
RunLoopUntilIdle();
EXPECT_EQ(cobalt_status, fuchsia::cobalt::Status::SHUT_DOWN);
EXPECT_FALSE(logger.is_bound());
}
} // namespace cobalt