blob: 92f512c4ff859c1d610a8da6a6a38c979cc4c215 [file] [log] [blame]
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "federated/storage_manager.h"
#include <cstdint>
#include <memory>
#include <optional>
#include <utility>
#include <base/strings/stringprintf.h>
#include <base/test/task_environment.h>
#include <base/time/time.h>
#include <gmock/gmock.h>
#include <google/protobuf/util/time_util.h>
#include "federated/example_database.h"
#include "federated/mock_example_database.h"
#include "federated/protos/cros_example_selector_criteria.pb.h"
#include "federated/test_utils.h"
#include "federated/utils.h"
namespace federated {
namespace {
using ::google::protobuf::util::TimeUtil;
using ::testing::_;
using ::testing::ByMove;
using ::testing::Expectation;
using ::testing::ExpectationSet;
using ::testing::Mock;
using ::testing::Return;
using ::testing::StrictMock;
using ::testing::Test;
} // namespace
class StorageManagerTest : public Test {
public:
StorageManagerTest()
: task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
example_database_(
new StrictMock<MockExampleDatabase>(base::FilePath(""))),
storage_manager_(new StorageManager()) {
// criteria_ is minimally initialized.
criteria_.set_task_name("test_task_name");
}
void SetUp() override {
storage_manager_->set_example_database_for_testing(example_database_);
}
void TearDown() override {
Mock::VerifyAndClearExpectations(example_database_);
}
protected:
base::test::TaskEnvironment task_environment_;
MockExampleDatabase* const example_database_;
const std::unique_ptr<StorageManager> storage_manager_;
fcp::client::CrosExampleSelectorCriteria criteria_;
};
TEST_F(StorageManagerTest, ExampleRecieved) {
EXPECT_CALL(*example_database_, IsOpen())
.WillOnce(Return(false))
.WillRepeatedly(Return(true));
EXPECT_CALL(*example_database_, InsertExample("client", _))
.WillRepeatedly(Return(true));
// First call will fail due to the database !IsOpen;
EXPECT_FALSE(storage_manager_->OnExampleReceived("client", "example"));
EXPECT_TRUE(storage_manager_->OnExampleReceived("client", "example"));
}
// Tests that the databases example iterator is faithfully returned.
TEST_F(StorageManagerTest, ExampleStreaming) {
base::Time end_timestamp = base::Time::Now();
base::Time start_timestamp = end_timestamp - base::Days(20);
EXPECT_CALL(*example_database_, IsOpen())
.WillOnce(Return(false))
.WillOnce(Return(true));
EXPECT_CALL(*example_database_,
ExampleCount("fake_client", start_timestamp, end_timestamp))
.WillOnce(Return(kMinExampleCount));
// Returns a valid number of examples, then returns absl::OutOfRangeError().
auto db_and_it = MockExampleDatabase::FakeIterator(kMinExampleCount);
EXPECT_CALL(*example_database_, GetIterator("fake_client", start_timestamp,
end_timestamp, false, 0))
.WillOnce(Return(ByMove(std::move(std::get<1>(db_and_it)))));
// Fails due to !example_database_->IsOpen.
EXPECT_EQ(storage_manager_->GetExampleIterator(
"fake_client", "fake_task_identifier", criteria_),
std::nullopt);
std::optional<ExampleDatabase::Iterator> it =
storage_manager_->GetExampleIterator("fake_client",
"fake_task_identifier", criteria_);
ASSERT_TRUE(it.has_value());
// Expects the examples we specified.
int count = 0;
while (true) {
const absl::StatusOr<ExampleRecord> record = it->Next();
if (!record.ok()) {
EXPECT_TRUE(absl::IsOutOfRange(record.status()));
break;
}
EXPECT_EQ(record->id, count + 1);
EXPECT_EQ(record->serialized_example,
base::StringPrintf("example_%d", count + 1));
EXPECT_EQ(record->timestamp, SecondsAfterEpoch(count + 1));
++count;
}
EXPECT_EQ(count, kMinExampleCount);
}
// Tests that minimum example limit is honored.
TEST_F(StorageManagerTest, ExampleStreamingMinimum) {
EXPECT_CALL(*example_database_, IsOpen()).WillRepeatedly(Return(true));
EXPECT_CALL(*example_database_, ExampleCount("fake_client", _, _))
.WillOnce(Return(kMinExampleCount - 1))
.WillOnce(Return(100));
// Uses default kMinExampleCount
EXPECT_EQ(storage_manager_->GetExampleIterator(
"fake_client", "fake_task_identifier", criteria_),
std::nullopt);
// Uses `min_examples`
criteria_.set_min_examples(101);
EXPECT_EQ(storage_manager_->GetExampleIterator(
"fake_client", "fake_task_identifier", criteria_),
std::nullopt);
}
// Tests that when reject_used_examples, and metatable has record for this task,
// selection range is altered.
TEST_F(StorageManagerTest, CriteriaRejectUsedExamples) {
EXPECT_CALL(*example_database_, IsOpen()).WillRepeatedly(Return(true));
base::Time end_timestamp = base::Time::Now();
base::Time default_start_timestamp = base::Time::Now() - base::Days(20);
base::Time last_used_example_timestamp = base::Time::Now() - base::Hours(10);
const MetaRecord meta_record = {"", 1, last_used_example_timestamp,
base::Time::Now()};
EXPECT_CALL(*example_database_, GetMetaRecord("fake_task_identifier"))
.WillOnce(Return(std::nullopt))
.WillOnce(Return(meta_record));
criteria_.set_reject_used_examples(true);
EXPECT_CALL(
*example_database_,
ExampleCount("fake_client", default_start_timestamp, end_timestamp))
.WillOnce(Return(kMinExampleCount));
EXPECT_CALL(*example_database_,
GetIterator("fake_client", default_start_timestamp, end_timestamp,
false, 0))
.WillOnce(Return(ByMove(ExampleDatabase::Iterator())));
EXPECT_TRUE(
storage_manager_
->GetExampleIterator("fake_client", "fake_task_identifier", criteria_)
.has_value());
EXPECT_CALL(
*example_database_,
ExampleCount("fake_client", last_used_example_timestamp, end_timestamp))
.WillOnce(Return(kMinExampleCount));
EXPECT_CALL(*example_database_,
GetIterator("fake_client", last_used_example_timestamp,
end_timestamp, false, 0))
.WillOnce(Return(ByMove(ExampleDatabase::Iterator())));
EXPECT_TRUE(
storage_manager_
->GetExampleIterator("fake_client", "fake_task_identifier", criteria_)
.has_value());
}
TEST_F(StorageManagerTest, CriteriaOrderAndLimit) {
EXPECT_CALL(*example_database_, IsOpen()).WillRepeatedly(Return(true));
EXPECT_CALL(*example_database_, ExampleCount("fake_client", _, _))
.WillRepeatedly(Return(kMinExampleCount));
// By default, order = ascending, no limit(0);
EXPECT_CALL(*example_database_,
GetIterator("fake_client", _, _,
/*descending=*/false, /*limit=*/0))
.WillOnce(Return(ByMove(ExampleDatabase::Iterator())));
EXPECT_TRUE(
storage_manager_
->GetExampleIterator("fake_client", "fake_task_identifier", criteria_)
.has_value());
// Respects the order setting.
criteria_.set_order(
fcp::client::CrosExampleSelectorCriteria::INSERTION_DESCENDING);
EXPECT_CALL(*example_database_, GetIterator("fake_client", _, _,
/*descending=*/true, /*limit=*/0))
.WillOnce(Return(ByMove(ExampleDatabase::Iterator())));
EXPECT_TRUE(
storage_manager_
->GetExampleIterator("fake_client", "fake_task_identifier", criteria_)
.has_value());
// Respects the limit (max_examples) setting.
criteria_.set_max_examples(100);
EXPECT_CALL(*example_database_,
GetIterator("fake_client", _, _,
/*descending=*/true, /*limit=*/100))
.WillOnce(Return(ByMove(ExampleDatabase::Iterator())));
EXPECT_TRUE(
storage_manager_
->GetExampleIterator("fake_client", "fake_task_identifier", criteria_)
.has_value());
// Invalid limit setting is ignored.
criteria_.set_max_examples(-1);
EXPECT_CALL(*example_database_, GetIterator("fake_client", _, _,
/*descending=*/true, /*limit=*/0))
.WillOnce(Return(ByMove(ExampleDatabase::Iterator())));
EXPECT_TRUE(
storage_manager_
->GetExampleIterator("fake_client", "fake_task_identifier", criteria_)
.has_value());
}
} // namespace federated