diff options
Diffstat (limited to 'test/fuse')
-rw-r--r-- | test/fuse/BUILD | 9 | ||||
-rw-r--r-- | test/fuse/README.md | 103 | ||||
-rw-r--r-- | test/fuse/linux/BUILD | 32 | ||||
-rw-r--r-- | test/fuse/linux/fuse_base.cc | 208 | ||||
-rw-r--r-- | test/fuse/linux/fuse_base.h | 99 | ||||
-rw-r--r-- | test/fuse/linux/stat_test.cc | 169 |
6 files changed, 0 insertions, 620 deletions
diff --git a/test/fuse/BUILD b/test/fuse/BUILD deleted file mode 100644 index 56157c96b..000000000 --- a/test/fuse/BUILD +++ /dev/null @@ -1,9 +0,0 @@ -load("//test/runner:defs.bzl", "syscall_test") - -package(licenses = ["notice"]) - -syscall_test( - fuse = "True", - test = "//test/fuse/linux:stat_test", - vfs2 = "True", -) diff --git a/test/fuse/README.md b/test/fuse/README.md deleted file mode 100644 index 734c3a4e3..000000000 --- a/test/fuse/README.md +++ /dev/null @@ -1,103 +0,0 @@ -# gVisor FUSE Test Suite - -This is an integration test suite for fuse(4) filesystem. It runs under both -gVisor and Linux, and ensures compatibility between the two. This test suite is -based on system calls test. - -This document describes the framework of fuse integration test and the -guidelines that should be followed when adding new fuse tests. - -## Integration Test Framework - -Please refer to the figure below. `>` is entering the function, `<` is leaving -the function, and `=` indicates sequentially entering and leaving. - -``` - | Client (Test Main Process) | Server (FUSE Daemon) - | | - | >TEST_F() | - | >SetUp() | - | =MountFuse() | - | >SetUpFuseServer() | - | [create communication pipes] | - | =fork() | =fork() - | >WaitCompleted() | - | [wait for MarkDone()] | - | | =ConsumeFuseInit() - | | =MarkDone() - | <WaitCompleted() | - | <SetUpFuseServer() | - | <SetUp() | - | >SetExpected() | - | [construct expected reaction] | - | | >FuseLoop() - | | >ReceiveExpected() - | | [wait data from pipe] - | [write data to pipe] | - | [wait for MarkDone()] | - | | [save data to memory] - | | =MarkDone() - | <SetExpected() | - | | <ReceiveExpected() - | | >read() - | | [wait for fs operation] - | >[Do fs operation] | - | [wait for fs response] | - | | <read() - | | =CompareRequest() - | | =write() [write fs response] - | <[Do fs operation] | - | =[Test fs operation result] | - | =[wait for MarkDone()] | - | | =MarkDone() - | >TearDown() | - | =UnmountFuse() | - | <TearDown() | - | <TEST_F() | -``` - -## Running the tests - -Based on syscall tests, fuse tests can run in different environments. To enable -fuse testing environment, the test targets should be appended with `_fuse`. - -For example, to run fuse test in `stat_test.cc`: - -```bash -$ bazel test //test/fuse:stat_test_runsc_ptrace_vfs2_fuse -``` - -Test all targets tagged with fuse: - -```bash -$ bazel test --test_tag_filters=fuse //test/fuse/... -``` - -## Writing a new FUSE test - -1. Add test targets in `BUILD` and `linux/BUILD`. -2. Inherit your test from `FuseTest` base class. It allows you to: - - Run a fake FUSE server in background during each test setup. - - Create pipes for communication and provide utility functions. - - Stop FUSE server after test completes. -3. Customize your comparison function for request assessment in FUSE server. -4. Add the mapping of the size of structs if you are working on new FUSE - opcode. - - Please update `FuseTest::GetPayloadSize()` for each new FUSE opcode. -5. Build the expected request-response pair of your FUSE operation. -6. Call `SetExpected()` function to inject the expected reaction. -7. Check the response and/or errors. -8. Finally call `WaitCompleted()` to ensure the FUSE server acts correctly. - -A few customized matchers used in syscalls test are encouraged to test the -outcome of filesystem operations. Such as: - -```cc -SyscallSucceeds() -SyscallSucceedsWithValue(...) -SyscallFails() -SyscallFailsWithErrno(...) -``` - -Please refer to [test/syscalls/README.md](../syscalls/README.md) for further -details. diff --git a/test/fuse/linux/BUILD b/test/fuse/linux/BUILD deleted file mode 100644 index 4871bb531..000000000 --- a/test/fuse/linux/BUILD +++ /dev/null @@ -1,32 +0,0 @@ -load("//tools:defs.bzl", "cc_binary", "cc_library", "gtest") - -package( - default_visibility = ["//:sandbox"], - licenses = ["notice"], -) - -cc_binary( - name = "stat_test", - testonly = 1, - srcs = ["stat_test.cc"], - deps = [ - gtest, - ":fuse_base", - "//test/util:test_main", - "//test/util:test_util", - ], -) - -cc_library( - name = "fuse_base", - testonly = 1, - srcs = ["fuse_base.cc"], - hdrs = ["fuse_base.h"], - deps = [ - gtest, - "//test/util:posix_error", - "//test/util:temp_path", - "//test/util:test_util", - "@com_google_absl//absl/strings:str_format", - ], -) diff --git a/test/fuse/linux/fuse_base.cc b/test/fuse/linux/fuse_base.cc deleted file mode 100644 index 9c3124472..000000000 --- a/test/fuse/linux/fuse_base.cc +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/fuse/linux/fuse_base.h" - -#include <fcntl.h> -#include <linux/fuse.h> -#include <string.h> -#include <sys/mount.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <sys/uio.h> -#include <unistd.h> - -#include <iostream> - -#include "gtest/gtest.h" -#include "absl/strings/str_format.h" -#include "test/util/posix_error.h" -#include "test/util/temp_path.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -void FuseTest::SetUp() { - MountFuse(); - SetUpFuseServer(); -} - -void FuseTest::TearDown() { UnmountFuse(); } - -// Since CompareRequest is running in background thread, gTest assertions and -// expectations won't directly reflect the test result. However, the FUSE -// background server still connects to the same standard I/O as testing main -// thread. So EXPECT_XX can still be used to show different results. To -// ensure failed testing result is observable, return false and the result -// will be sent to test main thread via pipe. -bool FuseTest::CompareRequest(void* expected_mem, size_t expected_len, - void* real_mem, size_t real_len) { - if (expected_len != real_len) return false; - return memcmp(expected_mem, real_mem, expected_len) == 0; -} - -// SetExpected is called by the testing main thread to set expected request- -// response pair of a single FUSE operation. -void FuseTest::SetExpected(struct iovec* iov_in, int iov_in_cnt, - struct iovec* iov_out, int iov_out_cnt) { - EXPECT_THAT(RetryEINTR(writev)(set_expected_[1], iov_in, iov_in_cnt), - SyscallSucceedsWithValue(::testing::Gt(0))); - WaitCompleted(); - - EXPECT_THAT(RetryEINTR(writev)(set_expected_[1], iov_out, iov_out_cnt), - SyscallSucceedsWithValue(::testing::Gt(0))); - WaitCompleted(); -} - -// WaitCompleted waits for the FUSE server to finish its job and check if it -// completes without errors. -void FuseTest::WaitCompleted() { - char success; - EXPECT_THAT(RetryEINTR(read)(done_[0], &success, sizeof(success)), - SyscallSucceedsWithValue(1)); -} - -void FuseTest::MountFuse() { - EXPECT_THAT(dev_fd_ = open("/dev/fuse", O_RDWR), SyscallSucceeds()); - - std::string mount_opts = absl::StrFormat("fd=%d,%s", dev_fd_, kMountOpts); - mount_point_ = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - EXPECT_THAT(mount("fuse", mount_point_.path().c_str(), "fuse", - MS_NODEV | MS_NOSUID, mount_opts.c_str()), - SyscallSucceedsWithValue(0)); -} - -void FuseTest::UnmountFuse() { - EXPECT_THAT(umount(mount_point_.path().c_str()), SyscallSucceeds()); - // TODO(gvisor.dev/issue/3330): ensure the process is terminated successfully. -} - -// ConsumeFuseInit consumes the first FUSE request and returns the -// corresponding PosixError. -PosixError FuseTest::ConsumeFuseInit() { - RETURN_ERROR_IF_SYSCALL_FAIL( - RetryEINTR(read)(dev_fd_, buf_.data(), buf_.size())); - - struct iovec iov_out[2]; - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_init_out), - .error = 0, - .unique = 2, - }; - // Returns a fake fuse_init_out with 7.0 version to avoid ECONNREFUSED - // error in the initialization of FUSE connection. - struct fuse_init_out out_payload = { - .major = 7, - }; - iov_out[0].iov_len = sizeof(out_header); - iov_out[0].iov_base = &out_header; - iov_out[1].iov_len = sizeof(out_payload); - iov_out[1].iov_base = &out_payload; - - RETURN_ERROR_IF_SYSCALL_FAIL(RetryEINTR(writev)(dev_fd_, iov_out, 2)); - return NoError(); -} - -// ReceiveExpected reads 1 pair of expected fuse request-response `iovec`s -// from pipe and save them into member variables of this testing instance. -void FuseTest::ReceiveExpected() { - // Set expected fuse_in request. - EXPECT_THAT(len_in_ = RetryEINTR(read)(set_expected_[0], mem_in_.data(), - mem_in_.size()), - SyscallSucceedsWithValue(::testing::Gt(0))); - MarkDone(len_in_ > 0); - - // Set expected fuse_out response. - EXPECT_THAT(len_out_ = RetryEINTR(read)(set_expected_[0], mem_out_.data(), - mem_out_.size()), - SyscallSucceedsWithValue(::testing::Gt(0))); - MarkDone(len_out_ > 0); -} - -// MarkDone writes 1 byte of success indicator through pipe. -void FuseTest::MarkDone(bool success) { - char data = success ? 1 : 0; - EXPECT_THAT(RetryEINTR(write)(done_[1], &data, sizeof(data)), - SyscallSucceedsWithValue(1)); -} - -// FuseLoop is the implementation of the fake FUSE server. Read from /dev/fuse, -// compare the request by CompareRequest (use derived function if specified), -// and write the expected response to /dev/fuse. -void FuseTest::FuseLoop() { - bool success = true; - ssize_t len = 0; - while (true) { - ReceiveExpected(); - - EXPECT_THAT(len = RetryEINTR(read)(dev_fd_, buf_.data(), buf_.size()), - SyscallSucceedsWithValue(len_in_)); - if (len != len_in_) success = false; - - if (!CompareRequest(buf_.data(), len_in_, mem_in_.data(), len_in_)) { - std::cerr << "the FUSE request is not expected" << std::endl; - success = false; - } - - EXPECT_THAT(len = RetryEINTR(write)(dev_fd_, mem_out_.data(), len_out_), - SyscallSucceedsWithValue(len_out_)); - if (len != len_out_) success = false; - MarkDone(success); - } -} - -// SetUpFuseServer creates 2 pipes. First is for testing client to send the -// expected request-response pair, and the other acts as a checkpoint for the -// FUSE server to notify the client that it can proceed. -void FuseTest::SetUpFuseServer() { - ASSERT_THAT(pipe(set_expected_), SyscallSucceedsWithValue(0)); - ASSERT_THAT(pipe(done_), SyscallSucceedsWithValue(0)); - - switch (fork()) { - case -1: - GTEST_FAIL(); - return; - case 0: - break; - default: - ASSERT_THAT(close(set_expected_[0]), SyscallSucceedsWithValue(0)); - ASSERT_THAT(close(done_[1]), SyscallSucceedsWithValue(0)); - WaitCompleted(); - return; - } - - ASSERT_THAT(close(set_expected_[1]), SyscallSucceedsWithValue(0)); - ASSERT_THAT(close(done_[0]), SyscallSucceedsWithValue(0)); - - MarkDone(ConsumeFuseInit().ok()); - - FuseLoop(); - _exit(0); -} - -// GetPayloadSize is a helper function to get the number of bytes of a -// specific FUSE operation struct. -size_t FuseTest::GetPayloadSize(uint32_t opcode, bool in) { - switch (opcode) { - case FUSE_INIT: - return in ? sizeof(struct fuse_init_in) : sizeof(struct fuse_init_out); - default: - break; - } - return 0; -} - -} // namespace testing -} // namespace gvisor diff --git a/test/fuse/linux/fuse_base.h b/test/fuse/linux/fuse_base.h deleted file mode 100644 index 3a2f255a9..000000000 --- a/test/fuse/linux/fuse_base.h +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_FUSE_FUSE_BASE_H_ -#define GVISOR_TEST_FUSE_FUSE_BASE_H_ - -#include <linux/fuse.h> -#include <sys/uio.h> - -#include <vector> - -#include "gtest/gtest.h" -#include "test/util/posix_error.h" -#include "test/util/temp_path.h" - -namespace gvisor { -namespace testing { - -constexpr char kMountOpts[] = "rootmode=755,user_id=0,group_id=0"; - -class FuseTest : public ::testing::Test { - public: - FuseTest() { - buf_.resize(FUSE_MIN_READ_BUFFER); - mem_in_.resize(FUSE_MIN_READ_BUFFER); - mem_out_.resize(FUSE_MIN_READ_BUFFER); - } - void SetUp() override; - void TearDown() override; - - // CompareRequest is used by the FUSE server and should be implemented to - // compare different FUSE operations. It compares the actual FUSE input - // request with the expected one set by `SetExpected()`. - virtual bool CompareRequest(void* expected_mem, size_t expected_len, - void* real_mem, size_t real_len); - - // SetExpected is called by the testing main thread. Writes a request- - // response pair into FUSE server's member variables via pipe. - void SetExpected(struct iovec* iov_in, int iov_in_cnt, struct iovec* iov_out, - int iov_out_cnt); - - // WaitCompleted waits for FUSE server to complete its processing. It - // complains if the FUSE server responds failure during tests. - void WaitCompleted(); - - protected: - TempPath mount_point_; - - private: - void MountFuse(); - void UnmountFuse(); - - // ConsumeFuseInit is only used during FUSE server setup. - PosixError ConsumeFuseInit(); - - // ReceiveExpected is the FUSE server side's corresponding code of - // `SetExpected()`. Save the request-response pair into its memory. - void ReceiveExpected(); - - // MarkDone is used by the FUSE server to tell testing main if it's OK to - // proceed next command. - void MarkDone(bool success); - - // FuseLoop is where the FUSE server stay until it is terminated. - void FuseLoop(); - - // SetUpFuseServer creates 2 pipes for communication and forks FUSE server. - void SetUpFuseServer(); - - // GetPayloadSize is a helper function to get the number of bytes of a - // specific FUSE operation struct. - size_t GetPayloadSize(uint32_t opcode, bool in); - - int dev_fd_; - int set_expected_[2]; - int done_[2]; - - std::vector<char> buf_; - std::vector<char> mem_in_; - std::vector<char> mem_out_; - ssize_t len_in_; - ssize_t len_out_; -}; - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_FUSE_FUSE_BASE_H_ diff --git a/test/fuse/linux/stat_test.cc b/test/fuse/linux/stat_test.cc deleted file mode 100644 index 172e09867..000000000 --- a/test/fuse/linux/stat_test.cc +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <errno.h> -#include <fcntl.h> -#include <linux/fuse.h> -#include <sys/stat.h> -#include <sys/statfs.h> -#include <sys/types.h> -#include <unistd.h> - -#include <vector> - -#include "gtest/gtest.h" -#include "test/fuse/linux/fuse_base.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -namespace { - -class StatTest : public FuseTest { - public: - bool CompareRequest(void* expected_mem, size_t expected_len, void* real_mem, - size_t real_len) override { - if (expected_len != real_len) return false; - struct fuse_in_header* real_header = - reinterpret_cast<fuse_in_header*>(real_mem); - - if (real_header->opcode != FUSE_GETATTR) { - std::cerr << "expect header opcode " << FUSE_GETATTR << " but got " - << real_header->opcode << std::endl; - return false; - } - return true; - } - - bool StatsAreEqual(struct stat expected, struct stat actual) { - // device number will be dynamically allocated by kernel, we cannot know - // in advance - actual.st_dev = expected.st_dev; - return memcmp(&expected, &actual, sizeof(struct stat)) == 0; - } -}; - -TEST_F(StatTest, StatNormal) { - struct iovec iov_in[2]; - struct iovec iov_out[2]; - - struct fuse_in_header in_header = { - .len = sizeof(struct fuse_in_header) + sizeof(struct fuse_getattr_in), - .opcode = FUSE_GETATTR, - .unique = 4, - .nodeid = 1, - .uid = 0, - .gid = 0, - .pid = 4, - .padding = 0, - }; - struct fuse_getattr_in in_payload = {0}; - iov_in[0].iov_len = sizeof(in_header); - iov_in[0].iov_base = &in_header; - iov_in[1].iov_len = sizeof(in_payload); - iov_in[1].iov_base = &in_payload; - - mode_t expected_mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; - struct timespec atime = {.tv_sec = 1595436289, .tv_nsec = 134150844}; - struct timespec mtime = {.tv_sec = 1595436290, .tv_nsec = 134150845}; - struct timespec ctime = {.tv_sec = 1595436291, .tv_nsec = 134150846}; - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_attr_out), - .error = 0, - .unique = 4, - }; - struct fuse_attr attr = { - .ino = 1, - .size = 512, - .blocks = 4, - .atime = static_cast<uint64_t>(atime.tv_sec), - .mtime = static_cast<uint64_t>(mtime.tv_sec), - .ctime = static_cast<uint64_t>(ctime.tv_sec), - .atimensec = static_cast<uint32_t>(atime.tv_nsec), - .mtimensec = static_cast<uint32_t>(mtime.tv_nsec), - .ctimensec = static_cast<uint32_t>(ctime.tv_nsec), - .mode = expected_mode, - .nlink = 2, - .uid = 1234, - .gid = 4321, - .rdev = 12, - .blksize = 4096, - }; - struct fuse_attr_out out_payload = { - .attr = attr, - }; - iov_out[0].iov_len = sizeof(out_header); - iov_out[0].iov_base = &out_header; - iov_out[1].iov_len = sizeof(out_payload); - iov_out[1].iov_base = &out_payload; - - SetExpected(iov_in, 2, iov_out, 2); - - struct stat stat_buf; - EXPECT_THAT(stat(mount_point_.path().c_str(), &stat_buf), SyscallSucceeds()); - - struct stat expected_stat = { - .st_ino = attr.ino, - .st_nlink = attr.nlink, - .st_mode = expected_mode, - .st_uid = attr.uid, - .st_gid = attr.gid, - .st_rdev = attr.rdev, - .st_size = static_cast<off_t>(attr.size), - .st_blksize = attr.blksize, - .st_blocks = static_cast<blkcnt_t>(attr.blocks), - .st_atim = atime, - .st_mtim = mtime, - .st_ctim = ctime, - }; - EXPECT_TRUE(StatsAreEqual(stat_buf, expected_stat)); - WaitCompleted(); -} - -TEST_F(StatTest, StatNotFound) { - struct iovec iov_in[2]; - struct iovec iov_out[2]; - - struct fuse_in_header in_header = { - .len = sizeof(struct fuse_in_header) + sizeof(struct fuse_getattr_in), - .opcode = FUSE_GETATTR, - .unique = 4, - }; - struct fuse_getattr_in in_payload = {0}; - iov_in[0].iov_len = sizeof(in_header); - iov_in[0].iov_base = &in_header; - iov_in[1].iov_len = sizeof(in_payload); - iov_in[1].iov_base = &in_payload; - - struct fuse_out_header out_header = { - .len = sizeof(struct fuse_out_header), - .error = -ENOENT, - .unique = 4, - }; - iov_out[0].iov_len = sizeof(out_header); - iov_out[0].iov_base = &out_header; - - SetExpected(iov_in, 2, iov_out, 1); - - struct stat stat_buf; - EXPECT_THAT(stat(mount_point_.path().c_str(), &stat_buf), - SyscallFailsWithErrno(ENOENT)); - WaitCompleted(); -} - -} // namespace - -} // namespace testing -} // namespace gvisor |