#include <gtest/gtest.h>
#include <stddef.h>
#include <memory>
#include <string>
#include "base/at_exit.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/process/kill.h"
#include "base/process/process.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/threading/thread.h"
#include "chromeos/process_proxy/process_proxy_registry.h"
namespace chromeos {
namespace {
const char kTestLineToSend[] = "abcdefgh\n";
const char kTestLineExpected[] = "abcdefgh\r\n";
const char kCatCommand[] = "cat";
const char kFakeUserHash[] = "0123456789abcdef";
const char kStdoutType[] = "stdout";
const int kTestLineNum = 100;
class TestRunner {
public:
TestRunner() = default;
virtual ~TestRunner() = default;
virtual void SetupExpectations(const std::string& id,
const base::Process* process) = 0;
virtual void OnSomeRead(const std::string& id,
const std::string& type,
const std::string& output) = 0;
virtual void StartRegistryTest(ProcessProxyRegistry* registry) = 0;
void set_done_read_closure(base::OnceClosure done_closure) {
done_read_closure_ = std::move(done_closure);
}
protected:
std::string id_;
raw_ptr<const base::Process, AcrossTasksDanglingUntriaged> process_;
base::OnceClosure done_read_closure_;
};
class RegistryTestRunner : public TestRunner {
public:
~RegistryTestRunner() override = default;
void SetupExpectations(const std::string& id,
const base::Process* process) override {
id_ = id;
process_ = process;
left_to_check_index_[0] = 0;
left_to_check_index_[1] = 0;
lines_left_ = 2 * kTestLineNum - 2;
expected_line_ = kTestLineExpected;
}
void OnSomeRead(const std::string& id,
const std::string& type,
const std::string& output) override {
EXPECT_EQ(type, kStdoutType);
EXPECT_EQ(id_, id);
bool valid = true;
for (size_t i = 0; i < output.length(); i++) {
valid = (ProcessReceivedCharacter(output[i], 0) ||
ProcessReceivedCharacter(output[i], 1));
EXPECT_TRUE(valid) << "Received: " << output;
}
if (!valid || TestSucceeded()) {
ASSERT_FALSE(done_read_closure_.is_null());
std::move(done_read_closure_).Run();
}
}
void StartRegistryTest(ProcessProxyRegistry* registry) override {
for (int i = 0; i < kTestLineNum; i++) {
registry->SendInput(id_, kTestLineToSend, base::BindOnce([](bool result) {
EXPECT_TRUE(result);
}));
}
}
private:
bool ProcessReceivedCharacter(char received, size_t stream) {
if (stream >= std::size(left_to_check_index_))
return false;
bool success =
UNSAFE_TODO(left_to_check_index_[stream]) < expected_line_.length() &&
expected_line_[UNSAFE_TODO(left_to_check_index_[stream])] == received;
if (success)
UNSAFE_TODO(left_to_check_index_[stream])++;
if (UNSAFE_TODO(left_to_check_index_[stream]) == expected_line_.length() &&
lines_left_ > 0) {
UNSAFE_TODO(left_to_check_index_[stream]) = 0;
lines_left_--;
}
return success;
}
bool TestSucceeded() {
return left_to_check_index_[0] == expected_line_.length() &&
left_to_check_index_[1] == expected_line_.length() &&
lines_left_ == 0;
}
size_t left_to_check_index_[2];
size_t lines_left_;
std::string expected_line_;
};
class RegistryNotifiedOnProcessExitTestRunner : public TestRunner {
public:
~RegistryNotifiedOnProcessExitTestRunner() override = default;
void SetupExpectations(const std::string& id,
const base::Process* process) override {
output_received_ = false;
id_ = id;
process_ = process;
}
void OnSomeRead(const std::string& id,
const std::string& type,
const std::string& output) override {
EXPECT_EQ(id_, id);
if (!output_received_) {
base::ScopedAllowBaseSyncPrimitivesForTesting allow_sync_primitives;
output_received_ = true;
EXPECT_EQ(type, "stdout");
EXPECT_EQ(output, "p");
process_->Terminate(0, true);
return;
}
EXPECT_EQ("exit", type);
ASSERT_FALSE(done_read_closure_.is_null());
std::move(done_read_closure_).Run();
}
void StartRegistryTest(ProcessProxyRegistry* registry) override {
registry->SendInput(
id_, "p", base::BindOnce([](bool result) { EXPECT_TRUE(result); }));
}
private:
bool output_received_;
};
}
class ProcessProxyTest : public testing::Test {
public:
ProcessProxyTest() = default;
~ProcessProxyTest() override = default;
protected:
void InitRegistryTest(base::OnceClosure done_closure) {
registry_ = ProcessProxyRegistry::Get();
base::CommandLine cmdline{{kCatCommand}};
bool success = registry_->OpenProcess(
cmdline, kFakeUserHash,
base::BindRepeating(&ProcessProxyTest::HandleRead,
base::Unretained(this)),
&id_);
process_ = registry_->GetProcessForTesting(id_);
EXPECT_TRUE(success);
test_runner_->set_done_read_closure(std::move(done_closure));
test_runner_->SetupExpectations(id_, process_);
test_runner_->StartRegistryTest(registry_);
}
void HandleRead(const std::string& id,
const std::string& output_type,
const std::string& output) {
test_runner_->OnSomeRead(id, output_type, output);
registry_->AckOutput(id);
}
void EndRegistryTest(base::OnceClosure done_closure) {
base::ScopedAllowBaseSyncPrimitivesForTesting allow_sync_primitives;
registry_->CloseProcess(id_);
int unused_exit_code = 0;
base::TerminationStatus status =
base::GetTerminationStatus(process_->Handle(), &unused_exit_code);
EXPECT_NE(base::TERMINATION_STATUS_STILL_RUNNING, status);
if (status == base::TERMINATION_STATUS_STILL_RUNNING) {
process_->Terminate(0, true);
}
registry_->ShutDown();
std::move(done_closure).Run();
}
void RunTest() {
base::test::TestFuture<void> init_registry_waiter;
ProcessProxyRegistry::GetTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&ProcessProxyTest::InitRegistryTest,
base::Unretained(this),
init_registry_waiter.GetSequenceBoundCallback()));
ASSERT_TRUE(init_registry_waiter.Wait());
base::test::TestFuture<void> end_registry_waiter;
ProcessProxyRegistry::GetTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&ProcessProxyTest::EndRegistryTest,
base::Unretained(this),
end_registry_waiter.GetSequenceBoundCallback()));
ASSERT_TRUE(end_registry_waiter.Wait());
}
std::unique_ptr<TestRunner> test_runner_;
private:
base::ShadowingAtExitManager shadowing_at_exit_manager_;
raw_ptr<ProcessProxyRegistry> registry_;
std::string id_;
raw_ptr<const base::Process, AcrossTasksDanglingUntriaged> process_ = nullptr;
base::test::TaskEnvironment task_environment_;
};
TEST_F(ProcessProxyTest, RegistryTest) {
test_runner_ = std::make_unique<RegistryTestRunner>();
RunTest();
}
TEST_F(ProcessProxyTest, DISABLED_RegistryNotifiedOnProcessExit) {
test_runner_ = std::make_unique<RegistryNotifiedOnProcessExitTestRunner>();
RunTest();
}
}