chromium/chromeos/ash/components/network/metrics/connection_info_metrics_logger_unittest.cc

// Copyright 2021 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/ash/components/network/metrics/connection_info_metrics_logger.h"

#include <memory>

#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/components/dbus/shill/shill_service_client.h"
#include "chromeos/ash/components/network/metrics/connection_results.h"
#include "chromeos/ash/components/network/metrics/network_metrics_helper.h"
#include "chromeos/ash/components/network/network_connection_handler.h"
#include "chromeos/ash/components/network/network_handler_test_helper.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

namespace ash {

namespace {

// Note: The actual network types used does not matter as networks are kept
// track of by GUID. This test uses Cellular and Wifi, but any combination of
// network type may be used.
const char kCellularConnectResultAllHistogram[] =
    "Network.Ash.Cellular.ConnectionResult.All";
const char kWifiConnectResultAllHistogram[] =
    "Network.Ash.WiFi.ConnectionResult.All";

const char kCellularConnectResultNonUserInitiatedHistogram[] =
    "Network.Ash.Cellular.ConnectionResult.NonUserInitiated";
const char kWifiConnectResultNonUserInitiatedHistogram[] =
    "Network.Ash.WiFi.ConnectionResult.NonUserInitiated";

const char kCellularConnectResultUserInitiatedHistogram[] =
    "Network.Ash.Cellular.ConnectionResult.UserInitiated";
const char kWifiConnectResultUserInitiatedHistogram[] =
    "Network.Ash.WiFi.ConnectionResult.UserInitiated";

const char kCellularConnectionStateHistogram[] =
    "Network.Ash.Cellular.DisconnectionsWithoutUserAction";
const char kWifiConnectionStateHistogram[] =
    "Network.Ash.WiFi.DisconnectionsWithoutUserAction";

const char kCellularGuid[] = "test_guid";
const char kCellularServicePath[] = "/service/network";
const char kCellularName[] = "network_name";

const char kWifiGuid[] = "test_guid2";
const char kWifiServicePath[] = "/service/network2";
const char kWifiName[] = "network_name2";

}  // namespace

class ConnectionInfoMetricsLoggerTest : public testing::Test {
 public:
  ConnectionInfoMetricsLoggerTest() = default;
  ConnectionInfoMetricsLoggerTest(const ConnectionInfoMetricsLoggerTest&) =
      delete;
  ConnectionInfoMetricsLoggerTest& operator=(
      const ConnectionInfoMetricsLoggerTest&) = delete;
  ~ConnectionInfoMetricsLoggerTest() override = default;

  void SetUp() override {
    network_handler_test_helper_ = std::make_unique<NetworkHandlerTestHelper>();
    histogram_tester_ = std::make_unique<base::HistogramTester>();

    shill_service_client_ = ShillServiceClient::Get()->GetTestInterface();
    shill_service_client_->ClearServices();
    base::RunLoop().RunUntilIdle();

    network_handler_test_helper_->RegisterPrefs(profile_prefs_.registry(),
                                                local_state_.registry());

    network_handler_test_helper_->InitializePrefs(&profile_prefs_,
                                                  &local_state_);
  }

  void TearDown() override {
    shill_service_client_->ClearServices();
    network_handler_test_helper_.reset();
  }

  void SetUpGenericCellularNetwork() {
    shill_service_client_->AddService(kCellularServicePath, kCellularGuid,
                                      kCellularName, shill::kTypeCellular,
                                      shill::kStateIdle,
                                      /*visible=*/true);
    base::RunLoop().RunUntilIdle();
  }

  void SetUpGenericWifiNetwork() {
    shill_service_client_->AddService(kWifiServicePath, kWifiGuid, kWifiName,
                                      shill::kTypeWifi, shill::kStateIdle,
                                      /*visible=*/true);
    base::RunLoop().RunUntilIdle();
  }

  void SetShillState(const std::string& service_path,
                     const std::string& shill_state) {
    shill_service_client_->SetServiceProperty(
        service_path, shill::kStateProperty, base::Value(shill_state));
    base::RunLoop().RunUntilIdle();
  }

  void SetShillError(const std::string& service_path,
                     const std::string& shill_error) {
    shill_service_client_->SetServiceProperty(
        service_path, shill::kErrorProperty, base::Value(shill_error));
    base::RunLoop().RunUntilIdle();
  }

  void TriggerUserInitiatedConnectRequested(const std::string& service_path) {
    NetworkHandler::Get()
        ->connection_info_metrics_logger_->ConnectToNetworkRequested(
            service_path);
  }

  void TriggerUserInitiatedConnectSuccess(const std::string& service_path) {
    NetworkHandler::Get()
        ->connection_info_metrics_logger_->ConnectSucceeded(service_path);
  }

  void TriggerUserInitiatedConnectFailure(const std::string& service_path,
                                          const std::string& error_name) {
    NetworkHandler::Get()
        ->connection_info_metrics_logger_->ConnectFailed(service_path,
                                                         error_name);
  }

 protected:
  base::test::TaskEnvironment task_environment_;
  std::unique_ptr<base::HistogramTester> histogram_tester_;
  std::unique_ptr<NetworkHandlerTestHelper> network_handler_test_helper_;
  raw_ptr<ShillServiceClient::TestInterface, DanglingUntriaged>
      shill_service_client_;
  TestingPrefServiceSimple profile_prefs_;
  TestingPrefServiceSimple local_state_;
};

TEST_F(ConnectionInfoMetricsLoggerTest, ConnectionState) {
  SetUpGenericCellularNetwork();
  SetUpGenericWifiNetwork();

  // Successful Cellular connect from disconnected to connected.
  SetShillState(kCellularServicePath, shill::kStateIdle);
  histogram_tester_->ExpectTotalCount(kCellularConnectionStateHistogram, 0);
  SetShillState(kCellularServicePath, shill::kStateOnline);
  histogram_tester_->ExpectTotalCount(kCellularConnectionStateHistogram, 1);
  histogram_tester_->ExpectBucketCount(
      kCellularConnectionStateHistogram,
      NetworkMetricsHelper::ConnectionState::kConnected, 1);

  // Disconnected Cellular with error.
  SetShillError(kCellularServicePath, shill::kErrorConnectFailed);
  SetShillState(kCellularServicePath, shill::kStateIdle);
  histogram_tester_->ExpectTotalCount(kCellularConnectionStateHistogram, 2);
  histogram_tester_->ExpectBucketCount(
      kCellularConnectionStateHistogram,
      NetworkMetricsHelper::ConnectionState::kDisconnectedWithoutUserAction, 1);

  // Successful Wifi connect from disconnected to connected.
  SetShillState(kWifiServicePath, shill::kStateIdle);
  histogram_tester_->ExpectTotalCount(kWifiConnectionStateHistogram, 0);
  SetShillState(kWifiServicePath, shill::kStateOnline);
  histogram_tester_->ExpectTotalCount(kWifiConnectionStateHistogram, 1);
  histogram_tester_->ExpectBucketCount(
      kWifiConnectionStateHistogram,
      NetworkMetricsHelper::ConnectionState::kConnected, 1);

  // Disconnecting Wifi due to suspend, state changes to idle with Shill error.
  SetShillError(kWifiServicePath, shill::kErrorConnectFailed);
  SetShillState(kWifiServicePath, shill::kStateIdle);
  histogram_tester_->ExpectTotalCount(kWifiConnectionStateHistogram, 1);
  histogram_tester_->ExpectBucketCount(
      kWifiConnectionStateHistogram,
      NetworkMetricsHelper::ConnectionState::kDisconnectedWithoutUserAction, 0);

  // Disconnecting Wifi due to some system error, state changes to failure with
  // Shill error.
  SetShillState(kWifiServicePath, shill::kStateOnline);
  SetShillError(kWifiServicePath, shill::kErrorConnectFailed);
  SetShillState(kWifiServicePath, shill::kStateFailure);
  histogram_tester_->ExpectBucketCount(
      kWifiConnectionStateHistogram,
      NetworkMetricsHelper::ConnectionState::kDisconnectedWithoutUserAction, 1);
}

TEST_F(ConnectionInfoMetricsLoggerTest, UserInitiatedConnectDisconnect) {
  SetUpGenericCellularNetwork();
  SetShillState(kCellularServicePath, shill::kStateIdle);
  SetUpGenericWifiNetwork();
  SetShillState(kWifiServicePath, shill::kStateIdle);
  histogram_tester_->ExpectTotalCount(
      kCellularConnectResultUserInitiatedHistogram, 0);

  TriggerUserInitiatedConnectRequested(kCellularServicePath);
  SetShillState(kCellularServicePath, shill::kStateAssociation);
  SetShillState(kCellularServicePath, shill::kStateOnline);
  TriggerUserInitiatedConnectSuccess(kCellularServicePath);
  histogram_tester_->ExpectTotalCount(
      kCellularConnectResultUserInitiatedHistogram, 1);
  histogram_tester_->ExpectTotalCount(
      kCellularConnectResultNonUserInitiatedHistogram, 0);

  // Auto connect followed by user-initiated connect.
  SetShillState(kCellularServicePath, shill::kStateIdle);
  SetShillState(kCellularServicePath, shill::kStateOnline);
  histogram_tester_->ExpectTotalCount(
      kCellularConnectResultUserInitiatedHistogram, 1);
  histogram_tester_->ExpectTotalCount(
      kCellularConnectResultNonUserInitiatedHistogram, 1);

  TriggerUserInitiatedConnectRequested(kWifiServicePath);
  TriggerUserInitiatedConnectSuccess(kWifiServicePath);
  histogram_tester_->ExpectTotalCount(kWifiConnectResultUserInitiatedHistogram,
                                      1);

  TriggerUserInitiatedConnectRequested(kCellularServicePath);
  TriggerUserInitiatedConnectFailure(
      kCellularServicePath, NetworkConnectionHandler::kErrorConnectFailed);
  histogram_tester_->ExpectTotalCount(
      kCellularConnectResultUserInitiatedHistogram, 2);

  TriggerUserInitiatedConnectRequested(kWifiServicePath);
  TriggerUserInitiatedConnectFailure(
      kWifiServicePath, NetworkConnectionHandler::kErrorConnectFailed);
  histogram_tester_->ExpectTotalCount(kWifiConnectResultUserInitiatedHistogram,
                                      2);
  histogram_tester_->ExpectTotalCount(
      kWifiConnectResultNonUserInitiatedHistogram, 0);
}

TEST_F(ConnectionInfoMetricsLoggerTest, AutoStatusTransitions) {
  SetUpGenericCellularNetwork();

  // Successful connect from disconnected to connected.
  SetShillState(kCellularServicePath, shill::kStateIdle);
  histogram_tester_->ExpectTotalCount(kCellularConnectResultAllHistogram, 0);
  SetShillState(kCellularServicePath, shill::kStateOnline);
  histogram_tester_->ExpectTotalCount(kCellularConnectResultAllHistogram, 1);
  histogram_tester_->ExpectBucketCount(kCellularConnectResultAllHistogram,
                                       ShillConnectResult::kSuccess, 1);
  histogram_tester_->ExpectTotalCount(
      kCellularConnectResultNonUserInitiatedHistogram, 1);
  histogram_tester_->ExpectBucketCount(
      kCellularConnectResultNonUserInitiatedHistogram,
      ShillConnectResult::kSuccess, 1);

  // Successful connect from connecting to connected.
  SetShillState(kCellularServicePath, shill::kStateAssociation);
  histogram_tester_->ExpectTotalCount(kCellularConnectResultAllHistogram, 1);
  SetShillState(kCellularServicePath, shill::kStateOnline);
  histogram_tester_->ExpectTotalCount(kCellularConnectResultAllHistogram, 2);
  histogram_tester_->ExpectBucketCount(kCellularConnectResultAllHistogram,
                                       ShillConnectResult::kSuccess, 2);
  histogram_tester_->ExpectTotalCount(
      kCellularConnectResultNonUserInitiatedHistogram, 2);
  histogram_tester_->ExpectBucketCount(
      kCellularConnectResultNonUserInitiatedHistogram,
      ShillConnectResult::kSuccess, 2);

  // Fail to connect from connecting to disconnecting, no valid shill error.
  SetShillState(kCellularServicePath, shill::kStateAssociation);
  histogram_tester_->ExpectTotalCount(kCellularConnectResultAllHistogram, 2);
  SetShillState(kCellularServicePath, shill::kStateDisconnecting);
  histogram_tester_->ExpectTotalCount(kCellularConnectResultAllHistogram, 2);

  // Fail to connect from disconnecting to disconnected.
  SetShillError(kCellularServicePath, shill::kErrorConnectFailed);
  SetShillState(kCellularServicePath, shill::kStateIdle);
  histogram_tester_->ExpectTotalCount(kCellularConnectResultAllHistogram, 3);
  histogram_tester_->ExpectBucketCount(kCellularConnectResultAllHistogram,
                                       ShillConnectResult::kErrorConnectFailed,
                                       1);
}

TEST_F(ConnectionInfoMetricsLoggerTest, MultipleNetworksStatusRecorded) {
  SetUpGenericCellularNetwork();
  SetUpGenericWifiNetwork();

  SetShillState(kCellularServicePath, shill::kStateIdle);
  histogram_tester_->ExpectTotalCount(kCellularConnectResultAllHistogram, 0);
  histogram_tester_->ExpectTotalCount(kWifiConnectResultAllHistogram, 0);

  SetShillState(kCellularServicePath, shill::kStateOnline);
  histogram_tester_->ExpectTotalCount(kCellularConnectResultAllHistogram, 1);
  histogram_tester_->ExpectTotalCount(kWifiConnectResultAllHistogram, 0);
  histogram_tester_->ExpectBucketCount(kCellularConnectResultAllHistogram,
                                       ShillConnectResult::kSuccess, 1);

  SetShillState(kWifiServicePath, shill::kStateOnline);
  histogram_tester_->ExpectTotalCount(kCellularConnectResultAllHistogram, 1);
  histogram_tester_->ExpectTotalCount(kWifiConnectResultAllHistogram, 1);
  histogram_tester_->ExpectBucketCount(kWifiConnectResultAllHistogram,
                                       ShillConnectResult::kSuccess, 1);

  SetShillState(kWifiServicePath, shill::kStateAssociation);
  SetShillError(kWifiServicePath, shill::kErrorConnectFailed);
  SetShillState(kWifiServicePath, shill::kStateIdle);
  histogram_tester_->ExpectTotalCount(kCellularConnectResultAllHistogram, 1);
  histogram_tester_->ExpectTotalCount(kWifiConnectResultAllHistogram, 2);
  histogram_tester_->ExpectBucketCount(kWifiConnectResultAllHistogram,
                                       ShillConnectResult::kSuccess, 1);
}

}  // namespace ash