blob: 460ab1ba74fc163dd8ba0b7de47395eca261359b [file] [log] [blame]
// Copyright 2024 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/windows_services/service_program/test_support/scoped_log_grabber.h"
#include <windows.h>
#include <ostream>
#include "base/command_line.h"
#include "base/containers/heap_array.h"
#include "base/containers/span.h"
#include "base/files/file.h"
#include "base/logging.h"
#include "base/test/mock_callback.h"
#include "base/test/multiprocess_test.h"
#include "chrome/windows_services/service_program/logging_support.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
using ::testing::_;
using ::testing::DoAll;
using ::testing::HasSubstr;
using ::testing::Return;
using ::testing::SaveArg;
namespace {
MULTIPROCESS_TEST_MAIN(WriteToLog) {
InitializeLogging(*base::CommandLine::ForCurrentProcess());
// Emitting a log message should go to the parent.
LOG(ERROR) << __func__;
return ERROR_SUCCESS;
}
// Emits a multi-line log message.
MULTIPROCESS_TEST_MAIN(WriteMultiLineToLog) {
InitializeLogging(*base::CommandLine::ForCurrentProcess());
LOG(ERROR) << __func__ << std::endl << __func__;
return ERROR_SUCCESS;
}
} // namespace
// A test fixture that redirects the test process's stderr to a pipe so that
// tests can validate messages written to stderr by code under test. There is
// also a facility for launching a child proc, whereby the child proc's log
// output is sent to the test's process's stderr.
class ScopedLogGrabberTest : public testing::Test {
protected:
ScopedLogGrabberTest() : original_stderr_(::GetStdHandle(STD_ERROR_HANDLE)) {}
~ScopedLogGrabberTest() override {
// Restore stderr for the test process.
::SetStdHandle(STD_ERROR_HANDLE, original_stderr_);
}
void SetUp() override {
ASSERT_NE(original_stderr_, INVALID_HANDLE_VALUE);
// Create a pipe to intercept this process's stderr.
HANDLE read_handle = nullptr;
HANDLE write_handle = nullptr;
ASSERT_TRUE(::CreatePipe(&read_handle, &write_handle,
/*lpPipeAttributes=*/nullptr, /*nSize=*/0));
read_pipe_ = base::File(std::exchange(read_handle, nullptr));
write_pipe_ = base::File(std::exchange(write_handle, nullptr));
ASSERT_TRUE(
::SetStdHandle(STD_ERROR_HANDLE, write_pipe_.GetPlatformFile()));
}
// Returns a handle to a child process that will run `function_name` (which
// must call `InitializeLogging()`) and grab its log output via `log_grabber`.
base::Process SpawnChild(std::string_view function_name,
const ScopedLogGrabber& log_grabber) {
base::CommandLine command_line =
base::GetMultiProcessTestChildBaseCommandLine();
log_grabber.AddLoggingSwitches(command_line);
base::LaunchOptions launch_options;
launch_options.start_hidden = true;
return base::SpawnMultiProcessTestChild(std::string(function_name),
command_line, launch_options);
}
// Returns the read end of the pipe connected to the test process's stderr.
base::File& read_pipe() { return read_pipe_; }
private:
// The process's original stderr handle for restoration at destruction.
const HANDLE original_stderr_;
// Handles to an anonymous pipe used to intercept this test process's stderr.
base::File read_pipe_;
base::File write_pipe_;
};
// Tests that log message from a child process are emitting to this process's
// stderr.
TEST_F(ScopedLogGrabberTest, TestLogging) {
// Prepare to capture logs from the child process.
ScopedLogGrabber log_grabber;
// Launch the child process with the logging options.
constexpr std::string_view kChildFunction = "WriteToLog";
base::Process child_process = SpawnChild(kChildFunction, log_grabber);
// Read logs from the child until it logs its name.
auto buffer = base::HeapArray<uint8_t>::Uninit(4096);
while (true) {
std::optional<size_t> bytes_read =
read_pipe().ReadAtCurrentPos(buffer.as_span());
ASSERT_TRUE(bytes_read.has_value());
const auto log_message = base::as_string_view(buffer.first(*bytes_read));
if (log_message.find(kChildFunction) != std::string_view::npos) {
break;
}
}
// Wait for the child to terminate.
int exit_code = ERROR_SUCCESS;
ASSERT_TRUE(child_process.WaitForExit(&exit_code));
EXPECT_EQ(exit_code, ERROR_SUCCESS);
}
// Tests that log message from a child process are passed to a callback.
TEST_F(ScopedLogGrabberTest, TestWithDelegate) {
// Prepare to capture logs from the child process with a callback. Ignore
base::MockCallback<ScopedLogGrabber::LogMessageCallback> mock_callback;
ScopedLogGrabber log_grabber;
log_grabber.SetLogMessageCallback(mock_callback.Get());
// Ignore all messages except one expected multi-line message.
base::ProcessId pid = base::kNullProcessId;
EXPECT_CALL(mock_callback, Run(_, _)).WillRepeatedly(Return(true));
EXPECT_CALL(mock_callback,
Run(_, HasSubstr("WriteMultiLineToLog\nWriteMultiLineToLog\n")))
.WillOnce(DoAll(SaveArg<0>(&pid), Return(true)));
// Launch the child process with the logging options.
constexpr std::string_view kChildFunction = "WriteMultiLineToLog";
base::Process child_process = SpawnChild(kChildFunction, log_grabber);
// Wait for the child to terminate.
int exit_code = ERROR_SUCCESS;
ASSERT_TRUE(child_process.WaitForExit(&exit_code));
EXPECT_EQ(exit_code, ERROR_SUCCESS);
// The callback should have been run with the child process's pid.
EXPECT_EQ(pid, child_process.Pid());
}
TEST(ScopedLogGrabberParseTest, ProcessId) {
// Inputs that don't match "^\[[0-9]+:.*"
EXPECT_EQ(ScopedLogGrabber::ParseProcessId(""), base::kNullProcessId);
EXPECT_EQ(ScopedLogGrabber::ParseProcessId("["), base::kNullProcessId);
EXPECT_EQ(ScopedLogGrabber::ParseProcessId("[1"), base::kNullProcessId);
EXPECT_EQ(ScopedLogGrabber::ParseProcessId("1:"), base::kNullProcessId);
EXPECT_EQ(ScopedLogGrabber::ParseProcessId("[a"), base::kNullProcessId);
EXPECT_EQ(ScopedLogGrabber::ParseProcessId("[:"), base::kNullProcessId);
EXPECT_EQ(ScopedLogGrabber::ParseProcessId("hi, mom"), base::kNullProcessId);
// Inputs that do.
EXPECT_EQ(ScopedLogGrabber::ParseProcessId("[1:"), 1U);
EXPECT_EQ(ScopedLogGrabber::ParseProcessId("[535:"), 535U);
EXPECT_EQ(ScopedLogGrabber::ParseProcessId(
"[11916:15856:ERROR:service_unittest.cc(91)] blah"),
11916U);
}