chromium/chrome/test/base/chromeos/ash_browser_test_starter.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 "chrome/test/base/chromeos/ash_browser_test_starter.h"

#include <memory>

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "base/command_line.h"
#include "base/containers/to_vector.h"
#include "base/environment.h"
#include "base/files/file_util.h"
#include "base/rand_util.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/test_future.h"
#include "base/test/test_switches.h"
#include "chrome/browser/ash/crosapi/browser_manager.h"
#include "chrome/browser/ash/crosapi/browser_manager_observer.h"
#include "chrome/browser/ash/crosapi/browser_util.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chromeos/ash/components/standalone_browser/feature_refs.h"
#include "chromeos/ash/components/standalone_browser/test_util.h"
#include "components/exo/wm_helper.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "components/user_manager/fake_device_ownership_waiter.h"
#include "content/public/common/content_switches.h"
#include "google_apis/gaia/gaia_switches.h"
#include "net/test/embedded_test_server/http_response.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/window.h"
#include "ui/views/views_switches.h"

namespace test {

namespace {

using ::net::test_server::HungResponse;

std::unique_ptr<net::test_server::HttpResponse> HandleGaiaURL(
    const GURL& base_url,
    const net::test_server::HttpRequest& request) {
  // Simulate failure for Gaia url request.
  return std::make_unique<HungResponse>();
}

class NewLacrosWindowWatcher : public exo::WMHelper::ExoWindowObserver {
 public:
  NewLacrosWindowWatcher() {
    exo::WMHelper::GetInstance()->AddExoWindowObserver(this);
  }

  ~NewLacrosWindowWatcher() override {
    exo::WMHelper::GetInstance()->RemoveExoWindowObserver(this);
  }

  NewLacrosWindowWatcher(const NewLacrosWindowWatcher&) = delete;
  NewLacrosWindowWatcher& operator=(const NewLacrosWindowWatcher&) = delete;

  // `exo::WMHelper::ExoWindowObserver`
  void OnExoWindowCreated(aura::Window* window) override {
    if (crosapi::browser_util::IsLacrosWindow(window)) {
      window_future_.SetValue(window);
    }
  }

  // Waits until a new Lacros window is created.
  // The watch period starts when this object was created, not when this method
  // is called. In other words, this method may return immediately if a Lacros
  // window was already created before.
  aura::Window* Await() { return window_future_.Take(); }

 private:
  base::test::TestFuture<aura::Window*> window_future_;
};

}  // namespace

AshBrowserTestStarter::~AshBrowserTestStarter() {
  CHECK(!initial_lacros_window_);  // Exo has already shut down.

  // Clean up the directories that tests were passed. This is
  // to save bot collect results time and faster CQ runtime.
  if (!ash_user_data_dir_for_cleanup_.empty() &&
      !::testing::Test::HasFailure() &&
      base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kTestLauncherBotMode)) {
    // Intentionally not check return value.
    base::DeletePathRecursively(ash_user_data_dir_for_cleanup_);
  }
}

AshBrowserTestStarter::AshBrowserTestStarter()
    : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
  https_server()->RegisterRequestHandler(
      base::BindRepeating(&HandleGaiaURL, base_url()));

  bool success = https_server()->InitializeAndListen();
  CHECK(success);
  https_server()->StartAcceptingConnections();
}

bool AshBrowserTestStarter::HasLacrosArgument() const {
  return base::CommandLine::ForCurrentProcess()->HasSwitch(
      ash::switches::kLacrosChromePath);
}

net::EmbeddedTestServer* AshBrowserTestStarter::https_server() {
  return &https_server_;
}

GURL AshBrowserTestStarter::base_url() {
  return https_server()->base_url();
}

bool AshBrowserTestStarter::PrepareEnvironmentForLacros() {
  DCHECK(HasLacrosArgument());
  std::unique_ptr<base::Environment> env(base::Environment::Create());
  if (!scoped_temp_dir_xdg_.CreateUniqueTempDir()) {
    return false;
  }
  env->SetVar("XDG_RUNTIME_DIR", scoped_temp_dir_xdg_.GetPath().AsUTF8Unsafe());

  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
  // Put lacros logs in CAS outputs on bots.
  if (command_line->HasSwitch(switches::kTestLauncherSummaryOutput)) {
    std::string test_name = base::JoinString(
        {::testing::UnitTest::GetInstance()
             ->current_test_info()
             ->test_suite_name(),
         ::testing::UnitTest::GetInstance()->current_test_info()->name()},
        ".");
    base::FilePath output_file_path =
        command_line->GetSwitchValuePath(switches::kTestLauncherSummaryOutput);
    base::FilePath test_output_folder =
        output_file_path.DirName().Append(test_name);
    // Need to create a unique user-data-dir across all test runners.
    // Using test name as folder name is usually enough. But there are edge
    // cases it needs to handle:
    //  * Test retry. On bots, usually we retry 1 time for test failures.
    //  * Flakiness endorser. It will run new tests 20 times to ensure it's not
    //    flaky.
    //  * Developer modified builder config. Developer may modify infra to run
    //    a test multiple times(e.g. 100) on bot to check flakiness.
    // To handle those edge cases, we would create the user-data-dir using some
    // random numbers to ensure it's unique.
    int retry_count = 1;
    bool success = false;
    while (!success && retry_count < 100) {
      if (base::PathExists(test_output_folder)) {
        test_output_folder = output_file_path.DirName().Append(
            (test_name + ".attempt_" +
             base::NumberToString(base::RandInt(0, 10000))));
      } else {
        success = base::CreateDirectory(test_output_folder);
      }
      ++retry_count;
    }

    // If we didn't create a unique directory, we would let
    // the browser test framework to create the tmp folder as usual.
    if (success) {
      command_line->AppendSwitchPath(switches::kUserDataDir,
                                     test_output_folder);
      ash_user_data_dir_for_cleanup_ = test_output_folder;
    }
  } else {
    LOG(WARNING)
        << "By default, lacros logs are in some random folder. If you need "
        << "lacros log, please run with --test-launcher-summary-output. e.g. "
        << "Run with --test-launcher-summary-output=/tmp/default/output.json. "
        << "For each lacros in a test, the log will be in "
        << "/tmp/default/test_suite.test_name folder.";
  }

  scoped_feature_list_.InitWithFeatures(
      ash::standalone_browser::GetFeatureRefs(), {});
  command_line->AppendSwitch(ash::switches::kEnableLacrosForTesting);
  command_line->AppendSwitch(ash::switches::kAshEnableWaylandServer);
  command_line->AppendSwitch(
      views::switches::kDisableInputEventActivationProtectionForTesting);
  command_line->AppendSwitch(ash::switches::kDisableLacrosKeepAliveForTesting);
  command_line->AppendSwitch(ash::switches::kDisableLoginLacrosOpening);
  command_line->AppendSwitch(switches::kNoStartupWindow);

  std::vector<std::string> lacros_args;
  lacros_args.emplace_back(base::StringPrintf("--%s", switches::kNoFirstRun));
  lacros_args.emplace_back(
      base::StringPrintf("--%s", switches::kIgnoreCertificateErrors));
  // For some StandaloneBrowserTestController features.
  lacros_args.emplace_back(
      base::StringPrintf("--%s", switches::kDomAutomationController));
  // Override Gaia url in Lacros so that the gaia requests will NOT be handled
  // with the real internet connection, but with the embedded test server. The
  // embedded test server will simulate failure of the Gaia url requests which
  // is expected in testing environment for Gaia authentication flow. This is a
  // workaround for fixing crbug/1371655.
  lacros_args.emplace_back(base::StringPrintf("--%s=%s", switches::kGaiaUrl,
                                              base_url().spec().c_str()));
  // Disable gpu process in Lacros since hardware accelerated rendering is
  // not possible yet in Ash X11 backend. See details in crbug/1478369.
  lacros_args.emplace_back("--disable-gpu");
  // Disable gpu sandbox in Lacros since it fails in Linux emulator environment.
  // See details in crbug/1483530.
  lacros_args.emplace_back("--disable-gpu-sandbox");
  ash::standalone_browser::AddLacrosArguments(lacros_args, command_line);

  return true;
}

void AshBrowserTestStarter::EnableFeaturesInLacros(
    const std::vector<base::test::FeatureRef>& features) {
  CHECK(HasLacrosArgument());

  std::vector<std::string> feature_strings = base::ToVector(  // IN-TEST
      features, [](base::test::FeatureRef feature) -> std::string {
        return feature->name;
      });

  std::string features_arg =
      "--enable-features=" + base::JoinString(feature_strings, ",");
  std::vector<std::string> lacros_args = {features_arg};
  ash::standalone_browser::AddLacrosArguments(
      lacros_args, base::CommandLine::ForCurrentProcess());
}

void AshBrowserTestStarter::StartLacros(InProcessBrowserTest* test_class_obj) {
  DCHECK(HasLacrosArgument());

  SetUpBrowserManager();

  {
    NewLacrosWindowWatcher watcher;
    crosapi::BrowserManager::Get()->NewWindow(
        /*incongnito=*/false, /*should_trigger_session_restore=*/false);
    initial_lacros_window_ = watcher.Await();
  }
  initial_lacros_window_->AddObserver(this);  // For OnWindowDestroying.

  CHECK(crosapi::BrowserManager::Get()->IsRunning());
}

void AshBrowserTestStarter::SetUpBrowserManager() {
  DCHECK(HasLacrosArgument());

  crosapi::BrowserManager::Get()->set_device_ownership_waiter_for_testing(
      std::make_unique<user_manager::FakeDeviceOwnershipWaiter>());
}

void AshBrowserTestStarter::OnWindowDestroying(aura::Window* window) {
  DCHECK_EQ(window, initial_lacros_window_);
  initial_lacros_window_ = nullptr;
}

}  // namespace test