blob: bd96c3f4c6754e9a3cce52a2cf57621226a760a8 [file] [log] [blame]
// 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 <algorithm>
#include <limits>
#include <memory>
#include <string>
#include "base/bind.h"
#include "base/callback.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/scoped_task_environment.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "mojo/public/cpp/system/file_data_pipe_producer.h"
#include "mojo/public/cpp/system/simple_watcher.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace mojo {
namespace {
// Test helper. Reads a consumer handle, accumulating data into a string. Reads
// until encountering an error (e.g. peer closure), at which point it invokes an
// async callback.
class DataPipeReader {
public:
explicit DataPipeReader(ScopedDataPipeConsumerHandle consumer_handle,
size_t read_size,
base::OnceClosure on_read_done)
: consumer_handle_(std::move(consumer_handle)),
read_size_(read_size),
on_read_done_(std::move(on_read_done)),
watcher_(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC) {
watcher_.Watch(
consumer_handle_.get(), MOJO_HANDLE_SIGNAL_READABLE,
MOJO_WATCH_CONDITION_SATISFIED,
base::Bind(&DataPipeReader::OnDataAvailable, base::Unretained(this)));
}
~DataPipeReader() = default;
const std::string& data() const { return data_; }
private:
void OnDataAvailable(MojoResult result, const HandleSignalsState& state) {
if (result == MOJO_RESULT_OK) {
uint32_t size = static_cast<uint32_t>(read_size_);
std::vector<char> buffer(size, 0);
MojoResult read_result;
do {
read_result = consumer_handle_->ReadData(buffer.data(), &size,
MOJO_READ_DATA_FLAG_NONE);
if (read_result == MOJO_RESULT_OK) {
std::copy(buffer.begin(), buffer.begin() + size,
std::back_inserter(data_));
}
} while (read_result == MOJO_RESULT_OK);
if (read_result == MOJO_RESULT_SHOULD_WAIT)
return;
}
if (result != MOJO_RESULT_CANCELLED)
watcher_.Cancel();
std::move(on_read_done_).Run();
}
ScopedDataPipeConsumerHandle consumer_handle_;
const size_t read_size_;
base::OnceClosure on_read_done_;
SimpleWatcher watcher_;
std::string data_;
DISALLOW_COPY_AND_ASSIGN(DataPipeReader);
};
class FileDataPipeProducerTest : public testing::Test {
public:
FileDataPipeProducerTest() { CHECK(temp_dir_.CreateUniqueTempDir()); }
~FileDataPipeProducerTest() override = default;
protected:
base::FilePath CreateTempFileWithContents(const std::string& contents) {
base::FilePath temp_file_path = temp_dir_.GetPath().AppendASCII(
base::StringPrintf("tmp%d", tmp_file_id_++));
base::File temp_file(temp_file_path,
base::File::FLAG_CREATE | base::File::FLAG_WRITE);
int bytes_written = temp_file.WriteAtCurrentPos(
contents.data(), static_cast<int>(contents.size()));
CHECK_EQ(static_cast<int>(contents.size()), bytes_written);
return temp_file_path;
}
static void WriteFromFileThenCloseWriter(
std::unique_ptr<FileDataPipeProducer> producer,
base::File file) {
FileDataPipeProducer* raw_producer = producer.get();
raw_producer->WriteFromFile(
std::move(file),
base::BindOnce([](std::unique_ptr<FileDataPipeProducer> producer,
MojoResult result) {},
std::move(producer)));
}
static void WriteFromFileThenCloseWriter(
std::unique_ptr<FileDataPipeProducer> producer,
base::File file,
size_t max_bytes) {
FileDataPipeProducer* raw_producer = producer.get();
raw_producer->WriteFromFile(
std::move(file), max_bytes,
base::BindOnce([](std::unique_ptr<FileDataPipeProducer> producer,
MojoResult result) {},
std::move(producer)));
}
static void WriteFromPathThenCloseWriter(
std::unique_ptr<FileDataPipeProducer> producer,
const base::FilePath& path) {
FileDataPipeProducer* raw_producer = producer.get();
raw_producer->WriteFromPath(
path, base::BindOnce([](std::unique_ptr<FileDataPipeProducer> producer,
MojoResult result) {},
std::move(producer)));
}
private:
base::test::ScopedTaskEnvironment task_environment_;
base::ScopedTempDir temp_dir_;
int tmp_file_id_ = 0;
DISALLOW_COPY_AND_ASSIGN(FileDataPipeProducerTest);
};
struct DataPipeObserverData {
int num_read_errors = 0;
size_t bytes_read = 0;
int done_called = 0;
};
class TestObserver : public FileDataPipeProducer::Observer {
public:
explicit TestObserver(DataPipeObserverData* observer_data)
: observer_data_(observer_data) {}
void OnBytesRead(const void* data,
size_t num_bytes_read,
base::File::Error read_result) override {
base::AutoLock auto_lock(lock_);
if (read_result == base::File::FILE_OK)
observer_data_->bytes_read += num_bytes_read;
else
observer_data_->num_read_errors++;
}
void OnDoneReading() override {
base::AutoLock auto_lock(lock_);
observer_data_->done_called++;
}
private:
DataPipeObserverData* observer_data_;
// Observer may be called on any sequence.
base::Lock lock_;
DISALLOW_COPY_AND_ASSIGN(TestObserver);
};
TEST_F(FileDataPipeProducerTest, WriteFromFile) {
const std::string kTestStringFragment = "Hello, world!";
constexpr size_t kNumRepetitions = 1000;
std::string test_string;
for (size_t i = 0; i < kNumRepetitions; ++i)
test_string += kTestStringFragment;
base::FilePath path = CreateTempFileWithContents(test_string);
base::RunLoop loop;
DataPipe pipe(16);
DataPipeReader reader(std::move(pipe.consumer_handle), 16,
loop.QuitClosure());
base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
DataPipeObserverData observer_data;
auto observer = std::make_unique<TestObserver>(&observer_data);
WriteFromFileThenCloseWriter(
std::make_unique<FileDataPipeProducer>(std::move(pipe.producer_handle),
std::move(observer)),
std::move(file));
loop.Run();
EXPECT_EQ(test_string, reader.data());
EXPECT_EQ(0, observer_data.num_read_errors);
EXPECT_EQ(test_string.size(), observer_data.bytes_read);
EXPECT_EQ(1, observer_data.done_called);
}
TEST_F(FileDataPipeProducerTest, WriteFromFilePartial) {
const std::string kTestString = "abcdefghijklmnopqrstuvwxyz";
base::FilePath path = CreateTempFileWithContents(kTestString);
constexpr size_t kBytesToWrite = 7;
base::RunLoop loop;
DataPipe pipe(static_cast<uint32_t>(kTestString.size()));
DataPipeReader reader(std::move(pipe.consumer_handle), kTestString.size(),
loop.QuitClosure());
base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
DataPipeObserverData observer_data;
auto observer = std::make_unique<TestObserver>(&observer_data);
WriteFromFileThenCloseWriter(
std::make_unique<FileDataPipeProducer>(std::move(pipe.producer_handle),
std::move(observer)),
std::move(file), kBytesToWrite);
loop.Run();
EXPECT_EQ(kTestString.substr(0, kBytesToWrite), reader.data());
EXPECT_EQ(0, observer_data.num_read_errors);
EXPECT_EQ(kBytesToWrite, observer_data.bytes_read);
EXPECT_EQ(1, observer_data.done_called);
}
TEST_F(FileDataPipeProducerTest, WriteFromInvalidFile) {
base::FilePath path(FILE_PATH_LITERAL("<nonexistent-file>"));
constexpr size_t kBytesToWrite = 7;
base::RunLoop loop;
DataPipe pipe(kBytesToWrite);
DataPipeObserverData observer_data;
auto observer = std::make_unique<TestObserver>(&observer_data);
DataPipeReader reader(std::move(pipe.consumer_handle), kBytesToWrite,
loop.QuitClosure());
base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
WriteFromFileThenCloseWriter(
std::make_unique<FileDataPipeProducer>(std::move(pipe.producer_handle),
std::move(observer)),
std::move(file), kBytesToWrite);
loop.Run();
EXPECT_EQ(0UL, reader.data().size());
EXPECT_EQ(0, observer_data.num_read_errors);
EXPECT_EQ(0UL, observer_data.bytes_read);
EXPECT_EQ(1, observer_data.done_called);
}
TEST_F(FileDataPipeProducerTest, WriteFromPath) {
const std::string kTestStringFragment = "Hello, world!";
constexpr size_t kNumRepetitions = 1000;
std::string test_string;
for (size_t i = 0; i < kNumRepetitions; ++i)
test_string += kTestStringFragment;
base::FilePath path = CreateTempFileWithContents(test_string);
base::RunLoop loop;
DataPipe pipe(16);
DataPipeReader reader(std::move(pipe.consumer_handle), 16,
loop.QuitClosure());
DataPipeObserverData observer_data;
auto observer = std::make_unique<TestObserver>(&observer_data);
WriteFromPathThenCloseWriter(
std::make_unique<FileDataPipeProducer>(std::move(pipe.producer_handle),
std::move(observer)),
path);
loop.Run();
EXPECT_EQ(test_string, reader.data());
EXPECT_EQ(0, observer_data.num_read_errors);
EXPECT_EQ(test_string.size(), observer_data.bytes_read);
EXPECT_EQ(1, observer_data.done_called);
}
TEST_F(FileDataPipeProducerTest, TinyFile) {
const std::string kTestString = ".";
base::FilePath path = CreateTempFileWithContents(kTestString);
base::RunLoop loop;
DataPipe pipe(16);
DataPipeReader reader(std::move(pipe.consumer_handle), 16,
loop.QuitClosure());
DataPipeObserverData observer_data;
auto observer = std::make_unique<TestObserver>(&observer_data);
WriteFromPathThenCloseWriter(
std::make_unique<FileDataPipeProducer>(std::move(pipe.producer_handle),
std::move(observer)),
path);
loop.Run();
EXPECT_EQ(kTestString, reader.data());
EXPECT_EQ(0, observer_data.num_read_errors);
EXPECT_EQ(kTestString.size(), observer_data.bytes_read);
EXPECT_EQ(1, observer_data.done_called);
}
TEST_F(FileDataPipeProducerTest, HugeFile) {
// We want a file size that is many times larger than the data pipe size.
// 63MB is large enough, while being small enough to fit in a typical tmpfs.
constexpr size_t kHugeFileSize = 63 * 1024 * 1024;
constexpr uint32_t kDataPipeSize = 512 * 1024;
std::string test_string(kHugeFileSize, 'a');
for (size_t i = 0; i + 3 < test_string.size(); i += 4) {
test_string[i + 1] = 'b';
test_string[i + 2] = 'c';
test_string[i + 3] = 'd';
}
base::FilePath path = CreateTempFileWithContents(test_string);
base::RunLoop loop;
DataPipe pipe(kDataPipeSize);
DataPipeReader reader(std::move(pipe.consumer_handle), kDataPipeSize,
loop.QuitClosure());
DataPipeObserverData observer_data;
auto observer = std::make_unique<TestObserver>(&observer_data);
WriteFromPathThenCloseWriter(
std::make_unique<FileDataPipeProducer>(std::move(pipe.producer_handle),
std::move(observer)),
path);
loop.Run();
EXPECT_EQ(test_string, reader.data());
EXPECT_EQ(0, observer_data.num_read_errors);
EXPECT_EQ(kHugeFileSize, observer_data.bytes_read);
EXPECT_EQ(1, observer_data.done_called);
}
} // namespace
} // namespace mojo