summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Jamroot.jam2
-rw-r--r--netfs/daemon/daemonDirectory.cpp1
-rw-r--r--netfs/fuse/fuseApp.cpp65
-rw-r--r--netfs/fuse/fuseApp.h21
-rw-r--r--netfs/fuse/fuseAppBase.cpp47
-rw-r--r--netfs/fuse/fuseAppBase.h47
-rw-r--r--netfs/fuse/netfs.cpp45
-rw-r--r--netfs/unittests/Jamfile.jam13
-rw-r--r--netfs/unittests/mockFuse.cpp41
-rw-r--r--netfs/unittests/mockFuse.h8
-rw-r--r--netfs/unittests/testFuse.cpp108
11 files changed, 225 insertions, 173 deletions
diff --git a/Jamroot.jam b/Jamroot.jam
index cd93e3c..7c6a0bd 100644
--- a/Jamroot.jam
+++ b/Jamroot.jam
@@ -25,9 +25,11 @@ project
<toolset>tidy:<checkxx>clang-*
<toolset>tidy:<checkxx>misc-*
<toolset>tidy:<checkxx>modernize-*
+ <toolset>tidy:<xcheckxx>modernize-use-trailing-return-type
<toolset>tidy:<checkxx>hicpp-*
<toolset>tidy:<xcheckxx>hicpp-vararg
<toolset>tidy:<xcheckxx>hicpp-signed-bitwise
+ <toolset>tidy:<xcheckxx>hicpp-no-array-decay
<toolset>tidy:<checkxx>performance-*
;
diff --git a/netfs/daemon/daemonDirectory.cpp b/netfs/daemon/daemonDirectory.cpp
index 5e5c8ad..22c0e4f 100644
--- a/netfs/daemon/daemonDirectory.cpp
+++ b/netfs/daemon/daemonDirectory.cpp
@@ -54,7 +54,6 @@ DirectoryServer::listdir(const Ice::Current&)
// LCOV_EXCL_STOP
}
struct stat s {};
- // NOLINTNEXTLINE(hicpp-no-array-decay)
if (::fstatat(fd, d->d_name, &s, AT_SYMLINK_NOFOLLOW) != 0) {
// LCOV_EXCL_START
throw NetFS::SystemError(errno);
diff --git a/netfs/fuse/fuseApp.cpp b/netfs/fuse/fuseApp.cpp
index ef1ca7f..aaec9bd 100644
--- a/netfs/fuse/fuseApp.cpp
+++ b/netfs/fuse/fuseApp.cpp
@@ -14,12 +14,22 @@ namespace AdHoc {
template class CallCacheable<struct stat, std::string>;
}
-NetFS::FuseApp::FuseApp(Ice::StringSeq a) :
- iceArgs(std::move(a)),
+NetFS::FuseApp::FuseApp(Ice::StringSeq && a) :
+ ic(Ice::initialize(a)),
sessionOpened(false),
openHandleId(0),
converter(userLookup, groupLookup)
{
+ if (!a.empty()) {
+ const auto & arg = a.front();
+ if (arg.find("://") != std::string::npos) {
+ fcr = configureFromUri(arg);
+ }
+ else if (auto colon = arg.find(':'); colon != std::string::npos) {
+ fcr = configureFromFile(arg.substr(0, colon), arg.substr(colon + 1));
+ }
+ }
+ BOOST_ASSERT(fcr);
}
NetFS::FuseApp::~FuseApp()
@@ -65,25 +75,12 @@ NetFS::FuseApp::~FuseApp()
}
}
-NetFS::Client::ConfigurationPtr
-NetFS::FuseApp::ReadConfiguration(const std::filesystem::path & path) const
-{
- auto s = Slicer::FileDeserializerFactory::createNew(path.extension().string(), path);
- return Slicer::DeserializeAnyWith<NetFS::Client::ConfigurationPtr>(s);
-}
-
-void *
-NetFS::FuseApp::init(struct fuse_conn_info *)
-{
- ic = Ice::initialize(iceArgs);
- fcr = configurator();
- return nullptr;
-}
-
NetFS::Client::ResourcePtr
-NetFS::FuseApp::configureFromFile(const std::string & configPath, const std::string & resourceName) const
+NetFS::FuseApp::configureFromFile(const std::filesystem::path & path, const std::string & resourceName) const
{
- return AdHoc::safeMapLookup<Client::ResourceNotFound>(ReadConfiguration(configPath)->Resources, resourceName);
+ auto s = Slicer::FileDeserializerFactory::createNew(path.extension().string(), path);
+ auto c = Slicer::DeserializeAnyWith<NetFS::Client::ConfigurationPtr>(s);
+ return AdHoc::safeMapLookup<Client::ResourceNotFound>(c->Resources, resourceName);
}
AdHocFormatter(IceEndpointFmt, "%? -h %? -p %?");
@@ -101,36 +98,6 @@ NetFS::FuseApp::configureFromUri(const std::string & uriString) const
return r;
}
-int
-NetFS::FuseApp::opt_parse(void *, const char * arg, int, struct fuse_args *)
-{
- if (strncmp(arg, "--Ice.", 6) == 0) {
- iceArgs.push_back(arg);
- return 0;
- }
- else if (strncmp(arg, "_netdev", 7) == 0) {
- return 0;
- }
- else if (arg[0] == '-') {
- return 1;
- }
- else if (!configurator) {
- if (strstr(arg, "://")) {
- configurator = std::bind(&NetFS::FuseApp::configureFromUri, this, arg);
- }
- else {
- const char * colon = strchr(arg, ':');
- configurator = std::bind(&NetFS::FuseApp::configureFromFile, this, std::string(arg, colon), colon + 1);
- }
- return 0;
- }
- else if (mountPoint.empty()) {
- mountPoint = arg;
- return 1;
- }
- return 1;
-}
-
void
NetFS::FuseApp::connectSession()
{
diff --git a/netfs/fuse/fuseApp.h b/netfs/fuse/fuseApp.h
index df342b6..5b5a8fa 100644
--- a/netfs/fuse/fuseApp.h
+++ b/netfs/fuse/fuseApp.h
@@ -16,7 +16,7 @@
#include <boost/icl/interval_map.hpp>
namespace NetFS {
- class DLL_PUBLIC FuseApp : public FuseAppBase {
+ class DLL_PUBLIC FuseApp : public FuseAppBaseT<FuseApp> {
private:
class OpenDir {
public:
@@ -49,15 +49,12 @@ namespace NetFS {
typedef std::map<int, OpenFilePtr> OpenFiles;
public:
- FuseApp(Ice::StringSeq);
+ FuseApp(Ice::StringSeq &&);
~FuseApp();
- private:
- void * init (struct fuse_conn_info * info) override;
- int opt_parse(void *, const char * arg, int key, struct fuse_args *) override;
-
+ protected:
void connectSession();
- void connectToService();
+ virtual void connectToService();
void connectToVolume();
void connectHandles();
void verifyConnection();
@@ -100,13 +97,10 @@ namespace NetFS {
virtual struct fuse_context * fuse_get_context() = 0;
protected:
- typedef std::function<Client::ResourcePtr()> Configurator;
- Configurator configurator;
- virtual NetFS::Client::ConfigurationPtr ReadConfiguration(const std::filesystem::path &) const;
- virtual NetFS::Client::ResourcePtr configureFromFile(const std::string &, const std::string &) const;
- virtual NetFS::Client::ResourcePtr configureFromUri(const std::string &) const;
+ NetFS::Client::ResourcePtr configureFromFile(const std::filesystem::path &, const std::string &) const;
+ NetFS::Client::ResourcePtr configureFromUri(const std::string &) const;
- private:
+ protected:
template<typename Handle, typename ... Params>
void setProxy(uint64_t & fh, const Params & ...);
template<typename Handle>
@@ -121,7 +115,6 @@ namespace NetFS {
ReqEnv reqEnv();
- Ice::StringSeq iceArgs;
Ice::CommunicatorPtr ic;
Client::ResourcePtr fcr;
mutable std::shared_mutex _proxymaplock;
diff --git a/netfs/fuse/fuseAppBase.cpp b/netfs/fuse/fuseAppBase.cpp
index 01ec8e1..0b6a90e 100644
--- a/netfs/fuse/fuseAppBase.cpp
+++ b/netfs/fuse/fuseAppBase.cpp
@@ -5,22 +5,28 @@
#include <unistd.h>
#include <cstdlib>
#include <typeinfo>
+#include <boost/assert.hpp>
FuseAppBase * FuseAppBase::fuseApp;
+FuseAppBase::FuseAppBase()
+{
+ BOOST_ASSERT(!fuseApp);
+ BOOST_ASSERT(this);
+ fuseApp = this;
+}
+
+FuseAppBase::~FuseAppBase()
+{
+ BOOST_ASSERT(fuseApp);
+ fuseApp = nullptr;
+}
+
// LCOV_EXCL_START
// These are all excluded from coverage because it is impossible
// to call them in a realistic manner. They exist only as the default
// implementation of the function which is never passed to libfuse
// unless it is overridden.
-void * FuseAppBase::init(fuse_conn_info*)
-{
- return nullptr;
-}
-int FuseAppBase::opt_parse(void*, const char *, int, fuse_args*)
-{
- return 1;
-}
int FuseAppBase::access(const char *, int)
{
return -ENOSYS;
@@ -190,11 +196,8 @@ void FuseAppBase::log(int level, const char * message) const noexcept
void FuseAppBase::logf(int level, const char * fmt, ...) const noexcept
{
va_list args;
- // NOLINTNEXTLINE(hicpp-no-array-decay)
va_start(args, fmt);
- // NOLINTNEXTLINE(hicpp-no-array-decay)
vlogf(level, fmt, args);
- // NOLINTNEXTLINE(hicpp-no-array-decay)
va_end(args);
}
@@ -208,25 +211,3 @@ void FuseAppBase::beforeOperation()
{
}
-void * FuseAppBase::fuseInit (struct fuse_conn_info *conn)
-{
- return fuseApp->init(conn);
-}
-void FuseAppBase::fuseDestroy(void *)
-{
- delete fuseApp;
-}
-
-struct fuse_args
-FuseAppBase::runint(int argc, char ** argv)
-{
- std::array<fuse_opt, 1> fuse_opts {};
- fuseApp = this;
- struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
- if (fuse_opt_parse(&args, fuseApp, fuse_opts.data(),
- &internalHelper<decltype(&FuseAppBase::opt_parse), &FuseAppBase::opt_parse>) == -1) {
- exit(1);
- }
- return args;
-}
-
diff --git a/netfs/fuse/fuseAppBase.h b/netfs/fuse/fuseAppBase.h
index 2775c00..97810d2 100644
--- a/netfs/fuse/fuseAppBase.h
+++ b/netfs/fuse/fuseAppBase.h
@@ -15,9 +15,9 @@
class DLL_PUBLIC FuseAppBase {
public:
- virtual ~FuseAppBase() = default;
- virtual void * init (struct fuse_conn_info * info);
- virtual int opt_parse(void *, const char * arg, int key, struct fuse_args *);
+ FuseAppBase();
+ virtual ~FuseAppBase();
+
virtual int access(const char *, int);
virtual int chmod(const char *, mode_t);
virtual int chown(const char *, uid_t, gid_t);
@@ -64,18 +64,19 @@ class DLL_PUBLIC FuseAppBase {
void logf(int level, const char * fmt, ...) const throw() __attribute__ ((__format__ (__printf__, 3, 4)));
virtual void vlogf(int level, const char * fmt, va_list) const throw() __attribute__ ((__format__ (__printf__, 3, 0))) = 0;
- virtual int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc) = 0;
- virtual int main(int, char **, const struct fuse_operations *) = 0;
+ mutable std::shared_mutex _lock;
+ protected:
+ static FuseAppBase * fuseApp;
+};
+template <typename FuseApp>
+class FuseAppBaseT : public FuseAppBase {
+ public:
#define GetHelper(func) getHelper<decltype(&FuseAppBase::func), decltype(&FuseApp::func), &FuseAppBase::func>(&FuseAppBase::func)
- template <typename FuseApp>
- static int run(int argc, char ** argv, FuseApp * fa)
- {
- auto args = fa->runint(argc, argv);
- struct fuse_operations operations = {
+ FuseAppBaseT() : operations({
GetHelper(getattr),
GetHelper(readlink),
- NULL, // getdir deprecated
+ nullptr, // getdir deprecated
GetHelper(mknod),
GetHelper(mkdir),
GetHelper(unlink),
@@ -86,7 +87,7 @@ class DLL_PUBLIC FuseAppBase {
GetHelper(chmod),
GetHelper(chown),
GetHelper(truncate),
- NULL, // utime deprecated
+ nullptr, // utime deprecated
GetHelper(open),
GetHelper(read),
GetHelper(write),
@@ -102,8 +103,8 @@ class DLL_PUBLIC FuseAppBase {
GetHelper(readdir),
GetHelper(releasedir),
GetHelper(fsyncdir),
- fuseInit,
- fuseDestroy,
+ nullptr, //fuseInit
+ nullptr, //fuseDestroy
GetHelper(access),
GetHelper(create),
GetHelper(ftruncate),
@@ -129,15 +130,14 @@ class DLL_PUBLIC FuseAppBase {
#endif
#endif
#endif
- };
- return fa->main(args.argc, args.argv, &operations);
+ })
+ {
}
- struct fuse_args runint(int, char **);
+#undef GetHelper
- private:
- static void * fuseInit(struct fuse_conn_info *conn);
- static void fuseDestroy(void *);
+ const struct fuse_operations operations;
+ private:
template <typename BFunc, typename IFunc, BFunc bfunc, typename ... Args>
static int(*getHelper(int(FuseAppBase::*)(Args...)))(Args...)
{
@@ -169,16 +169,13 @@ class DLL_PUBLIC FuseAppBase {
}
return nullptr;
}
+
+ protected:
template <typename Func, Func f, typename ... Args>
static int internalHelper(Args ... a)
{
return (fuseApp->*f)(a...);
}
-
- static FuseAppBase * fuseApp;
-
- protected:
- mutable std::shared_mutex _lock;
};
#endif
diff --git a/netfs/fuse/netfs.cpp b/netfs/fuse/netfs.cpp
index c21711c..14c3c93 100644
--- a/netfs/fuse/netfs.cpp
+++ b/netfs/fuse/netfs.cpp
@@ -1,10 +1,37 @@
#include "fuseApp.h"
#include <syslog.h>
-class FuseImpl : public NetFS::FuseApp {
+class FuseImpl : public fuse_args, public NetFS::FuseApp {
public:
- explicit FuseImpl(const Ice::StringSeq & a) :
- NetFS::FuseApp(a)
+ static int opt_parse(void * data, const char * arg, int, struct fuse_args *)
+ {
+ auto iceArgs = static_cast<Ice::StringSeq *>(data);
+ if (strncmp(arg, "--Ice.", 6) == 0) {
+ iceArgs->push_back(arg);
+ return 0;
+ }
+ else if (strncmp(arg, "_netdev", 7) == 0) {
+ return 0;
+ }
+ else if (arg[0] == '-') {
+ return 1;
+ }
+ else if (iceArgs->empty() || strncmp(iceArgs->front().c_str(), "--Ice.", 6) == 0) {
+ iceArgs->insert(iceArgs->begin(), arg);
+ return 0;
+ }
+ return 1;
+ }
+
+ FuseImpl(int c, char ** v) :
+ fuse_args(FUSE_ARGS_INIT(c, v)),
+ NetFS::FuseApp([this](){
+ Ice::StringSeq iceArgs;
+ if (fuse_opt_parse(this, &iceArgs, nullptr, opt_parse) == -1) {
+ exit(-1);
+ }
+ return iceArgs;
+ }())
{
openlog("netfs", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_USER);
}
@@ -24,15 +51,9 @@ class FuseImpl : public NetFS::FuseApp {
return ::fuse_get_context();
}
- // NOLINTNEXTLINE(modernize-avoid-c-arrays, hicpp-avoid-c-arrays)
- int fuse_opt_parse(struct fuse_args * args, void * data, const struct fuse_opt opts[], fuse_opt_proc_t proc) override
- {
- return ::fuse_opt_parse(args, data, opts, proc);
- }
-
- int main(int argc, char ** argv, const struct fuse_operations * ops) override
+ int run()
{
- return ::fuse_main(argc, argv, ops, this);
+ return ::fuse_main(argc, argv, &operations, this);
}
void vlogf(int priority, const char * fmt, va_list args) const noexcept override
@@ -44,6 +65,6 @@ class FuseImpl : public NetFS::FuseApp {
int
main(int argc, char* argv[])
{
- return FuseAppBase::run(argc, argv, new FuseImpl(Ice::argsToStringSeq(argc, argv)));
+ return FuseImpl(argc, argv).run();
}
diff --git a/netfs/unittests/Jamfile.jam b/netfs/unittests/Jamfile.jam
index 2d3fac3..2fbaf4a 100644
--- a/netfs/unittests/Jamfile.jam
+++ b/netfs/unittests/Jamfile.jam
@@ -50,7 +50,7 @@ run testCore.cpp
run testGlacier.cpp
: -- :
defaultDaemon.xml
- defaultFuse.xml
+ defaultFuse.xml
:
<define>BOOST_TEST_DYN_LINK
<library>boost_utf
@@ -60,10 +60,19 @@ run testGlacier.cpp
run testEdgeCases.cpp
: -- :
defaultDaemon.xml
- defaultFuse.xml
+ defaultFuse.xml
:
<define>BOOST_TEST_DYN_LINK
<library>boost_utf
<library>testMocks
: testEdgeCases ;
+run testFuse.cpp
+ : :
+ defaultDaemon.xml
+ :
+ <define>BOOST_TEST_DYN_LINK
+ <library>boost_utf
+ <library>testMocks
+ ;
+
diff --git a/netfs/unittests/mockFuse.cpp b/netfs/unittests/mockFuse.cpp
index da6b07f..fd93388 100644
--- a/netfs/unittests/mockFuse.cpp
+++ b/netfs/unittests/mockFuse.cpp
@@ -3,7 +3,6 @@
FuseMock::FuseMock(std::string ep, Ice::StringSeq a) :
NetFS::FuseApp(std::move(a)),
- ops({}),
testEndpoint(std::move(ep)),
context({})
{
@@ -19,34 +18,21 @@ FuseMock::fuse_get_context()
return &context;
}
-int
-// NOLINTNEXTLINE(modernize-avoid-c-arrays, hicpp-avoid-c-arrays)
-FuseMock::fuse_opt_parse(struct fuse_args * args, void * data, const struct fuse_opt [], fuse_opt_proc_t proc)
+void
+FuseMock::connectToService()
{
- for (int n = 0; n < args->argc; n += 1) {
- proc(data, args->argv[n], n, args);
+ if (testEndpoint.empty()) {
+ return FuseApp::connectToService();
}
- return 0;
-}
-int
-FuseMock::main(int, char **, const struct fuse_operations * o)
-{
- o->init(nullptr);
- ops = *o;
- return 0;
-}
-
-NetFS::Client::ConfigurationPtr
-FuseMock::ReadConfiguration(const std::filesystem::path & path) const
-{
- auto c = FuseApp::ReadConfiguration(path);
- for(const auto & r : c->Resources) {
- for(auto & e : r.second->Endpoints) {
- e = testEndpoint;
+ if (!service) {
+ Lock(_lock);
+ std::string addr = fcr->ServiceIdentity + ":" + testEndpoint;
+ service = Ice::checkedCast<NetFS::ServicePrx>(ic->stringToProxy(addr));
+ if (!service) {
+ throw std::runtime_error("Invalid service proxy: " + testEndpoint);
}
}
- return c;
}
void
@@ -57,12 +43,7 @@ FuseMock::vlogf(int, const char * fmt, va_list args) const noexcept
FuseMockHost::FuseMockHost(std::string ep, const Ice::StringSeq & a) :
app(std::make_unique<FuseMock>(std::move(ep), a)),
- fuse(&app->ops)
+ fuse(&app->operations)
{
- std::vector<char *> argv;
- for (auto & arg : a) {
- argv.push_back(const_cast<char *>(arg.c_str()));
- }
- FuseAppBase::run(a.size(), &argv.front(), app.get());
}
diff --git a/netfs/unittests/mockFuse.h b/netfs/unittests/mockFuse.h
index 7e4f3ad..4461da6 100644
--- a/netfs/unittests/mockFuse.h
+++ b/netfs/unittests/mockFuse.h
@@ -9,15 +9,9 @@ class DLL_PUBLIC FuseMock : public NetFS::FuseApp {
FuseMock(std::string, Ice::StringSeq);
struct fuse_context * fuse_get_context() override;
- int fuse_opt_parse(struct fuse_args * args, void * data, const struct fuse_opt [], fuse_opt_proc_t proc) override;
- int main(int, char **, const struct fuse_operations * o) override;
void vlogf(int, const char *, va_list) const throw() override;
- fuse_operations ops;
-
- protected:
- virtual NetFS::Client::ConfigurationPtr ReadConfiguration(const std::filesystem::path &) const override;
-
+ void connectToService() override;
private:
const std::string testEndpoint;
fuse_context context;
diff --git a/netfs/unittests/testFuse.cpp b/netfs/unittests/testFuse.cpp
new file mode 100644
index 0000000..c5f9bbf
--- /dev/null
+++ b/netfs/unittests/testFuse.cpp
@@ -0,0 +1,108 @@
+#define BOOST_TEST_MODULE TestNetFSFuse
+#define FUSE_USE_VERSION 26
+#include <boost/test/unit_test.hpp>
+#include <definedDirs.h>
+#include <filesystem>
+#include <ostream>
+#include <thread>
+#include <fuse.h>
+#include <fuseApp.h>
+#include "mockDaemon.h"
+
+static const std::filesystem::path mntpnt { binDir / "mnt" };
+const std::string testEndpoint("tcp -h localhost -p 12015");
+const std::string testUri("tcp://localhost:12015/testvol");
+
+class FuseMountPoint : public MockDaemonHost, public NetFS::FuseApp {
+ public:
+ FuseMountPoint() :
+ MockDaemonHost(::testEndpoint, {
+ "--NetFSD.ConfigPath=" + (rootDir / "defaultDaemon.xml").string()
+ }),
+ NetFS::FuseApp({::testUri})
+ {
+ std::filesystem::remove(mntpnt);
+ std::filesystem::create_directory(mntpnt);
+ struct fuse_args fargs { };
+ ch = ::fuse_mount(mntpnt.c_str(), &fargs);
+ BOOST_REQUIRE(ch);
+ fs = ::fuse_new(ch, &fargs, &operations, sizeof(fuse_operations), this);
+ BOOST_REQUIRE(fs);
+ th = std::make_unique<std::thread>(::fuse_loop, fs);
+ }
+
+ ~FuseMountPoint() override
+ {
+ if (ch) {
+ ::fuse_unmount(mntpnt.c_str(), ch);
+ }
+ if (th) {
+ th->join();
+ }
+ std::filesystem::remove(mntpnt);
+ }
+
+ FuseMountPoint(const FuseMountPoint &) = delete;
+ FuseMountPoint(FuseMountPoint &&) = delete;
+
+ FuseMountPoint & operator=(const FuseMountPoint &) = delete;
+ FuseMountPoint & operator=(FuseMountPoint &&) = delete;
+
+ struct fuse_context * fuse_get_context() override
+ {
+ return ::fuse_get_context();
+ }
+
+ static inline char * vstrdupf(const char * fmt, va_list args)
+ {
+ char * out {};
+ BOOST_REQUIRE_GE(vasprintf(&out, fmt, args), 0);
+ BOOST_REQUIRE(out);
+ return out;
+ }
+
+ void vlogf(int, const char * fmt, va_list args) const noexcept override
+ {
+ std::unique_ptr<char, void(*)(void*)> msg(vstrdupf(fmt, args), std::free);
+ BOOST_TEST_MESSAGE(msg.get());
+ }
+
+ struct fuse_chan * ch;
+ struct fuse * fs;
+ std::unique_ptr<std::thread> th;
+};
+
+struct pathrange {
+ const std::filesystem::path path;
+
+ [[nodiscard]] std::filesystem::directory_iterator begin() const
+ {
+ return std::filesystem::directory_iterator(path);
+ }
+
+ [[nodiscard]] std::filesystem::directory_iterator end() const
+ {
+ return {};
+ }
+};
+
+BOOST_FIXTURE_TEST_SUITE(fmp, FuseMountPoint);
+
+BOOST_AUTO_TEST_CASE(fuse, * boost::unit_test::timeout(5))
+{
+ BOOST_REQUIRE(std::filesystem::is_directory(mntpnt));
+}
+
+BOOST_AUTO_TEST_CASE(fuse_ls, * boost::unit_test::timeout(5))
+{
+ BOOST_REQUIRE(std::filesystem::is_directory(mntpnt));
+ std::filesystem::create_symlink(selfExe, mntpnt / "me");
+ std::set<std::filesystem::path> paths(std::filesystem::directory_iterator(mntpnt), {});
+ BOOST_REQUIRE_EQUAL(paths.size(), 1);
+ BOOST_CHECK_EQUAL(paths.begin()->filename(), "me");
+ BOOST_CHECK(std::filesystem::is_symlink(mntpnt / "me"));
+ BOOST_CHECK(std::filesystem::is_symlink(mntpnt / "me"));
+}
+
+BOOST_AUTO_TEST_SUITE_END();
+