0

Output whether the classification result is cached in debug UI

This makes it much easier to reason about what's happening in various
testing scenarios, by using the "Filtering Results" box in the
chrome://family-link-user-internals/ page.

See screenshot in linked bug.

Low-Coverage-Reason: TRIVIAL_CHANGE The family_link_user_internals/family_link_user_internals_message_handler.cc change has only small log text changes in a debug UI, with no existing test coverage
Bug: b:362907992
Change-Id: I378b660b1b2ac3e9ede4e60ac258d7f1e99d242f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5824007
Commit-Queue: James Lee <ljjlee@google.com>
Reviewed-by: Anqing Zhao <anqing@chromium.org>
Reviewed-by: Nohemi Fernandez <fernandex@chromium.org>
Reviewed-by: Sean Topping <seantopping@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1350631}
This commit is contained in:
James Lee
2024-09-04 07:15:36 +00:00
committed by Chromium LUCI CQ
parent 63227f0ed2
commit 2065e9304d
14 changed files with 163 additions and 73 deletions

@ -40,8 +40,7 @@ URLChecker::Check::Check(const GURL& url, CheckCallback callback) : url(url) {
URLChecker::Check::~Check() = default;
URLChecker::CheckResult::CheckResult(Classification classification)
: classification(classification),
timestamp(base::TimeTicks::Now()) {}
: classification(classification), timestamp(base::TimeTicks::Now()) {}
URLChecker::URLChecker(std::unique_ptr<URLCheckerClient> async_checker)
: URLChecker(std::move(async_checker), kDefaultCacheSize) {}
@ -82,7 +81,10 @@ bool URLChecker::CheckURL(const GURL& url, CheckCallback callback) {
DVLOG(1) << "Cache hit! " << url.spec() << " is "
<< (result.classification == Classification::UNSAFE ? "NOT" : "")
<< " safe";
std::move(callback).Run(url, result.classification, /*uncertain=*/false);
std::move(callback).Run(
url, result.classification,
ClassificationDetails{
.reason = ClassificationDetails::Reason::kCachedResponse});
base::UmaHistogramEnumeration(kCacheHitMetricKey,
CacheAccessStatus::kHit);
@ -122,7 +124,13 @@ void URLChecker::OnAsyncCheckComplete(CheckList::iterator it,
}
for (CheckCallback& callback : callbacks) {
std::move(callback).Run(url, classification, uncertain);
std::move(callback).Run(
url, classification,
ClassificationDetails{
.reason =
uncertain
? ClassificationDetails::Reason::kFailedUseDefault
: ClassificationDetails::Reason::kFreshServerResponse});
}
}

@ -20,6 +20,24 @@ namespace safe_search_api {
// The SafeSearch API classification of a URL.
enum class Classification { SAFE, UNSAFE };
// Additional details regarding how the `Classification` result was determined.
struct ClassificationDetails {
enum class Reason {
// Chrome sent a fresh external request to the server, and received a valid
// response.
kFreshServerResponse,
// Chrome used a cached response, stored from a previous successful request
// to the server.
kCachedResponse,
// Chrome tried but failed to query the server, and defaulted to
// `Classification::SAFE`.
kFailedUseDefault,
};
Reason reason;
};
// These values are sent to Uma to understand Cache utilization. Must not be
// renumbered.
enum class CacheAccessStatus {
@ -40,8 +58,9 @@ enum class CacheAccessStatus {
class URLChecker {
public:
// Used to report whether |url| should be blocked. Called from CheckURL.
using CheckCallback = base::OnceCallback<
void(const GURL&, Classification classification, bool /* uncertain */)>;
using CheckCallback = base::OnceCallback<void(const GURL&,
Classification classification,
ClassificationDetails details)>;
explicit URLChecker(std::unique_ptr<URLCheckerClient> async_checker);

@ -67,6 +67,12 @@ auto Recorded(const std::map<CacheAccessStatus, int>& expected) {
return base::BucketsInclude(buckets_array);
}
// A matcher which checks that the provided |ClassificationDetails| has the
// expected |reason| value.
MATCHER_P(ReasonEq, reason, "") {
return arg.reason == reason;
}
} // namespace
class SafeSearchURLCheckerTest : public testing::Test {
@ -81,7 +87,7 @@ class SafeSearchURLCheckerTest : public testing::Test {
MOCK_METHOD3(OnCheckDone,
void(const GURL& url,
Classification classification,
bool uncertain));
ClassificationDetails details));
protected:
GURL GetNewURL() {
@ -121,21 +127,29 @@ class SafeSearchURLCheckerTest : public testing::Test {
TEST_F(SafeSearchURLCheckerTest, Simple) {
{
GURL url(GetNewURL());
EXPECT_CALL(*this,
OnCheckDone(url, Classification::SAFE, /*uncertain=*/false));
EXPECT_CALL(
*this,
OnCheckDone(
url, Classification::SAFE,
ReasonEq(ClassificationDetails::Reason::kFreshServerResponse)));
ASSERT_FALSE(SendResponse(url, Classification::SAFE, /*uncertain=*/false));
}
{
GURL url(GetNewURL());
EXPECT_CALL(*this,
OnCheckDone(url, Classification::UNSAFE, /*uncertain=*/false));
EXPECT_CALL(
*this,
OnCheckDone(
url, Classification::UNSAFE,
ReasonEq(ClassificationDetails::Reason::kFreshServerResponse)));
ASSERT_FALSE(
SendResponse(url, Classification::UNSAFE, /*uncertain=*/false));
}
{
GURL url(GetNewURL());
EXPECT_CALL(*this,
OnCheckDone(url, Classification::SAFE, /*uncertain=*/true));
EXPECT_CALL(
*this, OnCheckDone(
url, Classification::SAFE,
ReasonEq(ClassificationDetails::Reason::kFailedUseDefault)));
ASSERT_FALSE(SendResponse(url, Classification::SAFE, /*uncertain=*/true));
}
@ -151,28 +165,44 @@ TEST_F(SafeSearchURLCheckerTest, Cache) {
GURL url3(GetNewURL());
// Populate the cache.
EXPECT_CALL(*this,
OnCheckDone(url1, Classification::SAFE, /*uncertain=*/false));
EXPECT_CALL(
*this,
OnCheckDone(
url1, Classification::SAFE,
ReasonEq(ClassificationDetails::Reason::kFreshServerResponse)));
ASSERT_FALSE(SendResponse(url1, Classification::SAFE, /*uncertain=*/false));
EXPECT_CALL(*this,
OnCheckDone(url2, Classification::SAFE, /*uncertain=*/false));
EXPECT_CALL(
*this,
OnCheckDone(
url2, Classification::SAFE,
ReasonEq(ClassificationDetails::Reason::kFreshServerResponse)));
ASSERT_FALSE(SendResponse(url2, Classification::SAFE, /*uncertain=*/false));
// Now we should get results synchronously, without a request to the api.
EXPECT_CALL(*this,
OnCheckDone(url2, Classification::SAFE, /*uncertain=*/false));
EXPECT_CALL(
*this,
OnCheckDone(url2, Classification::SAFE,
ReasonEq(ClassificationDetails::Reason::kCachedResponse)));
ASSERT_TRUE(CheckURL(url2));
EXPECT_CALL(*this,
OnCheckDone(url1, Classification::SAFE, /*uncertain=*/false));
EXPECT_CALL(
*this,
OnCheckDone(url1, Classification::SAFE,
ReasonEq(ClassificationDetails::Reason::kCachedResponse)));
ASSERT_TRUE(CheckURL(url1));
// Now |url2| is the LRU and should be evicted on the next check.
EXPECT_CALL(*this,
OnCheckDone(url3, Classification::SAFE, /*uncertain=*/false));
EXPECT_CALL(
*this,
OnCheckDone(
url3, Classification::SAFE,
ReasonEq(ClassificationDetails::Reason::kFreshServerResponse)));
ASSERT_FALSE(SendResponse(url3, Classification::SAFE, /*uncertain=*/false));
EXPECT_CALL(*this,
OnCheckDone(url2, Classification::SAFE, /*uncertain=*/false));
EXPECT_CALL(
*this,
OnCheckDone(
url2, Classification::SAFE,
ReasonEq(ClassificationDetails::Reason::kFreshServerResponse)));
ASSERT_FALSE(SendResponse(url2, Classification::SAFE, /*uncertain=*/false));
EXPECT_THAT(CacheHitMetric(), Recorded({{CacheAccessStatus::kHit, 2},
@ -185,7 +215,11 @@ TEST_F(SafeSearchURLCheckerTest, CoalesceRequestsToSameURL) {
ASSERT_FALSE(CheckURL(url));
ASSERT_FALSE(CheckURL(url));
// A single response should answer both of those checks
EXPECT_CALL(*this, OnCheckDone(url, Classification::SAFE, false)).Times(2);
EXPECT_CALL(
*this, OnCheckDone(
url, Classification::SAFE,
ReasonEq(ClassificationDetails::Reason::kFreshServerResponse)))
.Times(2);
fake_client_->RunCallback(ToAPIClassification(Classification::SAFE, false));
EXPECT_THAT(CacheHitMetric(), Recorded({{CacheAccessStatus::kHit, 0},
@ -197,14 +231,20 @@ TEST_F(SafeSearchURLCheckerTest, CacheTimeout) {
checker_->SetCacheTimeoutForTesting(base::Seconds(0));
EXPECT_CALL(*this,
OnCheckDone(url, Classification::SAFE, /*uncertain=*/false));
EXPECT_CALL(
*this,
OnCheckDone(
url, Classification::SAFE,
ReasonEq(ClassificationDetails::Reason::kFreshServerResponse)));
ASSERT_FALSE(SendResponse(url, Classification::SAFE, /*uncertain=*/false));
// Since the cache timeout is zero, the cache entry should be invalidated
// immediately.
EXPECT_CALL(*this,
OnCheckDone(url, Classification::UNSAFE, /*uncertain=*/false));
EXPECT_CALL(
*this,
OnCheckDone(
url, Classification::UNSAFE,
ReasonEq(ClassificationDetails::Reason::kFreshServerResponse)));
ASSERT_FALSE(SendResponse(url, Classification::UNSAFE, /*uncertain=*/false));
EXPECT_THAT(CacheHitMetric(), Recorded({{CacheAccessStatus::kHit, 0},