chromium/chrome/browser/ash/crosapi/test/ash_crosapi_tests_env.cc

// Copyright 2022 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/browser/ash/crosapi/test/ash_crosapi_tests_env.h"

#include <fcntl.h>

#include "ash/constants/ash_switches.h"
#include "base/base_paths.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/files/file_path_watcher.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/path_service.h"
#include "base/process/launch.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/bind.h"
#include "base/test/test_future.h"
#include "chrome/common/chrome_switches.h"
#include "chromeos/crosapi/mojom/crosapi.mojom.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/platform/named_platform_channel.h"
#include "mojo/public/cpp/platform/platform_channel.h"
#include "mojo/public/cpp/platform/socket_utils_posix.h"
#include "mojo/public/cpp/system/invitation.h"
#include "ui/gl/gl_switches.h"

namespace crosapi {

AshCrosapiTestCommandLineModifierDelegate::
    AshCrosapiTestCommandLineModifierDelegate() = default;

AshCrosapiTestCommandLineModifierDelegate::
    ~AshCrosapiTestCommandLineModifierDelegate() = default;

AshCrosapiTestEnv::AshCrosapiTestEnv() : AshCrosapiTestEnv(nullptr) {}

AshCrosapiTestEnv::AshCrosapiTestEnv(
    std::unique_ptr<AshCrosapiTestCommandLineModifierDelegate> delegate)
    : delegate_(std::move(delegate)) {
  Initialize();
}

AshCrosapiTestEnv::~AshCrosapiTestEnv() {
  if (process_.IsValid()) {
    process_.Terminate(/*exit_code=*/0, /*wait=*/true);
  }
}

void AshCrosapiTestEnv::Initialize() {
  // If the user has specified a path for the ash-chrome binary, use the path.
  // Note that both absolute and relative paths are accepted, and the last path
  // component should be 'chrome', 'test_ash_chrome' or equivalent.
  // The chrome passed should be built, and if you use some test-specific
  // features such as TestController crosapi, you need to pass
  // 'test_ash_chrome',
  base::FilePath ash_chrome_path =
      base::CommandLine::ForCurrentProcess()->GetSwitchValuePath(
          "ash-chrome-path");

  // By default, 'test_ash_chrome' is built and used under the directory that
  // contains application assets.
  if (ash_chrome_path.empty()) {
    CHECK(base::PathService::Get(base::DIR_ASSETS, &ash_chrome_path));
    ash_chrome_path = ash_chrome_path.Append("test_ash_chrome");
  }

  CHECK(base::PathExists(ash_chrome_path));
  // Sets chrome binary file to run ash process.
  base::CommandLine command_line(ash_chrome_path);

  // Sets a user data dir path.
  CHECK(user_data_dir_.CreateUniqueTempDir());
  command_line.AppendSwitchPath(switches::kUserDataDir,
                                user_data_dir_.GetPath());
  command_line.AppendSwitch(ash::switches::kUseMyFilesInUserDataDirForTesting);

#if defined(MEMORY_SANITIZER)
  // MSAN is incompatible with GL acceleration.
  command_line.AppendSwitch(switches::kOverrideUseSoftwareGLForTests);
#endif

  if (delegate_) {
    delegate_->AddExtraCommandLine(&command_line);
  }

  // Sets a socket path.
  base::ScopedTempDir temp_dir;
  CHECK(temp_dir.CreateUniqueTempDir());
  const std::string socket_path =
      temp_dir.GetPath().AppendASCII("lacros.socket").MaybeAsASCII();
  command_line.AppendSwitchASCII(ash::switches::kLacrosMojoSocketForTesting,
                                 socket_path);

  // Waits for socket connection to establish.
  // TODO(crbug.com/40240169): Separate logs generated during setup from those
  // generated during test.
  base::FilePathWatcher watcher;
  // TODO(crbug.com/40241195): Terminate runloop if the passed chrome couldn't
  // be exec.
  base::test::TestFuture<const base::FilePath&, bool> future;
  CHECK(watcher.Watch(base::FilePath(socket_path),
                      base::FilePathWatcher::Type::kNonRecursive,
                      future.GetRepeatingCallback()));
  process_ = base::LaunchProcess(command_line, {});
  CHECK(process_.IsValid());
  bool error = future.Get<1>();
  CHECK(!error);

  // Sets up to use Mojo interface.
  auto channel = mojo::NamedPlatformChannel::ConnectToServer(socket_path);
  base::ScopedFD socket_fd = channel.TakePlatformHandle().TakeFD();
  int flags = fcntl(socket_fd.get(), F_GETFL);
  fcntl(socket_fd.get(), F_SETFL, flags & ~O_NONBLOCK);
  std::vector<base::ScopedFD> descriptors;
  uint8_t buf[32];
  auto size = mojo::SocketRecvmsg(socket_fd.get(), buf, sizeof(buf),
                                  &descriptors, true /*block*/);
  CHECK_EQ(size, 1);
  CHECK_EQ(buf[0], 1u);
  CHECK_EQ(descriptors.size(), 2u);

  auto endpoint = mojo::PlatformChannelEndpoint(
      mojo::PlatformHandle(std::move(descriptors[1])));

  mojo::IncomingInvitation invitation =
      mojo::IncomingInvitation::Accept(std::move(endpoint));

  // Binds crosapi.
  crosapi_remote_ =
      mojo::Remote<mojom::Crosapi>(mojo::PendingRemote<mojom::Crosapi>(
          invitation.ExtractMessagePipe(0), 0u));
}

bool AshCrosapiTestEnv::IsValid() {
  return process_.IsValid() && crosapi_remote_.is_bound();
}

}  // namespace crosapi