chromium/chrome/browser/ash/file_system_provider/fileapi/buffering_file_stream_writer_unittest.cc

// 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 "chrome/browser/ash/file_system_provider/fileapi/buffering_file_stream_writer.h"

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "content/public/test/browser_task_environment.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash::file_system_provider {
namespace {

// Size of the intermediate buffer in bytes.
const int kIntermediateBufferLength = 8;

// Testing text to be written. The length must be 5 bytes, to invoke all code
// paths for buffering.
const char kShortTextToWrite[] = "Kitty";

// Testing text to be written. The length must be longr than the intermediate
// buffer length, in order to invoke a direct write.
const char kLongTextToWrite[] = "Welcome to my world!";

// Pushes a value to the passed log vector.
template <typename T>
void LogValue(std::vector<T>* log, T value) {
  log->push_back(value);
}

// Fake inner file stream writer.
class FakeFileStreamWriter : public storage::FileStreamWriter {
 public:
  FakeFileStreamWriter(std::vector<std::string>* write_log,
                       std::vector<int>* flush_log,
                       int* cancel_counter,
                       net::Error write_error)
      : pending_bytes_(0),
        write_log_(write_log),
        flush_log_(flush_log),
        cancel_counter_(cancel_counter),
        write_error_(write_error) {}

  FakeFileStreamWriter(const FakeFileStreamWriter&) = delete;
  FakeFileStreamWriter& operator=(const FakeFileStreamWriter&) = delete;

  ~FakeFileStreamWriter() override = default;

  // storage::FileStreamWriter overrides.
  int Write(net::IOBuffer* buf,
            int buf_len,
            net::CompletionOnceCallback callback) override {
    DCHECK(write_log_);
    write_log_->emplace_back(buf->data(), buf_len);
    pending_bytes_ += buf_len;
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(callback),
                       write_error_ == net::OK ? buf_len : write_error_));
    return net::ERR_IO_PENDING;
  }

  int Cancel(net::CompletionOnceCallback callback) override {
    DCHECK(cancel_counter_);
    DCHECK_EQ(net::OK, write_error_);
    ++(*cancel_counter_);
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), net::OK));
    return net::ERR_IO_PENDING;
  }

  int Flush(storage::FlushMode /*flush_mode*/,
            net::CompletionOnceCallback callback) override {
    DCHECK(flush_log_);
    flush_log_->push_back(pending_bytes_);
    pending_bytes_ = 0;
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), net::OK));
    return net::ERR_IO_PENDING;
  }

 private:
  int pending_bytes_;
  raw_ptr<std::vector<std::string>> write_log_;  // Not owned.
  raw_ptr<std::vector<int>> flush_log_;          // Not owned.
  raw_ptr<int> cancel_counter_;                  // Not owned.
  net::Error write_error_;
};

}  // namespace

class FileSystemProviderBufferingFileStreamWriterTest : public testing::Test {
 protected:
  FileSystemProviderBufferingFileStreamWriterTest() = default;

  void SetUp() override {
    short_text_buffer_ =
        base::MakeRefCounted<net::StringIOBuffer>(kShortTextToWrite);
    long_text_buffer_ =
        base::MakeRefCounted<net::StringIOBuffer>(kLongTextToWrite);
    ASSERT_LT(short_text_buffer_->size(), long_text_buffer_->size());
  }

  ~FileSystemProviderBufferingFileStreamWriterTest() override = default;

  content::BrowserTaskEnvironment task_environment_;
  scoped_refptr<net::StringIOBuffer> short_text_buffer_;
  scoped_refptr<net::StringIOBuffer> long_text_buffer_;
};

TEST_F(FileSystemProviderBufferingFileStreamWriterTest, Write) {
  std::vector<std::string> inner_write_log;
  std::vector<int> inner_flush_log;
  BufferingFileStreamWriter writer(
      base::WrapUnique(new FakeFileStreamWriter(
          &inner_write_log, &inner_flush_log, nullptr, net::OK)),
      kIntermediateBufferLength);

  ASSERT_LT(kIntermediateBufferLength, 2 * short_text_buffer_->size());

  // Writing for the first time should succeed, but buffer the write without
  // calling the inner file stream writer.
  {
    std::vector<int> write_log;
    const int result =
        writer.Write(short_text_buffer_.get(), short_text_buffer_->size(),
                     base::BindOnce(&LogValue<int>, &write_log));
    base::RunLoop().RunUntilIdle();

    EXPECT_EQ(short_text_buffer_->size(), result);
    EXPECT_EQ(0u, write_log.size());
    EXPECT_EQ(0u, inner_write_log.size());
    EXPECT_EQ(0u, inner_flush_log.size());
  }

  // Writing for the second time should however flush the intermediate buffer,
  // since it is full.
  {
    inner_write_log.clear();
    inner_flush_log.clear();

    std::vector<int> write_log;
    const int result =
        writer.Write(short_text_buffer_.get(), short_text_buffer_->size(),
                     base::BindOnce(&LogValue<int>, &write_log));
    base::RunLoop().RunUntilIdle();

    EXPECT_EQ(net::ERR_IO_PENDING, result);
    const std::string expected_inner_write =
        (std::string(kShortTextToWrite) + kShortTextToWrite)
            .substr(0, kIntermediateBufferLength);
    ASSERT_EQ(1u, inner_write_log.size());
    EXPECT_EQ(expected_inner_write, inner_write_log[0]);
    ASSERT_EQ(1u, write_log.size());
    EXPECT_EQ(short_text_buffer_->size(), write_log[0]);
    EXPECT_EQ(0u, inner_flush_log.size());
  }

  // There should be a remainder in the intermediate buffer. Calling flush
  // should invoke a write on the inner file stream writer.
  {
    inner_write_log.clear();
    inner_flush_log.clear();

    std::vector<int> flush_log;
    const int result = writer.Flush(storage::FlushMode::kEndOfFile,
                                    base::BindOnce(&LogValue<int>, &flush_log));
    base::RunLoop().RunUntilIdle();

    EXPECT_EQ(net::ERR_IO_PENDING, result);
    ASSERT_EQ(1u, flush_log.size());
    EXPECT_EQ(net::OK, flush_log[0]);

    const std::string expected_inner_write =
        (std::string(kShortTextToWrite) + kShortTextToWrite)
            .substr(kIntermediateBufferLength, std::string::npos);
    ASSERT_EQ(1u, inner_write_log.size());
    EXPECT_EQ(expected_inner_write, inner_write_log[0]);

    const int expected_inner_flush = 2 * short_text_buffer_->size();
    ASSERT_EQ(1u, inner_flush_log.size());
    EXPECT_EQ(expected_inner_flush, inner_flush_log[0]);
  }
}

TEST_F(FileSystemProviderBufferingFileStreamWriterTest, Write_WithError) {
  std::vector<std::string> inner_write_log;
  std::vector<int> inner_flush_log;
  BufferingFileStreamWriter writer(
      std::unique_ptr<storage::FileStreamWriter>(new FakeFileStreamWriter(
          &inner_write_log, &inner_flush_log, nullptr, net::ERR_FAILED)),
      kIntermediateBufferLength);

  ASSERT_LT(kIntermediateBufferLength, 2 * short_text_buffer_->size());

  // Writing for the first time should succeed, but buffer the write without
  // calling the inner file stream writer. Because of that, the error will
  // not be generated unless the intermediate buffer is flushed.
  {
    std::vector<int> write_log;
    const int result =
        writer.Write(short_text_buffer_.get(), short_text_buffer_->size(),
                     base::BindOnce(&LogValue<int>, &write_log));
    base::RunLoop().RunUntilIdle();

    EXPECT_EQ(short_text_buffer_->size(), result);
    EXPECT_EQ(0u, write_log.size());
    EXPECT_EQ(0u, inner_write_log.size());
    EXPECT_EQ(0u, inner_flush_log.size());
  }

  // Writing for the second time should however flush the intermediate buffer,
  // since it is full. That should generate an error.
  {
    inner_write_log.clear();
    inner_flush_log.clear();

    std::vector<int> write_log;
    const int result =
        writer.Write(short_text_buffer_.get(), short_text_buffer_->size(),
                     base::BindOnce(&LogValue<int>, &write_log));
    base::RunLoop().RunUntilIdle();

    EXPECT_EQ(net::ERR_IO_PENDING, result);
    const std::string expected_inner_write =
        (std::string(kShortTextToWrite) + kShortTextToWrite)
            .substr(0, kIntermediateBufferLength);
    ASSERT_EQ(1u, inner_write_log.size());
    EXPECT_EQ(expected_inner_write, inner_write_log[0]);
    ASSERT_EQ(1u, write_log.size());
    EXPECT_EQ(net::ERR_FAILED, write_log[0]);
    EXPECT_EQ(0u, inner_flush_log.size());
  }
}

TEST_F(FileSystemProviderBufferingFileStreamWriterTest, Write_Directly) {
  std::vector<std::string> inner_write_log;
  std::vector<int> inner_flush_log;
  BufferingFileStreamWriter writer(
      std::unique_ptr<storage::FileStreamWriter>(new FakeFileStreamWriter(
          &inner_write_log, &inner_flush_log, nullptr, net::OK)),
      kIntermediateBufferLength);

  ASSERT_GT(kIntermediateBufferLength, short_text_buffer_->size());
  ASSERT_LT(kIntermediateBufferLength, long_text_buffer_->size());

  // Write few bytes first, so the intermediate buffer is not empty.
  {
    std::vector<int> write_log;
    const int result =
        writer.Write(short_text_buffer_.get(), short_text_buffer_->size(),
                     base::BindOnce(&LogValue<int>, &write_log));
    base::RunLoop().RunUntilIdle();

    EXPECT_EQ(short_text_buffer_->size(), result);
    EXPECT_EQ(0u, write_log.size());
    EXPECT_EQ(0u, inner_flush_log.size());
  }

  // Request writing a long chunk of data, which is longr than size of the
  // intermediate buffer.
  {
    inner_write_log.clear();
    inner_flush_log.clear();

    std::vector<int> write_log;
    const int result =
        writer.Write(long_text_buffer_.get(), long_text_buffer_->size(),
                     base::BindOnce(&LogValue<int>, &write_log));
    base::RunLoop().RunUntilIdle();

    EXPECT_EQ(net::ERR_IO_PENDING, result);
    ASSERT_EQ(2u, inner_write_log.size());
    EXPECT_EQ(kShortTextToWrite, inner_write_log[0]);
    EXPECT_EQ(kLongTextToWrite, inner_write_log[1]);
    ASSERT_EQ(1u, write_log.size());
    EXPECT_EQ(long_text_buffer_->size(), write_log[0]);
    EXPECT_EQ(0u, inner_flush_log.size());
  }

  // Write a long chunk again, to test the case with an empty intermediate
  // buffer.
  {
    inner_write_log.clear();
    inner_flush_log.clear();

    std::vector<int> write_log;
    const int result =
        writer.Write(long_text_buffer_.get(), long_text_buffer_->size(),
                     base::BindOnce(&LogValue<int>, &write_log));
    base::RunLoop().RunUntilIdle();

    EXPECT_EQ(net::ERR_IO_PENDING, result);
    ASSERT_EQ(1u, inner_write_log.size());
    EXPECT_EQ(kLongTextToWrite, inner_write_log[0]);
    ASSERT_EQ(1u, write_log.size());
    EXPECT_EQ(long_text_buffer_->size(), write_log[0]);
    EXPECT_EQ(0u, inner_flush_log.size());
  }
}

TEST_F(FileSystemProviderBufferingFileStreamWriterTest, Cancel) {
  std::vector<std::string> inner_write_log;
  std::vector<int> inner_flush_log;
  int inner_cancel_counter = 0;

  BufferingFileStreamWriter writer(
      std::unique_ptr<storage::FileStreamWriter>(new FakeFileStreamWriter(
          &inner_write_log, &inner_flush_log, &inner_cancel_counter, net::OK)),
      kIntermediateBufferLength);

  // Write directly, so there is something to actually cancel. Note, that
  // buffered writes which do not invoke flushing the intermediate buffer finish
  // immediately, so they are not cancellable.
  std::vector<int> write_log;
  const int write_result =
      writer.Write(long_text_buffer_.get(), long_text_buffer_->size(),
                   base::BindOnce(&LogValue<int>, &write_log));
  EXPECT_EQ(net::ERR_IO_PENDING, write_result);

  std::vector<int> cancel_log;
  const int cancel_result =
      writer.Cancel(base::BindOnce(&LogValue<int>, &cancel_log));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(net::ERR_IO_PENDING, cancel_result);
  ASSERT_EQ(1u, cancel_log.size());
  EXPECT_EQ(net::OK, cancel_log[0]);
  EXPECT_EQ(1, inner_cancel_counter);
}

TEST_F(FileSystemProviderBufferingFileStreamWriterTest, Flush) {
  std::vector<std::string> inner_write_log;
  std::vector<int> inner_flush_log;
  BufferingFileStreamWriter writer(
      std::unique_ptr<storage::FileStreamWriter>(new FakeFileStreamWriter(
          &inner_write_log, &inner_flush_log, nullptr, net::OK)),
      kIntermediateBufferLength);

  // Write less bytes than size of the intermediate buffer.
  std::vector<int> write_log;
  const int write_result =
      writer.Write(short_text_buffer_.get(), short_text_buffer_->size(),
                   base::BindOnce(&LogValue<int>, &write_log));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(short_text_buffer_->size(), write_result);
  EXPECT_EQ(0u, write_log.size());
  EXPECT_EQ(0u, inner_write_log.size());
  EXPECT_EQ(0u, inner_flush_log.size());

  // Calling Flush() will force flushing the intermediate buffer before it's
  // filled out.
  std::vector<int> flush_log;
  const int flush_result =
      writer.Flush(storage::FlushMode::kEndOfFile,
                   base::BindOnce(&LogValue<int>, &flush_log));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(net::ERR_IO_PENDING, flush_result);
  ASSERT_EQ(1u, flush_log.size());
  EXPECT_EQ(net::OK, flush_log[0]);

  ASSERT_EQ(1u, inner_write_log.size());
  EXPECT_EQ(kShortTextToWrite, inner_write_log[0]);

  ASSERT_EQ(1u, inner_flush_log.size());
  EXPECT_EQ(short_text_buffer_->size(), inner_flush_log[0]);
}

TEST_F(FileSystemProviderBufferingFileStreamWriterTest, Flush_AfterWriteError) {
  std::vector<std::string> inner_write_log;
  std::vector<int> inner_flush_log;
  BufferingFileStreamWriter writer(
      std::unique_ptr<storage::FileStreamWriter>(new FakeFileStreamWriter(
          &inner_write_log, &inner_flush_log, nullptr, net::ERR_FAILED)),
      kIntermediateBufferLength);

  // Write less bytes than size of the intermediate buffer. This should succeed
  // since the inner file stream writer is not invoked.
  std::vector<int> write_log;
  const int write_result =
      writer.Write(short_text_buffer_.get(), short_text_buffer_->size(),
                   base::BindOnce(&LogValue<int>, &write_log));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(short_text_buffer_->size(), write_result);
  EXPECT_EQ(0u, write_log.size());
  EXPECT_EQ(0u, inner_write_log.size());
  EXPECT_EQ(0u, inner_flush_log.size());

  // Calling Flush() will force flushing the intermediate buffer before it's
  // filled out. That should cause failing.
  std::vector<int> flush_log;
  const int flush_result =
      writer.Flush(storage::FlushMode::kEndOfFile,
                   base::BindOnce(&LogValue<int>, &flush_log));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(net::ERR_IO_PENDING, flush_result);
  ASSERT_EQ(1u, flush_log.size());
  EXPECT_EQ(net::ERR_FAILED, flush_log[0]);

  ASSERT_EQ(1u, inner_write_log.size());
  EXPECT_EQ(kShortTextToWrite, inner_write_log[0]);

  // Flush of the inner file stream writer is not invoked, since a Write method
  // invocation fails before it.
  EXPECT_EQ(0u, inner_flush_log.size());
}

}  // namespace ash::file_system_provider