blob: c0f749c560bab68a1c70010a77e7a89bc0ccec18 [file] [log] [blame]
// Copyright 2018 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 "chrome/browser/chromeos/policy/app_install_event_log_collector.h"
#include <vector>
#include "base/command_line.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/time/time.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/prefs/browser_prefs.h"
#include "chrome/browser/ui/app_list/arc/arc_app_test.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/constants/chromeos_switches.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/fake_power_manager_client.h"
#include "chromeos/dbus/shill_service_client.h"
#include "chromeos/network/network_handler.h"
#include "components/arc/common/app.mojom.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/testing_pref_service.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
namespace em = enterprise_management;
namespace policy {
namespace {
constexpr char kEthernetServicePath[] = "/service/eth1";
constexpr char kWifiServicePath[] = "/service/wifi1";
constexpr char kPackageName[] = "com.example.app";
constexpr char kPackageName2[] = "com.example.app2";
class FakeAppInstallEventLogCollectorDelegate
: public AppInstallEventLogCollector::Delegate {
public:
FakeAppInstallEventLogCollectorDelegate() = default;
~FakeAppInstallEventLogCollectorDelegate() override = default;
struct Request {
Request(bool for_all,
bool add_disk_space_info,
const std::string& package_name,
const em::AppInstallReportLogEvent& event)
: for_all(for_all),
add_disk_space_info(add_disk_space_info),
package_name(package_name),
event(event) {}
const bool for_all;
const bool add_disk_space_info;
const std::string package_name;
const em::AppInstallReportLogEvent event;
};
// AppInstallEventLogCollector::Delegate:
void AddForAllPackages(
std::unique_ptr<em::AppInstallReportLogEvent> event) override {
++add_for_all_count_;
requests_.emplace_back(true /* for_all */, false /* add_disk_space_info */,
std::string() /* package_name */, *event);
}
void Add(const std::string& package_name,
bool add_disk_space_info,
std::unique_ptr<em::AppInstallReportLogEvent> event) override {
++add_count_;
requests_.emplace_back(false /* for_all */, add_disk_space_info,
package_name, *event);
}
int add_for_all_count() const { return add_for_all_count_; }
int add_count() const { return add_count_; }
const em::AppInstallReportLogEvent& last_event() const {
return last_request().event;
}
const Request& last_request() const { return requests_.back(); }
const std::vector<Request>& requests() const { return requests_; }
private:
int add_for_all_count_ = 0;
int add_count_ = 0;
std::vector<Request> requests_;
DISALLOW_COPY_AND_ASSIGN(FakeAppInstallEventLogCollectorDelegate);
};
int64_t TimeToTimestamp(base::Time time) {
return (time - base::Time::UnixEpoch()).InMicroseconds();
}
} // namespace
class AppInstallEventLogCollectorTest : public testing::Test {
protected:
AppInstallEventLogCollectorTest() = default;
~AppInstallEventLogCollectorTest() override = default;
void SetUp() override {
RegisterLocalState(pref_service_.registry());
TestingBrowserProcess::GetGlobal()->SetLocalState(&pref_service_);
chromeos::PowerManagerClient::Initialize();
chromeos::DBusThreadManager::Initialize();
chromeos::NetworkHandler::Initialize();
profile_ = std::make_unique<TestingProfile>();
service_test_ = chromeos::DBusThreadManager::Get()
->GetShillServiceClient()
->GetTestInterface();
service_test_->AddService(kEthernetServicePath, "eth1_guid", "eth1",
shill::kTypeEthernet, shill::kStateOffline,
true /* visible */);
service_test_->AddService(kWifiServicePath, "wifi1_guid", "wifi1",
shill::kTypeEthernet, shill::kStateOffline,
true /* visible */);
base::RunLoop().RunUntilIdle();
arc_app_test_.SetUp(profile_.get());
}
void TearDown() override {
arc_app_test_.TearDown();
profile_.reset();
chromeos::NetworkHandler::Shutdown();
chromeos::DBusThreadManager::Shutdown();
chromeos::PowerManagerClient::Shutdown();
TestingBrowserProcess::GetGlobal()->SetLocalState(nullptr);
}
void SetNetworkState(
network::NetworkConnectionTracker::NetworkConnectionObserver* observer,
const std::string& service_path,
const std::string& state) {
service_test_->SetServiceProperty(service_path, shill::kStateProperty,
base::Value(state));
base::RunLoop().RunUntilIdle();
network::mojom::ConnectionType connection_type =
network::mojom::ConnectionType::CONNECTION_NONE;
std::string network_state;
service_test_->GetServiceProperties(kWifiServicePath)
->GetString(shill::kStateProperty, &network_state);
if (network_state == shill::kStateOnline) {
connection_type = network::mojom::ConnectionType::CONNECTION_WIFI;
}
service_test_->GetServiceProperties(kEthernetServicePath)
->GetString(shill::kStateProperty, &network_state);
if (network_state == shill::kStateOnline) {
connection_type = network::mojom::ConnectionType::CONNECTION_ETHERNET;
}
if (observer)
observer->OnConnectionChanged(connection_type);
base::RunLoop().RunUntilIdle();
}
TestingProfile* profile() { return profile_.get(); }
FakeAppInstallEventLogCollectorDelegate* delegate() { return &delegate_; }
ArcAppListPrefs* app_prefs() { return arc_app_test_.arc_app_list_prefs(); }
const std::set<std::string> packages_ = {kPackageName};
chromeos::ShillServiceClient::TestInterface* service_test_ = nullptr;
private:
content::TestBrowserThreadBundle thread_bundle_;
std::unique_ptr<TestingProfile> profile_;
FakeAppInstallEventLogCollectorDelegate delegate_;
TestingPrefServiceSimple pref_service_;
ArcAppTest arc_app_test_;
DISALLOW_COPY_AND_ASSIGN(AppInstallEventLogCollectorTest);
};
// Test the case when collector is created and destroyed inside the one user
// session. In this case no event is generated. This happens for example when
// all apps are installed in context of the same user session.
TEST_F(AppInstallEventLogCollectorTest, NoEventsByDefault) {
std::unique_ptr<AppInstallEventLogCollector> collector =
std::make_unique<AppInstallEventLogCollector>(delegate(), profile(),
packages_);
collector.reset();
EXPECT_EQ(0, delegate()->add_count());
EXPECT_EQ(0, delegate()->add_for_all_count());
}
TEST_F(AppInstallEventLogCollectorTest, LoginLogout) {
std::unique_ptr<AppInstallEventLogCollector> collector =
std::make_unique<AppInstallEventLogCollector>(delegate(), profile(),
packages_);
EXPECT_EQ(0, delegate()->add_for_all_count());
collector->AddLoginEvent();
EXPECT_EQ(1, delegate()->add_for_all_count());
EXPECT_EQ(em::AppInstallReportLogEvent::SESSION_STATE_CHANGE,
delegate()->last_event().event_type());
EXPECT_EQ(em::AppInstallReportLogEvent::LOGIN,
delegate()->last_event().session_state_change_type());
EXPECT_TRUE(delegate()->last_event().has_online());
EXPECT_FALSE(delegate()->last_event().online());
collector->AddLogoutEvent();
EXPECT_EQ(2, delegate()->add_for_all_count());
EXPECT_EQ(em::AppInstallReportLogEvent::SESSION_STATE_CHANGE,
delegate()->last_event().event_type());
EXPECT_EQ(em::AppInstallReportLogEvent::LOGOUT,
delegate()->last_event().session_state_change_type());
EXPECT_FALSE(delegate()->last_event().has_online());
collector.reset();
EXPECT_EQ(2, delegate()->add_for_all_count());
EXPECT_EQ(0, delegate()->add_count());
}
TEST_F(AppInstallEventLogCollectorTest, LoginTypes) {
{
AppInstallEventLogCollector collector(delegate(), profile(), packages_);
collector.AddLoginEvent();
EXPECT_EQ(1, delegate()->add_for_all_count());
EXPECT_EQ(em::AppInstallReportLogEvent::SESSION_STATE_CHANGE,
delegate()->last_event().event_type());
EXPECT_EQ(em::AppInstallReportLogEvent::LOGIN,
delegate()->last_event().session_state_change_type());
EXPECT_TRUE(delegate()->last_event().has_online());
EXPECT_FALSE(delegate()->last_event().online());
}
{
// Check login after restart. No log is expected.
AppInstallEventLogCollector collector(delegate(), profile(), packages_);
base::CommandLine::ForCurrentProcess()->AppendSwitch(
chromeos::switches::kLoginUser);
collector.AddLoginEvent();
EXPECT_EQ(1, delegate()->add_for_all_count());
}
{
// Check logout on restart. No log is expected.
AppInstallEventLogCollector collector(delegate(), profile(), packages_);
g_browser_process->local_state()->SetBoolean(prefs::kWasRestarted, true);
collector.AddLogoutEvent();
EXPECT_EQ(1, delegate()->add_for_all_count());
}
EXPECT_EQ(0, delegate()->add_count());
}
TEST_F(AppInstallEventLogCollectorTest, SuspendResume) {
std::unique_ptr<AppInstallEventLogCollector> collector =
std::make_unique<AppInstallEventLogCollector>(delegate(), profile(),
packages_);
chromeos::FakePowerManagerClient::Get()->SendSuspendImminent(
power_manager::SuspendImminent_Reason_OTHER);
EXPECT_EQ(1, delegate()->add_for_all_count());
EXPECT_EQ(em::AppInstallReportLogEvent::SESSION_STATE_CHANGE,
delegate()->last_event().event_type());
EXPECT_EQ(em::AppInstallReportLogEvent::SUSPEND,
delegate()->last_event().session_state_change_type());
chromeos::FakePowerManagerClient::Get()->SendSuspendDone();
EXPECT_EQ(2, delegate()->add_for_all_count());
EXPECT_EQ(em::AppInstallReportLogEvent::SESSION_STATE_CHANGE,
delegate()->last_event().event_type());
EXPECT_EQ(em::AppInstallReportLogEvent::RESUME,
delegate()->last_event().session_state_change_type());
collector.reset();
EXPECT_EQ(0, delegate()->add_count());
}
// Connect to Ethernet. Start log collector. Verify that a login event with
// network state online is recorded. Then, connect to WiFi and disconnect from
// Ethernet, in this order. Verify that no event is recorded. Then, disconnect
// from WiFi. Verify that a connectivity change event is recorded. Then, connect
// to WiFi with a pending captive portal. Verify that no event is recorded.
// Then, pass the captive portal. Verify that a connectivity change is recorded.
TEST_F(AppInstallEventLogCollectorTest, ConnectivityChanges) {
SetNetworkState(nullptr, kEthernetServicePath, shill::kStateOnline);
std::unique_ptr<AppInstallEventLogCollector> collector =
std::make_unique<AppInstallEventLogCollector>(delegate(), profile(),
packages_);
EXPECT_EQ(0, delegate()->add_for_all_count());
collector->AddLoginEvent();
EXPECT_EQ(1, delegate()->add_for_all_count());
EXPECT_EQ(em::AppInstallReportLogEvent::SESSION_STATE_CHANGE,
delegate()->last_event().event_type());
EXPECT_EQ(em::AppInstallReportLogEvent::LOGIN,
delegate()->last_event().session_state_change_type());
EXPECT_TRUE(delegate()->last_event().online());
SetNetworkState(collector.get(), kWifiServicePath, shill::kStateOnline);
EXPECT_EQ(1, delegate()->add_for_all_count());
SetNetworkState(collector.get(), kEthernetServicePath, shill::kStateOffline);
EXPECT_EQ(1, delegate()->add_for_all_count());
SetNetworkState(collector.get(), kWifiServicePath, shill::kStateOffline);
EXPECT_EQ(2, delegate()->add_for_all_count());
EXPECT_EQ(em::AppInstallReportLogEvent::CONNECTIVITY_CHANGE,
delegate()->last_event().event_type());
EXPECT_FALSE(delegate()->last_event().online());
SetNetworkState(collector.get(), kWifiServicePath, shill::kStatePortal);
EXPECT_EQ(2, delegate()->add_for_all_count());
SetNetworkState(collector.get(), kWifiServicePath, shill::kStateOnline);
EXPECT_EQ(3, delegate()->add_for_all_count());
EXPECT_EQ(em::AppInstallReportLogEvent::CONNECTIVITY_CHANGE,
delegate()->last_event().event_type());
EXPECT_TRUE(delegate()->last_event().online());
collector.reset();
EXPECT_EQ(3, delegate()->add_for_all_count());
EXPECT_EQ(0, delegate()->add_count());
}
// Validates sequence of CloudDPS events.
TEST_F(AppInstallEventLogCollectorTest, CloudDPSEvent) {
std::unique_ptr<AppInstallEventLogCollector> collector =
std::make_unique<AppInstallEventLogCollector>(delegate(), profile(),
packages_);
base::Time time = base::Time::Now();
collector->OnCloudDpsRequested(time, {kPackageName, kPackageName2});
ASSERT_EQ(2, delegate()->add_count());
ASSERT_EQ(0, delegate()->add_for_all_count());
EXPECT_EQ(TimeToTimestamp(time), delegate()->requests()[0].event.timestamp());
EXPECT_EQ(kPackageName, delegate()->requests()[0].package_name);
EXPECT_EQ(em::AppInstallReportLogEvent::CLOUDDPS_REQUEST,
delegate()->requests()[0].event.event_type());
EXPECT_FALSE(delegate()->requests()[0].event.has_clouddps_response());
EXPECT_EQ(TimeToTimestamp(time), delegate()->requests()[1].event.timestamp());
EXPECT_EQ(kPackageName2, delegate()->requests()[1].package_name);
EXPECT_EQ(em::AppInstallReportLogEvent::CLOUDDPS_REQUEST,
delegate()->requests()[1].event.event_type());
EXPECT_EQ(0, delegate()->requests()[1].event.clouddps_response());
// One package succeeded.
time += base::TimeDelta::FromSeconds(1);
collector->OnCloudDpsSucceeded(time, {kPackageName});
ASSERT_EQ(3, delegate()->add_count());
ASSERT_EQ(0, delegate()->add_for_all_count());
EXPECT_EQ(TimeToTimestamp(time),
delegate()->last_request().event.timestamp());
EXPECT_EQ(kPackageName, delegate()->last_request().package_name);
EXPECT_EQ(em::AppInstallReportLogEvent::CLOUDDPS_RESPONSE,
delegate()->last_request().event.event_type());
EXPECT_FALSE(delegate()->requests()[0].event.has_clouddps_response());
// One package failed.
time += base::TimeDelta::FromSeconds(1);
collector->OnCloudDpsFailed(time, kPackageName2,
arc::mojom::InstallErrorReason::TIMEOUT);
ASSERT_EQ(4, delegate()->add_count());
ASSERT_EQ(0, delegate()->add_for_all_count());
EXPECT_EQ(TimeToTimestamp(time),
delegate()->last_request().event.timestamp());
EXPECT_EQ(kPackageName2, delegate()->last_request().package_name);
EXPECT_EQ(em::AppInstallReportLogEvent::CLOUDDPS_RESPONSE,
delegate()->last_request().event.event_type());
EXPECT_TRUE(delegate()->last_request().event.has_clouddps_response());
EXPECT_EQ(static_cast<int>(arc::mojom::InstallErrorReason::TIMEOUT),
delegate()->last_request().event.clouddps_response());
}
TEST_F(AppInstallEventLogCollectorTest, InstallPackages) {
arc::mojom::AppHost* const app_host = app_prefs();
std::unique_ptr<AppInstallEventLogCollector> collector =
std::make_unique<AppInstallEventLogCollector>(delegate(), profile(),
packages_);
app_host->OnInstallationStarted(kPackageName);
ASSERT_EQ(1, delegate()->add_count());
EXPECT_EQ(em::AppInstallReportLogEvent::INSTALLATION_STARTED,
delegate()->last_event().event_type());
EXPECT_EQ(kPackageName, delegate()->last_request().package_name);
EXPECT_TRUE(delegate()->last_request().add_disk_space_info);
// kPackageName2 is not in the pending set.
app_host->OnInstallationStarted(kPackageName2);
EXPECT_EQ(1, delegate()->add_count());
arc::mojom::InstallationResult result;
result.package_name = kPackageName;
result.success = true;
app_host->OnInstallationFinished(
arc::mojom::InstallationResultPtr(result.Clone()));
EXPECT_EQ(2, delegate()->add_count());
EXPECT_EQ(em::AppInstallReportLogEvent::INSTALLATION_FINISHED,
delegate()->last_event().event_type());
EXPECT_EQ(kPackageName, delegate()->last_request().package_name);
EXPECT_TRUE(delegate()->last_request().add_disk_space_info);
collector->OnPendingPackagesChanged({kPackageName, kPackageName2});
// Now kPackageName2 is in the pending set.
app_host->OnInstallationStarted(kPackageName2);
EXPECT_EQ(3, delegate()->add_count());
EXPECT_EQ(em::AppInstallReportLogEvent::INSTALLATION_STARTED,
delegate()->last_event().event_type());
EXPECT_EQ(kPackageName2, delegate()->last_request().package_name);
EXPECT_TRUE(delegate()->last_request().add_disk_space_info);
result.package_name = kPackageName2;
result.success = false;
app_host->OnInstallationFinished(
arc::mojom::InstallationResultPtr(result.Clone()));
EXPECT_EQ(4, delegate()->add_count());
EXPECT_EQ(em::AppInstallReportLogEvent::INSTALLATION_FAILED,
delegate()->last_event().event_type());
EXPECT_EQ(kPackageName2, delegate()->last_request().package_name);
EXPECT_TRUE(delegate()->last_request().add_disk_space_info);
EXPECT_EQ(0, delegate()->add_for_all_count());
}
} // namespace policy