chromium/ios/net/chunked_data_stream_uploader_unittest.cc

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ios/net/chunked_data_stream_uploader.h"

#include <array>
#include <memory>

#include "base/functional/bind.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"

namespace net {

namespace {
const int kDefaultIOBufferSize = 1024;
}

// Mock delegate to provide data from its internal buffer.
class MockChunkedDataStreamUploaderDelegate
    : public ChunkedDataStreamUploader::Delegate {
 public:
  MockChunkedDataStreamUploaderDelegate() : data_length_(0) {}
  ~MockChunkedDataStreamUploaderDelegate() override {}

  int OnRead(char* buffer, int buffer_length) override {
    int bytes_read = 0;
    if (data_length_ > 0) {
      CHECK_GE(buffer_length, data_length_);
      memcpy(buffer, data_, data_length_);
      bytes_read = data_length_;
      data_length_ = 0;
    }
    return bytes_read;
  }

  void SetReadData(const char* data, int data_length) {
    CHECK_GE(sizeof(data_), static_cast<size_t>(data_length));
    memcpy(data_, data, data_length);
    data_length_ = data_length;
    CHECK(!memcmp(data_, data, data_length));
  }

 private:
  char data_[kDefaultIOBufferSize];
  int data_length_;
};

class ChunkedDataStreamUploaderTest : public PlatformTest {
 public:
  ChunkedDataStreamUploaderTest() : callback_count(0) {
    delegate_ = std::make_unique<MockChunkedDataStreamUploaderDelegate>();
    uploader_owner_ =
        std::make_unique<ChunkedDataStreamUploader>(delegate_.get());
    uploader_ = uploader_owner_->GetWeakPtr();

    uploader_owner_->Init(base::BindRepeating([](int) {}),
                          net::NetLogWithSource());
  }

  void CompletionCallback(int result) { ++callback_count; }

 protected:
  std::unique_ptr<MockChunkedDataStreamUploaderDelegate> delegate_;

  std::unique_ptr<ChunkedDataStreamUploader> uploader_owner_;
  base::WeakPtr<ChunkedDataStreamUploader> uploader_;

  // Completion callback counter for each case.
  int callback_count;
};

// Tests that data from the application layer become ready before the network
// layer callback.
TEST_F(ChunkedDataStreamUploaderTest, ExternalDataReadyFirst) {
  // Application layer data is ready.
  const char kTestData[] = "Hello world!";
  delegate_->SetReadData(kTestData, sizeof(kTestData));
  uploader_->UploadWhenReady(false);

  // Network layer callback is called next, and the application data is expected
  // to be read to the |buffer|.
  auto buffer =
      base::MakeRefCounted<net::IOBufferWithSize>(kDefaultIOBufferSize);
  int bytes_read = uploader_->Read(
      buffer.get(), kDefaultIOBufferSize,
      base::BindRepeating(&ChunkedDataStreamUploaderTest::CompletionCallback,
                          base::Unretained(this)));

  EXPECT_EQ(sizeof(kTestData), static_cast<size_t>(bytes_read));
  EXPECT_FALSE(
      memcmp(kTestData, buffer->data(), static_cast<size_t>(bytes_read)));

  // Application finishes data upload. Called after all data has been uploaded.
  delegate_->SetReadData("", 0);
  uploader_->UploadWhenReady(true);
  bytes_read = uploader_->Read(
      buffer.get(), kDefaultIOBufferSize,
      base::BindRepeating(&ChunkedDataStreamUploaderTest::CompletionCallback,
                          base::Unretained(this)));
  EXPECT_EQ(0, bytes_read);
  EXPECT_TRUE(uploader_->IsEOF());

  // No completion callback is called because Read() call should return
  // directly.
  EXPECT_EQ(0, callback_count);
}

// Tests that callback from the network layer is called before the application
// layer data available.
TEST_F(ChunkedDataStreamUploaderTest, InternalReadReadyFirst) {
  // Network layer callback is called and the request is pending.
  auto buffer =
      base::MakeRefCounted<net::IOBufferWithSize>(kDefaultIOBufferSize);
  int ret = uploader_->Read(
      buffer.get(), kDefaultIOBufferSize,
      base::BindRepeating(&ChunkedDataStreamUploaderTest::CompletionCallback,
                          base::Unretained(this)));
  EXPECT_EQ(ERR_IO_PENDING, ret);

  // The data is writen into |buffer| once the application layer data is ready.
  const char kTestData[] = "Hello world!";
  delegate_->SetReadData(kTestData, sizeof(kTestData));
  uploader_->UploadWhenReady(false);
  EXPECT_FALSE(memcmp(kTestData, buffer->data(), sizeof(kTestData)));

  // Callback is called again after a successful read.
  ret = uploader_->Read(
      buffer.get(), kDefaultIOBufferSize,
      base::BindRepeating(&ChunkedDataStreamUploaderTest::CompletionCallback,
                          base::Unretained(this)));
  EXPECT_EQ(ERR_IO_PENDING, ret);

  // No more data is available, and the upload will be finished.
  delegate_->SetReadData("", 0);
  uploader_->UploadWhenReady(true);
  EXPECT_TRUE(uploader_->IsEOF());

  EXPECT_EQ(2, callback_count);
}

// Tests that null data is correctly handled when the callback comes first.
TEST_F(ChunkedDataStreamUploaderTest, NullContentWithReadFirst) {
  auto buffer =
      base::MakeRefCounted<net::IOBufferWithSize>(kDefaultIOBufferSize);
  int ret = uploader_->Read(
      buffer.get(), kDefaultIOBufferSize,
      base::BindRepeating(&ChunkedDataStreamUploaderTest::CompletionCallback,
                          base::Unretained(this)));
  EXPECT_EQ(ERR_IO_PENDING, ret);

  delegate_->SetReadData("", 0);
  uploader_->UploadWhenReady(true);
  EXPECT_TRUE(uploader_->IsEOF());

  EXPECT_EQ(1, callback_count);
}

// Tests that null data is correctly handled when data is available first.
TEST_F(ChunkedDataStreamUploaderTest, NullContentWithDataFirst) {
  delegate_->SetReadData("", 0);
  uploader_->UploadWhenReady(true);

  auto buffer =
      base::MakeRefCounted<net::IOBufferWithSize>(kDefaultIOBufferSize);
  int bytes_read = uploader_->Read(
      buffer.get(), kDefaultIOBufferSize,
      base::BindRepeating(&ChunkedDataStreamUploaderTest::CompletionCallback,
                          base::Unretained(this)));
  EXPECT_EQ(0, bytes_read);
  EXPECT_TRUE(uploader_->IsEOF());

  EXPECT_EQ(0, callback_count);
}

// A complex test case that the application layer data and network layer
// callback becomes ready first reciprocally.
TEST_F(ChunkedDataStreamUploaderTest, MixedScenarioTest) {
  // Data comes first.
  const char kTestData[] = "Hello world!";
  delegate_->SetReadData(kTestData, sizeof(kTestData));
  uploader_->UploadWhenReady(false);

  auto buffer =
      base::MakeRefCounted<net::IOBufferWithSize>(kDefaultIOBufferSize);
  int bytes_read = uploader_->Read(
      buffer.get(), kDefaultIOBufferSize,
      base::BindRepeating(&ChunkedDataStreamUploaderTest::CompletionCallback,
                          base::Unretained(this)));
  EXPECT_EQ(sizeof(kTestData), static_cast<size_t>(bytes_read));
  EXPECT_FALSE(
      memcmp(kTestData, buffer->data(), static_cast<size_t>(bytes_read)));

  // Callback comes first.
  int ret = uploader_->Read(
      buffer.get(), kDefaultIOBufferSize,
      base::BindRepeating(&ChunkedDataStreamUploaderTest::CompletionCallback,
                          base::Unretained(this)));
  EXPECT_EQ(ERR_IO_PENDING, ret);

  char test_data_long[kDefaultIOBufferSize];
  for (int i = 0; i < static_cast<int>(sizeof(test_data_long)); ++i) {
    test_data_long[i] = static_cast<char>(i & 0xFF);
  }
  delegate_->SetReadData(test_data_long, sizeof(test_data_long));
  uploader_->UploadWhenReady(false);
  EXPECT_FALSE(memcmp(test_data_long, buffer->data(), sizeof(test_data_long)));

  // Callback comes first again.
  ret = uploader_->Read(
      buffer.get(), kDefaultIOBufferSize,
      base::BindRepeating(&ChunkedDataStreamUploaderTest::CompletionCallback,
                          base::Unretained(this)));
  EXPECT_EQ(ERR_IO_PENDING, ret);
  delegate_->SetReadData(kTestData, sizeof(kTestData));
  uploader_->UploadWhenReady(false);
  EXPECT_FALSE(memcmp(kTestData, buffer->data(), sizeof(kTestData)));

  // Finish and data comes first.
  delegate_->SetReadData("", 0);
  uploader_->UploadWhenReady(true);
  bytes_read = uploader_->Read(
      buffer.get(), kDefaultIOBufferSize,
      base::BindRepeating(&ChunkedDataStreamUploaderTest::CompletionCallback,
                          base::Unretained(this)));
  EXPECT_EQ(0, bytes_read);
  EXPECT_TRUE(uploader_->IsEOF());

  // Completion callback is called only after Read() returns ERR_IO_PENDING;
  EXPECT_EQ(2, callback_count);
}

}  // namespace net