#include "lib/test.h"
#include <stdlib.h>

#ifdef __WIN32
    static bool win32 = true;
#else
    static bool win32 = false;
#endif

struct Test : AsyncTest {
    string procdir;
    string file;
    string file2;
    string dir;
    string dir2;
    function<void(const std::error_code&, const Fs::RequestSP&)> success;
    function<void(const std::error_code&, const Fs::RequestSP&)> fail;

    Test (int tmt = 0, int nevents = 0) : AsyncTest(tmt, nevents) {
        procdir = root_vdir + "/" + string::from_number(panda::unievent::getpid()) + "-" + string::from_number(rand());
        Fs::mkpath(procdir.c_str(), 0755);
        file  = path("file");
        file2 = path("file2");
        dir   = path("dir");
        dir2  = path("dir2");

        success = [this](auto err, auto) { CHECK(!err); happens(); };
        fail    = [this](auto err, auto) { CHECK(err); happens(); };
    }

    ~Test () {
        Fs::remove_all(procdir);
    }

    string path (string_view relpath) {
        return procdir + "/" + relpath;
    }
};

namespace test_sync {

TEST_PREFIX("fs-sync: ", "[fs]");

TEST("mkdir") {
    Test t;
    SECTION("non-existant") {
        auto ret = Fs::mkdir(t.dir);
        REQUIRE(ret);
        CHECK(Fs::isdir(t.dir));
    }
    SECTION("dir exists") {
        Fs::mkdir(t.dir);
        auto ret = Fs::mkdir(t.dir);
        REQUIRE(!ret);
        CHECK(ret.error() == std::errc::file_exists);
    }
    SECTION("file exists") {
        Fs::touch(t.file);
        auto ret = Fs::mkdir(t.file);
        REQUIRE(!ret);
        CHECK(ret.error() == std::errc::file_exists);
    }
}

TEST("rmdir") {
    Test t;
    SECTION("non-existant") {
        auto ret = Fs::rmdir(t.dir);
        REQUIRE(!ret);
        CHECK(ret.error() == std::errc::no_such_file_or_directory);
    }
    SECTION("dir exists") {
        Fs::mkdir(t.dir);
        CHECK(Fs::rmdir(t.dir));
        CHECK(!Fs::isdir(t.dir));
    }
    SECTION("file exists") {
        Fs::touch(t.file);
        auto ret = Fs::rmdir(t.file);
        REQUIRE(!ret);
        CHECK(ret.error()); // code may vary accross platforms
    }
    SECTION("non-empty dir") {
        Fs::mkdir(t.dir);
        Fs::touch(t.path("dir/file"));
        auto ret = Fs::rmdir(t.dir);
        REQUIRE(!ret);
        CHECK(ret.error() == std::errc::directory_not_empty);
    }
}

TEST("mkpath") {
    Test t;
    SECTION("non-existant") {
        CHECK(Fs::mkpath(t.dir));
        CHECK(Fs::isdir(t.dir));
    }
    SECTION("existant") {
        Fs::mkdir(t.dir);
        CHECK(Fs::mkpath(t.dir));
    }
    SECTION("deep") {
        CHECK(Fs::mkpath(t.path("dir2/dir3////dir4")));
        CHECK(Fs::isdir(t.path("dir2")));
        CHECK(Fs::isdir(t.path("dir2/dir3")));
        CHECK(Fs::isdir(t.path("dir2/dir3/dir4")));
    }
}

TEST("scandir") {
    Test t;
    SECTION("non-existant") {
        auto ret = Fs::scandir(t.dir);
        REQUIRE(!ret);
        CHECK(ret.error() == std::errc::no_such_file_or_directory);
    }
    SECTION("empty dir") {
        auto ret = Fs::scandir(t.path(""));
        REQUIRE(ret);
        CHECK(ret.value().size() == 0);
    }
    SECTION("file") {
        Fs::touch(t.file);
        auto ret = Fs::scandir(t.file);
        REQUIRE(!ret);
        CHECK(ret.error() == std::errc::not_a_directory);
    }
    SECTION("dir") {
        Fs::mkdir(t.path("adir"));
        Fs::mkdir(t.path("bdir"));
        Fs::touch(t.path("afile"));
        Fs::touch(t.path("bfile"));
        auto ret = Fs::scandir(t.path(""));
        REQUIRE(ret);
        auto& list = ret.value();
        CHECK(list.size() == 4);
        CHECK(list[0].name() == "adir");
        CHECK(list[0].type() == Fs::FileType::DIR);
        CHECK(list[1].name() == "afile");
        CHECK(list[1].type() == Fs::FileType::FILE);
        CHECK(list[2].name() == "bdir");
        CHECK(list[2].type() == Fs::FileType::DIR);
        CHECK(list[3].name() == "bfile");
        CHECK(list[3].type() == Fs::FileType::FILE);
    }
}

TEST("remove") {
    Test t;
    SECTION("non-existant") {
        CHECK(!Fs::remove(t.file));
    }
    SECTION("file") {
        Fs::touch(t.file);
        CHECK(Fs::remove(t.file));
        CHECK(!Fs::exists(t.file));
    }
    SECTION("dir") {
        Fs::mkdir(t.dir);
        CHECK(Fs::remove(t.dir));
        CHECK(!Fs::exists(t.dir));
    }
    SECTION("non-empty dir") {
        Fs::mkdir(t.dir);
        Fs::touch(t.path("dir/file"));
        auto ret = Fs::remove(t.dir);
        REQUIRE(!ret);
        CHECK(ret.error() == std::errc::directory_not_empty);
    }
}

TEST("remove_all") {
    Test t;
    SECTION("non-existant") {
        auto ret = Fs::remove_all(t.dir);
        REQUIRE(!ret);
        CHECK(ret.error() == std::errc::no_such_file_or_directory);
    }
    SECTION("file") {
        Fs::touch(t.file);
        CHECK(Fs::remove_all(t.file));
        CHECK(!Fs::exists(t.file));
    }
    SECTION("dir") {
        Fs::mkpath(t.path("dir/dir1/dir2/dir3"));
        Fs::mkpath(t.path("dir/dir4"));
        Fs::touch(t.path("dir/file1"));
        Fs::touch(t.path("dir/file2"));
        Fs::touch(t.path("dir/dir4/file3"));
        Fs::touch(t.path("dir/dir1/file4"));
        Fs::touch(t.path("dir/dir1/dir2/file5"));
        Fs::touch(t.path("dir/dir1/dir2/dir3/file6"));
        CHECK(Fs::remove_all(t.path("dir")));
        CHECK(!Fs::exists(t.path("dir")));
    }
}

TEST("open/close") {
    Test t;
    SECTION("non-existant no-create") {
        auto ret = Fs::open(t.file, Fs::OpenFlags::RDONLY);
        REQUIRE(!ret);
        CHECK(ret.error() == std::errc::no_such_file_or_directory);
    }
    SECTION("non-existant create") {
        auto ret = Fs::open(t.file, Fs::OpenFlags::RDWR | Fs::OpenFlags::CREAT);
        CHECK(ret.value());
        CHECK(Fs::close(*ret));
    }
    SECTION("existant") {
        Fs::touch(t.file);
        auto ret = Fs::open(t.file, Fs::OpenFlags::RDONLY);
        REQUIRE(ret);
        Fs::close(*ret);
    }
}

TEST("stat") {
    Test t;
    SECTION("non-existant") {
        auto ret = Fs::stat(t.file);
        CHECK(!ret);
    }
    SECTION("path") {
        Fs::touch(t.file);
        auto ret = Fs::stat(t.file);
        REQUIRE(ret);
        auto s = ret.value();
        CHECK(s.mtime.get());
        CHECK(s.type() == Fs::FileType::FILE);
    }
    SECTION("fd") {
        Fs::touch(t.file);
        auto fd = Fs::open(t.file, Fs::OpenFlags::RDONLY).value();
        auto ret = Fs::stat(fd);
        REQUIRE(ret);
        CHECK(ret.value().type() == Fs::FileType::FILE);
        Fs::close(fd);
    }
}

TEST("statfs") {
    if (win32) return;
    Test t;
    auto ret = Fs::statfs("/");
    REQUIRE(ret);
    auto val = ret.value();
    CHECK(val.bsize);
    CHECK(val.blocks);
}

TEST("exists/isfile/isdir") {
    Test t;
    CHECK(!Fs::exists(t.file));
    CHECK(!Fs::isfile(t.file));
    CHECK(!Fs::isdir(t.file));
    Fs::touch(t.file);
    CHECK(Fs::exists(t.file));
    CHECK(Fs::isfile(t.file));
    CHECK(!Fs::isdir(t.file));
    Fs::mkdir(t.dir);
    CHECK(Fs::exists(t.dir));
    CHECK(!Fs::isfile(t.dir));
    CHECK(Fs::isdir(t.dir));
}

TEST("access") {
    Test t;
    CHECK(!Fs::access(t.file));
    CHECK(!Fs::access(t.file, 4));
    Fs::touch(t.file);
    CHECK(Fs::access(t.file));
    CHECK(Fs::access(t.file, 6));
    if (!win32) {
        CHECK(!Fs::access(t.file, 1));
        CHECK(!Fs::access(t.file, 7));
    }
}

TEST("unlink") {
    Test t;
    SECTION("non-existant") {
        auto ret = Fs::unlink(t.file);
        REQUIRE(!ret);
        CHECK(ret.error() == std::errc::no_such_file_or_directory);
    }
    SECTION("file") {
        Fs::touch(t.file);
        CHECK(Fs::unlink(t.file));
        CHECK(!Fs::exists(t.file));
    }
    SECTION("dir") {
        Fs::mkdir(t.dir);
        auto ret = Fs::unlink(t.dir);
        REQUIRE(!ret);
        // can't check error - could be any on various platforms
    }
}

TEST("read/write") {
    Test t;
    auto fd = Fs::open(t.file, Fs::OpenFlags::RDWR | Fs::OpenFlags::CREAT).value();
    auto s = Fs::read(fd, 100).value();
    CHECK(s == "");

    CHECK(Fs::write(fd, "hello "));
    CHECK(Fs::write(fd, "world"));

    s = Fs::read(fd, 100, 0).value();
    CHECK(s == "hello world");

    std::vector<string_view> sv = {"d", "u", "d", "e"};
    Fs::write(fd, sv.begin(), sv.end(), 6);

    Fs::close(fd);

    CHECK(Fs::stat(t.file).value().size == 11);

    fd = Fs::open(t.file, Fs::OpenFlags::RDONLY).value();
    CHECK(Fs::read(fd, 11).value() == "hello duded");
    Fs::close(fd);
}

TEST("truncate") {
    Test t;
    auto fd = Fs::open(t.file, Fs::OpenFlags::RDWR | Fs::OpenFlags::CREAT).value();
    Fs::write(fd, "0123456789");
    Fs::close(fd);
    CHECK(Fs::stat(t.file).value().size == 10);

    fd = Fs::open(t.file, Fs::OpenFlags::RDWR).value();
    Fs::truncate(fd, 5);
    Fs::close(fd);
    CHECK(Fs::stat(t.file).value().size == 5);

    Fs::truncate(t.file);
    CHECK(Fs::stat(t.file).value().size == 0);
}

TEST("chmod") {
    if (win32) return; // it seems win32 ignores chmod
    Test t;
    Fs::touch(t.file, 0644);
    SECTION("path") {
        Fs::chmod(t.file, 0666);
        CHECK(Fs::stat(t.file).value().perms() == 0666);
    }
    SECTION("fd") {
        auto fd = Fs::open(t.file, Fs::OpenFlags::RDONLY).value();
        Fs::chmod(fd, 0600);
        CHECK(Fs::stat(t.file).value().perms() == 0600);
        Fs::close(fd);
    }
}

TEST("touch") {
    Test t;
    SECTION("non-existant") {
        CHECK(Fs::touch(t.file));
        CHECK(Fs::isfile(t.file));
    }
    SECTION("exists") {
        CHECK(Fs::touch(t.file));
        auto s = Fs::stat(t.file).value();
        auto mtime = s.mtime;
        auto atime = s.atime;
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
        CHECK(Fs::touch(t.file));
        CHECK(Fs::isfile(t.file));
        s = Fs::stat(t.file).value();
        CHECK(s.mtime > mtime);
        CHECK(s.atime > atime);
    }
}

TEST("utime") {
    Test t;
    SECTION("non-existant") {
        auto ret = Fs::utime(t.file, 1000, 1000);
        REQUIRE(!ret);
        CHECK(ret.error() == std::errc::no_such_file_or_directory);
    }
    SECTION("path") {
        Fs::touch(t.file);
        CHECK(Fs::utime(t.file, 1000, 1000));
        CHECK(Fs::stat(t.file).value().atime.get() == 1000);
        CHECK(Fs::stat(t.file).value().mtime.get() == 1000);
    }
    if (!win32) // win32 can't set utime via descriptor
        SECTION("fd") {
        Fs::touch(t.file);
        auto fd = Fs::open(t.file, Fs::OpenFlags::RDONLY).value();
        CHECK(Fs::utime(fd, 2000, 2000));
        Fs::close(fd);
        CHECK(Fs::stat(t.file).value().atime.get() == 2000);
        CHECK(Fs::stat(t.file).value().mtime.get() == 2000);
    }
}

// no tests for chown

TEST("rename") {
    Test t;
    SECTION("non-existant") {
        Fs::touch(t.file2);
        auto ret = Fs::rename(t.file, t.file2);
        REQUIRE(!ret);
        CHECK(ret.error() == std::errc::no_such_file_or_directory);
    }
    SECTION("exists file") {
        Fs::touch(t.file);
        CHECK(Fs::rename(t.file, t.file2));
        CHECK(Fs::isfile(t.file2));
    }
    SECTION("exists dir") {
        Fs::mkdir(t.dir);
        CHECK(Fs::rename(t.dir, t.dir2));
        CHECK(Fs::isdir(t.dir2));
    }
}

TEST("mkdtemp") {
    Test t;
    auto ret = Fs::mkdtemp(t.path("tmpXXXXXX")).value();
    CHECK(ret != "");
    CHECK(Fs::isdir(ret));
}

TEST("mkstemp") {
    Test t;
    auto ret = Fs::mkstemp(t.path("tmpXXXXXX")).value();
    CHECK(ret.path != "");
    CHECK(Fs::exists(ret.path));
    Fs::write(ret.fd, "hello world");
    Fs::close(ret.fd);
    CHECK(Fs::stat(ret.path).value().size == 11);
}

}

namespace test_async {

TEST_PREFIX("fs-async: ", "[fs]");

TEST("fs request") {
    Test t(2000);
    auto req = Fs::mkdtemp(t.path("tmpXXXXXX"), [](auto&&...){}, t.loop);
    CHECK(req->active());
    t.run();
    CHECK_FALSE(req->active());
}

TEST("mkdir") {
    Test t(10000, 1);
    SECTION("ok") {
        Fs::mkdir(t.dir, 0755, t.success, t.loop);
        t.run();
        CHECK(Fs::isdir(t.dir));
    }
    SECTION("err") {
        Fs::mkdir(t.dir);
        Fs::mkdir(t.dir, 0755, [&](auto& err, auto) {
            t.happens();
            CHECK(err == std::errc::file_exists);
        }, t.loop);
        t.run();
    }
}

TEST("rmdir") {
    Test t(10000, 1);
    SECTION("err") {
        Fs::rmdir(t.dir, [&](auto& err, auto) {
            t.happens();
            CHECK(err == std::errc::no_such_file_or_directory);
        }, t.loop);
        t.run();
    }
    SECTION("ok") {
        Fs::mkdir(t.dir);
        Fs::rmdir(t.dir, t.success, t.loop);
        t.run();
        CHECK(!Fs::exists(t.dir));
    }
}

TEST("mkpath") {
    Test t(10000, 1);
    Fs::mkpath(t.path("dir2/dir3////dir4"), 0755, t.success, t.loop);
    t.run();
    CHECK(Fs::isdir(t.path("dir2")));
    CHECK(Fs::isdir(t.path("dir2/dir3")));
    CHECK(Fs::isdir(t.path("dir2/dir3/dir4")));
}

TEST("scandir") {
    Test t(10000, 1);
    Fs::mkdir(t.path("adir"));
    Fs::mkdir(t.path("bdir"));
    Fs::touch(t.path("afile"));
    Fs::touch(t.path("bfile"));
    Fs::scandir(t.path(""), [&](auto& list, auto& err, auto) {
        t.happens();
        REQUIRE(!err);
        REQUIRE(list.size() == 4);
        CHECK(list[0].name() == "adir");
        CHECK(list[0].type() == Fs::FileType::DIR);
        CHECK(list[1].name() == "afile");
        CHECK(list[1].type() == Fs::FileType::FILE);
        CHECK(list[2].name() == "bdir");
        CHECK(list[2].type() == Fs::FileType::DIR);
        CHECK(list[3].name() == "bfile");
        CHECK(list[3].type() == Fs::FileType::FILE);
    }, t.loop);
    t.run();
}

TEST("remove") {
    Test t(10000, 1);
    Fs::touch(t.file);
    Fs::remove(t.file, t.success, t.loop);
    t.run();
    CHECK(!Fs::exists(t.file));
}

TEST("remove_all") {
    Test t(10000, 1);
    Fs::mkpath(t.path("dir/dir1/dir2/dir3"));
    Fs::mkpath(t.path("dir/dir4"));
    Fs::touch(t.path("dir/file1"));
    Fs::touch(t.path("dir/file2"));
    Fs::touch(t.path("dir/dir4/file3"));
    Fs::touch(t.path("dir/dir1/file4"));
    Fs::touch(t.path("dir/dir1/dir2/file5"));
    Fs::touch(t.path("dir/dir1/dir2/dir3/file6"));
    Fs::remove_all(t.dir, t.success, t.loop);
    t.run();
    CHECK(!Fs::exists(t.dir));
}

TEST("open/close") {
    Test t(10000, 1);
    t.set_expected(2);
    Fs::open(t.file, Fs::OpenFlags::RDWR | Fs::OpenFlags::CREAT, 0644, [&](auto fd, auto err, auto) {
        t.happens();
        REQUIRE(!err);
        REQUIRE(fd);
        Fs::close(fd, t.success, t.loop);
    }, t.loop);
    t.run();
}

TEST("stat") {
    Test t(10000, 1);
    Fs::touch(t.file);
    auto cb = [&](auto stat, auto err, auto) {
        CHECK(!err);
        CHECK(stat.mtime.get());
        CHECK(stat.type() == Fs::FileType::FILE);
        t.happens();
    };
    SECTION("path") {
        Fs::stat(t.file, cb, t.loop);
        t.run();
    }
    SECTION("fd") {
        auto fd = Fs::open(t.file, Fs::OpenFlags::RDONLY).value();
        Fs::stat(fd, cb, t.loop);
        t.run();
        Fs::close(fd);
    }
}

TEST("statfs") {
    if (win32) return;
    Test t(10000, 1);
    Fs::statfs("/", [&](auto& info, auto&, auto&){
        t.happens();
        CHECK(info.bsize);
        CHECK(info.blocks);
    }, t.loop);
    t.run();
}

TEST("exists/isfile/isdir") {
    Test t(10000, 1);
    t.set_expected(9);
    auto yes = [&](bool val, auto err, auto) {
        CHECK(!err);
        CHECK(val);
        t.happens();
    };
    auto no = [&](bool val, auto err, auto) {
        CHECK(!err);
        CHECK(!val);
        t.happens();
    };

    Fs::exists(t.file, no, t.loop);
    Fs::isfile(t.file, no, t.loop);
    Fs::isdir(t.file, no, t.loop);
    t.run();

    Fs::touch(t.file);

    Fs::exists(t.file, yes, t.loop);
    Fs::isfile(t.file, yes, t.loop);
    Fs::isdir(t.file, no, t.loop);
    t.run();

    Fs::mkdir(t.dir);

    Fs::exists(t.dir, yes, t.loop);
    Fs::isfile(t.dir, no, t.loop);
    Fs::isdir(t.dir, yes, t.loop);
    t.run();
}

TEST("access") {
    Test t(10000, 1);
    t.set_expected(win32 ? 4 : 6);
    Fs::access(t.file, 0, t.fail, t.loop);
    t.run();
    Fs::access(t.file, 4, t.fail, t.loop);
    t.run();
    Fs::touch(t.file);
    Fs::access(t.file, 0, t.success, t.loop);
    t.run();
    Fs::access(t.file, 6, t.success, t.loop);
    t.run();
    if (!win32) {
        Fs::access(t.file, 1, t.fail, t.loop);
        t.run();
        Fs::access(t.file, 7, t.fail, t.loop);
        t.run();
    }
}

TEST("unlink") {
    Test t(10000, 1);
    Fs::touch(t.file);
    Fs::unlink(t.file, t.success, t.loop);
    t.run();
    CHECK(!Fs::exists(t.file));
}

TEST("read/write") {
    Test t(10000, 1);
    t.set_expected(6);
    auto fd = Fs::open(t.file, Fs::OpenFlags::RDWR | Fs::OpenFlags::CREAT).value();
    Fs::read(fd, 100, 0, [&](auto s, auto err, auto) {
        t.happens();
        CHECK(!err);
        CHECK(s == "");
    }, t.loop);
    t.run();

    Fs::write(fd, "hello ", -1, t.success, t.loop);
    t.run();
    Fs::write(fd, "world", -1, t.success, t.loop);
    t.run();

    Fs::read(fd, 100, 0, [&](auto s, auto err, auto){
        t.happens();
        CHECK(!err);
        CHECK(s == "hello world");
    }, t.loop);
    t.run();

    std::vector<string_view> sv = {"d", "u", "d", "e"};
    Fs::write(fd, sv.begin(), sv.end(), 6, t.success, t.loop);
    t.run();

    Fs::close(fd);

    CHECK(Fs::stat(t.file).value().size == 11);

    fd = Fs::open(t.file, Fs::OpenFlags::RDONLY).value();
    Fs::read(fd, 11, 0, [&](auto s, auto err, auto){
        t.happens();
        CHECK(!err);
        CHECK(s == "hello duded");
    }, t.loop);
    t.run();
    Fs::close(fd);
}

TEST("truncate") {
    Test t(10000, 1);
    t.set_expected(2);
    auto fd = Fs::open(t.file, Fs::OpenFlags::RDWR | Fs::OpenFlags::CREAT).value();
    Fs::write(fd, "0123456789");
    Fs::close(fd);
    CHECK(Fs::stat(t.file).value().size == 10);

    fd = Fs::open(t.file, Fs::OpenFlags::RDWR).value();
    Fs::truncate(fd, 5, t.success, t.loop);
    t.run();
    Fs::close(fd);
    CHECK(Fs::stat(t.file).value().size == 5);

    Fs::truncate(t.file, 0, t.success, t.loop);
    t.run();
    CHECK(Fs::stat(t.file).value().size == 0);
}

TEST("chmod") {
    if (win32) return;
    Test t(10000, 1);
    Fs::touch(t.file, 0644);
    SECTION("path") {
        Fs::chmod(t.file, 0666, t.success, t.loop);
        t.run();
        CHECK(Fs::stat(t.file).value().perms() == 0666);
    }
    SECTION("fd") {
        auto fd = Fs::open(t.file, Fs::OpenFlags::RDONLY).value();
        Fs::chmod(fd, 0600, t.success, t.loop);
        t.run();
        CHECK(Fs::stat(t.file).value().perms() == 0600);
        Fs::close(fd);
    }
}

TEST("touch") {
    Test t(10000, 1);
    t.set_expected(2);
    Fs::touch(t.file, 0644, t.success, t.loop);
    t.run();
    auto s = Fs::stat(t.file).value();
    auto mtime = s.mtime;
    auto atime = s.atime;
    std::this_thread::sleep_for(std::chrono::milliseconds(1));

    Fs::touch(t.file, 0644, t.success, t.loop);
    t.run();
    CHECK(Fs::isfile(t.file));
    s = Fs::stat(t.file).value();
    CHECK(s.mtime > mtime);
    CHECK(s.atime > atime);
}

TEST("utime") {
    Test t(10000, 1);
    Fs::touch(t.file);
    SECTION("path") {
        Fs::utime(t.file, 1000, 1000, t.success, t.loop);
        t.run();
        CHECK(Fs::stat(t.file).value().atime.get() == 1000);
        CHECK(Fs::stat(t.file).value().mtime.get() == 1000);
    }
    if (!win32)
    SECTION("fd") {
        auto fd = Fs::open(t.file, Fs::OpenFlags::RDONLY).value();
        Fs::utime(fd, 2000, 2000, t.success, t.loop);
        t.run();
        Fs::close(fd);
        CHECK(Fs::stat(t.file).value().atime.get() == 2000);
        CHECK(Fs::stat(t.file).value().mtime.get() == 2000);
    }
}

// no tests for chown

TEST("rename") {
    Test t(10000, 1);
    Fs::touch(t.file);
    Fs::rename(t.file, t.file2, t.success, t.loop);
    t.run();
    CHECK(!Fs::exists(t.file));
    CHECK(Fs::isfile(t.file2));
}

TEST("mkdtemp") {
    Test t(2000, 1);
    Fs::mkdtemp(t.path("tmpXXXXXX"), [&](auto& path, auto& err, auto&){
        REQUIRE(!err);
        CHECK(path != "");
        CHECK(Fs::isdir(path));
        t.happens();
    }, t.loop);
    t.run();
}

TEST("mkstemp") {
    Test t(2000, 1);
    Fs::mkstemp(t.path("tmpXXXXXX"), [&](auto& path, auto fd, auto& err, auto&){
        REQUIRE(!err);
        CHECK(path != "");
        CHECK(Fs::exists(path));
        t.happens();

        Fs::write(fd, "hello world");
        Fs::close(fd);
        CHECK(Fs::stat(path).value().size == 11);
    }, t.loop);
    t.run();
}

}