0

Add AppCapabilityAccessCacheTest.

Add AppCapabilityAccessCacheTest to verify AppCapabilityAccessCache for
the non mojom CapabilityAccess struct.

Add the flag kAppServiceCapabilityAccessWithoutMojom for the non mojom
CapabilityAccess code. And the default value is disabled.

BUG=1253250

Change-Id: I298f88fde95d22e76c9ff0364112ddea46941e1f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3868771
Reviewed-by: Dominick Ng <dominickn@chromium.org>
Commit-Queue: Nancy Wang <nancylingwang@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1043759}
This commit is contained in:
Nancy Wang
2022-09-07 01:40:33 +00:00
committed by Chromium LUCI CQ
parent 0fc384c3ff
commit 1c1a0f3669
7 changed files with 467 additions and 1 deletions

@ -327,6 +327,7 @@ source_set("unit_tests") {
sources = [
"app_capability_access_cache_mojom_unittest.cc",
"app_capability_access_cache_unittest.cc",
"app_capability_access_cache_wrapper_unittest.cc",
"app_launch_util_unittest.cc",
"app_registry_cache_unittest.cc",

@ -17,6 +17,7 @@
#include "components/account_id/account_id.h"
#include "components/services/app_service/public/cpp/capability_access.h"
#include "components/services/app_service/public/cpp/capability_access_update.h"
#include "components/services/app_service/public/cpp/features.h"
namespace apps {
@ -131,6 +132,30 @@ class COMPONENT_EXPORT(APP_UPDATE) AppCapabilityAccessCache {
void ForEachApp(FunctionType f) {
DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
if (base::FeatureList::IsEnabled(kAppServiceCapabilityAccessWithoutMojom)) {
for (const auto& s_iter : states_) {
const CapabilityAccess* state = s_iter.second.get();
auto d_iter = deltas_in_progress_.find(s_iter.first);
const CapabilityAccess* delta =
(d_iter != deltas_in_progress_.end()) ? d_iter->second : nullptr;
f(CapabilityAccessUpdate(state, delta, account_id_));
}
for (const auto& d_iter : deltas_in_progress_) {
const CapabilityAccess* delta = d_iter.second;
auto s_iter = states_.find(d_iter.first);
if (s_iter != states_.end()) {
continue;
}
f(CapabilityAccessUpdate(nullptr, delta, account_id_));
}
return;
}
for (const auto& s_iter : mojom_states_) {
const apps::mojom::CapabilityAccess* state = s_iter.second.get();
@ -166,6 +191,22 @@ class COMPONENT_EXPORT(APP_UPDATE) AppCapabilityAccessCache {
bool ForOneApp(const std::string& app_id, FunctionType f) {
DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
if (base::FeatureList::IsEnabled(kAppServiceCapabilityAccessWithoutMojom)) {
auto s_iter = states_.find(app_id);
const CapabilityAccess* state =
(s_iter != states_.end()) ? s_iter->second.get() : nullptr;
auto d_iter = deltas_in_progress_.find(app_id);
const CapabilityAccess* delta =
(d_iter != deltas_in_progress_.end()) ? d_iter->second : nullptr;
if (state || delta) {
f(CapabilityAccessUpdate(state, delta, account_id_));
return true;
}
return false;
}
auto s_iter = mojom_states_.find(app_id);
const apps::mojom::CapabilityAccess* state =
(s_iter != mojom_states_.end()) ? s_iter->second.get() : nullptr;

@ -5,7 +5,9 @@
#include <set>
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "components/services/app_service/public/cpp/app_capability_access_cache.h"
#include "components/services/app_service/public/cpp/features.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@ -13,6 +15,12 @@ class AppCapabilityAccessCacheMojomTest
: public testing::Test,
public apps::AppCapabilityAccessCache::Observer {
protected:
AppCapabilityAccessCacheMojomTest() {
scoped_feature_list_.Reset();
scoped_feature_list_.InitAndDisableFeature(
apps::kAppServiceCapabilityAccessWithoutMojom);
}
static apps::mojom::CapabilityAccessPtr MakeCapabilityAccess(
const char* app_id,
apps::mojom::OptionalBool camera,
@ -58,6 +66,7 @@ class AppCapabilityAccessCacheMojomTest
const AccountId& account_id() const { return account_id_; }
base::test::ScopedFeatureList scoped_feature_list_;
std::set<std::string> updated_ids_;
std::set<std::string> accessing_camera_apps_;
std::set<std::string> accessing_microphone_apps_;

@ -0,0 +1,410 @@
// Copyright 2022 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 <set>
#include <utility>
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "components/services/app_service/public/cpp/app_capability_access_cache.h"
#include "components/services/app_service/public/cpp/capability_access.h"
#include "components/services/app_service/public/cpp/features.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
class AppCapabilityAccessCacheTest
: public testing::Test,
public apps::AppCapabilityAccessCache::Observer {
protected:
AppCapabilityAccessCacheTest() {
scoped_feature_list_.Reset();
scoped_feature_list_.InitAndEnableFeature(
apps::kAppServiceCapabilityAccessWithoutMojom);
}
static apps::CapabilityAccessPtr MakeCapabilityAccess(
const char* app_id,
absl::optional<bool> camera,
absl::optional<bool> microphone) {
apps::CapabilityAccessPtr access =
std::make_unique<apps::CapabilityAccess>(app_id);
access->camera = camera;
access->microphone = microphone;
return access;
}
void CallForEachApp(apps::AppCapabilityAccessCache& cache) {
cache.ForEachApp([this](const apps::CapabilityAccessUpdate& update) {
OnCapabilityAccessUpdate(update);
});
}
// apps::AppCapabilityAccessCache::Observer overrides.
void OnCapabilityAccessUpdate(
const apps::CapabilityAccessUpdate& update) override {
EXPECT_EQ(account_id_, update.AccountId());
updated_ids_.insert(update.AppId());
auto camera = update.Camera();
if (camera.value_or(false)) {
accessing_camera_apps_.insert(update.AppId());
} else {
accessing_camera_apps_.erase(update.AppId());
}
auto microphone = update.Microphone();
if (microphone.value_or(false)) {
accessing_microphone_apps_.insert(update.AppId());
} else {
accessing_microphone_apps_.erase(update.AppId());
}
}
void OnAppCapabilityAccessCacheWillBeDestroyed(
apps::AppCapabilityAccessCache* cache) override {
// The test code explicitly calls both AddObserver and RemoveObserver.
NOTREACHED();
}
const AccountId& account_id() const { return account_id_; }
protected:
base::test::ScopedFeatureList scoped_feature_list_;
std::set<std::string> updated_ids_;
std::set<std::string> accessing_camera_apps_;
std::set<std::string> accessing_microphone_apps_;
AccountId account_id_ = AccountId::FromUserEmail("test@gmail.com");
};
// Responds to a app_capability_access's OnCapabilityAccessUpdate to call back
// into AppCapabilityAccessCache, checking that AppCapabilityAccessCache
// presents a self-consistent snapshot. For example, the camera should match for
// the outer and inner CapabilityAccessUpdate.
//
// In the tests below, just "recursive" means that
// app_capability_access.OnCapabilityAccesses calls
// observer.OnCapabilityAccessUpdate which calls
// app_capability_access.ForEachApp and app_capability_access.ForOneApp.
// "Super-recursive" means that app_capability_access.OnCapabilityAccesses calls
// observer.OnCapabilityAccessUpdate calls
// app_capability_access.OnCapabilityAccesses which calls
// observer.OnCapabilityAccessUpdate.
class CapabilityAccessRecursiveObserver
: public apps::AppCapabilityAccessCache::Observer {
public:
explicit CapabilityAccessRecursiveObserver(
apps::AppCapabilityAccessCache* cache)
: cache_(cache) {
Observe(cache);
}
~CapabilityAccessRecursiveObserver() override = default;
void PrepareForOnCapabilityAccesses(int expected_num_apps,
std::vector<apps::CapabilityAccessPtr>*
super_recursive_accesses = nullptr) {
expected_num_apps_ = expected_num_apps;
num_apps_seen_on_capability_access_update_ = 0;
if (super_recursive_accesses) {
super_recursive_accesses_.swap(*super_recursive_accesses);
}
}
int NumAppsSeenOnCapabilityAccessUpdate() {
return num_apps_seen_on_capability_access_update_;
}
const std::set<std::string>& accessing_camera_apps() {
return accessing_camera_apps_;
}
const std::set<std::string>& accessing_microphone_apps() {
return accessing_microphone_apps_;
}
protected:
// apps::AppCapabilityAccessCache::Observer overrides.
void OnCapabilityAccessUpdate(
const apps::CapabilityAccessUpdate& outer) override {
EXPECT_EQ(account_id_, outer.AccountId());
int num_apps = 0;
cache_->ForEachApp(
[this, &outer, &num_apps](const apps::CapabilityAccessUpdate& inner) {
if (inner.Camera().value_or(false)) {
accessing_camera_apps_.insert(inner.AppId());
} else {
accessing_camera_apps_.erase(inner.AppId());
}
if (inner.Microphone().value_or(false)) {
accessing_microphone_apps_.insert(inner.AppId());
} else {
accessing_microphone_apps_.erase(inner.AppId());
}
if (outer.AppId() == inner.AppId()) {
ExpectEq(outer, inner);
}
num_apps++;
});
EXPECT_EQ(expected_num_apps_, num_apps);
EXPECT_FALSE(cache_->ForOneApp(
"no_such_app_id", [&outer](const apps::CapabilityAccessUpdate& inner) {
ExpectEq(outer, inner);
}));
EXPECT_TRUE(cache_->ForOneApp(
outer.AppId(), [&outer](const apps::CapabilityAccessUpdate& inner) {
ExpectEq(outer, inner);
}));
std::vector<apps::CapabilityAccessPtr> super_recursive;
while (!super_recursive_accesses_.empty()) {
apps::CapabilityAccessPtr access =
std::move(super_recursive_accesses_.back());
super_recursive_accesses_.pop_back();
if (access.get() == nullptr) {
// This is the placeholder 'punctuation'.
break;
}
super_recursive.push_back(std::move(access));
}
if (!super_recursive.empty()) {
cache_->OnCapabilityAccesses(std::move(super_recursive));
}
num_apps_seen_on_capability_access_update_++;
}
void OnAppCapabilityAccessCacheWillBeDestroyed(
apps::AppCapabilityAccessCache* cache) override {
Observe(nullptr);
}
static void ExpectEq(const apps::CapabilityAccessUpdate& outer,
const apps::CapabilityAccessUpdate& inner) {
EXPECT_EQ(outer.AppId(), inner.AppId());
EXPECT_EQ(outer.StateIsNull(), inner.StateIsNull());
EXPECT_EQ(outer.Camera(), inner.Camera());
EXPECT_EQ(outer.Microphone(), inner.Microphone());
}
private:
raw_ptr<apps::AppCapabilityAccessCache> cache_;
AccountId account_id_ = AccountId::FromUserEmail("test@gmail.com");
std::set<std::string> accessing_camera_apps_;
std::set<std::string> accessing_microphone_apps_;
int expected_num_apps_;
int num_apps_seen_on_capability_access_update_;
// Non-empty when this.OnCapabilityAccessUpdate should trigger more
// app_capability_access_.OnCapabilityAccesses calls.
//
// During OnCapabilityAccessUpdate, this vector (a stack) is popped from the
// back until a nullptr 'punctuation' element (a group terminator) is seen. If
// that group of popped elements (in LIFO order) is non-empty, that group
// forms the vector of CapabilityAccess's passed to
// app_capability_access_.OnCapabilityAccesses.
std::vector<apps::CapabilityAccessPtr> super_recursive_accesses_;
};
TEST_F(AppCapabilityAccessCacheTest, ForEachApp) {
std::vector<apps::CapabilityAccessPtr> deltas;
apps::AppCapabilityAccessCache cache;
cache.SetAccountId(account_id());
CallForEachApp(cache);
EXPECT_EQ(0u, updated_ids_.size());
deltas.clear();
deltas.push_back(MakeCapabilityAccess("a", true, true));
deltas.push_back(MakeCapabilityAccess("b", false, false));
deltas.push_back(MakeCapabilityAccess("c", true, false));
cache.OnCapabilityAccesses(std::move(deltas));
updated_ids_.clear();
CallForEachApp(cache);
EXPECT_EQ(3u, updated_ids_.size());
EXPECT_EQ(2u, accessing_camera_apps_.size());
EXPECT_EQ(cache.GetAppsAccessingCamera(), accessing_camera_apps_);
EXPECT_EQ(1u, accessing_microphone_apps_.size());
EXPECT_EQ(cache.GetAppsAccessingMicrophone(), accessing_microphone_apps_);
deltas.clear();
deltas.push_back(MakeCapabilityAccess("a", false, false));
deltas.push_back(MakeCapabilityAccess("d", false, true));
cache.OnCapabilityAccesses(std::move(deltas));
updated_ids_.clear();
CallForEachApp(cache);
EXPECT_EQ(4u, updated_ids_.size());
EXPECT_EQ(1u, accessing_camera_apps_.size());
EXPECT_EQ(cache.GetAppsAccessingCamera(), accessing_camera_apps_);
EXPECT_EQ(1u, accessing_microphone_apps_.size());
EXPECT_EQ(cache.GetAppsAccessingMicrophone(), accessing_microphone_apps_);
// Test that ForOneApp succeeds for "c" and fails for "e".
bool found_c = false;
EXPECT_TRUE(cache.ForOneApp(
"c", [&found_c](const apps::CapabilityAccessUpdate& update) {
found_c = true;
EXPECT_EQ("c", update.AppId());
EXPECT_TRUE(update.Camera().value_or(false));
EXPECT_FALSE(update.Microphone().value_or(true));
}));
EXPECT_TRUE(found_c);
bool found_e = false;
EXPECT_FALSE(cache.ForOneApp(
"e", [&found_e](const apps::CapabilityAccessUpdate& update) {
found_e = true;
EXPECT_EQ("e", update.AppId());
}));
EXPECT_FALSE(found_e);
}
TEST_F(AppCapabilityAccessCacheTest, Observer) {
std::vector<apps::CapabilityAccessPtr> deltas;
apps::AppCapabilityAccessCache cache;
cache.SetAccountId(account_id());
cache.AddObserver(this);
updated_ids_.clear();
deltas.clear();
deltas.push_back(MakeCapabilityAccess("a", true, true));
deltas.push_back(MakeCapabilityAccess("b", false, false));
deltas.push_back(MakeCapabilityAccess("c", true, false));
cache.OnCapabilityAccesses(std::move(deltas));
EXPECT_EQ(3u, updated_ids_.size());
EXPECT_EQ(2u, accessing_camera_apps_.size());
EXPECT_EQ(cache.GetAppsAccessingCamera(), accessing_camera_apps_);
EXPECT_EQ(1u, accessing_microphone_apps_.size());
EXPECT_EQ(cache.GetAppsAccessingMicrophone(), accessing_microphone_apps_);
updated_ids_.clear();
deltas.clear();
deltas.push_back(MakeCapabilityAccess("b", true, true));
deltas.push_back(MakeCapabilityAccess("c", false, true));
cache.OnCapabilityAccesses(std::move(deltas));
EXPECT_EQ(2u, updated_ids_.size());
EXPECT_EQ(2u, accessing_camera_apps_.size());
EXPECT_EQ(cache.GetAppsAccessingCamera(), accessing_camera_apps_);
EXPECT_EQ(3u, accessing_microphone_apps_.size());
EXPECT_EQ(cache.GetAppsAccessingMicrophone(), accessing_microphone_apps_);
cache.RemoveObserver(this);
updated_ids_.clear();
accessing_camera_apps_.clear();
accessing_microphone_apps_.clear();
deltas.clear();
deltas.push_back(MakeCapabilityAccess("d", false, true));
cache.OnCapabilityAccesses(std::move(deltas));
EXPECT_EQ(0u, accessing_camera_apps_.size());
EXPECT_EQ(0u, accessing_microphone_apps_.size());
}
TEST_F(AppCapabilityAccessCacheTest, Recursive) {
std::vector<apps::CapabilityAccessPtr> deltas;
apps::AppCapabilityAccessCache cache;
cache.SetAccountId(account_id());
CapabilityAccessRecursiveObserver observer(&cache);
observer.PrepareForOnCapabilityAccesses(2);
deltas.clear();
deltas.push_back(MakeCapabilityAccess("a", true, false));
deltas.push_back(MakeCapabilityAccess("b", false, true));
cache.OnCapabilityAccesses(std::move(deltas));
EXPECT_EQ(2, observer.NumAppsSeenOnCapabilityAccessUpdate());
observer.PrepareForOnCapabilityAccesses(3);
deltas.clear();
deltas.push_back(MakeCapabilityAccess("b", true, false));
deltas.push_back(MakeCapabilityAccess("c", true, true));
cache.OnCapabilityAccesses(std::move(deltas));
EXPECT_EQ(2, observer.NumAppsSeenOnCapabilityAccessUpdate());
observer.PrepareForOnCapabilityAccesses(3);
deltas.clear();
deltas.push_back(MakeCapabilityAccess("b", true, false));
deltas.push_back(MakeCapabilityAccess("b", true, false));
deltas.push_back(MakeCapabilityAccess("b", true, true));
cache.OnCapabilityAccesses(std::move(deltas));
EXPECT_EQ(1, observer.NumAppsSeenOnCapabilityAccessUpdate());
EXPECT_EQ(cache.GetAppsAccessingCamera(), observer.accessing_camera_apps());
EXPECT_EQ(cache.GetAppsAccessingMicrophone(),
observer.accessing_microphone_apps());
}
TEST_F(AppCapabilityAccessCacheTest, SuperRecursive) {
std::vector<apps::CapabilityAccessPtr> deltas;
apps::AppCapabilityAccessCache cache;
cache.SetAccountId(account_id());
CapabilityAccessRecursiveObserver observer(&cache);
// Set up a series of OnCapabilityAccesses to be called during
// observer.OnCapabilityAccessUpdate:
// - the 1st update is {"b, "c"}.
// - the 2nd update is {}.
// - the 3rd update is {"b", "c", "b"}.
// - the 4th update is {"a"}.
// - the 5th update is {}.
// - the 6th update is {"b"}.
//
// The vector is processed in LIFO order with nullptr punctuation to
// terminate each group. See the comment on the
// RecursiveObserver::super_recursive_accesses_ field.
std::vector<apps::CapabilityAccessPtr> super_recursive_accesses;
super_recursive_accesses.push_back(nullptr);
super_recursive_accesses.push_back(MakeCapabilityAccess("b", true, true));
super_recursive_accesses.push_back(nullptr);
super_recursive_accesses.push_back(nullptr);
super_recursive_accesses.push_back(MakeCapabilityAccess("a", true, true));
super_recursive_accesses.push_back(nullptr);
super_recursive_accesses.push_back(MakeCapabilityAccess("b", true, false));
super_recursive_accesses.push_back(MakeCapabilityAccess("a", false, false));
super_recursive_accesses.push_back(MakeCapabilityAccess("b", false, false));
super_recursive_accesses.push_back(nullptr);
super_recursive_accesses.push_back(nullptr);
super_recursive_accesses.push_back(MakeCapabilityAccess("c", true, true));
super_recursive_accesses.push_back(MakeCapabilityAccess("b", true, true));
observer.PrepareForOnCapabilityAccesses(3, &super_recursive_accesses);
deltas.clear();
deltas.push_back(MakeCapabilityAccess("a", true, false));
deltas.push_back(MakeCapabilityAccess("b", false, true));
deltas.push_back(MakeCapabilityAccess("c", false, false));
cache.OnCapabilityAccesses(std::move(deltas));
// After all of that, check that for each app_id, the last delta won.
EXPECT_EQ(3u, observer.accessing_camera_apps().size());
EXPECT_NE(observer.accessing_camera_apps().end(),
observer.accessing_camera_apps().find("a"));
EXPECT_NE(observer.accessing_camera_apps().end(),
observer.accessing_camera_apps().find("b"));
EXPECT_NE(observer.accessing_camera_apps().end(),
observer.accessing_camera_apps().find("c"));
EXPECT_EQ(3u, observer.accessing_microphone_apps().size());
EXPECT_NE(observer.accessing_microphone_apps().end(),
observer.accessing_microphone_apps().find("a"));
EXPECT_NE(observer.accessing_microphone_apps().end(),
observer.accessing_microphone_apps().find("b"));
EXPECT_NE(observer.accessing_microphone_apps().end(),
observer.accessing_microphone_apps().find("c"));
EXPECT_EQ(cache.GetAppsAccessingCamera(), observer.accessing_camera_apps());
EXPECT_EQ(cache.GetAppsAccessingMicrophone(),
observer.accessing_microphone_apps());
}

@ -309,7 +309,6 @@ class InitializedObserver : public apps::AppRegistryCache::Observer {
} // namespace
class AppRegistryCacheTest : public testing::Test,
public testing::WithParamInterface<bool>,
public AppRegistryCache::Observer {
public:
void CallForEachApp(AppRegistryCache& cache) {

@ -24,4 +24,8 @@ const base::Feature kAppServiceWithoutMojom{"AppServiceWithoutMojom",
const base::Feature kAppServiceGetMenuWithoutMojom{
"AppServiceGetMenuWithoutMojom", base::FEATURE_ENABLED_BY_DEFAULT};
const base::Feature kAppServiceCapabilityAccessWithoutMojom{
"AppServiceCapabilityAccessWithoutMojom",
base::FEATURE_DISABLED_BY_DEFAULT};
} // namespace apps

@ -22,6 +22,8 @@ COMPONENT_EXPORT(APP_TYPES)
extern const base::Feature kAppServiceWithoutMojom;
COMPONENT_EXPORT(APP_TYPES)
extern const base::Feature kAppServiceGetMenuWithoutMojom;
COMPONENT_EXPORT(APP_TYPES)
extern const base::Feature kAppServiceCapabilityAccessWithoutMojom;
} // namespace apps