blob: a6b7bd9c68eb53f200e39a95264fe29a16df2eb8 [file] [log] [blame]
// Copyright 2015 The Chromium 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 "components/password_manager/core/browser/android_affiliation/facet_manager.h"
#include <stddef.h>
#include <algorithm>
#include <memory>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/location.h"
#include "base/memory/ref_counted.h"
#include "base/rand_util.h"
#include "base/stl_util.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/test/test_simple_task_runner.h"
#include "base/time/clock.h"
#include "base/time/time.h"
#include "components/password_manager/core/browser/android_affiliation/facet_manager_host.h"
#include "components/password_manager/core/browser/android_affiliation/mock_affiliation_consumer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace password_manager {
// Helpers --------------------------------------------------------------------
namespace {
using StrategyOnCacheMiss = FacetManager::StrategyOnCacheMiss;
enum class NotificationAccuracy { PERFECT, TOO_LATE, TOO_EARLY, NEVER_CALLED };
// Helper class to post callbacks to FacetManager::NotifyAtRequestedTime(),
// delayed by the requested time plus/minus a configurable error term to
// simulate a real-life task runner.
class TestFacetManagerNotifier {
public:
TestFacetManagerNotifier(
scoped_refptr<base::TestMockTimeTaskRunner> task_runner,
base::TimeDelta too_late_delay)
: accuracy_(NotificationAccuracy::PERFECT),
too_late_delay_(too_late_delay),
task_runner_(task_runner),
facet_manager_(nullptr) {}
void Notify(base::Time time) {
base::TimeDelta delay = time - task_runner_->Now();
if (accuracy_ == NotificationAccuracy::TOO_LATE) {
delay += too_late_delay_;
} else if (accuracy_ == NotificationAccuracy::TOO_EARLY) {
// This formula is a simple stateless solution for notifying FacetManagers
// prematurely multiple times in a row while also ensuring that the tests
// are still fast, with no more than log2(delay.InSeconds()) repetitions.
delay = std::min(delay, delay / 2 + base::TimeDelta::FromSeconds(1));
} else if (accuracy_ == NotificationAccuracy::NEVER_CALLED) {
return;
}
task_runner_->PostDelayedTask(
FROM_HERE,
base::BindOnce(&FacetManager::NotifyAtRequestedTime,
base::Unretained(facet_manager_)),
delay);
}
void set_accuracy(NotificationAccuracy accuracy) { accuracy_ = accuracy; }
void set_facet_manager(FacetManager* facet_manager_under_test) {
facet_manager_ = facet_manager_under_test;
}
private:
NotificationAccuracy accuracy_;
const base::TimeDelta too_late_delay_;
scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
FacetManager* facet_manager_;
DISALLOW_COPY_AND_ASSIGN(TestFacetManagerNotifier);
};
// Stub/mock implementation for FacetManagerHost.
class MockFacetManagerHost : public FacetManagerHost {
public:
explicit MockFacetManagerHost(TestFacetManagerNotifier* notifier)
: notifier_(notifier), signaled_need_network_request_(false) {}
~MockFacetManagerHost() override {}
// Sets the |facet_uri| that will be expected to appear in calls coming from
// the FacetManager under test.
void set_expected_facet_uri(const FacetURI& facet_uri) {
expected_facet_uri_ = facet_uri;
}
// Returns the facet URI that will be expected to appear in calls coming from
// the FacetManager under test.
const FacetURI& expected_facet_uri() const { return expected_facet_uri_; }
// Sets up fake |database_content| as the canned response to be returned to
// the FacetManager every time it calls
// ReadAffiliationsAndBrandingFromDatabase().
void set_fake_database_content(
const AffiliatedFacetsWithUpdateTime& database_content) {
fake_database_content_ = database_content;
}
void clear_fake_database_content() {
fake_database_content_.last_update_time = base::Time();
}
// Returns whether SignalNeedNetworkRequest() has been called at least once.
size_t signaled_need_network_request() const {
return signaled_need_network_request_;
}
void reset_need_network_request() { signaled_need_network_request_ = false; }
private:
// FacetManagerHost:
bool ReadAffiliationsAndBrandingFromDatabase(
const FacetURI& facet_uri,
AffiliatedFacetsWithUpdateTime* affiliations) override {
EXPECT_EQ(expected_facet_uri_, facet_uri);
if (fake_database_content_.last_update_time.is_null())
return false;
*affiliations = fake_database_content_;
return true;
}
void SignalNeedNetworkRequest() override {
signaled_need_network_request_ = true;
}
void RequestNotificationAtTime(const FacetURI& facet_uri,
base::Time time) override {
EXPECT_EQ(expected_facet_uri_, facet_uri);
// The absolute timing of notification requests is not all that interesting,
// only the ability to perturb it slightly, which is done by the notifier.
notifier_->Notify(time);
}
TestFacetManagerNotifier* notifier_;
FacetURI expected_facet_uri_;
AffiliatedFacetsWithUpdateTime fake_database_content_;
bool signaled_need_network_request_;
DISALLOW_COPY_AND_ASSIGN(MockFacetManagerHost);
};
const bool kFalseTrue[] = {false, true};
const char kTestFacetURI1[] = "https://one.example.com";
const char kTestFacetURI2[] = "https://two.example.com";
const char kTestFacetURI3[] = "https://three.example.com";
AffiliatedFacets GetTestEquivalenceClass() {
return {
{FacetURI::FromCanonicalSpec(kTestFacetURI1)},
{FacetURI::FromCanonicalSpec(kTestFacetURI2)},
{FacetURI::FromCanonicalSpec(kTestFacetURI3)},
};
}
AffiliatedFacetsWithUpdateTime GetTestEquivalenceClassWithUpdateTime(
base::Time last_update_time) {
AffiliatedFacetsWithUpdateTime affiliation;
affiliation.last_update_time = last_update_time;
affiliation.facets = GetTestEquivalenceClass();
return affiliation;
}
base::TimeDelta GetCacheHardExpiryPeriod() {
return base::TimeDelta::FromHours(FacetManager::kCacheHardExpiryInHours);
}
base::TimeDelta GetCacheSoftExpiryPeriod() {
return base::TimeDelta::FromHours(FacetManager::kCacheSoftExpiryInHours);
}
base::TimeDelta GetShortTestPeriod() {
return base::TimeDelta::FromHours(1);
}
// Returns a smallest time difference that this test cares about.
base::TimeDelta Epsilon() {
return base::TimeDelta::FromMicroseconds(1);
}
// Returns |time| + |delay| or the maximum time if |delay| is the maximum delta.
base::Time SafeAdd(base::Time time, base::TimeDelta delay) {
if (delay == base::TimeDelta::Max())
return base::Time::Max();
return time + delay;
}
// Subdivides a time interval of a given |duration| into zero or more intervals
// that lend themselves to be used for sampling a quantity every that often.
// More specifically, returns the minimum number of intervals so that each
// sub-interval is at most GetTestShortInterval() long, and that the last of the
// sub-intervals (if any) is exactly Epsilon() long. No intervals are returned
// if |duration| is of length zero.
std::vector<base::TimeDelta> SamplingPoints(base::TimeDelta duration) {
std::vector<base::TimeDelta> deltas;
if (duration > base::TimeDelta()) {
while (duration > Epsilon()) {
deltas.push_back(std::min(GetShortTestPeriod(), duration - Epsilon()));
duration -= deltas.back();
}
deltas.push_back(Epsilon());
}
return deltas;
}
} // namespace
// Test framework -------------------------------------------------------------
class FacetManagerTest : public testing::Test {
public:
FacetManagerTest()
: consumer_task_runner_(new base::TestSimpleTaskRunner),
main_task_runner_(new base::TestMockTimeTaskRunner),
facet_manager_notifier_(main_task_runner_, GetShortTestPeriod()),
facet_manager_host_(&facet_manager_notifier_) {}
protected:
struct ExpectedFetchDetails {
// The expected time of the fetch being triggered.
base::Time time;
// A simulated delay after which the fetch will be completed.
// base::TimeDelta::Max() means that the fetch will be left hanging.
base::TimeDelta completion_delay;
};
void CreateFacetManager() {
// The order is important: FacetManager will read the DB in its constructor.
facet_manager_host_.set_expected_facet_uri(
FacetURI::FromCanonicalSpec(kTestFacetURI1));
facet_manager_ = std::make_unique<FacetManager>(
FacetURI::FromCanonicalSpec(kTestFacetURI1), fake_facet_manager_host(),
main_task_runner_->GetMockClock());
facet_manager_notifier_.set_facet_manager(facet_manager_.get());
facet_manager_creation_ = Now();
}
void DestroyFacetManager() {
main_task_runner_->ClearPendingTasks();
facet_manager_host_.set_expected_facet_uri(FacetURI());
facet_manager_notifier_.set_facet_manager(nullptr);
facet_manager_.reset();
}
void AdvanceTime(base::TimeDelta delta) {
main_task_runner_->FastForwardBy(delta);
}
base::Time Now() { return main_task_runner_->Now(); }
// Returns the elapsed time since CreateFacetManager() was last called.
base::TimeDelta DeltaNow() { return Now() - facet_manager_creation_; }
void GetAffiliationsAndBranding(StrategyOnCacheMiss cache_miss_strategy) {
facet_manager()->GetAffiliationsAndBranding(
cache_miss_strategy, mock_consumer()->GetResultCallback(),
consumer_task_runner());
}
void Prefetch(base::Time until) { facet_manager()->Prefetch(until); }
void CancelPrefetch(base::Time until) {
facet_manager()->CancelPrefetch(until);
}
void SchedulePrefetch(base::Time start, base::Time end) {
main_task_runner_->PostDelayedTask(
FROM_HERE,
base::BindOnce(&FacetManager::Prefetch,
base::Unretained(facet_manager()), end),
start - Now());
}
void ScheduleCancelPrefetch(base::Time cancellation_time,
base::Time original_end_of_prefetch) {
main_task_runner_->PostDelayedTask(
FROM_HERE,
base::BindOnce(&FacetManager::CancelPrefetch,
base::Unretained(facet_manager()),
original_end_of_prefetch),
cancellation_time - Now());
}
// Advances time |until_time|, and verifies that a Prefetch() request has the
// expected effects now and then (but not at |until_time|), namely that:
// * the FacetManager cannot be discarded, and
// * requests are served from the cache;
// and that no fetches are triggered in this interval.
void AdvanceTimeAndVerifyPrefetch(base::Time until_time) {
for (base::TimeDelta step : SamplingPoints(until_time - Now())) {
SCOPED_TRACE(testing::Message() << "dT: " << DeltaNow());
EXPECT_FALSE(facet_manager()->CanBeDiscarded());
EXPECT_FALSE(facet_manager()->CanCachedDataBeDiscarded());
ExpectRequestsServedFromCache();
ExpectNoFetchNeeded();
AdvanceTime(step);
}
}
// Advances time |until_time|, and verifies that between now and then, the
// FacetManager cannot be discarded, and a fetch is needed all the time.
void AdvanceTimeAndExpectFetchNeeded(base::Time until_time) {
for (base::TimeDelta step : SamplingPoints(until_time - Now())) {
SCOPED_TRACE(testing::Message() << "dT: " << DeltaNow());
EXPECT_FALSE(facet_manager()->CanBeDiscarded());
ASSERT_NO_FATAL_FAILURE(ExpectFetchNeeded());
AdvanceTime(step);
}
}
// Advances time |until_time|, and verifies that a Prefetch() request has the
// expected effects between now and then (but not at |until_time|), namely:
// * the FacetManager cannot be discarded, and
// * requests are served from the cache;
// and that no fetches are made exactly as prescribed in |expected_fetches|.
void AdvanceTimeAndVerifyPrefetchWithFetchesAt(
base::Time until_time,
const std::vector<ExpectedFetchDetails>& expected_fetches) {
for (const auto& next_fetch : expected_fetches) {
AdvanceTimeAndVerifyPrefetch(next_fetch.time);
ASSERT_NO_FATAL_FAILURE(AdvanceTimeAndExpectFetchNeeded(
std::min(until_time, SafeAdd(Now(), next_fetch.completion_delay))));
if (next_fetch.completion_delay < base::TimeDelta::Max())
CompleteFetch();
}
AdvanceTimeAndVerifyPrefetch(until_time);
}
void ExpectFetchNeeded() {
ASSERT_TRUE(facet_manager()->DoesRequireFetch());
ASSERT_TRUE(fake_facet_manager_host()->signaled_need_network_request());
}
void ExpectNoFetchNeeded() {
ASSERT_FALSE(facet_manager()->DoesRequireFetch());
ASSERT_FALSE(fake_facet_manager_host()->signaled_need_network_request());
}
void CompleteFetch() {
AffiliatedFacetsWithUpdateTime fetch_result(
GetTestEquivalenceClassWithUpdateTime(Now()));
fake_facet_manager_host()->set_fake_database_content(fetch_result);
fake_facet_manager_host()->reset_need_network_request();
facet_manager()->OnFetchSucceeded(fetch_result);
main_task_runner_->RunUntilIdle();
ASSERT_NO_FATAL_FAILURE(ExpectNoFetchNeeded());
ASSERT_TRUE(facet_manager()->IsCachedDataFresh());
}
void ExpectConsumerSuccessCallback() {
const auto equivalence_class(GetTestEquivalenceClass());
mock_consumer()->ExpectSuccessWithResult(equivalence_class);
EXPECT_THAT(
equivalence_class,
testing::Contains(testing::Field(
&Facet::uri, fake_facet_manager_host()->expected_facet_uri())));
consumer_task_runner()->RunUntilIdle();
testing::Mock::VerifyAndClearExpectations(mock_consumer());
}
void ExpectConsumerFailureCallback() {
mock_consumer()->ExpectFailure();
consumer_task_runner()->RunUntilIdle();
testing::Mock::VerifyAndClearExpectations(mock_consumer());
}
void ExpectRequestsServedFromCache() {
EXPECT_TRUE(facet_manager()->IsCachedDataFresh());
GetAffiliationsAndBranding(StrategyOnCacheMiss::FAIL);
ExpectConsumerSuccessCallback();
}
MockAffiliationConsumer* mock_consumer() { return &mock_consumer_; }
base::TestSimpleTaskRunner* consumer_task_runner() {
return consumer_task_runner_.get();
}
base::TestMockTimeTaskRunner* main_task_runner() {
return main_task_runner_.get();
}
MockFacetManagerHost* fake_facet_manager_host() {
return &facet_manager_host_;
}
TestFacetManagerNotifier* facet_manager_notifier() {
return &facet_manager_notifier_;
}
FacetManager* facet_manager() { return facet_manager_.get(); }
private:
// testing::Test:
void SetUp() override {
ASSERT_LT(3 * GetShortTestPeriod(), GetCacheSoftExpiryPeriod());
ASSERT_LT(GetShortTestPeriod(),
GetCacheHardExpiryPeriod() - GetCacheSoftExpiryPeriod());
}
void TearDown() override { DestroyFacetManager(); }
MockAffiliationConsumer mock_consumer_;
scoped_refptr<base::TestSimpleTaskRunner> consumer_task_runner_;
scoped_refptr<base::TestMockTimeTaskRunner> main_task_runner_;
TestFacetManagerNotifier facet_manager_notifier_;
MockFacetManagerHost facet_manager_host_;
std::unique_ptr<FacetManager> facet_manager_;
base::Time facet_manager_creation_;
DISALLOW_COPY_AND_ASSIGN(FacetManagerTest);
};
// Tests ----------------------------------------------------------------------
TEST_F(FacetManagerTest, NewInstanceCanBeDiscarded) {
CreateFacetManager();
EXPECT_TRUE(facet_manager()->CanBeDiscarded());
EXPECT_TRUE(facet_manager()->CanCachedDataBeDiscarded());
EXPECT_FALSE(facet_manager()->DoesRequireFetch());
EXPECT_FALSE(main_task_runner()->HasPendingTask());
}
// Both cached-only and on-demand GetAffiliationsAndBranding() requests should
// be served from cache if it contains fresh data. Nothing should happen on
// cache expiry.
TEST_F(FacetManagerTest, GetAffiliationsAndBrandingServedFromCache) {
fake_facet_manager_host()->set_fake_database_content(
GetTestEquivalenceClassWithUpdateTime(Now()));
AdvanceTime(GetCacheHardExpiryPeriod() - Epsilon());
CreateFacetManager();
EXPECT_TRUE(facet_manager()->IsCachedDataFresh());
GetAffiliationsAndBranding(StrategyOnCacheMiss::FAIL);
ExpectConsumerSuccessCallback();
EXPECT_TRUE(facet_manager()->CanBeDiscarded());
ASSERT_NO_FATAL_FAILURE(ExpectNoFetchNeeded());
GetAffiliationsAndBranding(StrategyOnCacheMiss::FETCH_OVER_NETWORK);
ExpectConsumerSuccessCallback();
EXPECT_TRUE(facet_manager()->CanBeDiscarded());
ASSERT_NO_FATAL_FAILURE(ExpectNoFetchNeeded());
AdvanceTime(Epsilon());
EXPECT_FALSE(facet_manager()->IsCachedDataFresh());
EXPECT_TRUE(facet_manager()->CanBeDiscarded());
ASSERT_NO_FATAL_FAILURE(ExpectNoFetchNeeded());
EXPECT_FALSE(main_task_runner()->HasPendingTask());
}
// On-demand GetAffiliationsAndBranding() requests should trigger a fetch if the
// cache has already stale data, or no corresponding data whatsoever. Nothing
// should happen once the newly fetched data expires.
TEST_F(FacetManagerTest,
OnDemandGetAffiliationsAndBrandingRequestTriggersFetch) {
for (const bool cache_initially_has_stale_data : kFalseTrue) {
SCOPED_TRACE(cache_initially_has_stale_data);
if (cache_initially_has_stale_data) {
fake_facet_manager_host()->set_fake_database_content(
GetTestEquivalenceClassWithUpdateTime(Now()));
AdvanceTime(GetCacheHardExpiryPeriod());
} else {
fake_facet_manager_host()->clear_fake_database_content();
}
CreateFacetManager();
EXPECT_FALSE(facet_manager()->IsCachedDataFresh());
GetAffiliationsAndBranding(StrategyOnCacheMiss::FETCH_OVER_NETWORK);
ASSERT_NO_FATAL_FAILURE(ExpectFetchNeeded());
EXPECT_FALSE(facet_manager()->CanBeDiscarded());
ASSERT_NO_FATAL_FAILURE(CompleteFetch());
ExpectConsumerSuccessCallback();
AdvanceTime(GetCacheHardExpiryPeriod() - Epsilon());
EXPECT_TRUE(facet_manager()->IsCachedDataFresh());
GetAffiliationsAndBranding(StrategyOnCacheMiss::FAIL);
ExpectConsumerSuccessCallback();
EXPECT_TRUE(facet_manager()->CanBeDiscarded());
ASSERT_NO_FATAL_FAILURE(ExpectNoFetchNeeded());
GetAffiliationsAndBranding(StrategyOnCacheMiss::FETCH_OVER_NETWORK);
ExpectConsumerSuccessCallback();
EXPECT_TRUE(facet_manager()->CanBeDiscarded());
ASSERT_NO_FATAL_FAILURE(ExpectNoFetchNeeded());
AdvanceTime(Epsilon());
ASSERT_NO_FATAL_FAILURE(ExpectNoFetchNeeded());
EXPECT_FALSE(facet_manager()->IsCachedDataFresh());
EXPECT_TRUE(facet_manager()->CanBeDiscarded());
EXPECT_FALSE(main_task_runner()->HasPendingTask());
}
}
TEST_F(FacetManagerTest,
CachedOnlyGetAffiliationsAndBrandingFailsDueToStaleCache) {
CreateFacetManager();
EXPECT_FALSE(facet_manager()->IsCachedDataFresh());
GetAffiliationsAndBranding(StrategyOnCacheMiss::FAIL);
ExpectConsumerFailureCallback();
ASSERT_NO_FATAL_FAILURE(ExpectNoFetchNeeded());
EXPECT_TRUE(facet_manager()->CanBeDiscarded());
EXPECT_FALSE(main_task_runner()->HasPendingTask());
}
TEST_F(FacetManagerTest,
GetAffiliationsAndBrandingFailureCallbackInvokedOnDestruction) {
CreateFacetManager();
EXPECT_FALSE(facet_manager()->IsCachedDataFresh());
GetAffiliationsAndBranding(StrategyOnCacheMiss::FETCH_OVER_NETWORK);
ASSERT_NO_FATAL_FAILURE(ExpectFetchNeeded());
EXPECT_FALSE(facet_manager()->CanBeDiscarded());
// Leave the fetch hanging and destroy the facet manager.
DestroyFacetManager();
ExpectConsumerFailureCallback();
fake_facet_manager_host()->reset_need_network_request();
}
// The following tests verify both typical and edge case behavior of Prefetch()
// requests: they should prevent the FacetManager from being discarded, and keep
// the data fresh by initial fetches and refetches (scheduled as described in
// facet_manager.cc).
//
// Legend:
// [---): Interval representing a finite Prefetch request (open from right).
// The data should be kept fresh, the FacetManager not discarded.
// [--->: Interval representing a indefinite Prefetch request.
// The data should be kept fresh, the FacetManager not discarded.
// F: Fetch (initial or refetch) should take place here.
// Fn: The time of the n-th fetch (starting from 1).
// D: Time interval equal to GetShortTestPeriod().
// N: Fetch is signaled to be needed here.
// X: A corresponding CancelPrefetch call is placed here.
// S: |kCacheSoftExpiryInHours| hours
// H: |kCacheHardExpiryInHours| hours
//
// Note: It is guaranteed that S < H and that H < 2*S.
//
// Prefetches with the cache is initially stale/empty:
//
// t=0 S H F2+S F2+H
// / / / / /
// ---o--------------------------o-------o---------------o-------o---------> t
// : : : : :
// [) : : : :
// [F--) : : : :
// [F------------------------): : : :
// [F--------------------------------): : :
// [F-------------------------F----------) : :
// [F-------------------------F----------------------): :
// [F-------------------------F------------------------------):
// [F-------------------------F-----------------------F------------------>
//
TEST_F(FacetManagerTest, PrefetchWithEmptyOrStaleCache) {
struct {
base::TimeDelta prefetch_length;
size_t expected_num_fetches;
} const kTestCases[] = {
// Note: Zero length prefetches are tested later.
{GetShortTestPeriod(), 1},
{GetCacheSoftExpiryPeriod(), 1},
{GetCacheHardExpiryPeriod(), 1},
{GetCacheHardExpiryPeriod() + GetShortTestPeriod(), 2},
{GetCacheSoftExpiryPeriod() + GetCacheSoftExpiryPeriod(), 2},
{GetCacheHardExpiryPeriod() + GetCacheSoftExpiryPeriod(), 2},
{base::TimeDelta::Max(), 3}};
const base::TimeDelta kExpectedFetchTimes[] = {
base::TimeDelta(),
GetCacheSoftExpiryPeriod(),
2 * GetCacheSoftExpiryPeriod()};
const base::TimeDelta kMaximumTestDuration = 2 * GetCacheHardExpiryPeriod();
for (const bool cache_initially_stale : kFalseTrue) {
for (size_t i = 0; i < base::size(kTestCases); ++i) {
SCOPED_TRACE(testing::Message() << "Test case: #" << i);
SCOPED_TRACE(cache_initially_stale ? "Cache initially stale"
: "Cache initially empty");
if (cache_initially_stale) {
fake_facet_manager_host()->set_fake_database_content(
GetTestEquivalenceClassWithUpdateTime(Now()));
AdvanceTime(GetCacheHardExpiryPeriod());
} else {
fake_facet_manager_host()->clear_fake_database_content();
}
std::vector<ExpectedFetchDetails> expected_fetches;
expected_fetches.resize(kTestCases[i].expected_num_fetches);
for (size_t f = 0; f < kTestCases[i].expected_num_fetches; ++f)
expected_fetches[f].time = Now() + kExpectedFetchTimes[f];
CreateFacetManager();
Prefetch(SafeAdd(Now(), kTestCases[i].prefetch_length));
ASSERT_NO_FATAL_FAILURE(AdvanceTimeAndVerifyPrefetchWithFetchesAt(
Now() + std::min(kTestCases[i].prefetch_length, kMaximumTestDuration),
expected_fetches));
ASSERT_NO_FATAL_FAILURE(ExpectNoFetchNeeded());
if (kTestCases[i].prefetch_length < base::TimeDelta::Max()) {
EXPECT_TRUE(facet_manager()->CanBeDiscarded());
EXPECT_FALSE(main_task_runner()->HasPendingTask());
} else {
EXPECT_FALSE(facet_manager()->CanBeDiscarded());
}
DestroyFacetManager();
}
}
}
// Prefetches with cached affiliation data that is fresh to some extent:
//
// Suppose an unrelated fetch at t=0 has resulted in affiliation information
// being stored into the cache (freshness interval marked with '='). See legend
// above.
//
// t=0 S H F2+S F2+H
// / / / / /
// ---o--------------------------o-------o---------------o-------o-----> t
// [F================================): : :
// : : : : :
// [) : : : :
// [--) : : : :
// [-------------------------): : : :
// [---------------------------------): : :
// [--------------------------F-----------) : :
// [--------------------------F----------------------): :
// [--------------------------F------------------------------):
// [--------------------------F-----------------------F------------->
// : : : :
// [) : : : :
// [--) : : : :
// [---------------------): : : :
// [-----------------------------): : :
// [----------------------F-----------) : :
// [----------------------F----------------------): :
// [----------------------F------------------------------):
// [----------------------F-----------------------F------------->
// : : : :
// [) : : :
// [----) : : :
// [------): : :
// [F----------) : :
// [F---------------------): :
// [F-----------------------------):
// [F----------------------F------------->
//
// t=0 S S+D H F2+S F2+H
// / \ / / \ /
// ---o--------------------------o-o-----o-----------------o-------o-----> t
// [F================================): : :
// : : : :
// : [) : : :
// : [----): : :
// : [F------) : :
// : [F---------------------): :
// : [F-----------------------------):
// : [F----------------------F------------->
//
TEST_F(FacetManagerTest, PrefetchTriggeredFetchSchedulingAfterNonEmptyCache) {
struct {
base::TimeDelta prefetch_start;
base::TimeDelta prefetch_end;
size_t expected_num_fetches;
} const kTestCases[] = {
// Note: Zero length prefetches are tested later.
// Prefetch starts at the exact time the data was incidentally fetched.
{base::TimeDelta(), GetShortTestPeriod(), 0},
{base::TimeDelta(), GetCacheSoftExpiryPeriod(), 0},
{base::TimeDelta(), GetCacheHardExpiryPeriod(), 0},
{base::TimeDelta(), GetCacheHardExpiryPeriod() + GetShortTestPeriod(), 1},
{base::TimeDelta(), 2 * GetCacheSoftExpiryPeriod(), 1},
{base::TimeDelta(),
GetCacheSoftExpiryPeriod() + GetCacheHardExpiryPeriod(),
1},
{base::TimeDelta(), base::TimeDelta::Max(), 2},
// Prefetch starts a short time after the unrelated fetch.
{GetShortTestPeriod(), 2 * GetShortTestPeriod(), 0},
{GetShortTestPeriod(), GetCacheSoftExpiryPeriod(), 0},
{GetShortTestPeriod(), GetCacheHardExpiryPeriod(), 0},
{GetShortTestPeriod(),
GetCacheHardExpiryPeriod() + GetShortTestPeriod(),
1},
{GetShortTestPeriod(), 2 * GetCacheSoftExpiryPeriod(), 1},
{GetShortTestPeriod(),
GetCacheSoftExpiryPeriod() + GetCacheHardExpiryPeriod(),
1},
{GetShortTestPeriod(), base::TimeDelta::Max(), 2},
// Prefetch starts at the soft expiry time of the unrelated fetch.
{GetCacheSoftExpiryPeriod(),
GetCacheSoftExpiryPeriod() + GetShortTestPeriod(),
0},
{GetCacheSoftExpiryPeriod(), GetCacheHardExpiryPeriod(), 0},
{GetShortTestPeriod(),
GetCacheHardExpiryPeriod() + GetShortTestPeriod(),
1},
{GetCacheSoftExpiryPeriod(), 2 * GetCacheSoftExpiryPeriod(), 1},
{GetCacheSoftExpiryPeriod(),
GetCacheSoftExpiryPeriod() + GetCacheHardExpiryPeriod(),
1},
{GetCacheSoftExpiryPeriod(), base::TimeDelta::Max(), 2}};
const base::TimeDelta kExpectedFetchTimes[] = {
GetCacheSoftExpiryPeriod(), 2 * GetCacheSoftExpiryPeriod()};
const base::TimeDelta kMaximumTestDuration = 2 * GetCacheHardExpiryPeriod();
for (size_t i = 0; i < base::size(kTestCases); ++i) {
SCOPED_TRACE(testing::Message() << "Test case: #" << i);
fake_facet_manager_host()->set_fake_database_content(
GetTestEquivalenceClassWithUpdateTime(Now()));
const base::Time prefetch_end = SafeAdd(Now(), kTestCases[i].prefetch_end);
const base::Time testing_end =
std::min(prefetch_end, Now() + kMaximumTestDuration);
std::vector<ExpectedFetchDetails> expected_fetches;
expected_fetches.resize(kTestCases[i].expected_num_fetches);
for (size_t f = 0; f < kTestCases[i].expected_num_fetches; ++f)
expected_fetches[f].time = Now() + kExpectedFetchTimes[f];
AdvanceTime(kTestCases[i].prefetch_start);
CreateFacetManager();
Prefetch(prefetch_end);
ASSERT_NO_FATAL_FAILURE(AdvanceTimeAndVerifyPrefetchWithFetchesAt(
testing_end, expected_fetches));
ASSERT_NO_FATAL_FAILURE(ExpectNoFetchNeeded());
if (kTestCases[i].prefetch_end < base::TimeDelta::Max()) {
EXPECT_TRUE(facet_manager()->CanBeDiscarded());
EXPECT_FALSE(main_task_runner()->HasPendingTask());
} else {
EXPECT_FALSE(facet_manager()->CanBeDiscarded());
}
DestroyFacetManager();
}
}
// Last block of tests from above.
TEST_F(FacetManagerTest, PrefetchTriggeredFetchSchedulingAfterNonEmptyCache2) {
struct {
base::TimeDelta prefetch_start;
base::TimeDelta prefetch_end;
size_t expected_num_fetches;
} const kTestCases[] = {
// Note: Zero length prefetches are tested later.
// Prefetch starts between the soft and hard expiry time.
{GetCacheSoftExpiryPeriod() + GetShortTestPeriod(),
GetCacheHardExpiryPeriod(),
0},
{GetCacheSoftExpiryPeriod() + GetShortTestPeriod(),
GetCacheHardExpiryPeriod() + GetShortTestPeriod(),
1},
{GetCacheSoftExpiryPeriod() + GetShortTestPeriod(),
2 * GetCacheSoftExpiryPeriod() + GetShortTestPeriod(),
1},
{GetCacheSoftExpiryPeriod() + GetShortTestPeriod(),
GetCacheHardExpiryPeriod() + GetCacheSoftExpiryPeriod() +
GetShortTestPeriod(),
1},
{GetCacheSoftExpiryPeriod() + GetShortTestPeriod(),
base::TimeDelta::Max(),
2}};
const base::TimeDelta kExpectedFetchTimes[] = {
GetCacheSoftExpiryPeriod() + GetShortTestPeriod(),
2 * GetCacheSoftExpiryPeriod() + GetShortTestPeriod()};
const base::TimeDelta kMaximumTestDuration = 2 * GetCacheHardExpiryPeriod();
for (size_t i = 0; i < base::size(kTestCases); ++i) {
SCOPED_TRACE(testing::Message() << "Test case: #" << i);
fake_facet_manager_host()->set_fake_database_content(
GetTestEquivalenceClassWithUpdateTime(Now()));
const base::Time prefetch_end = SafeAdd(Now(), kTestCases[i].prefetch_end);
const base::Time testing_end =
std::min(prefetch_end, Now() + kMaximumTestDuration);
std::vector<ExpectedFetchDetails> expected_fetches;
expected_fetches.resize(kTestCases[i].expected_num_fetches);
for (size_t f = 0; f < kTestCases[i].expected_num_fetches; ++f)
expected_fetches[f].time = Now() + kExpectedFetchTimes[f];
AdvanceTime(kTestCases[i].prefetch_start);
CreateFacetManager();
Prefetch(prefetch_end);
ASSERT_NO_FATAL_FAILURE(AdvanceTimeAndVerifyPrefetchWithFetchesAt(
testing_end, expected_fetches));
ASSERT_NO_FATAL_FAILURE(ExpectNoFetchNeeded());
if (kTestCases[i].prefetch_end < base::TimeDelta::Max()) {
EXPECT_TRUE(facet_manager()->CanBeDiscarded());
EXPECT_FALSE(main_task_runner()->HasPendingTask());
} else {
EXPECT_FALSE(facet_manager()->CanBeDiscarded());
}
DestroyFacetManager();
}
}
// Prefetches from above that have zero length.
TEST_F(FacetManagerTest, ExpiredPrefetchDoesNothing) {
base::TimeDelta kPrefetchStart[] = {base::TimeDelta(),
GetShortTestPeriod(),
GetCacheSoftExpiryPeriod(),
GetCacheHardExpiryPeriod(),
base::TimeDelta::Max()};
for (base::TimeDelta prefetch_start : kPrefetchStart) {
SCOPED_TRACE(testing::Message() << "Prefetch start: " << prefetch_start);
if (prefetch_start < base::TimeDelta::Max()) {
fake_facet_manager_host()->set_fake_database_content(
GetTestEquivalenceClassWithUpdateTime(Now()));
AdvanceTime(prefetch_start);
} else {
fake_facet_manager_host()->clear_fake_database_content();
}
CreateFacetManager();
Prefetch(Now());
ASSERT_NO_FATAL_FAILURE(ExpectNoFetchNeeded());
EXPECT_TRUE(facet_manager()->CanBeDiscarded());
EXPECT_FALSE(main_task_runner()->HasPendingTask());
DestroyFacetManager();
}
}
// Nested prefetches. See legend above.
//
// t=0 S H F2+S F2+H
// / / / / /
// ---o--------------------------o-------o---------------o-------o---------> t
// : : : : :
// [F=========================F==============================):
// [F=========================F=======================F===========>
// [--) : : : :
// : [--) : : : :
// : [----------------------): : : :
// : [------------------------------): : :
// : [----------------------------------------------): :
// : [------------------------------------------------------):
// : [------): : :
// : [----------------------): :
// : [------------------------------):
// : : [----): : :
// : : [--------------------): :
// : : [----------------------------):
// : : [------):
// : : : [----):
//
TEST_F(FacetManagerTest, NestedPrefetches) {
struct {
base::TimeDelta prefetch_length;
size_t expected_num_fetches;
} const kFirstPrefetchParams[] = {
{GetCacheHardExpiryPeriod() + GetCacheSoftExpiryPeriod(), 2},
{base::TimeDelta::Max(), 3},
};
struct {
base::TimeDelta second_prefetch_start;
base::TimeDelta second_prefetch_end;
} const kSecondPrefetchParams[] = {
{base::TimeDelta(), GetShortTestPeriod()},
{GetShortTestPeriod(), 2 * GetShortTestPeriod()},
{GetShortTestPeriod(), GetCacheSoftExpiryPeriod()},
{GetShortTestPeriod(), GetCacheHardExpiryPeriod()},
{GetShortTestPeriod(), 2 * GetCacheSoftExpiryPeriod()},
{GetShortTestPeriod(),
GetCacheSoftExpiryPeriod() + GetCacheHardExpiryPeriod()},
{GetCacheSoftExpiryPeriod(), GetCacheHardExpiryPeriod()},
{GetCacheSoftExpiryPeriod(), 2 * GetCacheSoftExpiryPeriod()},
{GetCacheSoftExpiryPeriod(),
GetCacheSoftExpiryPeriod() + GetCacheHardExpiryPeriod()},
{GetCacheSoftExpiryPeriod() + GetShortTestPeriod(),
GetCacheHardExpiryPeriod()},
{GetCacheSoftExpiryPeriod() + GetShortTestPeriod(),
2 * GetCacheSoftExpiryPeriod()},
{GetCacheSoftExpiryPeriod() + GetShortTestPeriod(),
GetCacheSoftExpiryPeriod() + GetCacheHardExpiryPeriod()},
{2 * GetCacheSoftExpiryPeriod(),
GetCacheHardExpiryPeriod() + GetCacheSoftExpiryPeriod()},
{2 * GetCacheSoftExpiryPeriod() + GetShortTestPeriod(),
GetCacheHardExpiryPeriod() + GetCacheSoftExpiryPeriod()}};
const base::TimeDelta kExpectedFetchTimes[] = {
base::TimeDelta(),
GetCacheSoftExpiryPeriod(),
2 * GetCacheSoftExpiryPeriod()};
const base::TimeDelta kTestDuration =
GetCacheSoftExpiryPeriod() + GetCacheHardExpiryPeriod();
for (size_t j = 0; j < base::size(kFirstPrefetchParams); ++j) {
for (size_t i = 0; i < base::size(kSecondPrefetchParams); ++i) {
SCOPED_TRACE(testing::Message() << "Test case: #" << j << "." << i);
fake_facet_manager_host()->clear_fake_database_content();
std::vector<ExpectedFetchDetails> expected_fetches;
expected_fetches.resize(kFirstPrefetchParams[j].expected_num_fetches);
for (size_t f = 0; f < kFirstPrefetchParams[j].expected_num_fetches; ++f)
expected_fetches[f].time = Now() + kExpectedFetchTimes[f];
CreateFacetManager();
Prefetch(SafeAdd(Now(), kFirstPrefetchParams[j].prefetch_length));
SchedulePrefetch(Now() + kSecondPrefetchParams[i].second_prefetch_start,
Now() + kSecondPrefetchParams[i].second_prefetch_end);
ASSERT_NO_FATAL_FAILURE(AdvanceTimeAndVerifyPrefetchWithFetchesAt(
Now() + kTestDuration, expected_fetches));
ASSERT_NO_FATAL_FAILURE(ExpectNoFetchNeeded());
if (kFirstPrefetchParams[j].prefetch_length < base::TimeDelta::Max()) {
EXPECT_TRUE(facet_manager()->CanBeDiscarded());
EXPECT_FALSE(main_task_runner()->HasPendingTask());
} else {
EXPECT_FALSE(facet_manager()->CanBeDiscarded());
}
DestroyFacetManager();
}
}
}
// Overlapping prefetches. See legend above.
//
// t=0 S H F2+S F2+H
// / / / / /
// ---o--------------------------o-------o---------------o-------o---------> t
// : : : : :
// [F================================): : :
// : [--------------------F------------------------------):
// : [--------------------F-----------------------F----------->
// : [F-----------------------------):
// : [F----------------------F----------->
//
TEST_F(FacetManagerTest, OverlappingPrefetches) {
struct {
base::TimeDelta second_prefetch_start;
base::TimeDelta second_prefetch_end;
size_t expected_num_fetches;
} const kTestCases[] = {
{GetShortTestPeriod(),
GetCacheSoftExpiryPeriod() + GetCacheHardExpiryPeriod(),
2},
{GetShortTestPeriod(), base::TimeDelta::Max(), 3},
{GetCacheSoftExpiryPeriod(),
GetCacheSoftExpiryPeriod() + GetCacheHardExpiryPeriod(),
2},
{GetCacheSoftExpiryPeriod(), base::TimeDelta::Max(), 3}};
const base::TimeDelta kExpectedFetchTimes[] = {
base::TimeDelta(),
GetCacheSoftExpiryPeriod(),
2 * GetCacheSoftExpiryPeriod()};
const base::TimeDelta kTestDuration =
GetCacheSoftExpiryPeriod() + GetCacheHardExpiryPeriod();
for (size_t i = 0; i < base::size(kTestCases); ++i) {
SCOPED_TRACE(testing::Message() << "Test case: #" << i);
fake_facet_manager_host()->clear_fake_database_content();
std::vector<ExpectedFetchDetails> expected_fetches;
expected_fetches.resize(kTestCases[i].expected_num_fetches);
for (size_t f = 0; f < kTestCases[i].expected_num_fetches; ++f)
expected_fetches[f].time = Now() + kExpectedFetchTimes[f];
CreateFacetManager();
Prefetch(SafeAdd(Now(), GetCacheHardExpiryPeriod()));
SchedulePrefetch(Now() + kTestCases[i].second_prefetch_start,
SafeAdd(Now(), kTestCases[i].second_prefetch_end));
ASSERT_NO_FATAL_FAILURE(AdvanceTimeAndVerifyPrefetchWithFetchesAt(
Now() + kTestDuration, expected_fetches));
ASSERT_NO_FATAL_FAILURE(ExpectNoFetchNeeded());
if (kTestCases[i].second_prefetch_end < base::TimeDelta::Max()) {
EXPECT_TRUE(facet_manager()->CanBeDiscarded());
EXPECT_FALSE(main_task_runner()->HasPendingTask());
} else {
EXPECT_FALSE(facet_manager()->CanBeDiscarded());
}
DestroyFacetManager();
}
}
// Prefetches with network fetches taking non-zero time. See legend above.
//
// t=0 S S+D H S+H S+H+2*D
// / \ / / \ /
// ---o--------------------------o-o-----o-----------------------o-o-o-----> t
// : : : : : : :
// [NNF------------------------------): : : :
// [F-------------------------NNF----------------------------): : :
// [NNNNNNNNNNNNNNNNNNNNNNNNNNF------------------------------): : :
// : : : : : : :
// [NNF----------------------------------) : : :
// [F-------------------------NNF------------------------------): :
// [NNNNNNNNNNNNNNNNNNNNNNNNNNNNF------------------------------): :
// [NNF-------------------------NNF------------------------------):
// : : : : :
// [NNN) : : : :
// [NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN): :
// [F-------------------------NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN):
// [NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN):
// [NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN...
//
TEST_F(FacetManagerTest, PrefetchWithNonInstantFetches) {
struct {
base::TimeDelta prefetch_length;
base::TimeDelta expected_fetch_time1;
base::TimeDelta fetch_completion_delay1;
base::TimeDelta expected_fetch_time2;
base::TimeDelta fetch_completion_delay2;
} const kTestCases[] = {
{GetCacheHardExpiryPeriod(),
base::TimeDelta(),
GetShortTestPeriod(),
base::TimeDelta::Max(),
base::TimeDelta::Max()},
{GetCacheHardExpiryPeriod() + GetCacheSoftExpiryPeriod(),
base::TimeDelta(),
base::TimeDelta(),
GetCacheSoftExpiryPeriod(),
GetCacheSoftExpiryPeriod() + GetShortTestPeriod()},
{GetCacheHardExpiryPeriod() + GetCacheSoftExpiryPeriod(),
base::TimeDelta(),
GetCacheSoftExpiryPeriod(),
base::TimeDelta::Max(),
base::TimeDelta::Max()},
{GetCacheHardExpiryPeriod() + GetShortTestPeriod(),
base::TimeDelta(),
GetShortTestPeriod(),
base::TimeDelta::Max(),
base::TimeDelta::Max()},
{GetCacheHardExpiryPeriod() + GetCacheSoftExpiryPeriod() +
GetShortTestPeriod(),
base::TimeDelta(),
base::TimeDelta(),
GetCacheSoftExpiryPeriod(),
GetCacheSoftExpiryPeriod() + GetShortTestPeriod()},
{GetCacheHardExpiryPeriod() + GetCacheSoftExpiryPeriod() +
GetShortTestPeriod(),
base::TimeDelta(),
GetCacheSoftExpiryPeriod() + GetShortTestPeriod(),
base::TimeDelta::Max(),
base::TimeDelta::Max()},
{GetCacheHardExpiryPeriod() + GetCacheSoftExpiryPeriod() +
2 * GetShortTestPeriod(),
base::TimeDelta(),
GetShortTestPeriod(),
GetCacheSoftExpiryPeriod() + GetShortTestPeriod(),
GetCacheSoftExpiryPeriod() + 2 * GetShortTestPeriod()},
{GetShortTestPeriod(),
base::TimeDelta(),
base::TimeDelta::Max(),
base::TimeDelta::Max(),
base::TimeDelta::Max()},
{GetCacheHardExpiryPeriod(),
base::TimeDelta(),
base::TimeDelta::Max(),
base::TimeDelta::Max(),
base::TimeDelta::Max()},
{GetCacheSoftExpiryPeriod() + GetCacheHardExpiryPeriod(),
base::TimeDelta(),
base::TimeDelta(),
GetCacheSoftExpiryPeriod(),
base::TimeDelta::Max()},
{GetCacheSoftExpiryPeriod() + GetCacheHardExpiryPeriod(),
base::TimeDelta(),
base::TimeDelta::Max(),
base::TimeDelta::Max(),
base::TimeDelta::Max()},
{base::TimeDelta::Max(),
base::TimeDelta(),
base::TimeDelta::Max(),
base::TimeDelta::Max(),
base::TimeDelta::Max()}};
const base::TimeDelta kMaximumTestDuration = GetCacheSoftExpiryPeriod() +
GetCacheHardExpiryPeriod() +
2 * GetShortTestPeriod();
for (size_t i = 0; i < base::size(kTestCases); ++i) {
SCOPED_TRACE(testing::Message() << "Test case: #" << i);
fake_facet_manager_host()->clear_fake_database_content();
const base::Time testing_end =
Now() + std::min(kTestCases[i].prefetch_length, kMaximumTestDuration);
std::vector<ExpectedFetchDetails> expected_fetches(1);
expected_fetches[0].time = Now() + kTestCases[i].expected_fetch_time1,
expected_fetches[0].completion_delay =
kTestCases[i].fetch_completion_delay1;
if (kTestCases[i].expected_fetch_time2 != base::TimeDelta::Max()) {
expected_fetches.resize(2);
expected_fetches[1].time = Now() + kTestCases[i].expected_fetch_time2,
expected_fetches[1].completion_delay =
kTestCases[i].fetch_completion_delay2;
}
CreateFacetManager();
Prefetch(SafeAdd(Now(), kTestCases[i].prefetch_length));
ASSERT_NO_FATAL_FAILURE(AdvanceTimeAndVerifyPrefetchWithFetchesAt(
testing_end, expected_fetches));
if (kTestCases[i].prefetch_length < base::TimeDelta::Max()) {
EXPECT_FALSE(facet_manager()->DoesRequireFetch());
EXPECT_TRUE(facet_manager()->CanBeDiscarded());
EXPECT_FALSE(main_task_runner()->HasPendingTask());
} else {
ASSERT_NO_FATAL_FAILURE(ExpectFetchNeeded());
EXPECT_FALSE(facet_manager()->CanBeDiscarded());
}
DestroyFacetManager();
}
}
// Canceling prefetches. See legend above.
//
// t=0 S H F2+S F2+H
// / / / / /
// ---o--------------------------o-------o---------------o-------o---------> t
// : : : : :
// [F--X- - - - - - - - - - - - - - -): : :
// [F-------------------------X - - -): : :
// [F----------------------------X- -): : :
// [F--X- - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->
// [F-------------------------X - - - - - - - - - - - - - - - - - ->
// [F-------------------------F--X- - - - - - - - - - - - - - - - ->
// [F-------------------------F----------X- - - - - - - - - - - - ->
// [F-------------------------F-----------------------X - - - - - ->
// [F-------------------------F-----------------------F--X- - - - ->
//
TEST_F(FacetManagerTest, CancelPrefetch) {
struct {
base::TimeDelta prefetch_length;
base::TimeDelta cancel_time;
size_t expected_num_fetches;
} const kTestCases[] = {
{GetCacheHardExpiryPeriod(), GetShortTestPeriod(), 1},
{GetCacheHardExpiryPeriod(), GetCacheSoftExpiryPeriod(), 1},
{GetCacheHardExpiryPeriod(),
GetCacheSoftExpiryPeriod() + GetShortTestPeriod(),
1},
{base::TimeDelta::Max(), GetShortTestPeriod(), 1},
{base::TimeDelta::Max(), GetCacheSoftExpiryPeriod(), 1},
{base::TimeDelta::Max(),
GetCacheSoftExpiryPeriod() + GetShortTestPeriod(),
2},
{base::TimeDelta::Max(),
GetCacheHardExpiryPeriod() + GetShortTestPeriod(),
2},
{base::TimeDelta::Max(), 2 * GetCacheSoftExpiryPeriod(), 2},
{base::TimeDelta::Max(),
2 * GetCacheSoftExpiryPeriod() + GetShortTestPeriod(),
3}};
const base::TimeDelta kExpectedFetchTimes[] = {
base::TimeDelta(),
GetCacheSoftExpiryPeriod(),
2 * GetCacheSoftExpiryPeriod()};
for (size_t i = 0; i < base::size(kTestCases); ++i) {
SCOPED_TRACE(testing::Message() << "Test case: #" << i);
fake_facet_manager_host()->clear_fake_database_content();
std::vector<ExpectedFetchDetails> expected_fetches;
expected_fetches.resize(kTestCases[i].expected_num_fetches);
for (size_t f = 0; f < kTestCases[i].expected_num_fetches; ++f)
expected_fetches[f].time = Now() + kExpectedFetchTimes[f];
CreateFacetManager();
Prefetch(SafeAdd(Now(), kTestCases[i].prefetch_length));
ScheduleCancelPrefetch(Now() + kTestCases[i].cancel_time,
SafeAdd(Now(), kTestCases[i].prefetch_length));
ASSERT_NO_FATAL_FAILURE(AdvanceTimeAndVerifyPrefetchWithFetchesAt(
Now() + kTestCases[i].cancel_time, expected_fetches));
ASSERT_NO_FATAL_FAILURE(ExpectNoFetchNeeded());
EXPECT_TRUE(facet_manager()->CanBeDiscarded());
AdvanceTime(GetCacheHardExpiryPeriod());
EXPECT_FALSE(main_task_runner()->HasPendingTask());
DestroyFacetManager();
}
}
// Canceling in case of multiple nested prefetches. See legend above.
//
// t=0 S H F2+S F2+H
// / / / / /
// ---o--------------------------o-------o---------------o-------o---------> t
// : : : : :
// [F-------------------------F---------------------------X- ):
// [---------------------------------X- - - - - - - - - - - -)
// [--X- - - - - - - - - - - - - - - - - - - - - - - - - - -)
//
TEST_F(FacetManagerTest, CancelNestedPrefetches) {
const base::TimeDelta kPrefetchLength =
GetCacheSoftExpiryPeriod() + GetCacheHardExpiryPeriod();
const base::TimeDelta kTestDuration =
kPrefetchLength + 2 * GetShortTestPeriod();
const base::TimeDelta kLastCancelTime =
2 * GetCacheSoftExpiryPeriod() + GetShortTestPeriod();
std::vector<ExpectedFetchDetails> expected_fetches(2);
expected_fetches[0].time = Now();
expected_fetches[1].time = Now() + GetCacheSoftExpiryPeriod();
CreateFacetManager();
Prefetch(Now() + kPrefetchLength);
SchedulePrefetch(Now() + GetShortTestPeriod(),
Now() + kPrefetchLength + GetShortTestPeriod());
SchedulePrefetch(Now() + 2 * GetShortTestPeriod(),
Now() + kPrefetchLength + 2 * GetShortTestPeriod());
ScheduleCancelPrefetch(Now() + 3 * GetShortTestPeriod(),
Now() + 2 * GetShortTestPeriod() + kPrefetchLength);
ScheduleCancelPrefetch(
Now() + GetCacheHardExpiryPeriod() + GetShortTestPeriod(),
Now() + kPrefetchLength + GetShortTestPeriod());
ScheduleCancelPrefetch(Now() + kLastCancelTime, Now() + kPrefetchLength);
ASSERT_NO_FATAL_FAILURE(AdvanceTimeAndVerifyPrefetchWithFetchesAt(
Now() + kLastCancelTime, expected_fetches));
EXPECT_TRUE(facet_manager()->CanBeDiscarded());
ASSERT_NO_FATAL_FAILURE(ExpectNoFetchNeeded());
AdvanceTime(kTestDuration - kLastCancelTime);
EXPECT_FALSE(main_task_runner()->HasPendingTask());
DestroyFacetManager();
}
// Canceling in case of duplicate prefetches with the same |until| value. See
// legend above.
//
// t=0 S H F2+S F2+H
// / / / / /
// ---o--------------------------o-------o---------------o-------o---------> t
// : : : : :
// [F-------------------------F-----------------------F--X- - - - ->
// [--------------------------X - - - - - - - - - - - - - - - - - ->
// [--X - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->
//
TEST_F(FacetManagerTest, CancelNestedPrefetchesWithMultiplicity) {
const base::TimeDelta kTestPeriod = 3 * GetCacheSoftExpiryPeriod();
const base::TimeDelta kLastCancelTime =
2 * GetCacheSoftExpiryPeriod() + GetShortTestPeriod();
std::vector<ExpectedFetchDetails> expected_fetches(3);
expected_fetches[0].time = Now();
expected_fetches[1].time = Now() + GetCacheSoftExpiryPeriod();
expected_fetches[2].time = Now() + 2 * GetCacheSoftExpiryPeriod();
CreateFacetManager();
Prefetch(base::Time::Max());
SchedulePrefetch(Now() + GetShortTestPeriod(), base::Time::Max());
SchedulePrefetch(Now() + 2 * GetShortTestPeriod(), base::Time::Max());
ScheduleCancelPrefetch(Now() + GetShortTestPeriod(), base::Time::Max());
ScheduleCancelPrefetch(Now() + GetCacheSoftExpiryPeriod(), base::Time::Max());
ScheduleCancelPrefetch(Now() + kLastCancelTime, base::Time::Max());
ASSERT_NO_FATAL_FAILURE(AdvanceTimeAndVerifyPrefetchWithFetchesAt(
Now() + kLastCancelTime, expected_fetches));
EXPECT_TRUE(facet_manager()->CanBeDiscarded());
ASSERT_NO_FATAL_FAILURE(ExpectNoFetchNeeded());
AdvanceTime(kTestPeriod - kLastCancelTime);
EXPECT_FALSE(main_task_runner()->HasPendingTask());
DestroyFacetManager();
}
TEST_F(FacetManagerTest, CancelingNonexistentPrefetchesIsSilentlyIgnored) {
const base::TimeDelta kPrefetchLength =
GetCacheSoftExpiryPeriod() + GetCacheHardExpiryPeriod();
std::vector<ExpectedFetchDetails> expected_fetches(2);
expected_fetches[0].time = Now();
expected_fetches[1].time = Now() + GetCacheSoftExpiryPeriod();
CreateFacetManager();
CancelPrefetch(Now() + GetShortTestPeriod());
CancelPrefetch(base::Time::Max());
EXPECT_TRUE(facet_manager()->CanBeDiscarded());
ASSERT_NO_FATAL_FAILURE(ExpectNoFetchNeeded());
EXPECT_FALSE(main_task_runner()->HasPendingTask());
Prefetch(Now() + kPrefetchLength);
ScheduleCancelPrefetch(Now() + GetShortTestPeriod(),
Now() + GetShortTestPeriod());
ScheduleCancelPrefetch(Now() + GetShortTestPeriod(), base::Time::Max());
ASSERT_NO_FATAL_FAILURE(AdvanceTimeAndVerifyPrefetchWithFetchesAt(
Now() + kPrefetchLength, expected_fetches));
EXPECT_TRUE(facet_manager()->CanBeDiscarded());
ASSERT_NO_FATAL_FAILURE(ExpectNoFetchNeeded());
EXPECT_FALSE(main_task_runner()->HasPendingTask());
DestroyFacetManager();
}
TEST_F(FacetManagerTest, CachedDataCannotBeDiscarded) {
CreateFacetManager();
const base::TimeDelta kPrefetchLength =
2 * GetCacheSoftExpiryPeriod() + GetCacheHardExpiryPeriod();
facet_manager_notifier()->set_accuracy(NotificationAccuracy::NEVER_CALLED);
const base::Time prefetch_end = Now() + kPrefetchLength;
std::vector<ExpectedFetchDetails> expected_fetches(1);
expected_fetches[0].time = Now();
Prefetch(prefetch_end);
ASSERT_NO_FATAL_FAILURE(AdvanceTimeAndVerifyPrefetchWithFetchesAt(
Now() + GetCacheSoftExpiryPeriod(), expected_fetches));
for (base::TimeDelta step : SamplingPoints(prefetch_end - Now())) {
SCOPED_TRACE(testing::Message() << "dT: " << DeltaNow());
EXPECT_FALSE(facet_manager()->CanBeDiscarded());
ASSERT_TRUE(facet_manager()->DoesRequireFetch());
if (DeltaNow() < GetCacheHardExpiryPeriod())
ExpectRequestsServedFromCache();
AdvanceTime(step);
}
EXPECT_TRUE(facet_manager()->CanBeDiscarded());
EXPECT_FALSE(main_task_runner()->HasPendingTask());
DestroyFacetManager();
}
// RequestNotificationAtTime() ends up calling NotifyAtRequestedTime() always
// a bit earlier than needed. This should result in NotifyAtRequestedTime()
// being called repeatedly until the callback is finally on time, but should
// not otherwise result in a change of behavior.
TEST_F(FacetManagerTest, RequestedNotificationsComeTooEarly) {
const base::TimeDelta kTestPeriod =
2 * GetCacheSoftExpiryPeriod() + GetCacheHardExpiryPeriod();
facet_manager_notifier()->set_accuracy(NotificationAccuracy::TOO_EARLY);
std::vector<ExpectedFetchDetails> expected_fetches(3);
expected_fetches[0].time = Now();
expected_fetches[1].time = Now() + GetCacheSoftExpiryPeriod();
expected_fetches[2].time = Now() + 2 * GetCacheSoftExpiryPeriod();
CreateFacetManager();
Prefetch(Now() + kTestPeriod);
ASSERT_NO_FATAL_FAILURE(AdvanceTimeAndVerifyPrefetchWithFetchesAt(
Now() + kTestPeriod, expected_fetches));
EXPECT_TRUE(facet_manager()->CanBeDiscarded());
EXPECT_FALSE(main_task_runner()->HasPendingTask());
DestroyFacetManager();
}
// RequestNotificationAtTime() ends up calling NotifyAtRequestedTime() always
// a short time after desired. This may result in SignalNeedNetworkRequest()
// coming in late, but DoesRequireFetch() should get set at the correct time.
TEST_F(FacetManagerTest, RequestedNotificationsComeTooLate) {
const base::TimeDelta kPrefetchLength =
2 * GetCacheSoftExpiryPeriod() + GetCacheHardExpiryPeriod();
facet_manager_notifier()->set_accuracy(NotificationAccuracy::TOO_LATE);
const base::Time prefetch_end = Now() + kPrefetchLength;
std::vector<ExpectedFetchDetails> expected_fetches(1);
expected_fetches[0].time = Now();
CreateFacetManager();
Prefetch(prefetch_end);
ASSERT_NO_FATAL_FAILURE(AdvanceTimeAndVerifyPrefetchWithFetchesAt(
Now() + GetCacheSoftExpiryPeriod(), expected_fetches));
for (int cycle = 0; cycle < 2; ++cycle) {
for (base::TimeDelta step : SamplingPoints(GetShortTestPeriod())) {
SCOPED_TRACE(testing::Message() << "dT: " << DeltaNow());
EXPECT_FALSE(facet_manager()->CanBeDiscarded());
ASSERT_TRUE(facet_manager()->DoesRequireFetch());
ExpectRequestsServedFromCache();
AdvanceTime(step);
}
ASSERT_NO_FATAL_FAILURE(ExpectFetchNeeded());
CompleteFetch();
const base::TimeDelta idle_period =
cycle ? prefetch_end - Now() : GetCacheSoftExpiryPeriod();
for (base::TimeDelta step : SamplingPoints(idle_period)) {
SCOPED_TRACE(testing::Message() << "dT: " << DeltaNow());
EXPECT_FALSE(facet_manager()->CanBeDiscarded());
ExpectNoFetchNeeded();
ExpectRequestsServedFromCache();
AdvanceTime(step);
}
}
ASSERT_EQ(kPrefetchLength, DeltaNow());
EXPECT_TRUE(facet_manager()->CanBeDiscarded());
AdvanceTime(GetShortTestPeriod());
EXPECT_FALSE(main_task_runner()->HasPendingTask());
DestroyFacetManager();
}
// RequestNotificationAtTime() ends up not calling NotifyAtRequestedTime() at
// all. This should result in SignalNeedNetworkRequest() not being called, but
// DoesRequireFetch() should be set as long as the prefetch is active.
TEST_F(FacetManagerTest, RequestedNotificationsNeverCome) {
const base::TimeDelta kPrefetchLength =
2 * GetCacheSoftExpiryPeriod() + GetCacheHardExpiryPeriod();
facet_manager_notifier()->set_accuracy(NotificationAccuracy::NEVER_CALLED);
const base::Time prefetch_end = Now() + kPrefetchLength;
std::vector<ExpectedFetchDetails> expected_fetches(1);
expected_fetches[0].time = Now();
CreateFacetManager();
Prefetch(prefetch_end);
ASSERT_NO_FATAL_FAILURE(AdvanceTimeAndVerifyPrefetchWithFetchesAt(
Now() + GetCacheSoftExpiryPeriod(), expected_fetches));
for (base::TimeDelta step : SamplingPoints(prefetch_end - Now())) {
SCOPED_TRACE(testing::Message() << "dT: " << DeltaNow());
EXPECT_FALSE(facet_manager()->CanBeDiscarded());
ASSERT_TRUE(facet_manager()->DoesRequireFetch());
if (DeltaNow() < GetCacheHardExpiryPeriod())
ExpectRequestsServedFromCache();
AdvanceTime(step);
}
EXPECT_TRUE(facet_manager()->CanBeDiscarded());
EXPECT_FALSE(main_task_runner()->HasPendingTask());
DestroyFacetManager();
}
TEST_F(FacetManagerTest, StaleCachedDataBeCanDiscardedWhilePendingFetch) {
CreateFacetManager();
ASSERT_FALSE(facet_manager()->IsCachedDataFresh());
GetAffiliationsAndBranding(StrategyOnCacheMiss::FETCH_OVER_NETWORK);
ASSERT_NO_FATAL_FAILURE(ExpectFetchNeeded());
EXPECT_FALSE(facet_manager()->CanBeDiscarded());
EXPECT_TRUE(facet_manager()->CanCachedDataBeDiscarded());
fake_facet_manager_host()->reset_need_network_request();
}
TEST_F(FacetManagerTest, CachedDataBeCanDiscardedAfterOnDemandGetAffiliatons) {
CreateFacetManager();
ASSERT_FALSE(facet_manager()->IsCachedDataFresh());
GetAffiliationsAndBranding(StrategyOnCacheMiss::FETCH_OVER_NETWORK);
ASSERT_NO_FATAL_FAILURE(ExpectFetchNeeded());
ASSERT_NO_FATAL_FAILURE(CompleteFetch());
ExpectConsumerSuccessCallback();
EXPECT_TRUE(facet_manager()->IsCachedDataFresh());
EXPECT_TRUE(facet_manager()->CanBeDiscarded());
EXPECT_TRUE(facet_manager()->CanCachedDataBeDiscarded());
}
// The cached data can be discarded (indicated by 'd') if and only if it is no
// longer needed to be kept fresh, or if it already stale.
//
// t=0 S H F2+S F2+H
// / / / / /
// ---o--------------------------o-------o------------------o-------o------> t
// : : :
// [F-------------------------NNNNNNNNNNNF---)
// ddd ddd...
//
TEST_F(FacetManagerTest,
CachedDataCanBeDiscardedAfterAndSometimesDuringPrefetch) {
CreateFacetManager();
Prefetch(Now() + GetCacheHardExpiryPeriod() + 2 * GetShortTestPeriod());
ASSERT_NO_FATAL_FAILURE(ExpectFetchNeeded());
ASSERT_NO_FATAL_FAILURE(CompleteFetch());
for (base::TimeDelta step : SamplingPoints(GetCacheHardExpiryPeriod())) {
SCOPED_TRACE(testing::Message() << "dT: " << DeltaNow());
EXPECT_FALSE(facet_manager()->CanCachedDataBeDiscarded());
AdvanceTime(step);
}
for (base::TimeDelta step : SamplingPoints(GetShortTestPeriod())) {
SCOPED_TRACE(testing::Message() << "dT: " << DeltaNow());
EXPECT_TRUE(facet_manager()->CanCachedDataBeDiscarded());
AdvanceTime(step);
}
ASSERT_NO_FATAL_FAILURE(ExpectFetchNeeded());
ASSERT_NO_FATAL_FAILURE(CompleteFetch());
for (base::TimeDelta step : SamplingPoints(GetShortTestPeriod())) {
SCOPED_TRACE(testing::Message() << "dT: " << DeltaNow());
EXPECT_FALSE(facet_manager()->CanCachedDataBeDiscarded());
AdvanceTime(step);
}
EXPECT_TRUE(facet_manager()->CanBeDiscarded());
EXPECT_TRUE(facet_manager()->CanCachedDataBeDiscarded());
}
TEST_F(FacetManagerTest, CachedDataBeCanDiscardedAfterCancelledPrefetch) {
CreateFacetManager();
Prefetch(base::Time::Max());
ASSERT_NO_FATAL_FAILURE(ExpectFetchNeeded());
ASSERT_NO_FATAL_FAILURE(CompleteFetch());
EXPECT_FALSE(facet_manager()->CanCachedDataBeDiscarded());
AdvanceTime(GetShortTestPeriod());
CancelPrefetch(base::Time::Max());
EXPECT_TRUE(facet_manager()->CanBeDiscarded());
EXPECT_TRUE(facet_manager()->CanCachedDataBeDiscarded());
}
} // namespace password_manager