| // 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 "chrome/browser/metrics/structured/ash_event_storage.h" |
| |
| #include <cstdint> |
| #include <vector> |
| |
| #include "base/files/file_path.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/task_environment.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/test/base/testing_browser_process.h" |
| #include "chrome/test/base/testing_profile_manager.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h" |
| #include "third_party/metrics_proto/structured_data.pb.h" |
| |
| namespace metrics::structured { |
| namespace { |
| StructuredEventProto BuildTestEvent( |
| uint64_t id = 0, |
| const std::vector<int64_t>& metrics = std::vector<int64_t>()) { |
| StructuredEventProto event; |
| event.set_device_project_id(id); |
| int metric_id = 0; |
| for (int64_t metric : metrics) { |
| auto* m = event.add_metrics(); |
| m->set_name_hash(metric_id++); |
| m->set_value_int64(metric); |
| } |
| return event; |
| } |
| } // namespace |
| |
| class AshEventStorageTest : public testing::Test { |
| public: |
| AshEventStorageTest() |
| : profile_manager_(TestingBrowserProcess::GetGlobal()) {} |
| |
| AshEventStorageTest(const AshEventStorageTest&) = delete; |
| AshEventStorageTest& operator=(const AshEventStorageTest&) = delete; |
| |
| void SetUp() override { |
| ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| ASSERT_TRUE(profile_manager_.SetUp()); |
| } |
| |
| void Wait() { task_environment_.RunUntilIdle(); } |
| |
| base::FilePath GetTestDirectory() { return temp_dir_.GetPath(); } |
| base::FilePath GetProfilePath() { return profile_manager_.profiles_dir(); } |
| base::FilePath GetUserDirectory() { |
| return GetProfilePath() |
| .Append(FILE_PATH_LITERAL("structured_metrics")) |
| .Append(FILE_PATH_LITERAL("user")); |
| } |
| |
| std::unique_ptr<AshEventStorage> BuildTestStorage() { |
| auto storage = std::make_unique<AshEventStorage>( |
| /*write_delay=*/base::Seconds(0), |
| GetTestDirectory() |
| .Append(FILE_PATH_LITERAL("structured_metrics")) |
| .Append(FILE_PATH_LITERAL("events"))); |
| // Wait for the device events to be loaded. |
| Wait(); |
| return storage; |
| } |
| |
| TestingProfile* AddProfile() { |
| return profile_manager_.CreateTestingProfile("p1"); |
| } |
| |
| StructuredDataProto GetReport(AshEventStorage* storage) { |
| StructuredDataProto structured_data; |
| |
| *structured_data.mutable_events() = storage->TakeEvents(); |
| |
| return structured_data; |
| } |
| |
| void ExpectNoErrors() { |
| histogram_tester_.ExpectTotalCount("UMA.StructuredMetrics.InternalError", |
| 0); |
| } |
| |
| private: |
| content::BrowserTaskEnvironment task_environment_; |
| base::HistogramTester histogram_tester_; |
| base::ScopedTempDir temp_dir_; |
| |
| protected: |
| TestingProfileManager profile_manager_; |
| }; |
| |
| TEST_F(AshEventStorageTest, StoreAndProvideEvents) { |
| std::unique_ptr<AshEventStorage> storage = BuildTestStorage(); |
| AddProfile(); |
| |
| Wait(); |
| |
| ASSERT_TRUE(storage->IsReady()); |
| |
| storage->AddEvent(BuildTestEvent()); |
| |
| EventsProto events; |
| storage->CopyEvents(&events); |
| EXPECT_EQ(events.events_size(), 1); |
| |
| StructuredDataProto proto = GetReport(storage.get()); |
| EXPECT_EQ(proto.events_size(), 1); |
| |
| // Storage should have no events after a successful dump. |
| events.Clear(); |
| storage->CopyEvents(&events); |
| EXPECT_EQ(events.events_size(), 0); |
| |
| ExpectNoErrors(); |
| } |
| |
| TEST_F(AshEventStorageTest, PreRecordedEventsProcessedCorrectly) { |
| std::unique_ptr<AshEventStorage> storage = BuildTestStorage(); |
| storage->AddEvent(BuildTestEvent()); |
| // Wait for the device storage to be ready so this functions in the correct |
| // order. If this isn't here there is a chance that the OnProfileAdded |
| // finishes, calling AshEventStorage::OnProfileReady, before device storage |
| // can loaded. This isn't an issue on device because there is likely to be a |
| // long enough delay between the storage being created and the user |
| // logging-in. |
| Wait(); |
| |
| // Add Profile and wait for the storage to be ready. |
| AddProfile(); |
| Wait(); |
| |
| ASSERT_TRUE(storage->IsReady()); |
| |
| EventsProto events; |
| storage->CopyEvents(&events); |
| EXPECT_EQ(events.events_size(), 1); |
| |
| ExpectNoErrors(); |
| } |
| |
| TEST_F(AshEventStorageTest, EventsClearedAfterReport) { |
| std::unique_ptr<AshEventStorage> storage = BuildTestStorage(); |
| AddProfile(); |
| |
| Wait(); |
| |
| storage->AddEvent(BuildTestEvent()); |
| storage->AddEvent(BuildTestEvent()); |
| |
| // Should provide both the previous events. |
| EXPECT_EQ(GetReport(storage.get()).events_size(), 2); |
| |
| // But the previous events shouldn't appear in the second report. |
| EXPECT_EQ(GetReport(storage.get()).events_size(), 0); |
| |
| storage->AddEvent(BuildTestEvent()); |
| // The third request should only contain the third event. |
| EXPECT_EQ(GetReport(storage.get()).events_size(), 1); |
| |
| ExpectNoErrors(); |
| } |
| |
| // Test that events recorded in one session are correctly persisted and are |
| // uploaded in the first report from a subsequent session. |
| TEST_F(AshEventStorageTest, EventsFromPreviousSessionAreLoaded) { |
| // Start first session and record one event. |
| std::unique_ptr<AshEventStorage> storage = BuildTestStorage(); |
| TestingProfile* profile = AddProfile(); |
| Wait(); |
| |
| storage->AddEvent(BuildTestEvent(0, {1234})); |
| |
| // Write events to disk and destroy the storage. |
| storage.reset(); |
| Wait(); |
| |
| storage = BuildTestStorage(); |
| |
| // Ideally, this would test signing in to the same profile, but it's not clear |
| // how to set that up, so instead we just call ProfileAdded() manually. |
| storage->ProfileAdded(*profile); |
| Wait(); |
| |
| // Start a second session and ensure the event is reported. |
| const auto data = GetReport(storage.get()); |
| ASSERT_EQ(data.events_size(), 1); |
| ASSERT_EQ(data.events(0).metrics_size(), 1); |
| EXPECT_EQ(data.events(0).metrics(0).value_int64(), 1234); |
| |
| ExpectNoErrors(); |
| } |
| |
| TEST_F(AshEventStorageTest, EventsPreProfilePersistedCorrectly) { |
| std::unique_ptr<AshEventStorage> storage = BuildTestStorage(); |
| Wait(); |
| |
| // Add event before OnProfileAdded is called. |
| storage->AddEvent(BuildTestEvent()); |
| ASSERT_TRUE(storage->IsReady()); |
| |
| // Ensure that the event is persisted. |
| EventsProto events; |
| storage->CopyEvents(&events); |
| EXPECT_EQ(events.events_size(), 1); |
| ExpectNoErrors(); |
| |
| AddProfile(); |
| Wait(); |
| |
| // Add another event OnProfileAdded is called. |
| storage->AddEvent(BuildTestEvent()); |
| |
| // Ensure that both events are in the report. |
| const auto data = GetReport(storage.get()); |
| ASSERT_EQ(data.events_size(), 2); |
| |
| ExpectNoErrors(); |
| } |
| |
| TEST_F(AshEventStorageTest, AddBatchEvents) { |
| std::unique_ptr<AshEventStorage> storage = BuildTestStorage(); |
| Wait(); |
| |
| AddProfile(); |
| Wait(); |
| |
| EventsProto proto; |
| *proto.add_events() = BuildTestEvent(); |
| *proto.add_events() = BuildTestEvent(); |
| *proto.add_events() = BuildTestEvent(); |
| storage->AddBatchEvents(proto.events()); |
| |
| const auto data = GetReport(storage.get()); |
| ASSERT_EQ(data.events_size(), 3); |
| |
| ExpectNoErrors(); |
| } |
| |
| TEST_F(AshEventStorageTest, MergePreUserAndUserEvents) { |
| std::unique_ptr<AshEventStorage> storage = BuildTestStorage(); |
| Wait(); |
| |
| // Add event before OnProfileAdded is called. |
| storage->AddEvent(BuildTestEvent()); |
| storage->AddEvent(BuildTestEvent()); |
| storage->AddEvent(BuildTestEvent()); |
| ASSERT_TRUE(storage->IsReady()); |
| |
| // There should be 3 events in the pre-profile storage. |
| EventsProto events_proto; |
| storage->CopyEvents(&events_proto); |
| EXPECT_EQ(events_proto.events_size(), 3); |
| |
| // Add profile and add an event while the profile events are being loaded. |
| AddProfile(); |
| storage->AddEvent(BuildTestEvent()); |
| Wait(); |
| |
| storage->AddEvent(BuildTestEvent()); |
| |
| const auto data = GetReport(storage.get()); |
| EXPECT_EQ(data.events_size(), 5); |
| |
| ExpectNoErrors(); |
| } |
| |
| } // namespace metrics::structured |