// Copyright 2017 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 "services/network/data_pipe_element_reader.h" #include #include #include #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/run_loop.h" #include "base/strings/string_number_conversions.h" #include "base/test/task_environment.h" #include "mojo/public/cpp/bindings/pending_remote.h" #include "mojo/public/cpp/bindings/receiver.h" #include "mojo/public/cpp/system/data_pipe.h" #include "mojo/public/cpp/system/data_pipe_utils.h" #include "net/base/io_buffer.h" #include "net/base/test_completion_callback.h" #include "services/network/public/mojom/data_pipe_getter.mojom.h" #include "testing/gtest/include/gtest/gtest.h" // Most tests of this class are at the URLLoader layer. These tests focus on // things too difficult to cover with integration tests. namespace network { namespace { class PassThroughDataPipeGetter : public mojom::DataPipeGetter { public: explicit PassThroughDataPipeGetter() = default; mojo::PendingRemote GetDataPipeGetterRemote() { EXPECT_FALSE(receiver_.is_bound()); return receiver_.BindNewPipeAndPassRemote(); } void WaitForRead(mojo::ScopedDataPipeProducerHandle* write_pipe, ReadCallback* read_callback) { DCHECK(!run_loop_); if (!write_pipe_.is_valid()) { run_loop_ = std::make_unique(); run_loop_->Run(); run_loop_.reset(); } EXPECT_TRUE(write_pipe_.is_valid()); EXPECT_TRUE(read_callback_); *write_pipe = std::move(write_pipe_); *read_callback = std::move(read_callback_); } private: // network::mojom::DataPipeGetter implementation: void Read(mojo::ScopedDataPipeProducerHandle pipe, ReadCallback callback) override { EXPECT_FALSE(write_pipe_.is_valid()); EXPECT_FALSE(read_callback_); write_pipe_ = std::move(pipe); read_callback_ = std::move(callback); if (run_loop_) run_loop_->Quit(); } void Clone( mojo::PendingReceiver receiver) override { NOTIMPLEMENTED(); } std::unique_ptr run_loop_; mojo::Receiver receiver_{this}; mojo::ScopedDataPipeProducerHandle write_pipe_; ReadCallback read_callback_; DISALLOW_COPY_AND_ASSIGN(PassThroughDataPipeGetter); }; class DataPipeElementReaderTest : public testing::Test { public: DataPipeElementReaderTest() : element_reader_(nullptr, data_pipe_getter_.GetDataPipeGetterRemote()) {} protected: base::test::SingleThreadTaskEnvironment task_environment_{ base::test::SingleThreadTaskEnvironment::MainThreadType::IO}; PassThroughDataPipeGetter data_pipe_getter_; DataPipeElementReader element_reader_; }; // Test the case where a second Init() call occurs when there's a pending Init() // call in progress. The first call should be dropped, in favor of the second // one. TEST_F(DataPipeElementReaderTest, InitInterruptsInit) { // Value deliberately outside of the range of an uint32_t, to catch any // accidental conversions to an int. const uint64_t kResponseBodySize = std::numeric_limits::max(); // The network stack calls Init. net::TestCompletionCallback first_init_callback; EXPECT_EQ(net::ERR_IO_PENDING, element_reader_.Init(first_init_callback.callback())); // Wait for DataPipeGetter::Read() to be called. mojo::ScopedDataPipeProducerHandle first_write_pipe; network::mojom::DataPipeGetter::ReadCallback first_read_pipe_callback; data_pipe_getter_.WaitForRead(&first_write_pipe, &first_read_pipe_callback); // The network stack calls Init again, interrupting the previous call. net::TestCompletionCallback second_init_callback; EXPECT_EQ(net::ERR_IO_PENDING, element_reader_.Init(second_init_callback.callback())); // Wait for DataPipeGetter::Read() to be called again. mojo::ScopedDataPipeProducerHandle second_write_pipe; network::mojom::DataPipeGetter::ReadCallback second_read_pipe_callback; data_pipe_getter_.WaitForRead(&second_write_pipe, &second_read_pipe_callback); // Sending data on the first read pipe should do nothing. std::move(first_read_pipe_callback) .Run(net::ERR_FAILED, kResponseBodySize - 1); // Run any pending tasks, to make sure nothing unexpected is queued. base::RunLoop().RunUntilIdle(); EXPECT_FALSE(first_init_callback.have_result()); EXPECT_FALSE(second_init_callback.have_result()); // Sending data on the second pipe should result in the second init callback // being invoked. std::move(second_read_pipe_callback).Run(net::OK, kResponseBodySize); EXPECT_EQ(net::OK, second_init_callback.WaitForResult()); EXPECT_FALSE(first_init_callback.have_result()); EXPECT_EQ(kResponseBodySize, element_reader_.GetContentLength()); EXPECT_EQ(kResponseBodySize, element_reader_.BytesRemaining()); EXPECT_FALSE(element_reader_.IsInMemory()); // Try to read from the body. auto io_buffer = base::MakeRefCounted(10); net::TestCompletionCallback read_callback; EXPECT_EQ(net::ERR_IO_PENDING, element_reader_.Read(io_buffer.get(), io_buffer->size(), read_callback.callback())); // Writes to the first write pipe should either fail, or succeed but be // ignored. mojo::BlockingCopyFromString("foo", first_write_pipe); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(read_callback.have_result()); } // Test the case where a second Init() call occurs when there's a pending Read() // call in progress. The old Read() should be dropped, in favor of the new // Init(). TEST_F(DataPipeElementReaderTest, InitInterruptsRead) { // Value deliberately outside of the range of an uint32_t, to catch any // accidental conversions to an int. const uint64_t kResponseBodySize = std::numeric_limits::max(); // The network stack calls Init. net::TestCompletionCallback first_init_callback; EXPECT_EQ(net::ERR_IO_PENDING, element_reader_.Init(first_init_callback.callback())); // Wait for DataPipeGetter::Read() to be called. mojo::ScopedDataPipeProducerHandle first_write_pipe; network::mojom::DataPipeGetter::ReadCallback first_read_pipe_callback; data_pipe_getter_.WaitForRead(&first_write_pipe, &first_read_pipe_callback); std::move(first_read_pipe_callback).Run(net::OK, kResponseBodySize); ASSERT_EQ(net::OK, first_init_callback.WaitForResult()); auto first_io_buffer = base::MakeRefCounted(10); net::TestCompletionCallback first_read_callback; EXPECT_EQ(net::ERR_IO_PENDING, element_reader_.Read(first_io_buffer.get(), first_io_buffer->size(), first_read_callback.callback())); // The network stack calls Init again, interrupting the previous read. net::TestCompletionCallback second_init_callback; EXPECT_EQ(net::ERR_IO_PENDING, element_reader_.Init(second_init_callback.callback())); // Wait for DataPipeGetter::Read() to be called again. mojo::ScopedDataPipeProducerHandle second_write_pipe; network::mojom::DataPipeGetter::ReadCallback second_read_pipe_callback; data_pipe_getter_.WaitForRead(&second_write_pipe, &second_read_pipe_callback); // Run any pending tasks, to make sure nothing unexpected is queued. base::RunLoop().RunUntilIdle(); EXPECT_FALSE(first_read_callback.have_result()); EXPECT_FALSE(second_init_callback.have_result()); // Sending data on the second pipe should result in the second init callback // being invoked. std::move(second_read_pipe_callback).Run(net::OK, kResponseBodySize); EXPECT_EQ(net::OK, second_init_callback.WaitForResult()); EXPECT_EQ(kResponseBodySize, element_reader_.GetContentLength()); EXPECT_EQ(kResponseBodySize, element_reader_.BytesRemaining()); EXPECT_FALSE(element_reader_.IsInMemory()); // Try to read from the body. auto io_buffer = base::MakeRefCounted(10); net::TestCompletionCallback second_read_callback; EXPECT_EQ(net::ERR_IO_PENDING, element_reader_.Read(io_buffer.get(), io_buffer->size(), second_read_callback.callback())); // Writes to the first write pipe should either fail, or succeed but be // ignored. mojo::BlockingCopyFromString("foo", first_write_pipe); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(second_read_callback.have_result()); } } // namespace } // namespace network