blob: 3e7e2448b8b45e1f4bd6dddcb0122a07cff48fb3 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/tracing/traces_internals/traces_internals_handler.h"
#include "base/base_paths.h"
#include "base/path_service.h"
#include "base/strings/string_view_util.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "base/test/test_proto_loader.h"
#include "base/token.h"
#include "content/browser/tracing/test_tracing_session.h"
#include "content/browser/tracing/trace_upload_list.h"
#include "content/browser/tracing/traces_internals/traces_internals.mojom.h"
#include "content/public/browser/background_tracing_manager.h"
#include "content/public/browser/tracing_delegate.h"
#include "content/public/test/browser_task_environment.h"
#include "mojo/public/cpp/base/big_buffer.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
using testing::_;
perfetto::protos::gen::TraceConfig ParseTraceConfigFromText(
const std::string& proto_text) {
base::TestProtoLoader config_loader(
base::PathService::CheckedGet(base::DIR_GEN_TEST_DATA_ROOT)
.Append(FILE_PATH_LITERAL("third_party/perfetto/protos/perfetto/"
"config/config.descriptor")),
"perfetto.protos.TraceConfig");
std::string serialized_message;
config_loader.ParseFromText(proto_text, serialized_message);
perfetto::protos::gen::TraceConfig destination;
destination.ParseFromString(serialized_message);
return destination;
}
class FakeTraceUploadList : public TraceUploadList {
public:
// Functions we want to intercept.
MOCK_METHOD(void, OpenDatabaseIfExists, (), (override));
MOCK_METHOD(void, GetAllTraceReports, (GetReportsCallback), (override));
MOCK_METHOD(void,
DeleteSingleTrace,
(const base::Token&, FinishedProcessingCallback),
(override));
MOCK_METHOD(void, DeleteAllTraces, (FinishedProcessingCallback), (override));
MOCK_METHOD(void,
UserUploadSingleTrace,
(const base::Token&, FinishedProcessingCallback),
(override));
MOCK_METHOD(void,
DownloadTrace,
(const base::Token&, GetProtoCallback),
(override));
};
class MockTracePage : public traces_internals::mojom::Page {
public:
MockTracePage() = default;
~MockTracePage() override = default;
mojo::PendingRemote<traces_internals::mojom::Page> BindAndGetRemote() {
CHECK(!receiver_.is_bound());
return receiver_.BindNewPipeAndPassRemote();
}
MOCK_METHOD(void,
OnTraceComplete,
(std::optional<mojo_base::BigBuffer>,
const std::optional<base::Token>&),
(override));
mojo::Receiver<traces_internals::mojom::Page> receiver_{this};
};
class MockTracingDelegate : public TracingDelegate {
public:
MOCK_METHOD(bool,
IsRecordingAllowed,
(bool, base::TimeTicks),
(const, override));
MOCK_METHOD(bool, ShouldSaveUnuploadedTrace, (), (const, override));
#if BUILDFLAG(IS_WIN)
MOCK_METHOD(void,
GetSystemTracingState,
(base::OnceCallback<void(bool, bool)>),
(override));
MOCK_METHOD(void,
EnableSystemTracing,
(base::OnceCallback<void(bool)>),
(override));
MOCK_METHOD(void,
DisableSystemTracing,
(base::OnceCallback<void(bool)>),
(override));
#endif
};
class TracesInternalsHandlerForTesting : public TracesInternalsHandler {
public:
TracesInternalsHandlerForTesting(
mojo::PendingReceiver<traces_internals::mojom::PageHandler> receiver,
mojo::PendingRemote<traces_internals::mojom::Page> page,
TraceUploadList& trace_upload_list,
BackgroundTracingManagerImpl& background_tracing_manager,
TracingDelegate* tracing_delegate)
: TracesInternalsHandler(std::move(receiver),
std::move(page),
trace_upload_list,
background_tracing_manager,
tracing_delegate) {}
protected:
std::unique_ptr<perfetto::TracingSession> CreateTracingSession() override {
return std::make_unique<TestTracingSession>();
}
};
// A fixture to test TracesInternalsHandler.
class TracesInternalsHandlerTest : public testing::Test {
public:
TracesInternalsHandlerTest() = default;
~TracesInternalsHandlerTest() override = default;
void SetUp() override {
background_tracing_manager_ =
std::make_unique<BackgroundTracingManagerImpl>(&mock_tracing_delegate_);
// Expect the Database to be opened before executing each test.
EXPECT_CALL(fake_trace_upload_list_, OpenDatabaseIfExists());
handler_ = std::make_unique<TracesInternalsHandlerForTesting>(
mojo::PendingReceiver<traces_internals::mojom::PageHandler>(),
mock_page_.BindAndGetRemote(), fake_trace_upload_list_,
*background_tracing_manager_, &mock_tracing_delegate_);
}
protected:
BrowserTaskEnvironment task_environment_;
std::unique_ptr<BackgroundTracingManagerImpl> background_tracing_manager_;
testing::StrictMock<FakeTraceUploadList> fake_trace_upload_list_;
testing::NiceMock<MockTracePage> mock_page_;
testing::NiceMock<MockTracingDelegate> mock_tracing_delegate_;
std::unique_ptr<TracesInternalsHandler> handler_;
};
TEST_F(TracesInternalsHandlerTest, TracingStartStop) {
auto trace_config =
ParseTraceConfigFromText(R"pb(
data_sources: { config: { name: "org.chromium.trace_metadata2" } }
)pb")
.SerializeAsString();
base::MockCallback<TracesInternalsHandler::StartTraceSessionCallback>
start_callback;
handler_->StartTraceSession(
mojo_base::BigBuffer(
base::as_bytes(base::span<const char>(trace_config))),
/*enable_privacy_filters=*/false, start_callback.Get());
{
base::RunLoop run_loop_start;
EXPECT_CALL(start_callback, Run(true))
.WillOnce(base::test::RunOnceClosure(run_loop_start.QuitClosure()));
run_loop_start.Run();
}
base::MockCallback<TracesInternalsHandler::StopTraceSessionCallback>
stop_callback;
handler_->StopTraceSession(stop_callback.Get());
{
base::RunLoop run_loop_stop;
EXPECT_CALL(stop_callback, Run(true)).Times(1);
EXPECT_CALL(mock_page_,
OnTraceComplete(
testing::Truly(
[](const std::optional<mojo_base::BigBuffer>& trace) {
return base::as_string_view(base::as_chars(
base::span(*trace))) == "this is a trace";
}),
_))
.WillOnce(base::test::RunOnceClosure(run_loop_stop.QuitClosure()));
run_loop_stop.Run();
}
}
TEST_F(TracesInternalsHandlerTest, TracingTimer) {
auto trace_config = ParseTraceConfigFromText(R"pb(
data_sources: { config: { name: "Stop" } }
)pb")
.SerializeAsString();
handler_->StartTraceSession(
mojo_base::BigBuffer(
base::as_bytes(base::span<const char>(trace_config))),
/*enable_privacy_filters=*/false, base::DoNothing());
base::RunLoop run_loop;
EXPECT_CALL(
mock_page_,
OnTraceComplete(
testing::Truly([](const std::optional<mojo_base::BigBuffer>& trace) {
return base::as_string_view(base::as_chars(base::span(*trace))) ==
"this is a trace";
}),
_))
.WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure()));
run_loop.Run();
}
TEST_F(TracesInternalsHandlerTest, TracingStartFail) {
auto trace_config = ParseTraceConfigFromText(R"pb(
data_sources: { config: { name: "Invalid" } }
)pb")
.SerializeAsString();
base::MockCallback<TracesInternalsHandler::StartTraceSessionCallback>
start_callback;
handler_->StartTraceSession(
mojo_base::BigBuffer(
base::as_bytes(base::span<const char>(trace_config))),
/*enable_privacy_filters=*/false, start_callback.Get());
{
base::RunLoop run_loop_stop;
EXPECT_CALL(start_callback, Run(false))
.WillOnce(base::test::RunOnceClosure(run_loop_stop.QuitClosure()));
run_loop_stop.Run();
}
}
TEST_F(TracesInternalsHandlerTest, TracingClone) {
auto trace_config =
ParseTraceConfigFromText(R"pb(
data_sources: { config: { name: "org.chromium.trace_metadata2" } }
)pb")
.SerializeAsString();
base::MockCallback<TracesInternalsHandler::StartTraceSessionCallback>
start_callback;
handler_->StartTraceSession(
mojo_base::BigBuffer(
base::as_bytes(base::span<const char>(trace_config))),
/*enable_privacy_filters=*/false, start_callback.Get());
{
base::RunLoop run_loop_start;
EXPECT_CALL(start_callback, Run(true))
.WillOnce(base::test::RunOnceClosure(run_loop_start.QuitClosure()));
run_loop_start.Run();
}
base::MockCallback<TracesInternalsHandler::CloneTraceSessionCallback>
clone_callback;
handler_->CloneTraceSession(clone_callback.Get());
{
base::RunLoop run_loop_clone;
EXPECT_CALL(clone_callback,
Run(testing::Truly(
[](const std::optional<mojo_base::BigBuffer>& trace) {
return base::as_string_view(base::as_chars(
base::span(*trace))) == "this is a trace";
}),
_))
.WillOnce(base::test::RunOnceClosure(run_loop_clone.QuitClosure()));
run_loop_clone.Run();
}
}
TEST_F(TracesInternalsHandlerTest, TracingBufferUsage) {
auto trace_config =
ParseTraceConfigFromText(R"pb(
data_sources: { config: { name: "org.chromium.trace_metadata2" } }
)pb")
.SerializeAsString();
base::MockCallback<TracesInternalsHandler::StartTraceSessionCallback>
start_callback;
handler_->StartTraceSession(
mojo_base::BigBuffer(
base::as_bytes(base::span<const char>(trace_config))),
/*enable_privacy_filters=*/false, start_callback.Get());
{
base::RunLoop run_loop_start;
EXPECT_CALL(start_callback, Run(true))
.WillOnce(base::test::RunOnceClosure(run_loop_start.QuitClosure()));
run_loop_start.Run();
}
base::MockCallback<TracesInternalsHandler::GetBufferUsageCallback>
buffer_callback;
handler_->GetBufferUsage(buffer_callback.Get());
{
base::RunLoop run_loop_buffer;
EXPECT_CALL(buffer_callback, Run(true, _, _))
.WillOnce(base::test::RunOnceClosure(run_loop_buffer.QuitClosure()));
run_loop_buffer.Run();
}
}
TEST_F(TracesInternalsHandlerTest, GetAllTraceReports) {
base::MockCallback<TracesInternalsHandler::GetAllTraceReportsCallback>
callback;
EXPECT_CALL(fake_trace_upload_list_, GetAllTraceReports)
.WillOnce([](FakeTraceUploadList::GetReportsCallback callback) {
std::vector<ClientTraceReport> trace_reports;
for (int report = 0; report < 2; ++report) {
ClientTraceReport test_report;
test_report.uuid = base::Token::CreateRandom();
test_report.creation_time = base::Time::Now();
test_report.scenario_name = "scenario1";
test_report.upload_rule_name = "rules1";
test_report.total_size = 23192873129873128;
test_report.skip_reason = SkipUploadReason::kNoSkip;
test_report.upload_state = ReportUploadState::kNotUploaded;
test_report.upload_time = base::Time::Now();
trace_reports.push_back(test_report);
}
std::move(callback).Run(trace_reports);
});
EXPECT_CALL(callback, Run)
.WillOnce([](std::vector<traces_internals::mojom::ClientTraceReportPtr>
all_reports) { EXPECT_EQ(all_reports.size(), 2u); });
handler_->GetAllTraceReports(callback.Get());
}
TEST_F(TracesInternalsHandlerTest, DeleteAllTraces) {
base::MockCallback<TracesInternalsHandler::DeleteAllTracesCallback> callback;
EXPECT_CALL(fake_trace_upload_list_, DeleteAllTraces)
.WillOnce([](FakeTraceUploadList::FinishedProcessingCallback callback) {
std::move(callback).Run(true);
});
EXPECT_CALL(callback, Run(true));
handler_->DeleteAllTraces(callback.Get());
}
TEST_F(TracesInternalsHandlerTest, DeleteSingleTrace) {
auto uuid = base::Token::CreateRandom();
base::MockCallback<TracesInternalsHandler::DeleteSingleTraceCallback>
callback;
EXPECT_CALL(fake_trace_upload_list_, DeleteSingleTrace)
.WillOnce(
[&uuid](const base::Token passed_uuid,
FakeTraceUploadList::FinishedProcessingCallback callback) {
EXPECT_EQ(uuid, passed_uuid);
std::move(callback).Run(false);
});
EXPECT_CALL(callback, Run(false));
handler_->DeleteSingleTrace(uuid, callback.Get());
}
TEST_F(TracesInternalsHandlerTest, UserUploadSingleTrace) {
auto uuid = base::Token::CreateRandom();
base::MockCallback<TracesInternalsHandler::UserUploadSingleTraceCallback>
callback;
EXPECT_CALL(fake_trace_upload_list_, UserUploadSingleTrace)
.WillOnce(
[&uuid](const base::Token passed_uuid,
FakeTraceUploadList::FinishedProcessingCallback callback) {
EXPECT_EQ(uuid, passed_uuid);
std::move(callback).Run(true);
});
EXPECT_CALL(callback, Run(true));
handler_->UserUploadSingleTrace(uuid, callback.Get());
}
TEST_F(TracesInternalsHandlerTest, DownloadTrace) {
auto uuid = base::Token::CreateRandom();
base::MockCallback<TracesInternalsHandler::DownloadTraceCallback> callback;
const auto result = std::optional<base::span<const char>>("PROTO RESULT");
EXPECT_CALL(fake_trace_upload_list_, DownloadTrace)
.WillOnce(
[&result, &uuid](const base::Token passed_uuid,
FakeTraceUploadList::GetProtoCallback callback) {
EXPECT_EQ(uuid, passed_uuid);
std::move(callback).Run(result);
});
EXPECT_CALL(callback, Run)
.WillOnce([&result](std::optional<mojo_base::BigBuffer> converted_value) {
EXPECT_EQ(
std::string_view(reinterpret_cast<char*>(converted_value->data()),
converted_value->size()),
std::string_view(result->data(), result->size()));
});
handler_->DownloadTrace(uuid, callback.Get());
}
#if BUILDFLAG(IS_WIN)
// Tests that TracesInternalsHandler delegates GetSystemTracingState to the
// TracingDelegate.
TEST_F(TracesInternalsHandlerTest, GetSystemTracingState) {
EXPECT_CALL(mock_tracing_delegate_, GetSystemTracingState(testing::_));
handler_->GetSystemTracingState({});
}
// Tests that TracesInternalsHandler delegates EnableSystemTracing to the
// TracingDelegate.
TEST_F(TracesInternalsHandlerTest, EnableSystemTracing) {
EXPECT_CALL(mock_tracing_delegate_, EnableSystemTracing(testing::_));
handler_->EnableSystemTracing({});
}
// Tests that TracesInternalsHandler delegates DisableSystemTracing to the
// TracingDelegate.
TEST_F(TracesInternalsHandlerTest, DisableSystemTracing) {
EXPECT_CALL(mock_tracing_delegate_, DisableSystemTracing(testing::_));
handler_->DisableSystemTracing({});
}
#endif // BUILDFLAG(IS_WIN)
} // namespace content