blob: 435e23abbcb9dd4fa3ee75ee28396a23038c54e5 [file] [log] [blame]
// Copyright 2022 The Fuchsia 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 <lib/fit/defer.h>
#include <lib/zxdump/fd-writer.h>
#include <stdio.h>
#include <unistd.h>
#include <string_view>
#include <thread>
#include <fbl/unique_fd.h>
#include <gtest/gtest.h>
namespace {
using namespace std::literals;
zxdump::ByteView AsBytes(std::string_view s) {
return {reinterpret_cast<const std::byte*>(s.data()), s.size()};
}
constexpr std::string_view kTestData = "foobarbazquuxchunk\0\0\0more"sv;
const auto kChunk = AsBytes("chunk"sv);
const auto kMore = AsBytes("more"sv);
TEST(ZxdumpTests, FdWriterToFile) {
FILE* tmpf = tmpfile();
ASSERT_TRUE(tmpf);
auto close_tmpf = fit::defer([tmpf]() { fclose(tmpf); });
fbl::unique_fd tmpfd(dup(fileno(tmpf)));
ASSERT_TRUE(tmpfd);
zxdump::FdWriter writer(std::move(tmpfd));
auto accum_fragment = writer.AccumulateFragmentsCallback();
auto write_chunk = writer.WriteCallback();
size_t offset = 0;
constexpr size_t kExpectedOffset = 3 + 3 + 3 + 4;
for (std::string_view s : {"foo"sv, "bar"sv, "baz"sv, "quux"sv}) {
auto frag = AsBytes(s);
auto result = accum_fragment(offset, frag);
EXPECT_TRUE(result.is_ok());
offset += frag.size();
}
EXPECT_EQ(offset, kExpectedOffset);
auto frags_result = writer.WriteFragments();
ASSERT_TRUE(frags_result.is_ok());
EXPECT_EQ(frags_result.value(), offset);
auto write_result = write_chunk(offset, kChunk);
EXPECT_TRUE(write_result.is_ok());
offset += kChunk.size() + 3;
write_result = write_chunk(offset, kMore);
EXPECT_TRUE(write_result.is_ok());
// Now verify the data written to the file.
rewind(tmpf);
EXPECT_FALSE(ferror(tmpf));
char buf[128];
static_assert(sizeof(buf) > kTestData.size());
size_t n = fread(buf, 1, sizeof(buf), tmpf);
EXPECT_FALSE(ferror(tmpf));
ASSERT_LE(n, sizeof(buf));
EXPECT_EQ(n, kTestData.size());
std::string_view tmpf_contents{buf, n};
EXPECT_EQ(tmpf_contents, kTestData);
}
class PipeReader {
public:
explicit PipeReader(fbl::unique_fd pipe)
// Note the order of initialization here matters!
: pipe_(std::move(pipe)),
pipe_buf_size_(fpathconf(pipe_.get(), _PC_PIPE_BUF)),
thread_(std::thread(&PipeReader::ReaderThread, this)) {}
// This must be called before destruction and nothing else after it.
std::string Finish() && {
thread_.join();
return std::move(contents_);
}
~PipeReader() { EXPECT_FALSE(thread_.joinable()); }
private:
static constexpr char kFillByte = 0x55;
// The reader thread will append everything read from the pipe to the string.
void ReaderThread() {
ssize_t n;
do {
size_t contents_size = contents_.size();
contents_.append(pipe_buf_size_, kFillByte);
n = read(pipe_.get(), &contents_[contents_size], pipe_buf_size_);
ASSERT_GE(n, 0) << "read: " << strerror(errno);
contents_.resize(contents_size + n);
} while (n > 0);
}
std::string contents_;
fbl::unique_fd pipe_;
size_t pipe_buf_size_;
std::thread thread_;
};
TEST(ZxdumpTests, FdWriterToPipe) {
fbl::unique_fd read_pipe, write_pipe;
{
int fds[2];
ASSERT_EQ(0, pipe(fds)) << "pipe: " << strerror(errno);
read_pipe.reset(fds[0]);
write_pipe.reset(fds[1]);
}
PipeReader reader(std::move(read_pipe));
std::optional<zxdump::FdWriter> writer(std::in_place, std::move(write_pipe));
auto accum_fragment = writer->AccumulateFragmentsCallback();
auto write_chunk = writer->WriteCallback();
size_t offset = 0;
constexpr size_t kExpectedOffset = 3 + 3 + 3 + 4;
for (std::string_view s : {"foo"sv, "bar"sv, "baz"sv, "quux"sv}) {
auto frag = AsBytes(s);
auto result = accum_fragment(offset, frag);
EXPECT_TRUE(result.is_ok());
offset += frag.size();
}
EXPECT_EQ(offset, kExpectedOffset);
auto frags_result = writer->WriteFragments();
ASSERT_TRUE(frags_result.is_ok());
EXPECT_EQ(frags_result.value(), offset);
auto write_result = write_chunk(offset, kChunk);
EXPECT_TRUE(write_result.is_ok());
offset += kChunk.size() + 3;
write_result = write_chunk(offset, kMore);
EXPECT_TRUE(write_result.is_ok());
// This closes the write side of the pipe so the reader can finish.
writer.reset();
std::string contents = std::move(reader).Finish();
EXPECT_EQ(contents, kTestData);
}
} // namespace