blob: aff6007941b5e18fe21fc859c578bb988d22d822 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/feedback/feedback_uploader.h"
#include <memory>
#include <set>
#include "base/containers/contains.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/task_traits.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "components/feedback/features.h"
#include "components/feedback/feedback_report.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace feedback {
namespace {
constexpr char kReportOne[] = "one";
constexpr char kReportTwo[] = "two";
constexpr char kReportThree[] = "three";
constexpr char kReportFour[] = "four";
constexpr char kReportFive[] = "five";
constexpr base::TimeDelta kRetryDelayForTest = base::Milliseconds(100);
class MockFeedbackUploader : public FeedbackUploader {
public:
MockFeedbackUploader(
bool is_off_the_record,
const base::FilePath& state_path,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
: FeedbackUploader(is_off_the_record, state_path, url_loader_factory) {}
MockFeedbackUploader(const MockFeedbackUploader&) = delete;
MockFeedbackUploader& operator=(const MockFeedbackUploader&) = delete;
void RunMessageLoop() {
if (ProcessingComplete())
return;
run_loop_ = std::make_unique<base::RunLoop>();
run_loop_->Run();
}
void SimulateLoadingOfflineReports() {
task_runner()->PostTask(
FROM_HERE,
base::BindOnce(
&FeedbackReport::LoadReportsAndQueue, feedback_reports_path(),
base::BindRepeating(&MockFeedbackUploader::QueueSingleReport,
base::SequencedTaskRunner::GetCurrentDefault(),
AsWeakPtr())));
}
const std::map<std::string, unsigned int>& dispatched_reports() const {
return dispatched_reports_;
}
void set_expected_reports(size_t value) { expected_reports_ = value; }
void set_simulate_failure(bool value) { simulate_failure_ = value; }
private:
static void QueueSingleReport(
scoped_refptr<base::SequencedTaskRunner> main_task_runner,
base::WeakPtr<FeedbackUploader> uploader,
scoped_refptr<FeedbackReport> report) {
main_task_runner->PostTask(
FROM_HERE, base::BindOnce(&MockFeedbackUploader::RequeueReport,
std::move(uploader), std::move(report)));
}
// FeedbackUploaderChrome:
void StartDispatchingReport() override {
if (base::Contains(dispatched_reports_, report_being_dispatched()->data()))
dispatched_reports_[report_being_dispatched()->data()]++;
else
dispatched_reports_[report_being_dispatched()->data()] = 1;
dispatched_reports_count_++;
if (simulate_failure_)
OnReportUploadFailure(true /* should_retry */);
else
OnReportUploadSuccess();
if (ProcessingComplete()) {
if (run_loop_)
run_loop_->Quit();
}
}
bool ProcessingComplete() {
return (dispatched_reports_count_ >= expected_reports_);
}
std::unique_ptr<base::RunLoop> run_loop_;
std::map<std::string, unsigned int> dispatched_reports_;
size_t dispatched_reports_count_ = 0;
size_t expected_reports_ = 0;
bool simulate_failure_ = false;
};
} // namespace
class FeedbackUploaderTest : public testing::Test {
public:
FeedbackUploaderTest() {
FeedbackUploader::SetMinimumRetryDelayForTesting(kRetryDelayForTest);
test_shared_loader_factory_ =
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
&test_url_loader_factory_);
EXPECT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
RecreateUploader();
}
FeedbackUploaderTest(const FeedbackUploaderTest&) = delete;
FeedbackUploaderTest& operator=(const FeedbackUploaderTest&) = delete;
~FeedbackUploaderTest() override = default;
void RecreateUploader() {
uploader_ = std::make_unique<MockFeedbackUploader>(
/*is_off_the_record=*/false, scoped_temp_dir_.GetPath(),
test_shared_loader_factory_);
}
void QueueReport(const std::string& data, bool has_email = true) {
uploader_->QueueReport(std::make_unique<std::string>(data), has_email);
}
MockFeedbackUploader* uploader() const { return uploader_.get(); }
void SetupTastTestFeature() {
scoped_feature_list_.InitWithFeatures(
{feedback::features::kSkipSendingFeedbackReportInTastTests}, {});
}
private:
network::TestURLLoaderFactory test_url_loader_factory_;
scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_;
base::test::TaskEnvironment task_environment_;
base::test::ScopedFeatureList scoped_feature_list_;
base::ScopedTempDir scoped_temp_dir_;
std::unique_ptr<MockFeedbackUploader> uploader_;
};
TEST_F(FeedbackUploaderTest, QueueMultiple) {
QueueReport(kReportOne);
QueueReport(kReportTwo);
QueueReport(kReportThree);
QueueReport(kReportFour);
EXPECT_EQ(uploader()->dispatched_reports().size(), 4u);
EXPECT_EQ(uploader()->dispatched_reports().at(kReportOne), 1u);
EXPECT_EQ(uploader()->dispatched_reports().at(kReportTwo), 1u);
EXPECT_EQ(uploader()->dispatched_reports().at(kReportThree), 1u);
EXPECT_EQ(uploader()->dispatched_reports().at(kReportFour), 1u);
}
TEST_F(FeedbackUploaderTest, QueueMultipleWithFailures) {
EXPECT_EQ(kRetryDelayForTest, uploader()->retry_delay());
QueueReport(kReportOne);
// Simulate a failure in reports two and three. Make sure the backoff delay
// will be applied twice, and the reports will eventually be sent.
uploader()->set_simulate_failure(true);
QueueReport(kReportTwo);
EXPECT_EQ(kRetryDelayForTest * 2, uploader()->retry_delay());
QueueReport(kReportThree);
EXPECT_EQ(kRetryDelayForTest * 4, uploader()->retry_delay());
uploader()->set_simulate_failure(false);
// Once a successful report is sent, the backoff delay is reset back to its
// minimum value.
QueueReport(kReportFour);
EXPECT_EQ(kRetryDelayForTest, uploader()->retry_delay());
QueueReport(kReportFive);
// Wait for the pending two failed reports to be sent.
uploader()->set_expected_reports(7);
uploader()->RunMessageLoop();
EXPECT_EQ(uploader()->dispatched_reports().size(), 5u);
EXPECT_EQ(uploader()->dispatched_reports().at(kReportOne), 1u);
EXPECT_EQ(uploader()->dispatched_reports().at(kReportTwo), 2u);
EXPECT_EQ(uploader()->dispatched_reports().at(kReportThree), 2u);
EXPECT_EQ(uploader()->dispatched_reports().at(kReportFour), 1u);
EXPECT_EQ(uploader()->dispatched_reports().at(kReportFive), 1u);
}
#if BUILDFLAG(IS_MAC) && defined(ARCH_CPU_ARM64)
// https://crbug.com/1222877
#define MAYBE_SimulateOfflineReports DISABLED_SimulateOfflineReports
#else
#define MAYBE_SimulateOfflineReports SimulateOfflineReports
#endif
TEST_F(FeedbackUploaderTest, MAYBE_SimulateOfflineReports) {
// Simulate offline reports by failing to upload three reports.
uploader()->set_simulate_failure(true);
QueueReport(kReportOne);
QueueReport(kReportTwo);
QueueReport(kReportThree);
// All three reports will be attempted to be uploaded, but the uploader queue
// will remain having three reports since they all failed.
uploader()->set_expected_reports(3);
uploader()->RunMessageLoop();
EXPECT_EQ(uploader()->dispatched_reports().size(), 3u);
EXPECT_FALSE(uploader()->QueueEmpty());
// Simulate a sign out / resign in by recreating the uploader. This should not
// clear any pending feedback report files on disk, and hence they can be
// reloaded.
RecreateUploader();
uploader()->SimulateLoadingOfflineReports();
uploader()->set_expected_reports(3);
uploader()->RunMessageLoop();
// The three reports were loaded, successfully uploaded, and the uploader
// queue is now empty.
EXPECT_EQ(uploader()->dispatched_reports().size(), 3u);
EXPECT_TRUE(uploader()->QueueEmpty());
}
TEST_F(FeedbackUploaderTest, TastTestsDontSendReports) {
SetupTastTestFeature();
QueueReport(kReportOne);
EXPECT_EQ(uploader()->dispatched_reports().size(), 0u);
}
} // namespace feedback