blob: 30bd3e5a77c232c4135ad6217f43d2f24c7fc7ef [file] [log] [blame]
// Copyright 2020 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/crosapi/test_mojo_connection_manager.h"
#include <fcntl.h>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_path_watcher.h"
#include "base/files/scoped_file.h"
#include "base/files/scoped_temp_dir.h"
#include "base/process/launch.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/thread_pool.h"
#include "base/test/bind_test_util.h"
#include "base/test/multiprocess_test.h"
#include "base/test/task_environment.h"
#include "base/test/test_timeouts.h"
#include "chrome/test/base/scoped_testing_local_state.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chromeos/crosapi/mojom/crosapi.mojom.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 "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"
namespace crosapi {
class TestLacrosChromeService : public crosapi::mojom::LacrosChromeService {
public:
TestLacrosChromeService(
mojo::PendingReceiver<mojom::LacrosChromeService> receiver,
base::RunLoop& run_loop)
: receiver_(this, std::move(receiver)), run_loop_(run_loop) {}
~TestLacrosChromeService() override = default;
void Init(crosapi::mojom::LacrosInitParamsPtr params) override {
init_is_called_ = true;
}
void RequestAshChromeServiceReceiver(
RequestAshChromeServiceReceiverCallback callback) override {
EXPECT_TRUE(init_is_called_);
std::move(callback).Run(ash_chrome_service_.BindNewPipeAndPassReceiver());
request_ash_chrome_service_is_called_ = true;
run_loop_.Quit();
}
void NewWindow(NewWindowCallback callback) override {}
bool init_is_called() { return init_is_called_; }
bool request_ash_chrome_service_is_called() {
return request_ash_chrome_service_is_called_;
}
private:
mojo::Receiver<mojom::LacrosChromeService> receiver_;
bool init_is_called_ = false;
bool request_ash_chrome_service_is_called_ = false;
base::RunLoop& run_loop_;
mojo::Remote<crosapi::mojom::AshChromeService> ash_chrome_service_;
};
using TestMojoConnectionManagerTest = testing::Test;
TEST_F(TestMojoConnectionManagerTest, ConnectWithLacrosChrome) {
// Constructing LacrosInitParams requires local state prefs.
ScopedTestingLocalState local_state(TestingBrowserProcess::GetGlobal());
// Create temp dir before task environment, just in case lingering tasks need
// to access it.
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
// Use IO type to support the FileDescriptorWatcher API on POSIX.
base::test::TaskEnvironment task_environment{
base::test::TaskEnvironment::MainThreadType::IO};
// Ash-chrome queues an invitation, drop a socket and wait for connection.
std::string socket_path =
temp_dir.GetPath().MaybeAsASCII() + "/lacros.socket";
// Sets up watcher to Wait for the socket to be created by
// |TestMojoConnectionManager| before attempting to connect. There is no
// garanteen that |OnTestingSocketCreated| has run after the run loop is done,
// so this test should NOT depend on the assumption.
base::FilePathWatcher watcher;
base::RunLoop run_loop1;
watcher.Watch(base::FilePath(socket_path), false,
base::BindRepeating(base::BindLambdaForTesting(
[&run_loop1](const base::FilePath& path, bool error) {
EXPECT_FALSE(error);
run_loop1.Quit();
})));
TestMojoConnectionManager test_mojo_connection_manager{
base::FilePath(socket_path)};
run_loop1.Run();
// Test connects with ash-chrome via the socket.
auto channel = mojo::NamedPlatformChannel::ConnectToServer(socket_path);
ASSERT_TRUE(channel.is_valid());
base::ScopedFD socket_fd = channel.TakePlatformHandle().TakeFD();
uint8_t buf[32];
std::vector<base::ScopedFD> descriptors;
ssize_t size;
base::RunLoop run_loop2;
base::ThreadPool::PostTaskAndReply(
FROM_HERE, {base::MayBlock()},
base::BindOnce(base::BindLambdaForTesting([&]() {
// Mark the channel as blocking.
int flags = fcntl(socket_fd.get(), F_GETFL);
PCHECK(flags != -1);
fcntl(socket_fd.get(), F_SETFL, flags & ~O_NONBLOCK);
size = mojo::SocketRecvmsg(socket_fd.get(), buf, sizeof(buf),
&descriptors, true /*block*/);
})),
run_loop2.QuitClosure());
run_loop2.Run();
EXPECT_EQ(1, size);
EXPECT_EQ(0u, buf[0]);
ASSERT_EQ(1u, descriptors.size());
// Test launches lacros-chrome as child process.
base::LaunchOptions options;
options.fds_to_remap.emplace_back(descriptors[0].get(), descriptors[0].get());
base::CommandLine lacros_cmd(base::GetMultiProcessTestChildBaseCommandLine());
lacros_cmd.AppendSwitchASCII(mojo::PlatformChannel::kHandleSwitch,
base::NumberToString(descriptors[0].release()));
base::Process lacros_process =
base::SpawnMultiProcessTestChild("LacrosMain", lacros_cmd, options);
// lacros-chrome accepts the invitation to establish mojo connection with
// ash-chrome.
int rv = -1;
ASSERT_TRUE(base::WaitForMultiprocessTestChildExit(
lacros_process, TestTimeouts::action_timeout(), &rv));
lacros_process.Close();
EXPECT_EQ(0, rv);
}
// Another process that emulates the behavior of lacros-chrome.
MULTIPROCESS_TEST_MAIN(LacrosMain) {
base::test::SingleThreadTaskEnvironment task_environment;
mojo::IncomingInvitation invitation = mojo::IncomingInvitation::Accept(
mojo::PlatformChannel::RecoverPassedEndpointFromCommandLine(
*base::CommandLine::ForCurrentProcess()));
base::RunLoop run_loop;
TestLacrosChromeService test_lacros_chrome_service(
mojo::PendingReceiver<crosapi::mojom::LacrosChromeService>(
invitation.ExtractMessagePipe(0)),
run_loop);
run_loop.Run();
DCHECK(test_lacros_chrome_service.init_is_called());
DCHECK(test_lacros_chrome_service.request_ash_chrome_service_is_called());
return 0;
}
} // namespace crosapi