// Copyright (c) 2014 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 "components/browser_watcher/watcher_metrics_provider_win.h" #include #include "base/process/process_handle.h" #include "base/strings/string16.h" #include "base/strings/stringprintf.h" #include "base/test/histogram_tester.h" #include "base/test/test_reg_util_win.h" #include "base/test/test_simple_task_runner.h" #include "base/win/registry.h" #include "components/browser_watcher/exit_funnel_win.h" #include "testing/gtest/include/gtest/gtest.h" namespace browser_watcher { namespace { const wchar_t kRegistryPath[] = L"Software\\WatcherMetricsProviderWinTest"; class WatcherMetricsProviderWinTest : public testing::Test { public: typedef testing::Test Super; void SetUp() override { Super::SetUp(); override_manager_.OverrideRegistry(HKEY_CURRENT_USER); test_task_runner_ = new base::TestSimpleTaskRunner(); } void AddProcessExitCode(bool use_own_pid, int exit_code) { int pid = 0; if (use_own_pid) { pid = base::GetCurrentProcId(); } else { // Make sure not to accidentally collide with own pid. do { pid = rand(); } while (pid == static_cast(base::GetCurrentProcId())); } base::win::RegKey key(HKEY_CURRENT_USER, kRegistryPath, KEY_WRITE); // Make up a unique key, starting with the given pid. base::string16 key_name(base::StringPrintf(L"%d-%d", pid, rand())); // Write the exit code to registry. ULONG result = key.WriteValue(key_name.c_str(), exit_code); ASSERT_EQ(result, ERROR_SUCCESS); } size_t ExitCodeRegistryPathValueCount() { base::win::RegKey key(HKEY_CURRENT_USER, kRegistryPath, KEY_READ); return key.GetValueCount(); } void AddExitFunnelEvent(int pid, const base::char16* name, int64 value) { base::string16 key_name = base::StringPrintf(L"%ls\\%d-%d", kRegistryPath, pid, pid); base::win::RegKey key(HKEY_CURRENT_USER, key_name.c_str(), KEY_WRITE); ASSERT_EQ(key.WriteValue(name, &value, sizeof(value), REG_QWORD), ERROR_SUCCESS); } protected: registry_util::RegistryOverrideManager override_manager_; base::HistogramTester histogram_tester_; scoped_refptr test_task_runner_; }; } // namespace TEST_F(WatcherMetricsProviderWinTest, RecordsStabilityHistogram) { // Record multiple success exits. for (size_t i = 0; i < 11; ++i) AddProcessExitCode(false, 0); // Record a single failure. AddProcessExitCode(false, 100); WatcherMetricsProviderWin provider(kRegistryPath, test_task_runner_.get()); provider.ProvideStabilityMetrics(NULL); histogram_tester_.ExpectBucketCount( WatcherMetricsProviderWin::kBrowserExitCodeHistogramName, 0, 11); histogram_tester_.ExpectBucketCount( WatcherMetricsProviderWin::kBrowserExitCodeHistogramName, 100, 1); histogram_tester_.ExpectTotalCount( WatcherMetricsProviderWin::kBrowserExitCodeHistogramName, 12); // Verify that the reported values are gone. EXPECT_EQ(ExitCodeRegistryPathValueCount(), 0); } TEST_F(WatcherMetricsProviderWinTest, DoesNotReportOwnProcessId) { // Record multiple success exits. for (size_t i = 0; i < 11; ++i) AddProcessExitCode(i, 0); // Record own process as STILL_ACTIVE. AddProcessExitCode(true, STILL_ACTIVE); WatcherMetricsProviderWin provider(kRegistryPath, test_task_runner_.get()); provider.ProvideStabilityMetrics(NULL); histogram_tester_.ExpectUniqueSample( WatcherMetricsProviderWin::kBrowserExitCodeHistogramName, 0, 11); // Verify that the reported values are gone. EXPECT_EQ(ExitCodeRegistryPathValueCount(), 1); } TEST_F(WatcherMetricsProviderWinTest, DeletesRecordedExitFunnelEvents) { // Record an exit funnel and make sure the registry is cleaned up on // reporting, without recording any events. AddExitFunnelEvent(100, L"One", 1000 * 1000); AddExitFunnelEvent(101, L"Two", 1010 * 1000); AddExitFunnelEvent(102, L"Three", 990 * 1000); base::win::RegistryKeyIterator it(HKEY_CURRENT_USER, kRegistryPath); EXPECT_EQ(it.SubkeyCount(), 3); WatcherMetricsProviderWin provider(kRegistryPath, test_task_runner_.get()); provider.ProvideStabilityMetrics(NULL); // Make sure the exit funnel events are no longer recorded in histograms. EXPECT_TRUE( histogram_tester_.GetAllSamples("Stability.ExitFunnel.One").empty()); EXPECT_TRUE( histogram_tester_.GetAllSamples("Stability.ExitFunnel.Two").empty()); EXPECT_TRUE( histogram_tester_.GetAllSamples("Stability.ExitFunnel.Three").empty()); // Make sure the subkeys are deleted on reporting. ASSERT_EQ(it.SubkeyCount(), 0); } TEST_F(WatcherMetricsProviderWinTest, DeletesExitcodeKeyWhenNotReporting) { // Test that the registry at kRegistryPath is deleted when reporting is // disabled. ExitFunnel funnel; // Record multiple success exits. for (size_t i = 0; i < 11; ++i) AddProcessExitCode(false, 0); // Record a single failure. AddProcessExitCode(false, 100); // Record an exit funnel. ASSERT_TRUE(funnel.InitImpl(kRegistryPath, 4, base::Time::Now())); AddExitFunnelEvent(100, L"One", 1000 * 1000); AddExitFunnelEvent(101, L"Two", 1010 * 1000); AddExitFunnelEvent(102, L"Three", 990 * 1000); // Make like the user is opted out of reporting. WatcherMetricsProviderWin provider(kRegistryPath, test_task_runner_.get()); provider.OnRecordingDisabled(); base::win::RegKey key; { // The deletion should be scheduled to the test_task_runner, and not happen // immediately. ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, kRegistryPath, KEY_READ)); } // Flush the task(s). test_task_runner_->RunPendingTasks(); // Make sure the subkey for the pseudo process has been deleted on reporting. ASSERT_EQ(ERROR_FILE_NOT_FOUND, key.Open(HKEY_CURRENT_USER, kRegistryPath, KEY_READ)); } TEST_F(WatcherMetricsProviderWinTest, DeletesOnly100FunnelsAtATime) { // Record 200 distinct exit funnels. for (size_t i = 0; i < 200; ++i) { AddExitFunnelEvent(i, L"One", 10); AddExitFunnelEvent(i, L"Two", 10); } base::win::RegistryKeyIterator it(HKEY_CURRENT_USER, kRegistryPath); EXPECT_EQ(it.SubkeyCount(), 200); { // Make like the user is opted out of reporting. WatcherMetricsProviderWin provider(kRegistryPath, test_task_runner_.get()); provider.OnRecordingDisabled(); // Flush the task(s). test_task_runner_->RunPendingTasks(); } // We expect only 100 of the funnels have been scrubbed. EXPECT_EQ(it.SubkeyCount(), 100); } } // namespace browser_watcher