blob: f73398c96eb3b703fbddb98c7193415a95401618 [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 "content/browser/preloading/prefetch/prefetch_data_pipe_tee.h"
#include "base/containers/span.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "mojo/public/cpp/system/string_data_source.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
namespace {
class DataPipeReader {
public:
explicit DataPipeReader(mojo::ScopedDataPipeConsumerHandle consumer_handle)
: consumer_handle_(std::move(consumer_handle)) {
DCHECK(consumer_handle_);
}
std::string ReadData(uint32_t size) {
size_ = size;
data_.clear();
if (!consumer_handle_) {
return std::string();
}
base::RunLoop run_loop;
on_read_done_ = run_loop.QuitClosure();
mojo::SimpleWatcher watcher(FROM_HERE,
mojo::SimpleWatcher::ArmingPolicy::AUTOMATIC,
base::SequencedTaskRunner::GetCurrentDefault());
watcher.Watch(consumer_handle_.get(), MOJO_HANDLE_SIGNAL_READABLE,
base::BindRepeating(&DataPipeReader::OnDataAvailable,
base::Unretained(this)));
run_loop.Run();
on_read_done_.Reset();
watcher.Cancel();
return data_;
}
DataPipeReader(const DataPipeReader&) = delete;
DataPipeReader& operator=(const DataPipeReader&) = delete;
~DataPipeReader() = default;
private:
void OnDataAvailable(MojoResult result) {
DCHECK_LT(data_.size(), size_);
size_t size = size_ - data_.size();
size_t actually_read_bytes = 0;
std::string buffer(size, '\0');
MojoResult read_result = consumer_handle_->ReadData(
MOJO_READ_DATA_FLAG_NONE, base::as_writable_byte_span(buffer),
actually_read_bytes);
if (read_result == MOJO_RESULT_OK) {
data_.append(std::string_view(buffer).substr(0, actually_read_bytes));
if (data_.size() >= size_) {
on_read_done_.Run();
}
} else if (read_result != MOJO_RESULT_SHOULD_WAIT) {
on_read_done_.Run();
}
}
mojo::ScopedDataPipeConsumerHandle consumer_handle_;
size_t size_;
std::string data_;
base::RepeatingClosure on_read_done_;
};
// If the `bool` param is `true`, `PrefetchDataPipeTee` reference (`tee_`) is
// released earlier to make sure that `PrefetchDataPipeTee` is kept alive and
// data pipe cloning completes correctly even if there are no strong references
// to `PrefetchDataPipeTee` from outside.
class PrefetchDataPipeTeeTest : public ::testing::Test,
public ::testing::WithParamInterface<bool> {
public:
void Write(const std::string& content,
base::OnceClosure write_complete_callback) {
source_producer_->Write(
std::make_unique<mojo::StringDataSource>(
content, mojo::StringDataSource::AsyncWritingMode::
STRING_MAY_BE_INVALIDATED_BEFORE_COMPLETION),
base::BindOnce(
[](base::OnceClosure write_complete_callback, MojoResult result) {
std::move(write_complete_callback).Run();
},
std::move(write_complete_callback)));
}
void Write(const std::vector<std::string>& contents) {
for (auto& content : contents) {
base::RunLoop loop;
Write(content, loop.QuitClosure());
loop.Run();
}
}
void WriteComplete() { source_producer_.reset(); }
void ResetTee() {
if (GetParam()) {
tee_.reset();
}
}
void ResetTeeForce() { tee_.reset(); }
PrefetchDataPipeTee& tee() { return *tee_; }
base::test::TaskEnvironment& task_environment() { return task_environment_; }
base::HistogramTester& histogram_tester() { return *histogram_tester_; }
static constexpr int kProducerPipeCapacity = 1024;
static constexpr int kBufferLimit = 8;
private:
void SetUp() override {
mojo::ScopedDataPipeConsumerHandle source_consumer_handle;
mojo::ScopedDataPipeProducerHandle source_producer_handle;
CHECK_EQ(mojo::CreateDataPipe(kProducerPipeCapacity, source_producer_handle,
source_consumer_handle),
MOJO_RESULT_OK);
source_producer_ = std::make_unique<mojo::DataPipeProducer>(
std::move(source_producer_handle));
tee_ = base::MakeRefCounted<PrefetchDataPipeTee>(
std::move(source_consumer_handle), kBufferLimit);
histogram_tester_ = std::make_unique<base::HistogramTester>();
}
std::unique_ptr<mojo::DataPipeProducer> source_producer_;
scoped_refptr<PrefetchDataPipeTee> tee_;
base::test::TaskEnvironment task_environment_;
std::unique_ptr<base::HistogramTester> histogram_tester_;
};
TEST_P(PrefetchDataPipeTeeTest, FirstTargetAddedThenLoaded) {
Write({"B"});
auto target1 = DataPipeReader(tee().Clone());
EXPECT_FALSE(tee().Clone());
// Data are sent to the target in a streaming fashion during loading.
EXPECT_EQ(target1.ReadData(1), "B");
Write({"o"});
EXPECT_EQ(target1.ReadData(1), "o");
Write({"d"});
EXPECT_EQ(target1.ReadData(1), "d");
Write({"y"});
EXPECT_EQ(target1.ReadData(1), "y");
WriteComplete();
task_environment().RunUntilIdle();
// After the source is complete and within the buffer limit, any number of
// targets can be added.
auto target2 = DataPipeReader(tee().Clone());
auto target3 = DataPipeReader(tee().Clone());
ResetTee();
EXPECT_EQ(target1.ReadData(32), "");
EXPECT_EQ(target2.ReadData(32), "Body");
EXPECT_EQ(target3.ReadData(32), "Body");
ResetTeeForce();
histogram_tester().ExpectUniqueSample(
"Preloading.Prefetch.PrefetchDataPipeTeeDtorState",
PrefetchDataPipeTee::State::kLoaded, 1);
EXPECT_EQ(1u, histogram_tester()
.GetTotalCountsForPrefix(
"Preloading.Prefetch.PrefetchDataPipeTeeCloneFailed.")
.size());
EXPECT_THAT(histogram_tester().GetAllSamples(
"Preloading.Prefetch.PrefetchDataPipeTeeCloneFailed.Loading"),
testing::UnorderedElementsAre(base::Bucket(2, 1)));
}
TEST_P(PrefetchDataPipeTeeTest, FirstTargetAddedAndRemoved) {
Write({"Bo", "dy"});
auto target1 = std::make_unique<DataPipeReader>(tee().Clone());
EXPECT_FALSE(tee().Clone());
EXPECT_EQ(target1->ReadData(4), "Body");
target1.reset();
task_environment().RunUntilIdle();
// After the first target is destructed, a new target can be added.
auto target2 = std::make_unique<DataPipeReader>(tee().Clone());
EXPECT_EQ(target2->ReadData(4), "Body");
Write({" exceeds", " ", "limit"});
EXPECT_EQ(target2->ReadData(14), " exceeds limit");
EXPECT_FALSE(tee().Clone());
target2.reset();
task_environment().RunUntilIdle();
// Even after targets are destructed, targets can't be added once the buffer
// limit is exceeded. Data can be written to the source data pipe, but are
// just discarded.
EXPECT_FALSE(tee().Clone());
Write({" foo"});
WriteComplete();
task_environment().RunUntilIdle();
EXPECT_FALSE(tee().Clone());
ResetTeeForce();
histogram_tester().ExpectUniqueSample(
"Preloading.Prefetch.PrefetchDataPipeTeeDtorState",
PrefetchDataPipeTee::State::kSizeExceeded, 1);
EXPECT_EQ(2u, histogram_tester()
.GetTotalCountsForPrefix(
"Preloading.Prefetch.PrefetchDataPipeTeeCloneFailed.")
.size());
EXPECT_THAT(histogram_tester().GetAllSamples(
"Preloading.Prefetch.PrefetchDataPipeTeeCloneFailed.Loading"),
testing::UnorderedElementsAre(base::Bucket(2, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(
"Preloading.Prefetch.PrefetchDataPipeTeeCloneFailed.SizeExceeded"),
testing::UnorderedElementsAre(base::Bucket(5, 1), base::Bucket(4, 1),
base::Bucket(6, 1)));
}
TEST_P(PrefetchDataPipeTeeTest, LoadedThenFirstTargetAdded) {
Write({"Bo", "dy"});
WriteComplete();
task_environment().RunUntilIdle();
// After the source is complete and within the buffer limit, any number of
// targets can be added.
auto target1 = DataPipeReader(tee().Clone());
auto target2 = DataPipeReader(tee().Clone());
ResetTee();
EXPECT_EQ(target1.ReadData(32), "Body");
EXPECT_EQ(target2.ReadData(32), "Body");
ResetTeeForce();
histogram_tester().ExpectUniqueSample(
"Preloading.Prefetch.PrefetchDataPipeTeeDtorState",
PrefetchDataPipeTee::State::kLoaded, 1);
EXPECT_EQ(0u, histogram_tester()
.GetTotalCountsForPrefix(
"Preloading.Prefetch.PrefetchDataPipeTeeCloneFailed.")
.size());
}
TEST_P(PrefetchDataPipeTeeTest, FirstTargetAddedThenExceedLimit) {
Write({"Bo", "dy"});
auto target1 = DataPipeReader(tee().Clone());
EXPECT_FALSE(tee().Clone());
Write({" exceeds", " ", "limit"});
EXPECT_FALSE(tee().Clone());
WriteComplete();
task_environment().RunUntilIdle();
// Even after the source is complete, no target can be added because the
// buffer is already discarded due to size limit.
EXPECT_FALSE(tee().Clone());
ResetTee();
EXPECT_EQ(target1.ReadData(32), "Body exceeds limit");
ResetTeeForce();
histogram_tester().ExpectUniqueSample(
"Preloading.Prefetch.PrefetchDataPipeTeeDtorState",
PrefetchDataPipeTee::State::kSizeExceeded, 1);
EXPECT_EQ(2u, histogram_tester()
.GetTotalCountsForPrefix(
"Preloading.Prefetch.PrefetchDataPipeTeeCloneFailed.")
.size());
EXPECT_THAT(histogram_tester().GetAllSamples(
"Preloading.Prefetch.PrefetchDataPipeTeeCloneFailed.Loading"),
testing::UnorderedElementsAre(base::Bucket(2, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(
"Preloading.Prefetch.PrefetchDataPipeTeeCloneFailed.SizeExceeded"),
testing::UnorderedElementsAre(base::Bucket(3, 1), base::Bucket(4, 1)));
}
TEST_P(PrefetchDataPipeTeeTest, ExceedLimitThenFirstTargetAdded) {
Write({"Bo", "dy", " exceeds", " ", "limit"});
auto target1 = DataPipeReader(tee().Clone());
EXPECT_FALSE(tee().Clone());
WriteComplete();
task_environment().RunUntilIdle();
// Even after the source is complete, no target can be added because the
// buffer is already discarded due to size limit.
EXPECT_FALSE(tee().Clone());
ResetTee();
EXPECT_EQ(target1.ReadData(32), "Body exceeds limit");
ResetTeeForce();
histogram_tester().ExpectUniqueSample(
"Preloading.Prefetch.PrefetchDataPipeTeeDtorState",
PrefetchDataPipeTee::State::kSizeExceeded, 1);
EXPECT_EQ(1u, histogram_tester()
.GetTotalCountsForPrefix(
"Preloading.Prefetch.PrefetchDataPipeTeeCloneFailed.")
.size());
EXPECT_THAT(
histogram_tester().GetAllSamples(
"Preloading.Prefetch.PrefetchDataPipeTeeCloneFailed.SizeExceeded"),
testing::UnorderedElementsAre(base::Bucket(2, 1), base::Bucket(3, 1)));
}
TEST_P(PrefetchDataPipeTeeTest, ExceedLimitLargeData) {
Write({"Bo", "dy", " exceeds", " ", "limit"});
// Larger than producer data pipe size created in `SetUp()`.
std::string large_content(kProducerPipeCapacity * 2, '-');
base::RunLoop write_loop1;
Write(large_content, write_loop1.QuitClosure());
task_environment().RunUntilIdle();
EXPECT_FALSE(write_loop1.AnyQuitCalled());
auto target1 = DataPipeReader(tee().Clone());
EXPECT_FALSE(tee().Clone());
ResetTee();
// Full data can be read.
std::string expected = "Body exceeds limit" + large_content;
EXPECT_EQ(target1.ReadData(expected.size()), expected);
write_loop1.Run();
// Data written after that can be also read.
base::RunLoop write_loop2;
Write(large_content, write_loop2.QuitClosure());
EXPECT_EQ(target1.ReadData(large_content.size()), large_content);
write_loop2.Run();
// End of data can be observed.
WriteComplete();
task_environment().RunUntilIdle();
EXPECT_EQ(target1.ReadData(32), "");
ResetTeeForce();
histogram_tester().ExpectUniqueSample(
"Preloading.Prefetch.PrefetchDataPipeTeeDtorState",
PrefetchDataPipeTee::State::kSizeExceeded, 1);
EXPECT_EQ(1u, histogram_tester()
.GetTotalCountsForPrefix(
"Preloading.Prefetch.PrefetchDataPipeTeeCloneFailed.")
.size());
EXPECT_THAT(
histogram_tester().GetAllSamples(
"Preloading.Prefetch.PrefetchDataPipeTeeCloneFailed.SizeExceeded"),
testing::UnorderedElementsAre(base::Bucket(2, 1)));
}
TEST_P(PrefetchDataPipeTeeTest, ExceedLimitAndLoadedThenFirstTargetAdded) {
Write({"Bo", "dy", " exceeds", " ", "limit"});
WriteComplete();
task_environment().RunUntilIdle();
auto target1 = DataPipeReader(tee().Clone());
EXPECT_FALSE(tee().Clone());
ResetTee();
EXPECT_EQ(target1.ReadData(32), "Body exceeds limit");
ResetTeeForce();
histogram_tester().ExpectUniqueSample(
"Preloading.Prefetch.PrefetchDataPipeTeeDtorState",
PrefetchDataPipeTee::State::kSizeExceeded, 1);
EXPECT_EQ(1u, histogram_tester()
.GetTotalCountsForPrefix(
"Preloading.Prefetch.PrefetchDataPipeTeeCloneFailed.")
.size());
EXPECT_THAT(
histogram_tester().GetAllSamples(
"Preloading.Prefetch.PrefetchDataPipeTeeCloneFailed.SizeExceeded"),
testing::UnorderedElementsAre(base::Bucket(2, 1)));
}
INSTANTIATE_TEST_SUITE_P(ParametrizedTests,
PrefetchDataPipeTeeTest,
testing::Bool());
} // namespace
} // namespace content