chromium/components/metrics/content/content_stability_metrics_provider_browsertest.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 "components/metrics/content/content_stability_metrics_provider.h"

#include <string>

#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/functional/callback.h"
#include "base/metrics/histogram.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "components/metrics/content/extensions_helper.h"
#include "components/prefs/testing_pref_service.h"
#include "components/variations/hashing.h"
#include "content/public/browser/browser_child_process_observer.h"
#include "content/public/browser/child_process_data.h"
#include "content/public/browser/child_process_termination_info.h"
#include "content/public/browser/service_process_host.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_base.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_service.mojom.h"
#include "sandbox/policy/mojom/sandbox.mojom.h"

#if BUILDFLAG(IS_WIN)
#include <windows.h>

#include "sandbox/win/src/sandbox_types.h"
#endif

namespace content {

template <>
sandbox::mojom::Sandbox GetServiceSandboxType<content::mojom::TestService>() {
  // On Windows, the sandbox does not like having a different binary name
  // 'non_existent_path' from the browser process, so set no sandbox here.
#if BUILDFLAG(IS_WIN)
  return sandbox::mojom::Sandbox::kNoSandbox;
#else
  return sandbox::mojom::Sandbox::kService;
#endif
}

}  // namespace content

namespace metrics {

class ContentStabilityProviderBrowserTest
    : public content::ContentBrowserTest,
      content::BrowserChildProcessObserver {
 public:
  ContentStabilityProviderBrowserTest() {
    feature_list_.InitAndDisableFeature(
        features::kSpareRendererForSitePerProcess);
  }

  // Either the process launched, or did not launch. Both cause the run_loop to
  // terminate.
  void BrowserChildProcessLaunchFailed(
      const content::ChildProcessData& data,
      const content::ChildProcessTerminationInfo& info) override {
    if (data.metrics_name == content::mojom::TestService::Name_)
      std::move(done_closure_).Run();
  }

  void BrowserChildProcessLaunchedAndConnected(
      const content::ChildProcessData& data) override {
    if (data.metrics_name == content::mojom::TestService::Name_)
      std::move(done_closure_).Run();
  }

 protected:
  void AddObserver() { content::BrowserChildProcessObserver::Add(this); }

  void RemoveObserver() { content::BrowserChildProcessObserver::Remove(this); }

  base::OnceClosure done_closure_;
  TestingPrefServiceSimple prefs_;
  base::test::ScopedFeatureList feature_list_;
};

IN_PROC_BROWSER_TEST_F(ContentStabilityProviderBrowserTest,
                       FailedUtilityProcessLaunches) {
  base::RunLoop run_loop;
  done_closure_ = run_loop.QuitClosure();
  AddObserver();

  ContentStabilityMetricsProvider provider(&prefs_, nullptr);
  base::HistogramTester histogram_tester;

  // Simulate a catastrophic utility process launch failure by specifying a bad
  // path.
  base::CommandLine::ForCurrentProcess()->AppendSwitchPath(
      switches::kBrowserSubprocessPath,
      base::FilePath(FILE_PATH_LITERAL("non_existent_path")));
  mojo::Remote<content::mojom::TestService> test_service;
  content::ServiceProcessHost::Launch(
      test_service.BindNewPipeAndPassReceiver());

  // run_loop runs until either the process launches or fails to launch.
  run_loop.Run();

  RemoveObserver();

  histogram_tester.ExpectUniqueSample(
      "ChildProcess.LaunchFailed.UtilityProcessHash",
      variations::HashName(content::mojom::TestService::Name_), 1);
#if BUILDFLAG(IS_WIN)
  int expected_error_code =
      sandbox::SBOX_ERROR_CANNOT_LAUNCH_UNSANDBOXED_PROCESS;
#else
  int expected_error_code =
      1003;  // content::LaunchResultCode::LAUNCH_RESULT_FAILURE.
#endif
  histogram_tester.ExpectUniqueSample(
      "ChildProcess.LaunchFailed.UtilityProcessErrorCode", expected_error_code,
      1);

#if BUILDFLAG(IS_WIN)
  // Last Error is only recorded on Windows.
  histogram_tester.ExpectUniqueSample("ChildProcess.LaunchFailed.WinLastError",
                                      DWORD{ERROR_FILE_NOT_FOUND}, 1);
#endif
}

// Class to execute a closure after we observer a renderer process launch or
// launch failure.
class RenderProcessCreationObserver
    : content::RenderProcessHostCreationObserver {
 public:
  RenderProcessCreationObserver(base::OnceClosure done_closure)
      : done_closure_(std::move(done_closure)) {}
  ~RenderProcessCreationObserver() override = default;

  void OnRenderProcessHostCreated(
      content::RenderProcessHost* process_host) override {
    if (done_closure_) {
      std::move(done_closure_).Run();
    }
  }

  void OnRenderProcessHostCreationFailed(
      content::RenderProcessHost* host,
      const content::ChildProcessTerminationInfo& info) override {
    ASSERT_EQ(base::TERMINATION_STATUS_LAUNCH_FAILED, info.status);
    if (done_closure_) {
      std::move(done_closure_).Run();
    }
  }

 private:
  base::OnceClosure done_closure_;
};

IN_PROC_BROWSER_TEST_F(ContentStabilityProviderBrowserTest,
                       FailedRendererProcessLaunches) {
  base::RunLoop run_loop;
  ContentStabilityMetricsProvider provider(&prefs_, nullptr);
  base::HistogramTester histogram_tester;
  {
    RenderProcessCreationObserver renderer_observer(run_loop.QuitClosure());

    // Simulate a catastrophic renderer process launch failure by specifying a
    // bad path.
    base::CommandLine::ForCurrentProcess()->AppendSwitchPath(
        switches::kBrowserSubprocessPath,
        base::FilePath(FILE_PATH_LITERAL("non_existent_path")));

    ASSERT_FALSE(content::NavigateToURL(shell(), GURL("about:blank")));

    // run_loop runs until either the process launches or fails to launch.
    run_loop.Run();
  }

  histogram_tester.ExpectUniqueSample(
      "BrowserRenderProcessHost.ChildLaunchFailures",
      1 /* CoarseRendererType::kRenderer */, 1);
#if BUILDFLAG(IS_WIN)
  int expected_error_code = sandbox::SBOX_ERROR_CREATE_PROCESS;
#else
  int expected_error_code =
      1003;  // content::LaunchResultCode::LAUNCH_RESULT_FAILURE.
#endif
  histogram_tester.ExpectUniqueSample(
      "BrowserRenderProcessHost.ChildLaunchFailureCodes", expected_error_code,
      1);

  histogram_tester.ExpectBucketCount(
      "Stability.Counts2", StabilityEventType::kRendererFailedLaunch, 1);
}

}  // namespace metrics