blob: b62a7a6dc03eabdf8460e7b2e34611156a140ea6 [file] [log] [blame] [edit]
// Copyright 2016 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 <assert.h>
#include <errno.h>
#include <iostream>
#include <iterator>
#include <memory>
#include <random>
#include "src/storage/host_fs_test/fixture.h"
namespace fs_test {
namespace {
constexpr int kFail = -1;
constexpr int kBusy = 0;
constexpr int kDone = 1;
constexpr int kBufSize = 65536;
static_assert(kBufSize == ((kBufSize / sizeof(uint64_t)) * sizeof(uint64_t)),
"kBufSize not multiple of uint64_t");
struct Worker;
using WorkerFn = int (*)(Worker* w, bool* fsck_needed);
struct Worker {
WorkerFn work;
std::default_random_engine rdata;
std::default_random_engine rops;
std::default_random_engine::result_type rdata_seed;
int fd;
int status;
uint32_t flags;
uint32_t size;
uint32_t pos;
union {
uint8_t u8[kBufSize];
uint64_t u64[kBufSize / sizeof(uint64_t)];
};
char name[256];
};
class RwWorkersTest : public HostFilesystemTest {
protected:
void NewWorker(const char* where, const char* fn, WorkerFn, uint32_t size, uint32_t flags);
int DoWork();
std::vector<std::unique_ptr<Worker>> all_workers_;
};
int WorkerWriter(Worker* w, bool* fsck_needed);
constexpr int kRandomIoSize = 1;
constexpr int KiB(int n) { return n * 1024; }
constexpr int MiB(int n) { return n * 1024 * 1024; }
struct {
WorkerFn work;
const char* name;
uint32_t size;
uint32_t flags;
} kWork[] = {
{
WorkerWriter,
"file0000",
KiB(512),
kRandomIoSize,
},
{
WorkerWriter,
"file0001",
MiB(10),
kRandomIoSize,
},
{
WorkerWriter,
"file0002",
KiB(512),
kRandomIoSize,
},
{
WorkerWriter,
"file0003",
KiB(512),
kRandomIoSize,
},
{
WorkerWriter,
"file0004",
KiB(512),
0,
},
{
WorkerWriter,
"file0005",
MiB(20),
0,
},
{
WorkerWriter,
"file0006",
KiB(512),
0,
},
{
WorkerWriter,
"file0007",
KiB(512),
0,
},
};
int WorkerRw(Worker* w, bool do_read) {
if (w->pos == w->size) {
return kDone;
}
// offset into buffer
uint32_t off = w->pos % kBufSize;
// fill our content buffer if it's empty
if (off == 0) {
std::uniform_int_distribution<uint64_t> distribution;
for (unsigned n = 0; n < kBufSize / sizeof(uint64_t); ++n) {
w->u64[n] = distribution(w->rdata);
}
}
// data in buffer available to write
uint32_t xfer = kBufSize - off;
// do not exceed our desired size
if (xfer > (w->size - w->pos)) {
xfer = w->size - w->pos;
}
std::uniform_int_distribution<uint32_t> distribution;
if ((w->flags & kRandomIoSize) && (xfer > 3000)) {
xfer = 3000 + distribution(w->rops) % (xfer - 3000);
}
ssize_t r;
if (do_read) {
uint8_t buffer[kBufSize];
if ((r = emu_read(w->fd, buffer, xfer)) < 0) {
std::cerr << "worker('" << w->name << "') read failed @" << w->pos << ": " << strerror(errno)
<< std::endl;
return kFail;
}
if (memcmp(buffer, w->u8 + off, r) != 0) {
std::cerr << "worker('" << w->name << ") verify failed @" << w->pos << std::endl;
return kFail;
}
} else {
if ((r = emu_write(w->fd, w->u8 + off, xfer)) < 0) {
std::cerr << "worker('" << w->name << "') write failed @" << w->pos << ": " << strerror(errno)
<< std::endl;
return kFail;
}
}
// advance
w->pos += r;
return kBusy;
}
int WorkerVerify(Worker* w, bool* fsck_needed) {
int r = WorkerRw(w, true);
if (r == kDone) {
emu_close(w->fd);
*fsck_needed = true;
}
return r;
}
int WorkerWriter(Worker* w, bool* fsck_needed) {
int r = WorkerRw(w, false);
*fsck_needed = true;
if (r == kDone) {
if (emu_lseek(w->fd, 0, SEEK_SET) != 0) {
std::cerr << "worker('" << w->name << "') seek failed: " << strerror(errno) << std::endl;
return kFail;
}
// start at 0 and reset our data generator seed
w->rdata.seed(w->rdata_seed);
w->pos = 0;
w->work = WorkerVerify;
return kBusy;
}
return r;
}
void RwWorkersTest::NewWorker(const char* where, const char* fn, WorkerFn work,
uint32_t size, uint32_t flags) {
auto w = std::make_unique<Worker>();
snprintf(w->name, sizeof(w->name), "%s%s", where, fn);
std::random_device random_device;
w->rdata.seed(w->rdata_seed = random_device());
w->rops.seed(random_device());
w->size = size;
w->work = work;
w->flags = flags;
if ((w->fd = emu_open(w->name, O_RDWR | O_CREAT | O_EXCL, 0644)) < 0) {
std::cerr << "worker('" << w->name << "') cannot create file" << std::endl;
return;
}
all_workers_.push_back(std::move(w));
}
int RwWorkersTest::DoWork() {
uint32_t busy_count = 0;
bool fsck_needed = false;
for (const auto& w : all_workers_) {
if (w->status == kBusy) {
++busy_count;
if ((w->status = w->work(w.get(), &fsck_needed)) == kFail) {
return kFail;
}
if (w->status == kDone) {
std::cerr << "worker('" << w->name << "') finished" << std::endl;
}
}
}
if (fsck_needed) {
EXPECT_EQ(RunFsck(), 0);
}
return busy_count ? kBusy : kDone;
}
TEST_F(RwWorkersTest, SingleThread) {
const char* where = "::";
for (const auto& work : kWork) {
NewWorker(where, work.name, work.work, work.size, work.flags);
}
for (;;) {
int r = DoWork();
ASSERT_NE(r, kFail);
if (r == kDone) {
break;
}
}
}
} // namespace
} // namespace fs_test