15 changes: 11 additions & 4 deletions test/tool_main_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ namespace fs = std::filesystem;

namespace {

// TODO: this is a workaround for older Clang versions
struct fs_path_hash {
auto operator()(const std::filesystem::path& p) const noexcept {
return std::filesystem::hash_value(p);
}
};

auto test_dir = fs::path(TEST_DATA_DIR).make_preferred();
auto audio_data_dir = test_dir / "pcmaudio";

Expand Down Expand Up @@ -700,7 +707,7 @@ TEST_P(term_logging_test, end_to_end) {
auto err = t.err();
auto it = log_level_strings.begin();

auto make_contains_regex = [fancy](auto m) {
auto make_contains_regex = [fancy = fancy](auto m) {
auto const& [color, prefix] = m->second;
auto beg = fancy ? color : std::string_view{};
auto end = fancy && !color.empty() ? "<normal>" : "";
Expand Down Expand Up @@ -2311,7 +2318,7 @@ TEST_P(map_file_error_test, delayed) {
auto fs = t.fs_from_file("test.dwarfs");
// fs.dump(std::cout, 2);

std::unordered_map<fs::path, std::string> actual_files;
std::unordered_map<fs::path, std::string, fs_path_hash> actual_files;
fs.walk([&](auto const& dev) {
auto iv = dev.inode();
if (iv.is_regular_file()) {
Expand Down Expand Up @@ -2360,8 +2367,8 @@ TEST_P(map_file_error_test, delayed) {
failed_expected.end(),
std::inserter(surprisingly_missing, surprisingly_missing.begin()));

std::unordered_map<fs::path, std::string> original_files(files.begin(),
files.end());
std::unordered_map<fs::path, std::string, fs_path_hash> original_files(
files.begin(), files.end());

EXPECT_EQ(0, surprisingly_missing.size());

Expand Down
135 changes: 104 additions & 31 deletions test/tools_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#ifdef __APPLE__
#include <sys/mount.h>
#else
#include <sys/vfs.h>
#endif
#include <sys/xattr.h>
#endif

Expand Down Expand Up @@ -123,10 +127,27 @@ class scoped_no_leak_check {
};

#ifndef _WIN32
ssize_t portable_listxattr(const char* path, char* list, size_t size) {
#ifdef __APPLE__
return ::listxattr(path, list, size, 0);
#else
return ::listxattr(path, list, size);
#endif
}

ssize_t portable_getxattr(const char* path, const char* name, void* value,
size_t size) {
#ifdef __APPLE__
return ::getxattr(path, name, value, size, 0, 0);
#else
return ::getxattr(path, name, value, size);
#endif
}

pid_t get_dwarfs_pid(fs::path const& path) {
std::array<char, 32> attr_buf;
auto attr_len = ::getxattr(path.c_str(), "user.dwarfs.driver.pid",
attr_buf.data(), attr_buf.size());
auto attr_len = portable_getxattr(path.c_str(), "user.dwarfs.driver.pid",
attr_buf.data(), attr_buf.size());
if (attr_len < 0) {
throw std::runtime_error("could not read pid from xattr");
}
Expand Down Expand Up @@ -332,7 +353,7 @@ class subprocess {
(append_arg(cmdline_, std::forward<Args>(args)), ...);

try {
// std::cout << "running: " << cmdline() << "\n";
// std::cerr << "running: " << cmdline() << "\n";
c_ = bp::child(prog.string(), bp::args(cmdline_), bp::std_in.close(),
bp::std_out > out_, bp::std_err > err_, ios_
#ifdef _WIN32
Expand All @@ -346,6 +367,14 @@ class subprocess {
}
}

~subprocess() {
if (pt_) {
std::cerr << "subprocess still running in destructor: " << cmdline()
<< "\n";
pt_->join();
}
}

std::string cmdline() const {
std::string cmd = prog_.string();
if (!cmdline_.empty()) {
Expand Down Expand Up @@ -378,10 +407,13 @@ class subprocess {
}

void interrupt() {
std::cerr << "interrupting: " << cmdline() << "\n";
#ifdef _WIN32
::GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pid());
#else
::kill(pid(), SIGINT);
if (auto rv = ::kill(pid(), SIGINT); rv != 0) {
std::cerr << "kill(" << pid() << ", SIGINT) = " << rv << "\n";
}
#endif
}

Expand Down Expand Up @@ -438,7 +470,7 @@ class subprocess {
std::vector<std::string> cmdline_;
};

#ifndef _WIN32
#if !(defined(_WIN32) || defined(__APPLE__))
class process_guard {
public:
process_guard() = default;
Expand Down Expand Up @@ -497,11 +529,21 @@ class driver_runner {

wait_until_file_ready(mountpoint, std::chrono::seconds(5));
#else
std::vector<std::string> options;
#ifdef __APPLE__
options.push_back("-o");
options.push_back("noapplexattr");
#endif
if (!subprocess::check_run(driver, make_tool_arg(tool_arg), image,
mountpoint, std::forward<Args>(args)...)) {
mountpoint, options,
std::forward<Args>(args)...)) {
throw std::runtime_error("error running " + driver.string());
}
#ifdef __APPLE__
wait_until_file_ready(mountpoint, std::chrono::seconds(5));
#else
dwarfs_guard_ = process_guard(get_dwarfs_pid(mountpoint));
#endif
#endif
}

Expand All @@ -518,7 +560,7 @@ class driver_runner {
#endif
std::forward<Args>(args)...);
process_->run_background();
#ifndef _WIN32
#if !(defined(_WIN32) || defined(__APPLE__))
dwarfs_guard_ = process_guard(process_->pid());
#endif
}
Expand All @@ -531,6 +573,27 @@ class driver_runner {
#endif

if (!mountpoint_.empty()) {
#ifdef __APPLE__
auto umount = dwarfs::test::find_binary("umount");
if (!umount) {
throw std::runtime_error("no umount binary found");
}
subprocess::check_run(umount.value(), mountpoint_);
bool rv{true};
if (process_) {
process_->wait();
auto ec = process_->exit_code();
if (ec != 0) {
std::cerr << "driver failed to unmount:\nout:\n"
<< process_->out() << "err:\n"
<< process_->err() << "exit code: " << ec << "\n";
rv = false;
}
}
process_.reset();
mountpoint_.clear();
return rv;
#else
#ifndef _WIN32
if (process_) {
#endif
Expand All @@ -552,6 +615,7 @@ class driver_runner {
mountpoint_.clear();
return dwarfs_guard_.check_exit(std::chrono::seconds(5));
}
#endif
#endif
}
return false;
Expand Down Expand Up @@ -582,7 +646,7 @@ class driver_runner {
}

private:
#ifndef _WIN32
#if !(defined(_WIN32) || defined(__APPLE__))
static fs::path find_fusermount() {
auto fusermount_bin = dwarfs::test::find_binary("fusermount");
if (!fusermount_bin) {
Expand All @@ -606,7 +670,7 @@ class driver_runner {

fs::path mountpoint_;
std::unique_ptr<subprocess> process_;
#ifndef _WIN32
#if !(defined(_WIN32) || defined(__APPLE__))
process_guard dwarfs_guard_;
#endif
};
Expand Down Expand Up @@ -878,24 +942,26 @@ TEST_P(tools_test, end_to_end) {
std::string buf;
buf.resize(1);

auto r = ::listxattr(path.c_str(), buf.data(), buf.size());
auto r = portable_listxattr(path.c_str(), buf.data(), buf.size());
EXPECT_LT(r, 0) << runner.cmdline();
EXPECT_EQ(ERANGE, errno) << runner.cmdline();
r = ::listxattr(path.c_str(), buf.data(), 0);
EXPECT_GT(r, 0) << runner.cmdline();
r = portable_listxattr(path.c_str(), nullptr, 0);
ASSERT_GT(r, 0) << runner.cmdline() << ::strerror(errno);
buf.resize(r);
r = ::listxattr(path.c_str(), buf.data(), buf.size());
r = portable_listxattr(path.c_str(), buf.data(), buf.size());
EXPECT_GT(r, 0) << runner.cmdline();
EXPECT_EQ(ref, buf) << runner.cmdline();

buf.resize(1);
r = ::getxattr(path.c_str(), kInodeInfoXattr, buf.data(), buf.size());
r = portable_getxattr(path.c_str(), kInodeInfoXattr, buf.data(),
buf.size());
EXPECT_LT(r, 0) << runner.cmdline();
EXPECT_EQ(ERANGE, errno) << runner.cmdline();
r = ::getxattr(path.c_str(), kInodeInfoXattr, buf.data(), 0);
EXPECT_GT(r, 0) << runner.cmdline();
r = portable_getxattr(path.c_str(), kInodeInfoXattr, nullptr, 0);
ASSERT_GT(r, 0) << runner.cmdline() << ::strerror(errno);
buf.resize(r);
r = ::getxattr(path.c_str(), kInodeInfoXattr, buf.data(), buf.size());
r = portable_getxattr(path.c_str(), kInodeInfoXattr, buf.data(),
buf.size());
EXPECT_GT(r, 0) << runner.cmdline();

auto info = folly::parseJson(buf);
Expand Down Expand Up @@ -1045,18 +1111,25 @@ TEST_P(tools_test, end_to_end) {
EXPECT_EQ(cdr.symlinks.size(), 2) << cdr;
}

#ifdef _WIN32
#define EXPECT_EC_UNIX_WIN(ec, unix, windows) \
#define EXPECT_EC_IMPL(ec, cat, val) \
EXPECT_TRUE(ec) << runner.cmdline(); \
EXPECT_EQ(std::system_category(), (ec).category()) << runner.cmdline(); \
EXPECT_EQ(windows, (ec).value()) << runner.cmdline() << ": " << (ec).message()
EXPECT_EQ(cat, (ec).category()) << runner.cmdline(); \
EXPECT_EQ(val, (ec).value()) << runner.cmdline() << ": " << (ec).message()

#ifdef _WIN32
#define EXPECT_EC_UNIX_MAC_WIN(ec, unix, mac, windows) \
EXPECT_EC_IMPL(ec, std::system_category(), windows)
#elif defined(__APPLE__)
#define EXPECT_EC_UNIX_MAC_WIN(ec, unix, mac, windows) \
EXPECT_EC_IMPL(ec, std::generic_category(), mac)
#else
#define EXPECT_EC_UNIX_WIN(ec, unix, windows) \
EXPECT_TRUE(ec) << runner.cmdline(); \
EXPECT_EQ(std::generic_category(), (ec).category()) << runner.cmdline(); \
EXPECT_EQ(unix, (ec).value()) << runner.cmdline() << ": " << (ec).message()
#define EXPECT_EC_UNIX_MAC_WIN(ec, unix, mac, windows) \
EXPECT_EC_IMPL(ec, std::generic_category(), unix)
#endif

#define EXPECT_EC_UNIX_WIN(ec, unix, windows) \
EXPECT_EC_UNIX_MAC_WIN(ec, unix, unix, windows)

TEST_P(tools_test, mutating_and_error_ops) {
auto mode = GetParam();

Expand Down Expand Up @@ -1136,7 +1209,7 @@ TEST_P(tools_test, mutating_and_error_ops) {
{
std::error_code ec;
fs::rename(file, name_inside_fs, ec);
EXPECT_EC_UNIX_WIN(ec, ENOSYS, ERROR_ACCESS_DENIED);
EXPECT_EC_UNIX_MAC_WIN(ec, ENOSYS, EACCES, ERROR_ACCESS_DENIED);
}

{
Expand All @@ -1148,7 +1221,7 @@ TEST_P(tools_test, mutating_and_error_ops) {
{
std::error_code ec;
fs::rename(empty_dir, name_inside_fs, ec);
EXPECT_EC_UNIX_WIN(ec, ENOSYS, ERROR_ACCESS_DENIED);
EXPECT_EC_UNIX_MAC_WIN(ec, ENOSYS, EACCES, ERROR_ACCESS_DENIED);
}

{
Expand All @@ -1162,7 +1235,7 @@ TEST_P(tools_test, mutating_and_error_ops) {
{
std::error_code ec;
fs::create_hard_link(file, name_inside_fs, ec);
EXPECT_EC_UNIX_WIN(ec, ENOSYS, ERROR_ACCESS_DENIED);
EXPECT_EC_UNIX_MAC_WIN(ec, ENOSYS, EACCES, ERROR_ACCESS_DENIED);
}

{
Expand All @@ -1176,7 +1249,7 @@ TEST_P(tools_test, mutating_and_error_ops) {
{
std::error_code ec;
fs::create_symlink(file, name_inside_fs, ec);
EXPECT_EC_UNIX_WIN(ec, ENOSYS, ERROR_ACCESS_DENIED);
EXPECT_EC_UNIX_MAC_WIN(ec, ENOSYS, EACCES, ERROR_ACCESS_DENIED);
}

{
Expand All @@ -1189,7 +1262,7 @@ TEST_P(tools_test, mutating_and_error_ops) {
{
std::error_code ec;
fs::create_directory_symlink(empty_dir, name_inside_fs, ec);
EXPECT_EC_UNIX_WIN(ec, ENOSYS, ERROR_ACCESS_DENIED);
EXPECT_EC_UNIX_MAC_WIN(ec, ENOSYS, EACCES, ERROR_ACCESS_DENIED);
}

{
Expand All @@ -1212,7 +1285,7 @@ TEST_P(tools_test, mutating_and_error_ops) {
{
std::error_code ec;
fs::create_directory(name_inside_fs, ec);
EXPECT_EC_UNIX_WIN(ec, ENOSYS, ERROR_ACCESS_DENIED);
EXPECT_EC_UNIX_MAC_WIN(ec, ENOSYS, EACCES, ERROR_ACCESS_DENIED);
}

// read directory as file (non-mutating)
Expand Down