Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(496)

Unified Diff: content/browser/download/download_file_unittest.cc

Issue 2712713007: Make DownloadFileImpl handle multiple byte streams. (Closed)
Patch Set: Remove the AppendDataToFile call, fix the browsertest. Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « content/browser/download/download_file_impl.cc ('k') | content/browser/download/download_stats.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: content/browser/download/download_file_unittest.cc
diff --git a/content/browser/download/download_file_unittest.cc b/content/browser/download/download_file_unittest.cc
index 5a3262257dae8382091f78712b00d48e9c442eb6..ecf45ef6d146d4a6e0755a0292f54f89ec0a36ac 100644
--- a/content/browser/download/download_file_unittest.cc
+++ b/content/browser/download/download_file_unittest.cc
@@ -11,6 +11,7 @@
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/location.h"
+#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_number_conversions.h"
@@ -44,6 +45,15 @@ using ::testing::StrictMock;
namespace content {
namespace {
+// Struct for SourceStream states verification.
+struct SourceStreamTestData {
+ SourceStreamTestData(int64_t offset, int64_t bytes_written, bool finished)
+ : offset(offset), bytes_written(bytes_written), finished(finished) {}
+ int64_t offset;
+ int64_t bytes_written;
+ bool finished;
+};
+
std::string GetHexEncodedHashValue(crypto::SecureHash* hash_state) {
if (!hash_state)
return std::string();
@@ -104,11 +114,13 @@ class TestDownloadFileImpl : public DownloadFileImpl {
const base::FilePath& default_downloads_directory,
std::unique_ptr<ByteStreamReader> stream,
const net::NetLogWithSource& net_log,
+ bool is_sparse_file,
base::WeakPtr<DownloadDestinationObserver> observer)
: DownloadFileImpl(std::move(save_info),
default_downloads_directory,
std::move(stream),
net_log,
+ is_sparse_file,
observer) {}
protected:
@@ -133,6 +145,8 @@ class DownloadFileTest : public testing::Test {
static const char kTestData1[];
static const char kTestData2[];
static const char kTestData3[];
+ static const char kTestData4[];
+ static const char kTestData5[];
static const char kDataHash[];
static const char kEmptyHash[];
static const uint32_t kDummyDownloadId;
@@ -143,6 +157,7 @@ class DownloadFileTest : public testing::Test {
: observer_(new StrictMock<MockDownloadDestinationObserver>),
observer_factory_(observer_.get()),
input_stream_(NULL),
+ input_stream_1_(NULL),
bytes_(-1),
bytes_per_sec_(-1) {}
@@ -177,7 +192,9 @@ class DownloadFileTest : public testing::Test {
closure.Run();
}
- bool CreateDownloadFile(int offset, bool calculate_hash) {
+ bool CreateDownloadFile(int offset,
+ bool calculate_hash,
+ bool is_sparse_file = false) {
// There can be only one.
DCHECK(!download_file_.get());
@@ -193,7 +210,8 @@ class DownloadFileTest : public testing::Test {
download_file_.reset(new TestDownloadFileImpl(
std::move(save_info), base::FilePath(),
std::unique_ptr<ByteStreamReader>(input_stream_),
- net::NetLogWithSource(), observer_factory_.GetWeakPtr()));
+ net::NetLogWithSource(), is_sparse_file,
+ observer_factory_.GetWeakPtr()));
EXPECT_CALL(*input_stream_, Read(_, _))
.WillOnce(Return(ByteStreamReader::STREAM_EMPTY))
@@ -211,36 +229,54 @@ class DownloadFileTest : public testing::Test {
return result == DOWNLOAD_INTERRUPT_REASON_NONE;
}
- void DestroyDownloadFile(int offset) {
+ void DestroyDownloadFile(int offset, bool compare_disk_data = true) {
EXPECT_FALSE(download_file_->InProgress());
// Make sure the data has been properly written to disk.
- std::string disk_data;
- EXPECT_TRUE(base::ReadFileToString(download_file_->FullPath(), &disk_data));
- EXPECT_EQ(expected_data_, disk_data);
+ if (compare_disk_data) {
+ std::string disk_data;
+ EXPECT_TRUE(
+ base::ReadFileToString(download_file_->FullPath(), &disk_data));
+ EXPECT_EQ(expected_data_, disk_data);
+ }
// Make sure the Browser and File threads outlive the DownloadFile
// to satisfy thread checks inside it.
download_file_.reset();
}
- // Setup the stream to do be a data append; don't actually trigger
- // the callback or do verifications.
- void SetupDataAppend(const char **data_chunks, size_t num_chunks,
- ::testing::Sequence s) {
- DCHECK(input_stream_);
+ // Setup the stream to append data or write from |offset| to the file.
+ // Don't actually trigger the callback or do verifications.
+ void SetupDataAppend(const char** data_chunks,
+ size_t num_chunks,
+ MockByteStreamReader* stream_reader,
+ ::testing::Sequence s,
+ int64_t offset = -1) {
+ DCHECK(stream_reader);
+ size_t current_pos = static_cast<size_t>(offset);
for (size_t i = 0; i < num_chunks; i++) {
const char *source_data = data_chunks[i];
size_t length = strlen(source_data);
scoped_refptr<net::IOBuffer> data = new net::IOBuffer(length);
memcpy(data->data(), source_data, length);
- EXPECT_CALL(*input_stream_, Read(_, _))
+ EXPECT_CALL(*stream_reader, Read(_, _))
.InSequence(s)
- .WillOnce(DoAll(SetArgPointee<0>(data),
- SetArgPointee<1>(length),
+ .WillOnce(DoAll(SetArgPointee<0>(data), SetArgPointee<1>(length),
Return(ByteStreamReader::STREAM_HAS_DATA)))
.RetiresOnSaturation();
- expected_data_ += source_data;
+
+ if (offset < 0) {
+ // Append data.
+ expected_data_ += source_data;
+ continue;
+ }
+
+ // Write from offset. May fill holes with '\0'.
+ size_t new_len = current_pos + length;
+ if (new_len > expected_data_.size())
+ expected_data_.append(new_len - expected_data_.size(), '\0');
+ expected_data_.replace(current_pos, length, source_data);
+ current_pos += length;
}
}
@@ -254,7 +290,7 @@ class DownloadFileTest : public testing::Test {
// TODO(rdsmith): Manage full percentage issues properly.
void AppendDataToFile(const char **data_chunks, size_t num_chunks) {
::testing::Sequence s1;
- SetupDataAppend(data_chunks, num_chunks, s1);
+ SetupDataAppend(data_chunks, num_chunks, input_stream_, s1);
EXPECT_CALL(*input_stream_, Read(_, _))
.InSequence(s1)
.WillOnce(Return(ByteStreamReader::STREAM_EMPTY))
@@ -264,24 +300,24 @@ class DownloadFileTest : public testing::Test {
}
void SetupFinishStream(DownloadInterruptReason interrupt_reason,
- ::testing::Sequence s) {
- EXPECT_CALL(*input_stream_, Read(_, _))
+ MockByteStreamReader* stream_reader,
+ ::testing::Sequence s) {
+ EXPECT_CALL(*stream_reader, Read(_, _))
.InSequence(s)
.WillOnce(Return(ByteStreamReader::STREAM_COMPLETE))
.RetiresOnSaturation();
- EXPECT_CALL(*input_stream_, GetStatus())
+ EXPECT_CALL(*stream_reader, GetStatus())
.InSequence(s)
.WillOnce(Return(interrupt_reason))
.RetiresOnSaturation();
- EXPECT_CALL(*input_stream_, RegisterCallback(_))
- .RetiresOnSaturation();
+ EXPECT_CALL(*stream_reader, RegisterCallback(_)).RetiresOnSaturation();
}
void FinishStream(DownloadInterruptReason interrupt_reason,
bool check_observer,
const std::string& expected_hash) {
::testing::Sequence s1;
- SetupFinishStream(interrupt_reason, s1);
+ SetupFinishStream(interrupt_reason, input_stream_, s1);
sink_callback_.Run();
VerifyStreamAndSize();
if (check_observer) {
@@ -355,16 +391,81 @@ class DownloadFileTest : public testing::Test {
return result_reason;
}
+ // Prepare two byte streams to write to the same file sink.
+ void PrepareMultipleStreams(int64_t second_stream_length) {
+ // Create a sparse file.
+ ASSERT_TRUE(CreateDownloadFile(0, true, true));
+ base::FilePath initial_path(download_file_->FullPath());
+ EXPECT_TRUE(base::PathExists(initial_path));
+ DCHECK(download_file_);
+
+ const char* stream_0_data[] = {kTestData1, kTestData2};
+ const char* stream_1_data[] = {kTestData4, kTestData5};
+ size_t stream_1_offset = strlen(kTestData1) + strlen(kTestData2);
+
+ // Register second SourceStream entry for the second stream.
+ // The first stream should be registered in ctor of DownloadFile.
+ DownloadFileImpl::SourceStreams& source_streams =
+ download_file_->source_streams_;
+ EXPECT_EQ(static_cast<size_t>(1), source_streams.size());
+ source_streams[stream_1_offset] =
+ base::MakeUnique<DownloadFileImpl::SourceStream>(stream_1_offset,
+ second_stream_length);
+
+ // Create the second byte stream. Will be moved to DownloadFile.
+ input_stream_1_ = new MockByteStreamReader();
+
+ ::testing::Sequence s0;
+ ::testing::Sequence s1;
+ SetupDataAppend(stream_1_data, 2, input_stream_1_, s1, stream_1_offset);
+ SetupDataAppend(stream_0_data, 2, input_stream_, s0, 0);
+ SetupFinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, input_stream_, s0);
+
+ // Expectation on MockByteStreamReader for MultipleStreams tests:
+ // 1. RegisterCallback: Must called twice. One to set the callback, the
+ // other to release the stream.
+ // 2. Read: If filled with N buffer, called (N+1) times, where the last Read
+ // call doesn't read any data but returns STRAM_COMPLETE.
+ // The stream may terminate in the middle and less Read calls are expected.
+ // 3. GetStatus: Only called if the stream is completed and last Read call
+ // returns STREAM_COMPLETE.
+ if (second_stream_length == 0)
+ SetupFinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, input_stream_1_, s1);
+ else
+ EXPECT_CALL(*input_stream_1_, RegisterCallback(_)).RetiresOnSaturation();
+
+ EXPECT_CALL(*input_stream_1_, RegisterCallback(_)).RetiresOnSaturation();
+ }
+
+ void VerifySourceStreamsStates(const SourceStreamTestData& data) {
+ DCHECK(download_file_->source_streams_.find(data.offset) !=
+ download_file_->source_streams_.end());
+ DownloadFileImpl::SourceStream* stream =
+ download_file_->source_streams_[data.offset].get();
+ DCHECK(stream);
+ EXPECT_EQ(data.offset, stream->offset());
+ EXPECT_EQ(data.bytes_written, stream->bytes_written());
+ EXPECT_EQ(data.finished, stream->is_finished());
+ }
+
+ int64_t TotalBytesReceived() const {
+ DCHECK(download_file_);
+ return download_file_->TotalBytesReceived();
+ }
+
std::unique_ptr<StrictMock<MockDownloadDestinationObserver>> observer_;
base::WeakPtrFactory<DownloadDestinationObserver> observer_factory_;
// DownloadFile instance we are testing.
- std::unique_ptr<DownloadFile> download_file_;
+ std::unique_ptr<DownloadFileImpl> download_file_;
// Stream for sending data into the download file.
// Owned by download_file_; will be alive for lifetime of download_file_.
StrictMock<MockByteStreamReader>* input_stream_;
+ // A second byte stream to test multiple stream write.
+ MockByteStreamReader* input_stream_1_;
+
// Sink callback data for stream.
base::Closure sink_callback_;
@@ -422,6 +523,8 @@ const char DownloadFileTest::kTestData1[] =
"Let's write some data to the file!\n";
const char DownloadFileTest::kTestData2[] = "Writing more data.\n";
const char DownloadFileTest::kTestData3[] = "Final line.";
+const char DownloadFileTest::kTestData4[] = "abcdefg";
+const char DownloadFileTest::kTestData5[] = "01234";
const char DownloadFileTest::kDataHash[] =
"CBF68BF10F8003DB86B31343AFAC8C7175BD03FB5FC905650F8C80AF087443A8";
const char DownloadFileTest::kEmptyHash[] =
@@ -752,8 +855,8 @@ TEST_F(DownloadFileTest, StreamNonEmptySuccess) {
const char* chunks1[] = { kTestData1, kTestData2 };
::testing::Sequence s1;
- SetupDataAppend(chunks1, 2, s1);
- SetupFinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, s1);
+ SetupDataAppend(chunks1, 2, input_stream_, s1);
+ SetupFinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, input_stream_, s1);
EXPECT_CALL(*(observer_.get()), MockDestinationCompleted(_, _));
sink_callback_.Run();
VerifyStreamAndSize();
@@ -768,8 +871,9 @@ TEST_F(DownloadFileTest, StreamNonEmptyError) {
const char* chunks1[] = { kTestData1, kTestData2 };
::testing::Sequence s1;
- SetupDataAppend(chunks1, 2, s1);
- SetupFinishStream(DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED, s1);
+ SetupDataAppend(chunks1, 2, input_stream_, s1);
+ SetupFinishStream(DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED,
+ input_stream_, s1);
EXPECT_CALL(*(observer_.get()),
MockDestinationError(
@@ -791,4 +895,105 @@ TEST_F(DownloadFileTest, StreamNonEmptyError) {
DestroyDownloadFile(0);
}
+// Tests for concurrent streams handling, used for parallel download.
+//
+// Activate both streams at the same time.
+TEST_F(DownloadFileTest, MutipleStreamsWrite) {
+ PrepareMultipleStreams(0);
+ EXPECT_CALL(*(observer_.get()), MockDestinationCompleted(_, _));
+
+ int64_t stream_0_length =
+ static_cast<int64_t>(strlen(kTestData1) + strlen(kTestData2));
+ int64_t stream_1_length =
+ static_cast<int64_t>(strlen(kTestData4) + strlen(kTestData5));
+
+ download_file_->AddByteStream(
+ std::unique_ptr<MockByteStreamReader>(input_stream_1_), stream_0_length);
+ sink_callback_.Run();
+ base::RunLoop().RunUntilIdle();
+
+ SourceStreamTestData stream_data_0(0, stream_0_length, true);
+ SourceStreamTestData stream_data_1(stream_0_length, stream_1_length, true);
+ VerifySourceStreamsStates(stream_data_0);
+ VerifySourceStreamsStates(stream_data_1);
+ EXPECT_EQ(stream_0_length + stream_1_length, TotalBytesReceived());
+
+ DestroyDownloadFile(0);
+}
+
+// Activate and deplete one stream, later add the second stream.
+TEST_F(DownloadFileTest, MutipleStreamsOneStreamFirst) {
+ PrepareMultipleStreams(0);
+
+ int64_t stream_0_length =
+ static_cast<int64_t>(strlen(kTestData1) + strlen(kTestData2));
+ int64_t stream_1_length =
+ static_cast<int64_t>(strlen(kTestData4) + strlen(kTestData5));
+
+ // Deplete the first stream.
+ sink_callback_.Run();
+ base::RunLoop().RunUntilIdle();
+
+ SourceStreamTestData stream_data_0(0, stream_0_length, true);
+ SourceStreamTestData stream_data_1(stream_0_length, 0, false);
+ VerifySourceStreamsStates(stream_data_0);
+ VerifySourceStreamsStates(stream_data_1);
+ EXPECT_EQ(stream_0_length, TotalBytesReceived());
+
+ // Won't inform the observer until the second stream is depleted.
+ EXPECT_CALL(*(observer_.get()), MockDestinationCompleted(_, _));
+
+ // Drain the second stream after the first stream is depleted.
+ download_file_->AddByteStream(
+ std::unique_ptr<MockByteStreamReader>(input_stream_1_), stream_0_length);
+ base::RunLoop().RunUntilIdle();
+
+ stream_data_1.bytes_written = stream_1_length;
+ stream_data_1.finished = true;
+ VerifySourceStreamsStates(stream_data_0);
+ VerifySourceStreamsStates(stream_data_1);
+ EXPECT_EQ(stream_0_length + stream_1_length, TotalBytesReceived());
+
+ DestroyDownloadFile(0);
+}
+
+// Two streams write to one sink, the second stream has a limited length.
+TEST_F(DownloadFileTest, MutipleStreamsLimitedLength) {
+ // The second stream has two buffers, kTestData4 and kTestData5.
+ // The length limit is set to less than the length of kTestData4.
+ // kTestData4 should be partially written to disk, where kTestData5 should be
+ // ignored.
+ int64_t stream_0_length =
+ static_cast<int64_t>(strlen(kTestData1) + strlen(kTestData2));
+ int64_t stream_1_length = static_cast<int64_t>(strlen(kTestData4)) - 1;
+ PrepareMultipleStreams(stream_1_length);
+
+ EXPECT_CALL(*(observer_.get()), MockDestinationCompleted(_, _));
+
+ download_file_->AddByteStream(
+ std::unique_ptr<MockByteStreamReader>(input_stream_1_), stream_0_length);
+ sink_callback_.Run();
+ base::RunLoop().RunUntilIdle();
+
+ SourceStreamTestData stream_data_0(0, stream_0_length, true);
+ SourceStreamTestData stream_data_1(stream_0_length, stream_1_length, true);
+ VerifySourceStreamsStates(stream_data_0);
+ VerifySourceStreamsStates(stream_data_1);
+ EXPECT_EQ(stream_0_length + stream_1_length, TotalBytesReceived());
+
+ std::string disk_data, expected_data;
+ EXPECT_TRUE(base::ReadFileToString(download_file_->FullPath(), &disk_data));
+ expected_data.append(kTestData1).append(kTestData2).append(kTestData4);
+ expected_data = expected_data.substr(0, stream_0_length + stream_1_length);
+ EXPECT_EQ(expected_data, disk_data);
+
+ // Finish the second stream.
+ // TODO(xingliu): Refactor test code to deal with unfinished streams.
+ scoped_refptr<net::IOBuffer> data = new net::IOBuffer(strlen(kTestData5));
+ size_t size;
+ input_stream_1_->Read(&data, &size);
+
+ DestroyDownloadFile(0, false);
+}
+
} // namespace content
« no previous file with comments | « content/browser/download/download_file_impl.cc ('k') | content/browser/download/download_stats.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698