blob: 006eb25ea566a548c10a4750090ffe59f9fe3443 [file] [log] [blame]
// Copyright 2020 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 "server.h"
#include <lib/sync/completion.h>
#include <unistd.h>
#include <thread>
#include <fbl/auto_call.h>
#include <zxtest/zxtest.h>
#include "test/stub-block-device.h"
namespace {
class ServerTestFixture : public zxtest::Test {
public:
ServerTestFixture() : client_(blkdev_.proto()) {}
protected:
void SetUp() override {}
void TearDown() override;
void CreateThread();
void WaitForThreadStart();
void WaitForThreadExit();
void JoinThread();
StubBlockDevice blkdev_;
ddk::BlockProtocolClient client_;
std::unique_ptr<Server> server_;
fzl::fifo<block_fifo_request_t, block_fifo_response_t> fifo_;
private:
static int RunServer(void* arg);
sync_completion_t thread_started_;
sync_completion_t thread_exited_;
bool is_thread_running_ = false;
std::thread thread_;
};
void ServerTestFixture::TearDown() { ASSERT_FALSE(is_thread_running_); }
int ServerTestFixture::RunServer(void* arg) {
ServerTestFixture* fix = static_cast<ServerTestFixture*>(arg);
sync_completion_signal(&fix->thread_started_);
fix->server_->Serve();
sync_completion_signal(&fix->thread_exited_);
return 0;
}
void ServerTestFixture::CreateThread() {
std::thread th(RunServer, this);
thread_ = std::move(th);
is_thread_running_ = true;
}
void ServerTestFixture::WaitForThreadStart() {
ASSERT_OK(sync_completion_wait(&thread_started_, ZX_SEC(5)));
}
void ServerTestFixture::WaitForThreadExit() {
ASSERT_OK(sync_completion_wait(&thread_exited_, ZX_SEC(5)));
}
void ServerTestFixture::JoinThread() {
thread_.join();
is_thread_running_ = false;
}
TEST_F(ServerTestFixture, CreateServer) { ASSERT_OK(Server::Create(&client_, &fifo_, &server_)); }
TEST_F(ServerTestFixture, StartServer) {
ASSERT_OK(Server::Create(&client_, &fifo_, &server_));
CreateThread();
WaitForThreadStart();
// This code is racy with Serve() being called. This is expected.
// The server should handle shutdown commands at any time.
server_->Shutdown();
WaitForThreadExit();
JoinThread();
}
TEST_F(ServerTestFixture, CloseFifo) {
ASSERT_OK(Server::Create(&client_, &fifo_, &server_));
CreateThread();
WaitForThreadStart();
// Allow the server thread to do some work. The thread may not always get to make progress
// before the fifo is closed, but the server thread should handle it regardless.
usleep(20 * 1000);
fifo_.reset();
WaitForThreadExit();
JoinThread();
}
TEST_F(ServerTestFixture, SplitRequestAfterFailedRequestReturnsFailure) {
ASSERT_OK(Server::Create(&client_, &fifo_, &server_));
CreateThread();
fbl::AutoCall cleanup([&] {
server_->Shutdown();
JoinThread();
});
zx::vmo vmo;
constexpr int kTestBlockCount = 257;
ASSERT_OK(zx::vmo::create(kTestBlockCount * kBlockSize, 0, &vmo));
vmoid_t vmoid;
ASSERT_OK(server_->AttachVmo(std::move(vmo), &vmoid));
block_fifo_request_t request = {
.opcode = BLOCK_OP_WRITE | BLOCK_GROUP_ITEM,
.reqid = 100,
.group = 5,
.vmoid = vmoid,
.length = 4,
.vmo_offset = 0,
.dev_offset = 0,
};
blkdev_.set_callback([&](const block_op_t&) { return ZX_ERR_IO; });
size_t actual_count = 0;
ASSERT_OK(fifo_.write(&request, 1, &actual_count));
ASSERT_EQ(actual_count, 1);
request = {
.opcode = BLOCK_OP_READ,
.reqid = 101,
.vmoid = vmoid,
};
ASSERT_OK(fifo_.write(&request, 1, &actual_count));
ASSERT_EQ(actual_count, 1);
block_fifo_response_t response;
zx_signals_t seen;
ASSERT_OK(fifo_.wait_one(ZX_FIFO_READABLE | ZX_FIFO_PEER_CLOSED, zx::time::infinite(), &seen));
ASSERT_OK(fifo_.read_one(&response));
// Should get the response for the read.
EXPECT_EQ(response.reqid, 101);
request = {
.opcode = BLOCK_OP_WRITE | BLOCK_GROUP_ITEM | BLOCK_GROUP_LAST,
.reqid = 102,
.group = 5,
.vmoid = vmoid,
.length = kTestBlockCount,
.vmo_offset = 0,
.dev_offset = 0,
};
ASSERT_OK(fifo_.write(&request, 1, &actual_count));
ASSERT_EQ(actual_count, 1);
// It's the last one so it should trigger a response.
ASSERT_OK(fifo_.wait_one(ZX_FIFO_READABLE | ZX_FIFO_PEER_CLOSED, zx::time::infinite(), &seen));
ASSERT_OK(fifo_.read_one(&response));
EXPECT_EQ(response.reqid, 102);
// Make sure the group is correctly cleaned up and able to be used for another request.
blkdev_.set_callback({});
block_fifo_request_t requests[] = {
{
.opcode = BLOCK_OP_WRITE | BLOCK_GROUP_ITEM,
.reqid = 103,
.group = 5,
.vmoid = vmoid,
.length = 257,
.vmo_offset = 0,
.dev_offset = 0,
},
{
.opcode = BLOCK_OP_WRITE | BLOCK_GROUP_ITEM | BLOCK_GROUP_LAST,
.reqid = 104,
.group = 5,
.vmoid = vmoid,
.length = 257,
.vmo_offset = 0,
.dev_offset = 0,
},
};
ASSERT_OK(fifo_.write(requests, 2, &actual_count));
ASSERT_EQ(actual_count, 2);
ASSERT_OK(fifo_.wait_one(ZX_FIFO_READABLE | ZX_FIFO_PEER_CLOSED, zx::time::infinite(), &seen));
ASSERT_OK(fifo_.read_one(&response));
EXPECT_EQ(response.reqid, 104);
}
} // namespace