blob: 74fa9b7ce570989a3b45d0c7f548177a9acd688a [file] [log] [blame]
// Copyright 2021 The Chromium OS 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 "missive/storage/storage_queue.h"
#include <cstdint>
#include <initializer_list>
#include <utility>
#include <vector>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/optional.h>
#include <base/strings/strcat.h>
#include <base/strings/string_number_conversions.h>
#include <base/test/task_environment.h>
#include <crypto/sha2.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "missive/encryption/scoped_encryption_feature.h"
#include "missive/encryption/test_encryption_module.h"
#include "missive/proto/record.pb.h"
#include "missive/storage/resources/resource_interface.h"
#include "missive/storage/storage_configuration.h"
#include "missive/util/status.h"
#include "missive/util/statusor.h"
#include "missive/util/test_support_callbacks.h"
using ::testing::_;
using ::testing::Between;
using ::testing::Eq;
using ::testing::Invoke;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::Sequence;
using ::testing::StrEq;
using ::testing::WithArg;
namespace reporting {
namespace {
// Metadata file name prefix.
constexpr char METADATA_NAME[] = "META";
class MockUploadClient : public ::testing::NiceMock<UploaderInterface> {
public:
// Mapping of <generation id, sequencing id> to matching record digest.
// Whenever a record is uploaded and includes last record digest, this map
// should have that digest already recorded. Only the first record in a
// generation is uploaded without last record digest. "Optional" is set to
// no-value if there was a gap record instead of a real one.
using LastRecordDigestMap =
std::map<std::pair<int64_t /*generation id */, int64_t /*sequencing id*/>,
base::Optional<std::string /*digest*/>>;
explicit MockUploadClient(LastRecordDigestMap* last_record_digest_map)
: last_record_digest_map_(last_record_digest_map) {}
void ProcessRecord(EncryptedRecord encrypted_record,
base::OnceCallback<void(bool)> processed_cb) override {
WrappedRecord wrapped_record;
ASSERT_TRUE(wrapped_record.ParseFromString(
encrypted_record.encrypted_wrapped_record()));
// Verify generation match.
const auto& sequencing_information =
encrypted_record.sequencing_information();
if (generation_id_.has_value() &&
generation_id_.value() != sequencing_information.generation_id()) {
std::move(processed_cb)
.Run(UploadRecordFailure(
sequencing_information.sequencing_id(),
Status(
error::DATA_LOSS,
base::StrCat(
{"Generation id mismatch, expected=",
base::NumberToString(generation_id_.value()), " actual=",
base::NumberToString(
sequencing_information.generation_id())}))));
return;
}
if (!generation_id_.has_value()) {
generation_id_ = sequencing_information.generation_id();
}
// Verify digest and its match.
{
std::string serialized_record;
wrapped_record.record().SerializeToString(&serialized_record);
const auto record_digest = crypto::SHA256HashString(serialized_record);
DCHECK_EQ(record_digest.size(), crypto::kSHA256Length);
if (record_digest != wrapped_record.record_digest()) {
std::move(processed_cb)
.Run(UploadRecordFailure(
sequencing_information.sequencing_id(),
Status(error::DATA_LOSS, "Record digest mismatch")));
return;
}
// Store record digest for the next record in sequence to verify.
last_record_digest_map_->emplace(
std::make_pair(sequencing_information.sequencing_id(),
sequencing_information.generation_id()),
record_digest);
// If last record digest is present, match it and validate,
// unless previous record was a gap.
if (wrapped_record.has_last_record_digest()) {
auto it = last_record_digest_map_->find(
std::make_pair(sequencing_information.sequencing_id() - 1,
sequencing_information.generation_id()));
if (it == last_record_digest_map_->end() ||
(it->second.has_value() &&
it->second.value() != wrapped_record.last_record_digest())) {
std::move(processed_cb)
.Run(UploadRecordFailure(
sequencing_information.sequencing_id(),
Status(error::DATA_LOSS, "Last record digest mismatch")));
return;
}
}
}
EncounterSeqId(sequencing_information.sequencing_id());
std::move(processed_cb)
.Run(UploadRecord(sequencing_information.sequencing_id(),
wrapped_record.record().data()));
}
void ProcessGap(SequencingInformation sequencing_information,
uint64_t count,
base::OnceCallback<void(bool)> processed_cb) override {
// Verify generation match.
if (generation_id_.has_value() &&
generation_id_.value() != sequencing_information.generation_id()) {
std::move(processed_cb)
.Run(UploadRecordFailure(
sequencing_information.sequencing_id(),
Status(
error::DATA_LOSS,
base::StrCat(
{"Generation id mismatch, expected=",
base::NumberToString(generation_id_.value()), " actual=",
base::NumberToString(
sequencing_information.generation_id())}))));
return;
}
if (!generation_id_.has_value()) {
generation_id_ = sequencing_information.generation_id();
}
last_record_digest_map_->emplace(
std::make_pair(sequencing_information.sequencing_id(),
sequencing_information.generation_id()),
base::nullopt);
for (uint64_t c = 0; c < count; ++c) {
EncounterSeqId(sequencing_information.sequencing_id() +
static_cast<int64_t>(c));
}
std::move(processed_cb)
.Run(UploadGap(sequencing_information.sequencing_id(), count));
}
void Completed(Status status) override { UploadComplete(status); }
MOCK_METHOD(void, EncounterSeqId, (int64_t), (const));
MOCK_METHOD(bool, UploadRecord, (int64_t, base::StringPiece), (const));
MOCK_METHOD(bool, UploadRecordFailure, (int64_t, Status), (const));
MOCK_METHOD(bool, UploadGap, (int64_t, uint64_t), (const));
MOCK_METHOD(void, UploadComplete, (Status), (const));
// Helper class for setting up mock client expectations of a successful
// completion.
class SetUp {
public:
explicit SetUp(MockUploadClient* client) : client_(client) {}
~SetUp() {
EXPECT_CALL(*client_, UploadComplete(Eq(Status::StatusOK())))
.Times(1)
.InSequence(client_->test_upload_sequence_,
client_->test_encounter_sequence_);
}
SetUp& Required(int64_t sequence_number, base::StringPiece value) {
EXPECT_CALL(*client_,
UploadRecord(Eq(sequence_number), StrEq(std::string(value))))
.InSequence(client_->test_upload_sequence_)
.WillOnce(Return(true));
return *this;
}
SetUp& Possible(int64_t sequence_number, base::StringPiece value) {
EXPECT_CALL(*client_,
UploadRecord(Eq(sequence_number), StrEq(std::string(value))))
.Times(Between(0, 1))
.InSequence(client_->test_upload_sequence_)
.WillRepeatedly(Return(true));
return *this;
}
SetUp& RequiredGap(int64_t sequence_number, uint64_t count) {
EXPECT_CALL(*client_, UploadGap(Eq(sequence_number), Eq(count)))
.InSequence(client_->test_upload_sequence_)
.WillOnce(Return(true));
return *this;
}
SetUp& PossibleGap(int64_t sequence_number, uint64_t count) {
EXPECT_CALL(*client_, UploadGap(Eq(sequence_number), Eq(count)))
.Times(Between(0, 1))
.InSequence(client_->test_upload_sequence_)
.WillRepeatedly(Return(true));
return *this;
}
SetUp& Failure(int64_t sequence_number, Status error) {
EXPECT_CALL(*client_, UploadRecordFailure(Eq(sequence_number), Eq(error)))
.InSequence(client_->test_upload_sequence_)
.WillOnce(Return(true));
return *this;
}
// The following two expectations refer to the fact that specific
// sequencing ids have been encountered, regardless of whether they
// belonged to records or gaps. The expectations are set on a separate
// test sequence.
SetUp& RequiredSeqId(int64_t sequence_number) {
EXPECT_CALL(*client_, EncounterSeqId(Eq(sequence_number)))
.Times(1)
.InSequence(client_->test_encounter_sequence_);
return *this;
}
SetUp& PossibleSeqId(int64_t sequence_number) {
EXPECT_CALL(*client_, EncounterSeqId(Eq(sequence_number)))
.Times(Between(0, 1))
.InSequence(client_->test_encounter_sequence_);
return *this;
}
private:
MockUploadClient* const client_;
};
private:
base::Optional<int64_t> generation_id_;
LastRecordDigestMap* const last_record_digest_map_;
Sequence test_encounter_sequence_;
Sequence test_upload_sequence_;
};
class StorageQueueTest : public ::testing::TestWithParam<size_t> {
protected:
void SetUp() override {
// Enable encryption.
ASSERT_TRUE(location_.CreateUniqueTempDir());
options_.set_directory(base::FilePath(location_.GetPath()))
.set_single_file_size(GetParam());
}
void TearDown() override {
ResetTestStorageQueue();
// Make sure all memory is deallocated.
ASSERT_THAT(GetMemoryResource()->GetUsed(), Eq(0u));
// Make sure all disk is not reserved (files remain, but Storage is not
// responsible for them anymore).
ASSERT_THAT(GetDiskResource()->GetUsed(), Eq(0u));
}
void CreateTestStorageQueueOrDie(const QueueOptions& options) {
ASSERT_FALSE(storage_queue_) << "StorageQueue already assigned";
test_encryption_module_ =
base::MakeRefCounted<test::TestEncryptionModule>();
test::TestEvent<Status> key_update_event;
test_encryption_module_->UpdateAsymmetricKey("DUMMY KEY", 0,
key_update_event.cb());
ASSERT_OK(key_update_event.result());
test::TestEvent<StatusOr<scoped_refptr<StorageQueue>>>
storage_queue_create_event;
StorageQueue::Create(
options,
base::BindRepeating(&StorageQueueTest::AsyncStartMockUploader,
base::Unretained(this)),
test_encryption_module_, storage_queue_create_event.cb());
StatusOr<scoped_refptr<StorageQueue>> storage_queue_result =
storage_queue_create_event.result();
ASSERT_OK(storage_queue_result) << "Failed to create StorageQueue, error="
<< storage_queue_result.status();
storage_queue_ = std::move(storage_queue_result.ValueOrDie());
}
void ResetTestStorageQueue() {
task_environment_.RunUntilIdle();
storage_queue_.reset();
}
void InjectFailures(std::initializer_list<int64_t> sequencing_ids) {
storage_queue_->TestInjectBlockReadErrors(sequencing_ids);
}
QueueOptions BuildStorageQueueOptionsImmediate() const {
return QueueOptions(options_).set_subdirectory("D1").set_file_prefix(
"F0001");
}
QueueOptions BuildStorageQueueOptionsPeriodic(
base::TimeDelta upload_period = base::TimeDelta::FromSeconds(1)) const {
return BuildStorageQueueOptionsImmediate().set_upload_period(upload_period);
}
QueueOptions BuildStorageQueueOptionsOnlyManual() const {
return BuildStorageQueueOptionsPeriodic(base::TimeDelta::Max());
}
void AsyncStartMockUploader(
UploaderInterface::UploaderInterfaceResultCb start_uploader_cb) {
auto uploader =
std::make_unique<MockUploadClient>(&last_record_digest_map_);
set_mock_uploader_expectations_.Call(uploader.get());
std::move(start_uploader_cb).Run(std::move(uploader));
}
Status WriteString(base::StringPiece data) {
EXPECT_TRUE(storage_queue_) << "StorageQueue not created yet";
test::TestEvent<Status> w;
Record record;
record.set_data(std::string(data));
record.set_destination(UPLOAD_EVENTS);
record.set_dm_token("DM TOKEN");
storage_queue_->Write(std::move(record), w.cb());
return w.result();
}
void WriteStringOrDie(base::StringPiece data) {
const Status write_result = WriteString(data);
ASSERT_OK(write_result) << write_result;
}
void ConfirmOrDie(base::Optional<std::int64_t> sequencing_id,
bool force = false) {
test::TestEvent<Status> c;
storage_queue_->Confirm(sequencing_id, force, c.cb());
const Status c_result = c.result();
ASSERT_OK(c_result) << c_result;
}
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
test::ScopedEncryptionFeature encryption_feature_{/*enable=*/true};
base::ScopedTempDir location_;
StorageOptions options_;
scoped_refptr<test::TestEncryptionModule> test_encryption_module_;
scoped_refptr<StorageQueue> storage_queue_;
// Test-wide global mapping of <generation id, sequencing id> to record
// digest. Serves all MockUploadClients created by test fixture.
MockUploadClient::LastRecordDigestMap last_record_digest_map_;
::testing::MockFunction<void(MockUploadClient*)>
set_mock_uploader_expectations_;
};
constexpr std::array<const char*, 3> kData = {"Rec1111", "Rec222", "Rec33"};
constexpr std::array<const char*, 3> kMoreData = {"More1111", "More222",
"More33"};
TEST_P(StorageQueueTest, WriteIntoNewStorageQueueAndReopen) {
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull())).Times(0);
CreateTestStorageQueueOrDie(BuildStorageQueueOptionsPeriodic());
WriteStringOrDie(kData[0]);
WriteStringOrDie(kData[1]);
WriteStringOrDie(kData[2]);
ResetTestStorageQueue();
CreateTestStorageQueueOrDie(BuildStorageQueueOptionsPeriodic());
}
TEST_P(StorageQueueTest, WriteIntoNewStorageQueueReopenAndWriteMore) {
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull())).Times(0);
CreateTestStorageQueueOrDie(BuildStorageQueueOptionsPeriodic());
WriteStringOrDie(kData[0]);
WriteStringOrDie(kData[1]);
WriteStringOrDie(kData[2]);
ResetTestStorageQueue();
CreateTestStorageQueueOrDie(BuildStorageQueueOptionsPeriodic());
WriteStringOrDie(kMoreData[0]);
WriteStringOrDie(kMoreData[1]);
WriteStringOrDie(kMoreData[2]);
}
TEST_P(StorageQueueTest, WriteIntoNewStorageQueueAndUpload) {
CreateTestStorageQueueOrDie(BuildStorageQueueOptionsPeriodic());
WriteStringOrDie(kData[0]);
WriteStringOrDie(kData[1]);
WriteStringOrDie(kData[2]);
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Required(0, kData[0])
.Required(1, kData[1])
.Required(2, kData[2]);
}));
// Trigger upload.
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
}
TEST_P(StorageQueueTest, WriteIntoNewStorageQueueAndUploadWithFailures) {
CreateTestStorageQueueOrDie(BuildStorageQueueOptionsPeriodic());
WriteStringOrDie(kData[0]);
WriteStringOrDie(kData[1]);
WriteStringOrDie(kData[2]);
// Inject simulated failures.
InjectFailures({1});
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Required(0, kData[0])
.RequiredGap(1, 1)
.Possible(2, kData[2]); // Depending on records binpacking
}));
// Trigger upload.
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
}
TEST_P(StorageQueueTest, WriteIntoNewStorageQueueReopenWriteMoreAndUpload) {
CreateTestStorageQueueOrDie(BuildStorageQueueOptionsPeriodic());
WriteStringOrDie(kData[0]);
WriteStringOrDie(kData[1]);
WriteStringOrDie(kData[2]);
ResetTestStorageQueue();
CreateTestStorageQueueOrDie(BuildStorageQueueOptionsPeriodic());
WriteStringOrDie(kMoreData[0]);
WriteStringOrDie(kMoreData[1]);
WriteStringOrDie(kMoreData[2]);
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Required(0, kData[0])
.Required(1, kData[1])
.Required(2, kData[2])
.Required(3, kMoreData[0])
.Required(4, kMoreData[1])
.Required(5, kMoreData[2]);
}));
// Trigger upload.
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
}
TEST_P(StorageQueueTest,
WriteIntoNewStorageQueueReopenWithMissingMetadataWriteMoreAndUpload) {
CreateTestStorageQueueOrDie(BuildStorageQueueOptionsPeriodic());
WriteStringOrDie(kData[0]);
WriteStringOrDie(kData[1]);
WriteStringOrDie(kData[2]);
// Save copy of options.
const QueueOptions options = storage_queue_->options();
ResetTestStorageQueue();
// Delete all metadata files.
base::FileEnumerator dir_enum(options.directory(),
/*recursive=*/false,
base::FileEnumerator::FILES,
base::StrCat({METADATA_NAME, ".*"}));
base::FilePath full_name;
while (full_name = dir_enum.Next(), !full_name.empty()) {
base::DeleteFile(full_name);
}
// Reopen, starting a new generation.
CreateTestStorageQueueOrDie(BuildStorageQueueOptionsPeriodic());
WriteStringOrDie(kMoreData[0]);
WriteStringOrDie(kMoreData[1]);
WriteStringOrDie(kMoreData[2]);
// Set uploader expectations. Previous data is all lost.
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Required(0, kMoreData[0])
.Required(1, kMoreData[1])
.Required(2, kMoreData[2]);
}));
// Trigger upload.
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
}
TEST_P(StorageQueueTest,
WriteIntoNewStorageQueueReopenWithMissingDataWriteMoreAndUpload) {
CreateTestStorageQueueOrDie(BuildStorageQueueOptionsPeriodic());
WriteStringOrDie(kData[0]);
WriteStringOrDie(kData[1]);
WriteStringOrDie(kData[2]);
// Save copy of options.
const QueueOptions options = storage_queue_->options();
ResetTestStorageQueue();
// Reopen with the same generation and sequencing information.
CreateTestStorageQueueOrDie(BuildStorageQueueOptionsPeriodic());
// Delete the first data file.
base::FilePath full_name =
options.directory().Append(base::StrCat({options.file_prefix(), ".0"}));
base::DeleteFile(full_name);
// Write more data.
WriteStringOrDie(kMoreData[0]);
WriteStringOrDie(kMoreData[1]);
WriteStringOrDie(kMoreData[2]);
// Set uploader expectations. Previous data is all lost.
// The expected results depend on the test configuration.
switch (options.single_file_size()) {
case 1: // single record in file - deletion killed the first record
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.PossibleGap(0, 1)
.Required(1, kData[1])
.Required(2, kData[2])
.Required(3, kMoreData[0])
.Required(4, kMoreData[1])
.Required(5, kMoreData[2]);
}));
break;
case 256: // two records in file - deletion killed the first two records.
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.PossibleGap(0, 2)
.Failure(
2, Status(error::DATA_LOSS, "Last record digest mismatch"))
.Required(3, kMoreData[0])
.Required(4, kMoreData[1])
.Required(5, kMoreData[2]);
}));
break;
default: // UNlimited file size - deletion above killed all the data.
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client).PossibleGap(0, 1);
}));
}
// Trigger upload.
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
}
TEST_P(StorageQueueTest, WriteIntoNewStorageQueueAndFlush) {
CreateTestStorageQueueOrDie(BuildStorageQueueOptionsOnlyManual());
WriteStringOrDie(kData[0]);
WriteStringOrDie(kData[1]);
WriteStringOrDie(kData[2]);
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Required(0, kData[0])
.Required(1, kData[1])
.Required(2, kData[2]);
}));
// Flush manually.
storage_queue_->Flush();
}
TEST_P(StorageQueueTest, WriteIntoNewStorageQueueReopenWriteMoreAndFlush) {
CreateTestStorageQueueOrDie(BuildStorageQueueOptionsOnlyManual());
WriteStringOrDie(kData[0]);
WriteStringOrDie(kData[1]);
WriteStringOrDie(kData[2]);
ResetTestStorageQueue();
CreateTestStorageQueueOrDie(BuildStorageQueueOptionsOnlyManual());
WriteStringOrDie(kMoreData[0]);
WriteStringOrDie(kMoreData[1]);
WriteStringOrDie(kMoreData[2]);
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Required(0, kData[0])
.Required(1, kData[1])
.Required(2, kData[2])
.Required(3, kMoreData[0])
.Required(4, kMoreData[1])
.Required(5, kMoreData[2]);
}));
// Flush manually.
storage_queue_->Flush();
}
TEST_P(StorageQueueTest, ValidateVariousRecordSizes) {
std::vector<std::string> data;
for (size_t i = 16; i < 16 + 16; ++i) {
data.emplace_back(i, 'R');
}
CreateTestStorageQueueOrDie(BuildStorageQueueOptionsOnlyManual());
for (const auto& record : data) {
WriteStringOrDie(record);
}
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([data](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp client_setup(mock_upload_client);
for (size_t i = 0; i < data.size(); ++i) {
client_setup.Required(i, data[i]);
}
}));
// Flush manually.
storage_queue_->Flush();
}
TEST_P(StorageQueueTest, WriteAndRepeatedlyUploadWithConfirmations) {
CreateTestStorageQueueOrDie(BuildStorageQueueOptionsPeriodic());
WriteStringOrDie(kData[0]);
WriteStringOrDie(kData[1]);
WriteStringOrDie(kData[2]);
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Required(0, kData[0])
.Required(1, kData[1])
.Required(2, kData[2]);
}));
// Forward time to trigger upload
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
// Confirm #0 and forward time again, removing record #0
ConfirmOrDie(/*sequencing_id=*/0);
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Required(1, kData[1])
.Required(2, kData[2]);
}));
// Forward time to trigger upload
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
// Confirm #1 and forward time again, removing record #1
ConfirmOrDie(/*sequencing_id=*/1);
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client).Required(2, kData[2]);
}));
// Forward time to trigger upload
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
// Add more data and verify that #2 and new data are returned.
WriteStringOrDie(kMoreData[0]);
WriteStringOrDie(kMoreData[1]);
WriteStringOrDie(kMoreData[2]);
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Required(2, kData[2])
.Required(3, kMoreData[0])
.Required(4, kMoreData[1])
.Required(5, kMoreData[2]);
}));
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
// Confirm #2 and forward time again, removing record #2
ConfirmOrDie(/*sequencing_id=*/2);
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Required(3, kMoreData[0])
.Required(4, kMoreData[1])
.Required(5, kMoreData[2]);
}));
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
}
TEST_P(StorageQueueTest, WriteAndRepeatedlyUploadWithConfirmationsAndReopen) {
CreateTestStorageQueueOrDie(BuildStorageQueueOptionsPeriodic());
WriteStringOrDie(kData[0]);
WriteStringOrDie(kData[1]);
WriteStringOrDie(kData[2]);
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Required(0, kData[0])
.Required(1, kData[1])
.Required(2, kData[2]);
}));
// Forward time to trigger upload
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
// Confirm #0 and forward time again, removing record #0
ConfirmOrDie(/*sequencing_id=*/0);
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Required(1, kData[1])
.Required(2, kData[2]);
}));
// Forward time to trigger upload
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
// Confirm #1 and forward time again, removing record #1
ConfirmOrDie(/*sequencing_id=*/1);
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client).Required(2, kData[2]);
}));
// Forward time to trigger upload
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
ResetTestStorageQueue();
CreateTestStorageQueueOrDie(BuildStorageQueueOptionsPeriodic());
// Add more data and verify that #2 and new data are returned.
WriteStringOrDie(kMoreData[0]);
WriteStringOrDie(kMoreData[1]);
WriteStringOrDie(kMoreData[2]);
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Possible(0, kData[0])
.Possible(1, kData[1])
.Required(2, kData[2])
.Required(3, kMoreData[0])
.Required(4, kMoreData[1])
.Required(5, kMoreData[2]);
}));
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
// Confirm #2 and forward time again, removing record #2
ConfirmOrDie(/*sequencing_id=*/2);
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Required(3, kMoreData[0])
.Required(4, kMoreData[1])
.Required(5, kMoreData[2]);
}));
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
}
TEST_P(StorageQueueTest,
WriteAndRepeatedlyUploadWithConfirmationsAndReopenWithFailures) {
CreateTestStorageQueueOrDie(BuildStorageQueueOptionsPeriodic());
WriteStringOrDie(kData[0]);
WriteStringOrDie(kData[1]);
WriteStringOrDie(kData[2]);
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Required(0, kData[0])
.Required(1, kData[1])
.Required(2, kData[2]);
}));
// Forward time to trigger upload
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
// Confirm #0 and forward time again, removing record #0
ConfirmOrDie(/*sequencing_id=*/0);
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Required(1, kData[1])
.Required(2, kData[2]);
}));
// Forward time to trigger upload
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
// Confirm #1 and forward time again, removing record #1
ConfirmOrDie(/*sequencing_id=*/1);
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client).Required(2, kData[2]);
}));
// Forward time to trigger upload
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
ResetTestStorageQueue();
CreateTestStorageQueueOrDie(BuildStorageQueueOptionsPeriodic());
// Add more data and verify that #2 and new data are returned.
WriteStringOrDie(kMoreData[0]);
WriteStringOrDie(kMoreData[1]);
WriteStringOrDie(kMoreData[2]);
// Inject simulated failures.
InjectFailures({4, 5});
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Possible(0, kData[0])
.Possible(1, kData[1])
.Required(2, kData[2])
.Required(3, kMoreData[0])
// Gap may be 2 records at once or 2 gaps 1 record each.
.PossibleGap(4, 2)
.PossibleGap(4, 1)
.PossibleGap(5, 1);
}));
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
// Confirm #2 and forward time again, removing record #2
ConfirmOrDie(/*sequencing_id=*/2);
// Reset simulated failures.
InjectFailures({});
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Required(3, kMoreData[0])
.Required(4, kMoreData[1])
.Required(5, kMoreData[2]);
}));
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
}
TEST_P(StorageQueueTest, WriteAndRepeatedlyImmediateUpload) {
CreateTestStorageQueueOrDie(BuildStorageQueueOptionsImmediate());
// Upload is initiated asynchronously, so it may happen after the next
// record is also written. Because of that we set expectations for the
// data after the current one as |Possible|.
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Required(0, kData[0])
.Possible(1, kData[1])
.Possible(2, kData[2]);
}))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Required(0, kData[0])
.Required(1, kData[1])
.Possible(2, kData[2]);
}))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Required(0, kData[0])
.Required(1, kData[1])
.Required(2, kData[2]);
}));
WriteStringOrDie(kData[0]);
WriteStringOrDie(kData[1]);
WriteStringOrDie(kData[2]);
}
TEST_P(StorageQueueTest, WriteAndRepeatedlyImmediateUploadWithConfirmations) {
CreateTestStorageQueueOrDie(BuildStorageQueueOptionsImmediate());
test::TestCallbackWaiter waiter[6];
// Upload is initiated asynchronously, so it may happen after the next
// record is also written. Because of the Confirmation below, we set
// expectations for the data that may be eliminated by Confirmation as
// |Possible|.
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([&waiter](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Possible(0, kData[0])
.Possible(1, kData[1])
.Possible(2, kData[2]);
waiter[0].Signal();
}))
.WillOnce(Invoke([&waiter](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Possible(0, kData[0])
.Possible(1, kData[1])
.Possible(2, kData[2]);
waiter[1].Signal();
}))
.WillOnce(Invoke([&waiter](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Possible(0, kData[0])
.Possible(1, kData[1])
.Required(2, kData[2]);
waiter[2].Signal();
}))
// After adding more data verify that #2 and new data are returned.
// Upload is initiated asynchronously, so it may happen after the next
// record is also written. Because of that we set expectations for the
// records after the current one as |Possible|.
.WillOnce(Invoke([&waiter](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Required(2, kData[2])
.Required(3, kMoreData[0])
.Possible(4, kMoreData[1])
.Possible(5, kMoreData[2]);
waiter[3].Signal();
}))
.WillOnce(Invoke([&waiter](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Required(2, kData[2])
.Required(3, kMoreData[0])
.Required(4, kMoreData[1])
.Possible(5, kMoreData[2]);
waiter[4].Signal();
}))
.WillOnce(Invoke([&waiter](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Required(2, kData[2])
.Required(3, kMoreData[0])
.Required(4, kMoreData[1])
.Required(5, kMoreData[2]);
waiter[5].Signal();
}));
waiter[0].Attach();
WriteStringOrDie(kData[0]);
waiter[0].Wait();
waiter[1].Attach();
WriteStringOrDie(kData[1]);
waiter[1].Wait();
waiter[2].Attach();
WriteStringOrDie(kData[2]);
waiter[2].Wait();
// Confirm #1, removing data #0 and #1
ConfirmOrDie(/*sequencing_id=*/1);
// Add more data to verify that #2 and new data are returned.
waiter[3].Attach();
WriteStringOrDie(kMoreData[0]);
waiter[3].Wait();
waiter[4].Attach();
WriteStringOrDie(kMoreData[1]);
waiter[4].Wait();
waiter[5].Attach();
WriteStringOrDie(kMoreData[2]);
waiter[5].Wait();
}
TEST_P(StorageQueueTest, WriteEncryptFailure) {
CreateTestStorageQueueOrDie(BuildStorageQueueOptionsPeriodic());
DCHECK(test_encryption_module_);
EXPECT_CALL(*test_encryption_module_, EncryptRecordImpl(_, _))
.WillOnce(WithArg<1>(
Invoke([](base::OnceCallback<void(StatusOr<EncryptedRecord>)> cb) {
std::move(cb).Run(Status(error::UNKNOWN, "Failing for tests"));
})));
const Status result = WriteString("TEST_MESSAGE");
EXPECT_FALSE(result.ok());
EXPECT_EQ(result.error_code(), error::UNKNOWN);
}
TEST_P(StorageQueueTest, ForceConfirm) {
CreateTestStorageQueueOrDie(BuildStorageQueueOptionsPeriodic());
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(NotNull()))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.Required(0, kData[0])
.Required(1, kData[1])
.Required(2, kData[2]);
}))
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client).Required(2, kData[2]);
}))
// #0 and #1 could be returned as Gaps
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.RequiredSeqId(0)
.RequiredSeqId(1)
.RequiredSeqId(2)
// 0-2 must have been encountered, but actual contents
// can be different:
.Possible(0, kData[0])
.PossibleGap(0, 1)
.PossibleGap(0, 2)
.Possible(1, kData[1])
.Required(2, kData[2]);
}))
// #0 and #1 could be returned as Gaps
.WillOnce(Invoke([](MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(mock_upload_client)
.RequiredSeqId(1)
.RequiredSeqId(2)
// 0-2 must have been encountered, but actual contents
// can be different:
.PossibleGap(1, 1)
.Possible(1, kData[1])
.Required(2, kData[2]);
}));
WriteStringOrDie(kData[0]);
WriteStringOrDie(kData[1]);
WriteStringOrDie(kData[2]);
// Forward time to trigger upload
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
// Confirm #1 and forward time again, possibly removing records #0 and #1
ConfirmOrDie(/*sequencing_id=*/1);
// Forward time to trigger upload
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
// Now force confirm the very beginning and forward time again.
ConfirmOrDie(/*sequencing_id=*/base::nullopt, /*force=*/true);
// Forward time to trigger upload
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
// Force confirm #0 and forward time again.
ConfirmOrDie(/*sequencing_id=*/0, /*force=*/true);
// Forward time to trigger upload
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
}
INSTANTIATE_TEST_SUITE_P(VaryingFileSize,
StorageQueueTest,
testing::Values(128 * 1024LL * 1024LL,
256 /* two records in file */,
1 /* single record in file */));
// TODO(b/157943006): Additional tests:
// 1) Options object with a bad path.
// 2) Have bad prefix files in the directory.
// 3) Attempt to create file with duplicated file extension.
// 4) Disk and memory limit exceeded.
// 5) Other negative tests.
} // namespace
} // namespace reporting