blob: e41e8ab7a0de00d1ae6e1d861b317977135dba3a [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/printing/printer_config_cache.h"
#include <vector>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/bind.h"
#include "base/test/simple_test_clock.h"
#include "base/test/task_environment.h"
#include "base/time/clock.h"
#include "base/time/time.h"
#include "net/http/http_status_code.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
// Maintainer's notes:
//
// 1. The use of base::Unretained throughout this suite is appropriate
// because the sequences of each test live as long as the test does.
// Real consumers probably can't do this.
// 2. The passage of time is controlled by a mock clock, so most Fetch()
// invocations not preceded by clock advancement never hit the
// "networked fetch" codepath. In such tests, the values of the
// TimeDelta argument are arbitrary and meaningless.
namespace chromeos {
namespace {
// Defines some resources (URLs and contents) used throughout this
// test suite.
// Name of the "known-good" resource.
const char kKnownGoodResourceURL[] =
"https://printerconfigurations.googleusercontent.com/chromeos_printing/"
"known-good";
// Arbitrary content for the "known-good" resource.
const char kKnownGoodResourceContent[] = "yakisaba";
// Name of the "known-bad" resource.
const char kKnownBadResourceURL[] =
"https://printerconfigurations.googleusercontent.com/chromeos_printing/"
"known-bad";
// Defines an arbitrary time increment by which we advance the Clock.
constexpr base::TimeDelta kTestingIncrement = base::Seconds(1LL);
// Defines a time of fetch used to construct FetchResult instances that
// you'll use with the TimeInsensitiveFetchResultEquals matcher.
constexpr base::Time kUnusedTimeOfFetch;
MATCHER_P(TimeInsensitiveFetchResultEquals, expected, "") {
return arg.succeeded == expected.succeeded && arg.key == expected.key &&
arg.contents == expected.contents;
}
MATCHER_P(FetchResultEquals, expected, "") {
return arg.succeeded == expected.succeeded && arg.key == expected.key &&
arg.contents == expected.contents &&
arg.time_of_fetch == expected.time_of_fetch;
}
class PrinterConfigCacheTest : public ::testing::Test {
public:
// Creates |this| with
// * a testing task environment for testing sequenced code,
// * a testing clock for time-aware testing, and
// * a loader factory dispenser (specified by header comment on
// Create()).
PrinterConfigCacheTest()
: task_environment_(base::test::TaskEnvironment::MainThreadType::IO),
cache_(PrinterConfigCache::Create(
&clock_,
base::BindLambdaForTesting([&]() {
return reinterpret_cast<network::mojom::URLLoaderFactory*>(
&loader_factory_);
}),
/*use_localhost_as_root=*/false)) {}
// Sets up the default responses to dispense.
void SetUp() override {
// Dispenses the "known-good" resource with its content.
loader_factory_.AddResponse(kKnownGoodResourceURL,
kKnownGoodResourceContent);
// Dispenses the "known-bad" resource with no content and an
// arbitrary HTTP error.
loader_factory_.AddResponse(kKnownBadResourceURL, "",
net::HTTP_NOT_ACCEPTABLE);
}
// Method passed as a FetchCallback (partially bound) to
// cache_.Fetch(). Saves the |result| in the |fetched_results_|.
// Invokes the |quit_closure| to signal the enclosing RunLoop that
// this method has been called.
void CaptureFetchResult(base::RepeatingClosure quit_closure,
const PrinterConfigCache::FetchResult& result) {
fetched_results_.push_back(result);
// The caller may elect to pass a default-constructed
// RepeatingClosure, indicating that they don't want anything run.
if (quit_closure) {
quit_closure.Run();
}
}
void AdvanceClock(base::TimeDelta amount = kTestingIncrement) {
clock_.Advance(amount);
}
protected:
// Landing area used to collect Fetch()ed results.
std::vector<PrinterConfigCache::FetchResult> fetched_results_;
// Loader factory for testing loaned to |cache_|.
network::TestURLLoaderFactory loader_factory_;
// Environment for task schedulers.
base::test::TaskEnvironment task_environment_;
// Controlled clock that dispenses times of Fetch().
base::SimpleTestClock clock_;
// Class under test.
std::unique_ptr<PrinterConfigCache> cache_;
};
// Tests that we can succeed in Fetch()ing anything at all.
TEST_F(PrinterConfigCacheTest, SucceedAtSingleFetch) {
base::RunLoop run_loop;
// Fetches the "known-good" resource.
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
&PrinterConfigCache::Fetch, base::Unretained(cache_.get()),
"known-good", base::Seconds(0LL),
base::BindOnce(&PrinterConfigCacheTest::CaptureFetchResult,
base::Unretained(this), run_loop.QuitClosure())));
run_loop.Run();
ASSERT_EQ(fetched_results_.size(), 1ULL);
EXPECT_THAT(
fetched_results_.front(),
TimeInsensitiveFetchResultEquals(PrinterConfigCache::FetchResult::Success(
"known-good", "yakisaba", kUnusedTimeOfFetch)));
}
// Tests that we fail to Fetch() the "known-bad" resource.
TEST_F(PrinterConfigCacheTest, FailAtSingleFetch) {
base::RunLoop run_loop;
// Fetches the "known-bad" resource.
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
&PrinterConfigCache::Fetch, base::Unretained(cache_.get()),
"known-bad", base::Seconds(0LL),
base::BindOnce(&PrinterConfigCacheTest::CaptureFetchResult,
base::Unretained(this), run_loop.QuitClosure())));
run_loop.Run();
ASSERT_EQ(fetched_results_.size(), 1ULL);
EXPECT_THAT(fetched_results_.front(),
TimeInsensitiveFetchResultEquals(
PrinterConfigCache::FetchResult::Failure("known-bad")));
}
// Tests that we can force a networked Fetch() by demanding
// fresh content.
TEST_F(PrinterConfigCacheTest, RefreshSubsequentFetch) {
// Fetches the "known-good" resource with its stock contents.
base::RunLoop first_run_loop;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&PrinterConfigCache::Fetch, base::Unretained(cache_.get()),
"known-good", base::Seconds(0LL),
base::BindOnce(&PrinterConfigCacheTest::CaptureFetchResult,
base::Unretained(this),
first_run_loop.QuitClosure())));
first_run_loop.Run();
ASSERT_EQ(fetched_results_.size(), 1ULL);
// To detect a networked fetch, we'll change the served content
// and check that the subsequent Fetch() recovers the new content.
loader_factory_.AddResponse(kKnownGoodResourceURL, "one Argentinian peso");
// We've mutated the content; now, this fetches the "known-good"
// resource with its new contents.
base::RunLoop second_run_loop;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&PrinterConfigCache::Fetch, base::Unretained(cache_.get()),
"known-good", base::Seconds(0LL),
base::BindOnce(&PrinterConfigCacheTest::CaptureFetchResult,
base::Unretained(this),
second_run_loop.QuitClosure())));
second_run_loop.Run();
ASSERT_EQ(fetched_results_.size(), 2ULL);
EXPECT_THAT(
fetched_results_,
testing::ElementsAre(
TimeInsensitiveFetchResultEquals(
PrinterConfigCache::FetchResult::Success("known-good", "yakisaba",
kUnusedTimeOfFetch)),
TimeInsensitiveFetchResultEquals(
PrinterConfigCache::FetchResult::Success(
"known-good", "one Argentinian peso", kUnusedTimeOfFetch))));
}
// Tests that we can Fetch() locally cached contents by specifying a
// wide age limit.
TEST_F(PrinterConfigCacheTest, LocallyPerformSubsequentFetch) {
// Fetches the "known-good" resource with its stock contents.
base::RunLoop first_run_loop;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&PrinterConfigCache::Fetch, base::Unretained(cache_.get()),
"known-good", base::Seconds(0LL),
base::BindOnce(&PrinterConfigCacheTest::CaptureFetchResult,
base::Unretained(this),
first_run_loop.QuitClosure())));
first_run_loop.Run();
ASSERT_EQ(fetched_results_.size(), 1ULL);
// As in the RefreshSubsequentFetch test, we'll change the served
// content to detect networked fetch requests made.
loader_factory_.AddResponse(kKnownGoodResourceURL, "apologize darn you");
// The "live" content in the serving root has changed; now, we perform
// some local fetches without hitting the network. These Fetch()es
// will return the stock content.
base::RunLoop second_run_loop;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&PrinterConfigCache::Fetch, base::Unretained(cache_.get()),
"known-good",
// Avoids hitting the network by using a long
// timeout. Bear in mind that this test controls
// the passage of time, so nonzero timeout is
// "long" here...
base::Seconds(1LL),
base::BindOnce(&PrinterConfigCacheTest::CaptureFetchResult,
base::Unretained(this),
// Avoids quitting this RunLoop.
base::RepeatingClosure())));
// Performs a local Fetch() a few more times for no particular reason.
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
&PrinterConfigCache::Fetch, base::Unretained(cache_.get()),
"known-good", base::Seconds(3600LL),
base::BindOnce(&PrinterConfigCacheTest::CaptureFetchResult,
base::Unretained(this), base::RepeatingClosure())));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
&PrinterConfigCache::Fetch, base::Unretained(cache_.get()),
"known-good", base::Seconds(86400LL),
base::BindOnce(&PrinterConfigCacheTest::CaptureFetchResult,
base::Unretained(this), base::RepeatingClosure())));
// Performs a live Fetch(), returning the live (mutated) contents.
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&PrinterConfigCache::Fetch, base::Unretained(cache_.get()),
"known-good",
// Forces the networked fetch.
base::Seconds(0LL),
base::BindOnce(&PrinterConfigCacheTest::CaptureFetchResult,
base::Unretained(this),
// Ends our RunLoop.
second_run_loop.QuitClosure())));
second_run_loop.Run();
ASSERT_EQ(fetched_results_.size(), 5ULL);
EXPECT_THAT(
fetched_results_,
testing::ElementsAre(
TimeInsensitiveFetchResultEquals(
PrinterConfigCache::FetchResult::Success("known-good", "yakisaba",
kUnusedTimeOfFetch)),
TimeInsensitiveFetchResultEquals(
PrinterConfigCache::FetchResult::Success("known-good", "yakisaba",
kUnusedTimeOfFetch)),
TimeInsensitiveFetchResultEquals(
PrinterConfigCache::FetchResult::Success("known-good", "yakisaba",
kUnusedTimeOfFetch)),
TimeInsensitiveFetchResultEquals(
PrinterConfigCache::FetchResult::Success("known-good", "yakisaba",
kUnusedTimeOfFetch)),
TimeInsensitiveFetchResultEquals(
PrinterConfigCache::FetchResult::Success(
"known-good", "apologize darn you", kUnusedTimeOfFetch))));
}
// Tests that Fetch() respects its |expiration| argument. This is a
// purely time-bound variation on the LocallyPerformSubsequentFetch
// test; the served content doesn't change between RunLoops.
TEST_F(PrinterConfigCacheTest, FetchExpirationIsRespected) {
// This Fetch() is given a useful |expiration|, but it won't matter
// here since there are no locally resident cache entries at this
// time; it'll have to be a networked fetch.
base::RunLoop first_run_loop;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&PrinterConfigCache::Fetch, base::Unretained(cache_.get()),
"known-good", base::Seconds(32LL),
base::BindOnce(&PrinterConfigCacheTest::CaptureFetchResult,
base::Unretained(this),
first_run_loop.QuitClosure())));
first_run_loop.Run();
ASSERT_EQ(fetched_results_.size(), 1ULL);
const base::Time time_zero = clock_.Now();
// Advance clock to T+31.
AdvanceClock(base::Seconds(31LL));
// This Fetch() is given the same useful |expiration|; it only matters
// in that the clock does not yet indicate that the locally resident
// cache entry has expired.
base::RunLoop second_run_loop;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&PrinterConfigCache::Fetch, base::Unretained(cache_.get()),
"known-good", base::Seconds(32LL),
base::BindOnce(&PrinterConfigCacheTest::CaptureFetchResult,
base::Unretained(this),
second_run_loop.QuitClosure())));
second_run_loop.Run();
ASSERT_EQ(fetched_results_.size(), 2ULL);
// We don't capture the time right Now() because the above Fetch()
// should have replied with local contents, fetched at time_zero.
// Advance clock to T+32.
AdvanceClock(base::Seconds(1));
// This third Fetch() will be given the same |expiration| as ever.
// The two previous calls to AdvanceClock() will have moved the time
// beyond the staleness threshold, though, so this Fetch() will be
// networked.
base::RunLoop third_run_loop;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&PrinterConfigCache::Fetch, base::Unretained(cache_.get()),
"known-good",
// Entry fetched at T+0 is now stale at T+32.
base::Seconds(32LL),
base::BindOnce(&PrinterConfigCacheTest::CaptureFetchResult,
base::Unretained(this),
third_run_loop.QuitClosure())));
third_run_loop.Run();
ASSERT_EQ(fetched_results_.size(), 3ULL);
const base::Time time_of_third_fetch = clock_.Now();
EXPECT_THAT(fetched_results_,
testing::ElementsAre(
FetchResultEquals(PrinterConfigCache::FetchResult::Success(
"known-good", "yakisaba", time_zero)),
FetchResultEquals(PrinterConfigCache::FetchResult::Success(
"known-good", "yakisaba", time_zero)),
FetchResultEquals(PrinterConfigCache::FetchResult::Success(
"known-good", "yakisaba", time_of_third_fetch))));
}
// Tests that we can Drop() locally cached contents.
TEST_F(PrinterConfigCacheTest, DropLocalContents) {
base::RunLoop first_run_loop;
// Fetches the "known-good" resource with its stock contents.
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&PrinterConfigCache::Fetch, base::Unretained(cache_.get()),
"known-good", base::Seconds(604800LL),
base::BindOnce(&PrinterConfigCacheTest::CaptureFetchResult,
base::Unretained(this),
first_run_loop.QuitClosure())));
first_run_loop.Run();
// Drops that which we just fetched. This isn't immediately externally
// visible, but its effects will soon be made apparent.
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&PrinterConfigCache::Drop,
base::Unretained(cache_.get()), "known-good"));
// Mutates the contents served for the "known-good" resource.
loader_factory_.AddResponse(kKnownGoodResourceURL, "ultimate dogeza");
// Fetches the "known-good" resource anew with a wide timeout.
// This is where the side effect of the prior Drop() call manifests:
// the "known-good" resource is no longer cached, so not even a wide
// timeout will spare us a networked fetch.
base::RunLoop second_run_loop;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&PrinterConfigCache::Fetch, base::Unretained(cache_.get()),
"known-good", base::Seconds(18748800LL),
base::BindOnce(&PrinterConfigCacheTest::CaptureFetchResult,
base::Unretained(this),
second_run_loop.QuitClosure())));
second_run_loop.Run();
// We detect the networked fetch to by observing mutated
// contents.
ASSERT_EQ(fetched_results_.size(), 2ULL);
EXPECT_THAT(
fetched_results_,
testing::ElementsAre(
TimeInsensitiveFetchResultEquals(
PrinterConfigCache::FetchResult::Success("known-good", "yakisaba",
kUnusedTimeOfFetch)),
TimeInsensitiveFetchResultEquals(
PrinterConfigCache::FetchResult::Success(
"known-good", "ultimate dogeza", kUnusedTimeOfFetch))));
}
} // namespace
} // namespace chromeos