| // Copyright 2020 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 "chrome/browser/error_reporting/chrome_js_error_report_processor.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/callback.h" |
| #include "base/callback_helpers.h" |
| #include "base/run_loop.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/simple_test_clock.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/crash_upload_list/crash_upload_list.h" |
| #include "chrome/browser/error_reporting/mock_chrome_js_error_report_processor.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "components/crash/content/browser/error_reporting/javascript_error_report.h" |
| #include "components/crash/content/browser/error_reporting/mock_crash_endpoint.h" |
| #include "components/crash/core/app/crashpad.h" |
| #include "components/upload_list/upload_list.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::testing::AllOf; |
| using ::testing::HasSubstr; |
| using ::testing::SizeIs; |
| |
| JavaScriptErrorReport MakeErrorReport(const std::string& message) { |
| JavaScriptErrorReport report; |
| report.message = message; |
| return report; |
| } |
| |
| class ChromeJsErrorReportProcessorTest : public ::testing::Test { |
| public: |
| ChromeJsErrorReportProcessorTest() |
| : task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP), |
| processor_(base::MakeRefCounted<MockChromeJsErrorReportProcessor>()) {} |
| |
| void SetUp() override { |
| // Set clock to something arbitrary which is not the null value. |
| test_clock_.SetNow(base::Time::FromTimeT(kFakeNow)); |
| test_server_ = std::make_unique<net::test_server::EmbeddedTestServer>(); |
| endpoint_ = std::make_unique<MockCrashEndpoint>(test_server_.get()); |
| processor_->SetCrashEndpoint(endpoint_->GetCrashEndpointURL()); |
| processor_->set_clock_for_testing(&test_clock_); |
| } |
| |
| void FinishCallback(base::RepeatingClosure run_loop_quit) { |
| // Callback should always be on the originating thread. |
| CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| finish_callback_was_called_ = true; |
| run_loop_quit.Run(); |
| } |
| |
| // Wrapper around processor_->SendErrorReport. Runs a RunLoop until the |
| // callback is called. |
| void SendErrorReport(JavaScriptErrorReport report) { |
| base::RunLoop run_loop; |
| processor_->SendErrorReport( |
| std::move(report), |
| base::BindOnce(&ChromeJsErrorReportProcessorTest::FinishCallback, |
| base::Unretained(this), run_loop.QuitClosure()), |
| &browser_context_); |
| run_loop.Run(); |
| } |
| |
| // Helper for TEST_F(ChromeJsErrorReportProcessorTest, AllFields) and |
| // TEST_F(ChromeJsErrorReportProcessorTest, WorksWithoutMemfdCreate). |
| void TestAllFields(); |
| |
| protected: |
| base::SimpleTestClock test_clock_; |
| content::BrowserTaskEnvironment task_environment_; |
| TestingProfile browser_context_; |
| std::unique_ptr<net::test_server::EmbeddedTestServer> test_server_; |
| std::unique_ptr<MockCrashEndpoint> endpoint_; |
| bool finish_callback_was_called_ = false; |
| scoped_refptr<MockChromeJsErrorReportProcessor> processor_; |
| |
| static constexpr time_t kFakeNow = 1586581472; |
| static constexpr char kFirstMessage[] = "An Error Is Me"; |
| static constexpr char kFirstMessageQuery[] = |
| "error_message=An%20Error%20Is%20Me"; |
| static constexpr char kSecondMessage[] = "A bad error"; |
| static constexpr char kSecondMessageQuery[] = "error_message=A%20bad%20error"; |
| static constexpr char kThirdMessage[] = "Wow that's a lot of errors"; |
| static constexpr char kThirdMessageQuery[] = |
| "error_message=Wow%20that%27s%20a%20lot%20of%20errors"; |
| static constexpr char kProduct[] = "Chrome_ChromeOS"; |
| static constexpr char kSecondProduct[] = "Chrome_Linux"; |
| }; |
| |
| constexpr time_t ChromeJsErrorReportProcessorTest::kFakeNow; |
| constexpr char ChromeJsErrorReportProcessorTest::kFirstMessage[]; |
| constexpr char ChromeJsErrorReportProcessorTest::kFirstMessageQuery[]; |
| constexpr char ChromeJsErrorReportProcessorTest::kSecondMessage[]; |
| constexpr char ChromeJsErrorReportProcessorTest::kSecondMessageQuery[]; |
| constexpr char ChromeJsErrorReportProcessorTest::kThirdMessage[]; |
| constexpr char ChromeJsErrorReportProcessorTest::kThirdMessageQuery[]; |
| constexpr char ChromeJsErrorReportProcessorTest::kProduct[]; |
| constexpr char ChromeJsErrorReportProcessorTest::kSecondProduct[]; |
| |
| TEST_F(ChromeJsErrorReportProcessorTest, Basic) { |
| auto report = MakeErrorReport("Hello World"); |
| report.url = "https://www.chromium.org/Home"; |
| |
| SendErrorReport(std::move(report)); |
| EXPECT_TRUE(finish_callback_was_called_); |
| |
| const absl::optional<MockCrashEndpoint::Report>& actual_report = |
| endpoint_->last_report(); |
| ASSERT_TRUE(actual_report); |
| EXPECT_THAT(actual_report->query, HasSubstr("error_message=Hello%20World")); |
| EXPECT_THAT(actual_report->query, HasSubstr("type=JavascriptError")); |
| EXPECT_THAT(actual_report->query, HasSubstr("browser_process_uptime_ms=")); |
| EXPECT_THAT(actual_report->query, HasSubstr("renderer_process_uptime_ms=0")); |
| // TODO(iby) research why URL is repeated... |
| EXPECT_THAT(actual_report->query, |
| HasSubstr("src=https%3A%2F%2Fwww.chromium.org%2FHome")); |
| EXPECT_THAT(actual_report->query, |
| HasSubstr("full_url=https%3A%2F%2Fwww.chromium.org%2FHome")); |
| EXPECT_THAT(actual_report->query, HasSubstr("url=%2FHome")); |
| EXPECT_THAT(actual_report->query, HasSubstr("browser=Chrome")); |
| EXPECT_THAT(actual_report->query, Not(HasSubstr("source_system="))); |
| EXPECT_THAT(actual_report->query, HasSubstr("num-experiments=1")); |
| EXPECT_THAT( |
| actual_report->query, |
| HasSubstr(base::StrCat( |
| {"variations=", |
| MockChromeJsErrorReportProcessor::kDefaultExperimentListString}))); |
| |
| #if !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_CHROMEOS_LACROS) |
| // This is from MockChromeJsErrorReportProcessor::GetOsVersion() |
| EXPECT_THAT(actual_report->query, HasSubstr("os_version=7.20.1")); |
| #endif |
| // These are from MockCrashEndpoint::Client::GetProductNameAndVersion, which |
| // is only defined for non-MAC POSIX systems. TODO(https://crbug.com/1121816): |
| // Get this info for non-POSIX platforms. |
| #if defined(OS_POSIX) && !defined(OS_MAC) |
| EXPECT_THAT(actual_report->query, HasSubstr("prod=Chrome_ChromeOS")); |
| EXPECT_THAT(actual_report->query, HasSubstr("ver=1.2.3.4")); |
| EXPECT_THAT(actual_report->query, HasSubstr("browser_version=1.2.3.4")); |
| EXPECT_THAT(actual_report->query, HasSubstr("channel=Stable")); |
| #endif |
| EXPECT_EQ(actual_report->content, ""); |
| } |
| |
| void ChromeJsErrorReportProcessorTest::TestAllFields() { |
| auto report = MakeErrorReport("Hello World"); |
| report.url = "https://www.chromium.org/Home/scripts.js"; |
| report.product = "Unit test"; |
| report.version = "6.2.3.4"; |
| report.line_number = 83; |
| report.column_number = 14; |
| report.page_url = "https://www.chromium.org/Home.html"; |
| report.stack_trace = "bad_func(1, 2)\nonclick()\n"; |
| report.renderer_process_uptime_ms = 1234; |
| report.window_type = WindowType::kSystemWebApp; |
| report.source_system = JavaScriptErrorReport::SourceSystem::kWebUIObserver; |
| |
| SendErrorReport(std::move(report)); |
| EXPECT_TRUE(finish_callback_was_called_); |
| |
| const absl::optional<MockCrashEndpoint::Report>& actual_report = |
| endpoint_->last_report(); |
| ASSERT_TRUE(actual_report); |
| EXPECT_THAT(actual_report->query, HasSubstr("error_message=Hello%20World")); |
| EXPECT_THAT(actual_report->query, HasSubstr("type=JavascriptError")); |
| EXPECT_THAT(actual_report->query, HasSubstr("browser_process_uptime_ms=")); |
| EXPECT_THAT(actual_report->query, |
| HasSubstr("renderer_process_uptime_ms=1234")); |
| EXPECT_THAT(actual_report->query, HasSubstr("window_type=SYSTEM_WEB_APP")); |
| // TODO(iby) research why URL is repeated... |
| EXPECT_THAT( |
| actual_report->query, |
| HasSubstr("src=https%3A%2F%2Fwww.chromium.org%2FHome%2Fscripts.js")); |
| EXPECT_THAT( |
| actual_report->query, |
| HasSubstr("full_url=https%3A%2F%2Fwww.chromium.org%2FHome%2Fscripts.js")); |
| EXPECT_THAT(actual_report->query, HasSubstr("url=%2FHome%2Fscripts.js")); |
| EXPECT_THAT(actual_report->query, |
| HasSubstr("page_url=https%3A%2F%2Fwww.chromium.org%2FHome.html")); |
| EXPECT_THAT(actual_report->query, HasSubstr("browser=Chrome")); |
| // product is double-escaped. The first time, it transforms to Unit%20test, |
| // then the % is turned into %25. |
| EXPECT_THAT(actual_report->query, HasSubstr("prod=Unit%2520test")); |
| EXPECT_THAT(actual_report->query, HasSubstr("ver=6.2.3.4")); |
| EXPECT_THAT(actual_report->query, HasSubstr("line=83")); |
| EXPECT_THAT(actual_report->query, HasSubstr("column=14")); |
| EXPECT_THAT(actual_report->query, HasSubstr("source_system=webui_observer")); |
| EXPECT_THAT(actual_report->query, HasSubstr("num-experiments=1")); |
| EXPECT_THAT( |
| actual_report->query, |
| HasSubstr(base::StrCat( |
| {"variations=", |
| MockChromeJsErrorReportProcessor::kDefaultExperimentListString}))); |
| |
| #if !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_CHROMEOS_LACROS) |
| // This is from MockChromeJsErrorReportProcessor::GetOsVersion() |
| EXPECT_THAT(actual_report->query, HasSubstr("os_version=7.20.1")); |
| #endif |
| // These are from MockCrashEndpoint::Client::GetProductNameAndVersion, which |
| // is only defined for non-MAC POSIX systems. TODO(https://crbug.com/1121816): |
| // Get this info for non-POSIX platforms. |
| #if defined(OS_POSIX) && !defined(OS_MAC) |
| EXPECT_THAT(actual_report->query, HasSubstr("browser_version=1.2.3.4")); |
| EXPECT_THAT(actual_report->query, HasSubstr("channel=Stable")); |
| #endif |
| EXPECT_EQ(actual_report->content, "bad_func(1, 2)\nonclick()\n"); |
| } |
| |
| TEST_F(ChromeJsErrorReportProcessorTest, AllFields) { |
| TestAllFields(); |
| } |
| |
| #if !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_CHROMEOS_LACROS) |
| // On Chrome OS, consent checks are handled in the crash_reporter, not in the |
| // browser. |
| TEST_F(ChromeJsErrorReportProcessorTest, NoConsent) { |
| endpoint_->set_consented(false); |
| auto report = MakeErrorReport("Hello World"); |
| report.url = "https://www.chromium.org/Home"; |
| |
| SendErrorReport(std::move(report)); |
| EXPECT_TRUE(finish_callback_was_called_); |
| |
| EXPECT_FALSE(endpoint_->last_report()); |
| } |
| #endif // !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_CHROMEOS_LACROS) |
| |
| TEST_F(ChromeJsErrorReportProcessorTest, StackTraceWithErrorMessage) { |
| auto report = MakeErrorReport("Hello World"); |
| report.url = "https://www.chromium.org/Home"; |
| report.stack_trace = "Hello World\nbad_func(1, 2)\nonclick()\n"; |
| |
| SendErrorReport(std::move(report)); |
| EXPECT_TRUE(finish_callback_was_called_); |
| |
| const absl::optional<MockCrashEndpoint::Report>& actual_report = |
| endpoint_->last_report(); |
| ASSERT_TRUE(actual_report); |
| EXPECT_THAT(actual_report->query, HasSubstr("error_message=Hello%20World")); |
| EXPECT_EQ(actual_report->content, "bad_func(1, 2)\nonclick()\n"); |
| } |
| |
| TEST_F(ChromeJsErrorReportProcessorTest, RedactMessage) { |
| auto report = MakeErrorReport("alpha@beta.org says hi to gamma@omega.co.uk"); |
| report.url = "https://www.chromium.org/Home"; |
| report.stack_trace = |
| "alpha@beta.org says hi to gamma@omega.co.uk\n" |
| "bad_func(1, 2)\nonclick()\n"; |
| |
| SendErrorReport(std::move(report)); |
| EXPECT_TRUE(finish_callback_was_called_); |
| |
| const absl::optional<MockCrashEndpoint::Report>& actual_report = |
| endpoint_->last_report(); |
| ASSERT_TRUE(actual_report); |
| // Escaped version of "<email: 1> says hi to <email: 2>" |
| EXPECT_THAT(actual_report->query, |
| HasSubstr("error_message=%3Cemail%3A%201%3E%20says%20hi%20to%20" |
| "%3Cemail%3A%202%3E")); |
| // Redacted messages still need to be removed from stack trace. |
| EXPECT_EQ(actual_report->content, "bad_func(1, 2)\nonclick()\n"); |
| } |
| |
| TEST_F(ChromeJsErrorReportProcessorTest, NoMoreThanOneDuplicatePerHour) { |
| SendErrorReport(MakeErrorReport(kFirstMessage)); |
| EXPECT_TRUE(finish_callback_was_called_); |
| EXPECT_EQ(endpoint_->report_count(), 1); |
| |
| finish_callback_was_called_ = false; |
| test_clock_.SetNow(test_clock_.Now() + base::TimeDelta::FromMinutes(1)); |
| SendErrorReport(MakeErrorReport(kFirstMessage)); |
| EXPECT_TRUE(finish_callback_was_called_); |
| EXPECT_EQ(endpoint_->report_count(), 1); |
| } |
| |
| TEST_F(ChromeJsErrorReportProcessorTest, MultipleDistinctReportsAllowed) { |
| SendErrorReport(MakeErrorReport(kFirstMessage)); |
| EXPECT_EQ(endpoint_->report_count(), 1); |
| |
| SendErrorReport(MakeErrorReport(kSecondMessage)); |
| EXPECT_EQ(endpoint_->report_count(), 2); |
| EXPECT_THAT(endpoint_->last_report()->query, HasSubstr(kSecondMessageQuery)); |
| } |
| |
| TEST_F(ChromeJsErrorReportProcessorTest, DuplicatesAllowedAfterAnHour) { |
| SendErrorReport(MakeErrorReport(kFirstMessage)); |
| EXPECT_EQ(endpoint_->report_count(), 1); |
| |
| test_clock_.SetNow(test_clock_.Now() + base::TimeDelta::FromMinutes(45)); |
| SendErrorReport(MakeErrorReport(kFirstMessage)); |
| EXPECT_EQ(endpoint_->report_count(), 1); |
| |
| test_clock_.SetNow(test_clock_.Now() + base::TimeDelta::FromMinutes(20)); |
| SendErrorReport(MakeErrorReport(kFirstMessage)); |
| EXPECT_EQ(endpoint_->report_count(), 2); |
| } |
| |
| TEST_F(ChromeJsErrorReportProcessorTest, DuplicateTimingIsIndependent) { |
| SendErrorReport(MakeErrorReport(kFirstMessage)); |
| EXPECT_EQ(endpoint_->report_count(), 1); |
| |
| test_clock_.SetNow(test_clock_.Now() + base::TimeDelta::FromMinutes(15)); |
| SendErrorReport(MakeErrorReport(kSecondMessage)); |
| EXPECT_EQ(endpoint_->report_count(), 2); |
| |
| test_clock_.SetNow(test_clock_.Now() + base::TimeDelta::FromMinutes(15)); |
| SendErrorReport(MakeErrorReport(kThirdMessage)); |
| EXPECT_EQ(endpoint_->report_count(), 3); |
| |
| test_clock_.SetNow(test_clock_.Now() + base::TimeDelta::FromMinutes(15)); |
| // 45 minutes from first error, all of these should be ignored. |
| SendErrorReport(MakeErrorReport(kFirstMessage)); |
| SendErrorReport(MakeErrorReport(kSecondMessage)); |
| SendErrorReport(MakeErrorReport(kThirdMessage)); |
| EXPECT_EQ(endpoint_->report_count(), 3); // Unchanged |
| |
| // An hour+ from first error. First error should be OK to send again, others |
| // should not. |
| test_clock_.SetNow(test_clock_.Now() + base::TimeDelta::FromMinutes(20)); |
| SendErrorReport(MakeErrorReport(kFirstMessage)); |
| SendErrorReport(MakeErrorReport(kSecondMessage)); |
| SendErrorReport(MakeErrorReport(kThirdMessage)); |
| EXPECT_EQ(endpoint_->report_count(), 4); |
| EXPECT_THAT(endpoint_->last_report()->query, HasSubstr(kFirstMessageQuery)); |
| |
| // An hour+ from second error. First error should be back in cooldown, and |
| // third error should still be blocked from its original send. |
| test_clock_.SetNow(test_clock_.Now() + base::TimeDelta::FromMinutes(15)); |
| SendErrorReport(MakeErrorReport(kFirstMessage)); |
| SendErrorReport(MakeErrorReport(kSecondMessage)); |
| SendErrorReport(MakeErrorReport(kThirdMessage)); |
| EXPECT_EQ(endpoint_->report_count(), 5); |
| EXPECT_THAT(endpoint_->last_report()->query, HasSubstr(kSecondMessageQuery)); |
| |
| // An hour+ from third error. First and second are still in cooldown. |
| test_clock_.SetNow(test_clock_.Now() + base::TimeDelta::FromMinutes(15)); |
| SendErrorReport(MakeErrorReport(kFirstMessage)); |
| SendErrorReport(MakeErrorReport(kSecondMessage)); |
| SendErrorReport(MakeErrorReport(kThirdMessage)); |
| EXPECT_EQ(endpoint_->report_count(), 6); |
| EXPECT_THAT(endpoint_->last_report()->query, HasSubstr(kThirdMessageQuery)); |
| } |
| |
| TEST_F(ChromeJsErrorReportProcessorTest, |
| BackwardsClockResetsAllDuplicateBlocks) { |
| SendErrorReport(MakeErrorReport(kFirstMessage)); |
| EXPECT_EQ(endpoint_->report_count(), 1); |
| |
| test_clock_.SetNow(test_clock_.Now() + base::TimeDelta::FromMinutes(15)); |
| SendErrorReport(MakeErrorReport(kSecondMessage)); |
| EXPECT_EQ(endpoint_->report_count(), 2); |
| |
| test_clock_.SetNow(test_clock_.Now() + base::TimeDelta::FromMinutes(15)); |
| SendErrorReport(MakeErrorReport(kThirdMessage)); |
| EXPECT_EQ(endpoint_->report_count(), 3); |
| |
| // Move clock back 10 hours. |
| test_clock_.SetNow(test_clock_.Now() + base::TimeDelta::FromMinutes(-600)); |
| SendErrorReport(MakeErrorReport(kFirstMessage)); |
| EXPECT_EQ(endpoint_->report_count(), 4); |
| EXPECT_THAT(endpoint_->last_report()->query, HasSubstr(kFirstMessageQuery)); |
| |
| test_clock_.SetNow(test_clock_.Now() + base::TimeDelta::FromMinutes(15)); |
| SendErrorReport(MakeErrorReport(kFirstMessage)); |
| SendErrorReport(MakeErrorReport(kSecondMessage)); |
| EXPECT_EQ(endpoint_->report_count(), 5); |
| EXPECT_THAT(endpoint_->last_report()->query, HasSubstr(kSecondMessageQuery)); |
| |
| // First and second are still in cooldown. |
| test_clock_.SetNow(test_clock_.Now() + base::TimeDelta::FromMinutes(15)); |
| SendErrorReport(MakeErrorReport(kFirstMessage)); |
| SendErrorReport(MakeErrorReport(kSecondMessage)); |
| SendErrorReport(MakeErrorReport(kThirdMessage)); |
| EXPECT_EQ(endpoint_->report_count(), 6); |
| EXPECT_THAT(endpoint_->last_report()->query, HasSubstr(kThirdMessageQuery)); |
| } |
| |
| TEST_F(ChromeJsErrorReportProcessorTest, |
| BackwardsClockResetsSomeDuplicateBlocks) { |
| SendErrorReport(MakeErrorReport(kFirstMessage)); |
| EXPECT_EQ(endpoint_->report_count(), 1); |
| |
| test_clock_.SetNow(test_clock_.Now() + base::TimeDelta::FromMinutes(15)); |
| SendErrorReport(MakeErrorReport(kSecondMessage)); |
| EXPECT_EQ(endpoint_->report_count(), 2); |
| |
| test_clock_.SetNow(test_clock_.Now() + base::TimeDelta::FromMinutes(15)); |
| SendErrorReport(MakeErrorReport(kThirdMessage)); |
| EXPECT_EQ(endpoint_->report_count(), 3); |
| |
| // Move clock back before 3rd message was sent. |
| test_clock_.SetNow(test_clock_.Now() + base::TimeDelta::FromMinutes(-10)); |
| SendErrorReport(MakeErrorReport(kFirstMessage)); |
| SendErrorReport(MakeErrorReport(kSecondMessage)); |
| SendErrorReport(MakeErrorReport(kThirdMessage)); |
| EXPECT_EQ(endpoint_->report_count(), 4); |
| EXPECT_THAT(endpoint_->last_report()->query, HasSubstr(kThirdMessageQuery)); |
| } |
| TEST_F(ChromeJsErrorReportProcessorTest, DuplicateMapIsCleanedUpAfterAnHour) { |
| SendErrorReport(MakeErrorReport(kFirstMessage)); |
| SendErrorReport(MakeErrorReport(kSecondMessage)); |
| |
| test_clock_.SetNow(test_clock_.Now() + base::TimeDelta::FromMinutes(70)); |
| SendErrorReport(MakeErrorReport(kThirdMessage)); |
| // Only record for third message should be present now. |
| EXPECT_THAT(processor_->get_recent_error_reports_for_testing(), SizeIs(1)); |
| } |
| |
| TEST_F(ChromeJsErrorReportProcessorTest, DifferentProductsAreDistinct) { |
| auto report = MakeErrorReport(kFirstMessage); |
| report.product = kProduct; |
| SendErrorReport(std::move(report)); |
| EXPECT_EQ(endpoint_->report_count(), 1); |
| |
| auto report2 = MakeErrorReport(kFirstMessage); |
| report2.product = kSecondProduct; |
| SendErrorReport(std::move(report2)); |
| EXPECT_EQ(endpoint_->report_count(), 2); |
| |
| auto report3 = MakeErrorReport(kFirstMessage); |
| // No product at all, using default. |
| SendErrorReport(std::move(report3)); |
| EXPECT_EQ(endpoint_->report_count(), 3); |
| } |
| |
| TEST_F(ChromeJsErrorReportProcessorTest, DifferentLineNumbersAreDistinct) { |
| // Many error messages have the same message, especially exceptions. Make sure |
| // errors from different source lines are treated as distinct. |
| auto report = MakeErrorReport(kFirstMessage); |
| report.line_number = 10; |
| SendErrorReport(std::move(report)); |
| EXPECT_EQ(endpoint_->report_count(), 1); |
| |
| auto report2 = MakeErrorReport(kFirstMessage); |
| report2.line_number = 20; |
| SendErrorReport(std::move(report2)); |
| EXPECT_EQ(endpoint_->report_count(), 2); |
| |
| auto report3 = MakeErrorReport(kFirstMessage); |
| // No line number at all. |
| SendErrorReport(std::move(report3)); |
| EXPECT_EQ(endpoint_->report_count(), 3); |
| } |
| |
| TEST_F(ChromeJsErrorReportProcessorTest, DifferentColumnNumbersAreDistinct) { |
| auto report = MakeErrorReport(kFirstMessage); |
| report.column_number = 10; |
| SendErrorReport(std::move(report)); |
| EXPECT_EQ(endpoint_->report_count(), 1); |
| |
| auto report2 = MakeErrorReport(kFirstMessage); |
| report2.column_number = 20; |
| SendErrorReport(std::move(report2)); |
| EXPECT_EQ(endpoint_->report_count(), 2); |
| |
| auto report3 = MakeErrorReport(kFirstMessage); |
| // No column number at all. |
| SendErrorReport(std::move(report3)); |
| EXPECT_EQ(endpoint_->report_count(), 3); |
| } |
| |
| #if !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_CHROMEOS_LACROS) |
| static std::string UploadInfoStateToString( |
| UploadList::UploadInfo::State state) { |
| switch (state) { |
| case UploadList::UploadInfo::State::NotUploaded: |
| return "NotUploaded"; |
| case UploadList::UploadInfo::State::Pending: |
| return "Pending"; |
| case UploadList::UploadInfo::State::Pending_UserRequested: |
| return "Pending_UserRequested"; |
| case UploadList::UploadInfo::State::Uploaded: |
| return "Uploaded"; |
| default: |
| return base::StrCat({"Unknown upload state ", |
| base::NumberToString(static_cast<int>(state))}); |
| } |
| } |
| |
| static std::string UploadInfoVectorToString( |
| const std::vector<UploadList::UploadInfo>& uploads) { |
| std::string result = "["; |
| bool first = true; |
| for (const UploadList::UploadInfo& upload : uploads) { |
| if (first) { |
| first = false; |
| } else { |
| result += ", "; |
| } |
| base::StrAppend(&result, |
| {"{state ", UploadInfoStateToString(upload.state), |
| ", upload_id ", upload.upload_id, ", upload_time ", |
| base::NumberToString(upload.upload_time.ToTimeT()), |
| ", local_id ", upload.local_id, ", capture_time ", |
| base::NumberToString(upload.capture_time.ToTimeT()), |
| ", source ", upload.source, ", file size ", |
| base::UTF16ToUTF8(upload.file_size), "}"}); |
| } |
| result += "]"; |
| return result; |
| } |
| |
| TEST_F(ChromeJsErrorReportProcessorTest, UpdatesUploadsLog) { |
| base::ScopedPathOverride crash_dir_override(chrome::DIR_CRASH_DUMPS); |
| processor_->set_update_report_database(true); |
| |
| constexpr char kCrashId[] = "123abc456def"; |
| endpoint_->set_response(net::HTTP_OK, kCrashId); |
| |
| SendErrorReport(MakeErrorReport(kFirstMessage)); |
| EXPECT_EQ(endpoint_->report_count(), 1); |
| |
| auto upload_list = CreateCrashUploadList(); |
| base::RunLoop run_loop; |
| upload_list->Load(run_loop.QuitClosure()); |
| run_loop.Run(); |
| std::vector<UploadList::UploadInfo> uploads; |
| upload_list->GetUploads(50, &uploads); |
| EXPECT_EQ(uploads.size(), 1U) << UploadInfoVectorToString(uploads); |
| |
| bool found = false; |
| for (const UploadList::UploadInfo& upload : uploads) { |
| if (upload.state == UploadList::UploadInfo::State::Uploaded && |
| upload.upload_id == kCrashId) { |
| EXPECT_FALSE(found) << "Found twice"; |
| found = true; |
| EXPECT_EQ(upload.upload_time.ToTimeT(), kFakeNow); |
| } |
| } |
| EXPECT_TRUE(found) << "Didn't find upload record in " |
| << UploadInfoVectorToString(uploads); |
| } |
| #endif // !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_CHROMEOS_LACROS) |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) |
| TEST_F(ChromeJsErrorReportProcessorTest, WorksWithoutMemfdCreate) { |
| processor_->set_force_non_memfd_for_test(); |
| TestAllFields(); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) |