blob: 182e7335f096ee94747abdfce8b020c24f71c55a [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 "base/metrics/histogram_shared_memory.h"
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/metrics/persistent_histogram_allocator.h"
#include "base/process/launch.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/test/multiprocess_test.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_timeouts.h"
#include "base/unguessable_token.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"
#if BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
#include "base/files/platform_file.h"
#include "base/posix/global_descriptors.h"
#endif
namespace base {
namespace {
constexpr size_t kArbitrarySize = 64 << 10;
#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE)
constexpr GlobalDescriptors::Key kArbitraryDescriptorKey = 42;
#endif
} // namespace
TEST(HistogramSharedMemoryTest, Create) {
UnsafeSharedMemoryRegion region;
constexpr int kProcessId = 1234;
constexpr int kProcessType = 5678;
constexpr char kProcessName[] = "TestProcess";
auto shared_memory = HistogramSharedMemory::Create(
kProcessId, {kProcessType, kProcessName, kArbitrarySize});
ASSERT_TRUE(shared_memory.has_value());
ASSERT_TRUE(shared_memory->region.IsValid());
EXPECT_EQ(kArbitrarySize, shared_memory->region.GetSize());
ASSERT_TRUE(shared_memory->allocator);
EXPECT_EQ(kArbitrarySize, shared_memory->allocator->size());
}
namespace {
// Constants from content::ProcessType;
constexpr int PROCESS_TYPE_RENDERER = 3;
constexpr int PROCESS_TYPE_GPU = 9;
constexpr int PROCESS_TYPE_UTILITY = 6;
} // namespace
TEST(HistogramSharedMemoryTest, PassOnCommandLineIsDisabled) {
test::ScopedFeatureList feature_list;
feature_list.InitAndDisableFeature(kPassHistogramSharedMemoryOnLaunch);
EXPECT_FALSE(
HistogramSharedMemory::PassOnCommandLineIsEnabled(PROCESS_TYPE_RENDERER));
EXPECT_FALSE(
HistogramSharedMemory::PassOnCommandLineIsEnabled(PROCESS_TYPE_GPU));
EXPECT_FALSE(
HistogramSharedMemory::PassOnCommandLineIsEnabled(PROCESS_TYPE_UTILITY));
}
TEST(HistogramSharedMemoryTest, PassOnCommandLineIsEnabled) {
test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(kPassHistogramSharedMemoryOnLaunch);
EXPECT_TRUE(
HistogramSharedMemory::PassOnCommandLineIsEnabled(PROCESS_TYPE_RENDERER));
#if BUILDFLAG(IS_CHROMEOS)
EXPECT_FALSE(
HistogramSharedMemory::PassOnCommandLineIsEnabled(PROCESS_TYPE_GPU));
#else // !BUILDFLAG(IS_CHROMEOS)
EXPECT_TRUE(
HistogramSharedMemory::PassOnCommandLineIsEnabled(PROCESS_TYPE_GPU));
#endif // !BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(IS_ANDROID)
EXPECT_FALSE(
HistogramSharedMemory::PassOnCommandLineIsEnabled(PROCESS_TYPE_UTILITY));
#else // !BUILDFLAG(IS_ANDROID)
EXPECT_TRUE(
HistogramSharedMemory::PassOnCommandLineIsEnabled(PROCESS_TYPE_UTILITY));
#endif // !BUILDFLAG(IS_ANDROID)
}
MULTIPROCESS_TEST_MAIN(InitFromLaunchParameters) {
// On POSIX we generally use the descriptor map to look up inherited handles.
// On most POSIX platforms we have to manually sure the mapping is updated,
// for the purposes of this test.
//
// Note:
// - This doesn't apply on Apple platforms (which use Rendezvous Keys)
// - On Android the global descriptor table is managed by the launcher
// service, so we don't have to manually update the mapping here.
#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE) && !BUILDFLAG(IS_ANDROID)
GlobalDescriptors::GetInstance()->Set(
kArbitraryDescriptorKey,
kArbitraryDescriptorKey + GlobalDescriptors::kBaseDescriptor);
#endif
EXPECT_FALSE(GlobalHistogramAllocator::Get());
// Simulate launching with the serialized parameters.
HistogramSharedMemory::InitFromLaunchParameters(
*CommandLine::ForCurrentProcess());
EXPECT_TRUE(GlobalHistogramAllocator::Get());
return 0;
}
#if !BUILDFLAG(IS_IOS)
using HistogramSharedMemoryTest = ::testing::TestWithParam<bool>;
INSTANTIATE_TEST_SUITE_P(All,
HistogramSharedMemoryTest,
::testing::Values(/*launch_options.elevated=*/false
#if BUILDFLAG(IS_WIN)
,
/*launch_options.elevated=*/true
#endif
));
TEST_P(HistogramSharedMemoryTest, PassSharedMemoryRegion_Enabled) {
// Ensure the feature is enabled.
test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(kPassHistogramSharedMemoryOnLaunch);
// Create a shared memory region to pass.
auto memory = UnsafeSharedMemoryRegion::Create(kArbitrarySize);
ASSERT_TRUE(memory.IsValid());
// Initialize the command line and launch options.
CommandLine command_line = GetMultiProcessTestChildBaseCommandLine();
command_line.AppendSwitchASCII("type", "test-child");
LaunchOptions launch_options;
// On windows, check both the elevated and non-elevated launches.
#if BUILDFLAG(IS_WIN)
launch_options.start_hidden = true;
launch_options.elevated = GetParam();
#elif BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE)
ScopedFD descriptor_to_share;
#endif
// Update the launch parameters.
HistogramSharedMemory::AddToLaunchParameters(memory,
#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE)
kArbitraryDescriptorKey,
descriptor_to_share,
#endif
&command_line, &launch_options);
// The metrics shared memory handle should be added to the command line.
ASSERT_TRUE(command_line.HasSwitch(switches::kMetricsSharedMemoryHandle));
SCOPED_TRACE(
command_line.GetSwitchValueASCII(switches::kMetricsSharedMemoryHandle));
#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE)
// On posix, AddToLaunchParameters() ignores the launch options and instead
// returns the descriptor to be shared. This is because the browser child
// launcher helper manages a separate list of files to share via the zygote,
// if available. If, like in this test scenario, there's ultimately no zygote
// to use, launch helper updates the launch options to share the descriptor
// mapping relative to a base descriptor.
launch_options.fds_to_remap.emplace_back(descriptor_to_share.get(),
kArbitraryDescriptorKey);
// GlobalDescriptors::GetInstance()->Set(kArbitraryDescriptorKey,
// descriptor_to_share);
#if !BUILDFLAG(IS_ANDROID)
for (auto& pair : launch_options.fds_to_remap) {
pair.second += base::GlobalDescriptors::kBaseDescriptor;
}
#endif // !BUILDFLAG(IS_ANDROID)
#endif // BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE)
// Launch the child process.
Process process = SpawnMultiProcessTestChild("InitFromLaunchParameters",
command_line, launch_options);
// The child process returns non-zero if it could not open the shared memory
// region based on the launch parameters.
int exit_code;
EXPECT_TRUE(WaitForMultiprocessTestChildExit(
process, TestTimeouts::action_timeout(), &exit_code));
EXPECT_EQ(0, exit_code);
}
#endif
} // namespace base