blob: a2d07bd8da4d6164afc2b1326445a8c2a915aaac [file] [log] [blame]
// Copyright 2018 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 <random>
#include <utility>
#include "./gtest.h"
#include "./logging.h"
#include "encoder/client_secret.h"
#include "encoder/encoder.h"
#include "encoder/file_observation_store.h"
#include "encoder/posix_file_system.h"
// Generated from file_observation_store_test_config.yaml
#include "encoder/file_observation_store_test_config.h"
namespace cobalt {
namespace encoder {
using config::ClientConfig;
using util::EncryptedMessageMaker;
namespace {
// These values must match the values specified in the invocation of
// generate_test_config_h() in CMakeLists.txt. and in the invocation of
// cobalt_config_header("generate_shipping_manager_test_config") in BUILD.gn.
const uint32_t kCustomerId = 1;
const uint32_t kProjectId = 1;
const size_t kNoOpEncodingByteOverhead = 34;
const size_t kMaxBytesPerObservation = 100;
const size_t kMaxBytesPerEnvelope = 400;
const size_t kMaxBytesTotal = 1000;
const std::string &test_dir_base = "/tmp/fos_test";
std::string GetTestDirName(const std::string &base) {
std::stringstream fname;
fname << base << "_"
<< std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
return fname.str();
}
// Returns a ProjectContext obtained by parsing the configuration specified
// in shipping_manager_test_config.yaml
std::shared_ptr<ProjectContext> GetTestProject() {
// Parse the base64-encoded, serialized CobaltConfig in
// shipping_manager_test_config.h. This is generated from
// shipping_manager_test_config.yaml. Edit that yaml file to make changes. The
// variable name below, |cobalt_config_base64|, must match what is
// specified in the build files.
std::unique_ptr<ClientConfig> client_config =
ClientConfig::CreateFromCobaltConfigBase64(cobalt_config_base64);
EXPECT_NE(nullptr, client_config);
return std::shared_ptr<ProjectContext>(new ProjectContext(
kCustomerId, kProjectId,
std::shared_ptr<ClientConfig>(client_config.release())));
}
class FakeSystemData : public SystemDataInterface {
public:
FakeSystemData() {
system_profile_.set_os(SystemProfile::FUCHSIA);
system_profile_.set_arch(SystemProfile::ARM_64);
system_profile_.set_board_name("Fake Board Name");
}
const SystemProfile &system_profile() const override {
return system_profile_;
};
static void CheckSystemProfile(const Envelope &envelope) {
// SystemProfile is not placed in the envelope at this time.
EXPECT_EQ(SystemProfile::UNKNOWN_OS, envelope.system_profile().os());
EXPECT_EQ(SystemProfile::UNKNOWN_ARCH, envelope.system_profile().arch());
EXPECT_EQ("", envelope.system_profile().board_name());
}
private:
SystemProfile system_profile_;
};
class FileObservationStoreTest : public ::testing::Test {
public:
FileObservationStoreTest()
: encrypt_to_analyzer_("", EncryptedMessage::NONE),
test_dir_name_(GetTestDirName(test_dir_base)),
project_(GetTestProject()),
encoder_(project_, ClientSecret::GenerateNewSecret(), &system_data_) {
MakeStore();
}
void MakeStore() {
store_.reset(new FileObservationStore(
kMaxBytesPerObservation, kMaxBytesPerEnvelope, kMaxBytesTotal,
std::make_unique<PosixFileSystem>(), test_dir_name_));
}
void TearDown() override { store_->Delete(); }
ObservationStore::StoreStatus AddObservation(
size_t num_bytes, uint32_t metric_id = kDefaultMetricId) {
CHECK(num_bytes > kNoOpEncodingByteOverhead) << " num_bytes=" << num_bytes;
Encoder::Result result = encoder_.EncodeString(
metric_id, kNoOpEncodingId,
std::string("x", num_bytes - kNoOpEncodingByteOverhead));
auto message = std::make_unique<EncryptedMessage>();
encrypt_to_analyzer_.Encrypt(*result.observation, message.get());
return store_->AddEncryptedObservation(std::move(message),
std::move(result.metadata));
}
private:
EncryptedMessageMaker encrypt_to_analyzer_;
protected:
std::string test_dir_name_;
std::unique_ptr<FileObservationStore> store_;
FakeSystemData system_data_;
std::shared_ptr<ProjectContext> project_;
Encoder encoder_;
};
} // namespace
TEST_F(FileObservationStoreTest, AddRetrieveSingleObservation) {
EXPECT_EQ(ObservationStore::kOk, AddObservation(50));
auto envelope = store_->TakeNextEnvelopeHolder();
// Since we haven't written kMaxBytesPerEnvelope yet, there are no finalized
// envelopes, TakeNextEnvelopeHolder should force the active file to finalize.
EXPECT_NE(envelope, nullptr);
}
TEST_F(FileObservationStoreTest, AddRetrieveFullEnvelope) {
for (int i = 0; i < 4; i++) {
EXPECT_EQ(ObservationStore::kOk, AddObservation(100));
}
auto envelope = store_->TakeNextEnvelopeHolder();
ASSERT_NE(envelope, nullptr);
auto read_env = envelope->GetEnvelope();
EXPECT_EQ(read_env.batch_size(), 1);
EXPECT_EQ(read_env.batch(0).encrypted_observation_size(), 4);
}
TEST_F(FileObservationStoreTest, AddRetrieveMultipleFullEnvelopes) {
for (int i = 0; i < 5 * 4; i++) {
EXPECT_EQ(ObservationStore::kOk, AddObservation(100));
}
for (int i = 0; i < 5; i++) {
auto envelope = store_->TakeNextEnvelopeHolder();
ASSERT_NE(envelope, nullptr);
auto read_env = envelope->GetEnvelope();
EXPECT_EQ(read_env.batch_size(), 1);
EXPECT_EQ(read_env.batch(0).encrypted_observation_size(), 4);
}
}
TEST_F(FileObservationStoreTest, Add2FullAndReturn1) {
for (int i = 0; i < 2 * 4; i++) {
EXPECT_EQ(ObservationStore::kOk, AddObservation(100));
}
auto first_envelope = store_->TakeNextEnvelopeHolder();
ASSERT_NE(first_envelope, nullptr);
auto second_envelope = store_->TakeNextEnvelopeHolder();
ASSERT_NE(second_envelope, nullptr);
EXPECT_TRUE(store_->Empty());
// Delete the second envelope
second_envelope = nullptr;
EXPECT_TRUE(store_->Empty());
store_->ReturnEnvelopeHolder(std::move(first_envelope));
EXPECT_FALSE(store_->Empty());
}
TEST_F(FileObservationStoreTest, RecoverAfterCrashWithNoObservations) {
EXPECT_TRUE(store_->Empty());
// Simulate the store crashing.
store_ = nullptr;
// Store restarts.
MakeStore();
// The store should still be empty.
EXPECT_TRUE(store_->Empty());
}
TEST_F(FileObservationStoreTest, RecoverAfterCrash) {
// Add some observations, but not enough to finalize.
for (int i = 0; i < 3; i++) {
EXPECT_EQ(ObservationStore::kOk, AddObservation(100));
EXPECT_EQ(store_->ListFinalizedFiles().size(), 0u);
}
// Simulate the store crashing.
store_ = nullptr;
// Store restarts.
MakeStore();
// The store should finalize the in-progress envelope.
EXPECT_FALSE(store_->Empty());
EXPECT_EQ(store_->ListFinalizedFiles().size(), 1u);
}
TEST_F(FileObservationStoreTest, IgnoresUnexpectedFiles) {
{ std::ofstream dummy(test_dir_name_ + "/BAD_FILE"); }
EXPECT_EQ(store_->ListFinalizedFiles().size(), 0u);
EXPECT_EQ(store_->TakeNextEnvelopeHolder(), nullptr);
{ std::ofstream empty_invalid(test_dir_name_ + "/10000000-100000.data"); }
EXPECT_EQ(store_->ListFinalizedFiles().size(), 0u);
EXPECT_EQ(store_->TakeNextEnvelopeHolder(), nullptr);
{ std::ofstream empty_valid(test_dir_name_ + "/1234567890123-1234567.data"); }
EXPECT_EQ(store_->ListFinalizedFiles().size(), 1u);
EXPECT_NE(store_->TakeNextEnvelopeHolder(), nullptr);
}
TEST_F(FileObservationStoreTest, HandlesCorruptFiles) {
{
std::ofstream file(test_dir_name_ + "/1234567890123-1234567.data");
file << "CORRUPT DATA!!!";
}
EXPECT_EQ(store_->ListFinalizedFiles().size(), 1u);
auto env = store_->TakeNextEnvelopeHolder();
ASSERT_NE(env, nullptr);
auto read_env = env->GetEnvelope();
EXPECT_EQ(read_env.batch_size(), 0);
}
TEST_F(FileObservationStoreTest, StressTest) {
std::random_device rd;
for (int i = 0; i < 5000; i++) {
// Between 5-15 observations.
auto observations = (rd() % 10) + 5;
// Between 50-100 bytes per observation.
auto size = (rd() % 50) + 50;
for (auto j = 0u; j < observations; j++) {
EXPECT_EQ(ObservationStore::kOk, AddObservation(size));
}
while (true) {
auto holder = store_->TakeNextEnvelopeHolder();
if (holder == nullptr) {
break;
}
auto should_return = rd() % 2;
if (should_return == 1) {
store_->ReturnEnvelopeHolder(std::move(holder));
} else {
auto env = holder->GetEnvelope();
ASSERT_GT(env.batch_size(), 0);
}
}
ASSERT_EQ(store_->Size(), 0u);
}
}
} // namespace encoder
} // namespace cobalt