aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.dir-locals.el26
-rw-r--r--README.md7
-rw-r--r--configure.ac13
-rwxr-xr-xexample/fselclient.c4
-rw-r--r--include/fuse_common.h4
-rw-r--r--include/fuse_lowlevel.h26
-rw-r--r--lib/fuse.c9
-rwxr-xr-xlib/fuse_loop_mt.c2
-rwxr-xr-xlib/fuse_lowlevel.c155
-rw-r--r--lib/fuse_session.c5
-rwxr-xr-xlib/fuse_signals.c1
-rw-r--r--lib/mount.c14
-rw-r--r--lib/mount_bsd.c2
-rw-r--r--test/.gitignore2
-rw-r--r--test/Makefile7
-rw-r--r--test/Makefile.am2
-rw-r--r--test/conftest.py85
-rw-r--r--test/pytest.ini2
-rw-r--r--test/test.c2
-rwxr-xr-xtest/test_examples.py321
-rwxr-xr-xtest/test_fuse.py32
-rw-r--r--test/util.py38
22 files changed, 616 insertions, 143 deletions
diff --git a/.dir-locals.el b/.dir-locals.el
new file mode 100644
index 0000000..c70a23a
--- /dev/null
+++ b/.dir-locals.el
@@ -0,0 +1,26 @@
+((nil . ((eval . (add-hook 'before-save-hook
+ 'whitespace-cleanup nil t))))
+ (python-mode . ((indent-tabs-mode . nil)))
+ (c-mode . ((c-file-style . "stroustrup")
+ (indent-tabs-mode . t)
+ (tab-width . 8)
+ (c-basic-offset . 8)
+ (c-file-offsets .
+ ((block-close . 0)
+ (brace-list-close . 0)
+ (brace-list-entry . 0)
+ (brace-list-intro . +)
+ (case-label . 0)
+ (class-close . 0)
+ (defun-block-intro . +)
+ (defun-close . 0)
+ (defun-open . 0)
+ (else-clause . 0)
+ (inclass . +)
+ (label . 0)
+ (statement . 0)
+ (statement-block-intro . +)
+ (statement-case-intro . +)
+ (statement-cont . +)
+ (substatement . +)
+ (topmost-intro . 0))))))
diff --git a/README.md b/README.md
index 18f5d9b..33c9df9 100644
--- a/README.md
+++ b/README.md
@@ -56,6 +56,12 @@ tarball, build and install with
make -j8
make install
+To run some self tests, you need a Python 3 environment with the
+[py.test](http://www.pytest.org/) module installed. To run the tests,
+execute
+
+ python3 -m pytest test/
+
You may also need to add `/usr/local/lib` to `/etc/ld.so.conf` and/or
run *ldconfig*. If you're building from the git repository (instead of
using a release tarball), you also need to run `./makeconf.sh` to
@@ -111,4 +117,3 @@ https://lists.sourceforge.net/lists/listinfo/fuse-devel).
Please report any bugs on the GitHub issue tracker at
https://github.com/libfuse/libfuse/issues.
-
diff --git a/configure.ac b/configure.ac
index 29a281d..7ca9f4f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -29,13 +29,15 @@ AC_ARG_ENABLE(util,
[ --enable-util Compile with util ])
AC_ARG_ENABLE(example,
[ --enable-example Compile with examples ])
+AC_ARG_ENABLE(test,
+ [ --enable-test Compile with tests ])
AC_ARG_ENABLE(mtab,
[ --disable-mtab Disable and ignore usage of /etc/mtab ])
AC_ARG_WITH(pkgconfigdir,
- [ --with-pkgconfigdir=DIR pkgconfig file in DIR @<:@LIBDIR/pkgconfig@:>@],
- [pkgconfigdir=$withval],
- [pkgconfigdir='${libdir}/pkgconfig'])
+ [ --with-pkgconfigdir=DIR pkgconfig file in DIR @<:@LIBDIR/pkgconfig@:>@],
+ [pkgconfigdir=$withval],
+ [pkgconfigdir='${libdir}/pkgconfig'])
AC_SUBST(pkgconfigdir)
subdirs2="include"
@@ -49,6 +51,9 @@ fi
if test "$enable_example" != "no"; then
subdirs2="$subdirs2 example";
fi
+if test "$enable_test" != "no"; then
+ subdirs2="$subdirs2 test";
+fi
if test "$enable_mtab" = "no"; then
AC_DEFINE(IGNORE_MTAB, 1, [Don't update /etc/mtab])
fi
@@ -120,7 +125,7 @@ if test "$arch" = linux -a "$cross_compiling" != "yes"; then
fi
fi
-AC_CONFIG_FILES([fuse3.pc Makefile lib/Makefile util/Makefile example/Makefile include/Makefile doc/Makefile])
+AC_CONFIG_FILES([fuse3.pc Makefile lib/Makefile util/Makefile example/Makefile include/Makefile doc/Makefile test/Makefile])
AC_OUTPUT
if test "$util_linux_ok" = no; then
diff --git a/example/fselclient.c b/example/fselclient.c
index ac8b7b0..637cb07 100755
--- a/example/fselclient.c
+++ b/example/fselclient.c
@@ -40,7 +40,7 @@ int main(void)
{
static const char hex_map[FSEL_FILES] = "0123456789ABCDEF";
int fds[FSEL_FILES];
- int i, nfds;
+ int i, nfds, tries;
for (i = 0; i < FSEL_FILES; i++) {
char name[] = { hex_map[i], '\0' };
@@ -52,7 +52,7 @@ int main(void)
}
nfds = fds[FSEL_FILES - 1] + 1;
- while (1) {
+ for(tries=0; tries < 16; tries++) {
static char buf[4096];
fd_set rfds;
int rc;
diff --git a/include/fuse_common.h b/include/fuse_common.h
index d200d34..75e55e9 100644
--- a/include/fuse_common.h
+++ b/include/fuse_common.h
@@ -114,7 +114,7 @@ struct fuse_file_info {
#define FUSE_CAP_SPLICE_READ (1 << 9)
#define FUSE_CAP_FLOCK_LOCKS (1 << 10)
#define FUSE_CAP_IOCTL_DIR (1 << 11)
-#define FUSE_CAP_AUTO_INVAL_DATA (1 << 12)
+#define FUSE_CAP_AUTO_INVAL_DATA (1 << 12)
#define FUSE_CAP_READDIRPLUS (1 << 13)
#define FUSE_CAP_READDIRPLUS_AUTO (1 << 14)
#define FUSE_CAP_ASYNC_DIO (1 << 15)
@@ -429,7 +429,7 @@ struct fuse_bufvec {
};
/* Initialize bufvec with a single buffer of given size */
-#define FUSE_BUFVEC_INIT(size__) \
+#define FUSE_BUFVEC_INIT(size__) \
((struct fuse_bufvec) { \
/* .count= */ 1, \
/* .idx = */ 0, \
diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h
index 89d10a7..a01dbf6 100644
--- a/include/fuse_lowlevel.h
+++ b/include/fuse_lowlevel.h
@@ -602,7 +602,7 @@ struct fuse_lowlevel_ops {
*
* fi->fh will contain the value set by the opendir method, or
* will be undefined if the opendir method didn't set any value.
- *
+ *
* Returning a directory entry from readdir() does not affect
* its lookup count.
*
@@ -932,11 +932,11 @@ struct fuse_lowlevel_ops {
* kernel supports splicing from the fuse device, then the
* data will be made available in pipe for supporting zero
* copy data transfer.
- *
- * buf->count is guaranteed to be one (and thus buf->idx is
- * always zero). The write_buf handler must ensure that
- * bufv->off is correctly updated (reflecting the number of
- * bytes read from bufv->buf[0]).
+ *
+ * buf->count is guaranteed to be one (and thus buf->idx is
+ * always zero). The write_buf handler must ensure that
+ * bufv->off is correctly updated (reflecting the number of
+ * bytes read from bufv->buf[0]).
*
* Introduced in version 2.9
*
@@ -1036,7 +1036,7 @@ struct fuse_lowlevel_ops {
*
* fi->fh will contain the value set by the opendir method, or
* will be undefined if the opendir method didn't set any value.
- *
+ *
* In contrast to readdir() (which does not affect the lookup counts),
* the lookup count of every entry returned by readdirplus(), except "."
* and "..", is incremented by one.
@@ -1402,7 +1402,7 @@ int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph);
* @return zero for success, -errno for failure
*/
int fuse_lowlevel_notify_inval_inode(struct fuse_chan *ch, fuse_ino_t ino,
- off_t off, off_t len);
+ off_t off, off_t len);
/**
* Notify to invalidate parent attributes and the dentry matching
@@ -1419,7 +1419,7 @@ int fuse_lowlevel_notify_inval_inode(struct fuse_chan *ch, fuse_ino_t ino,
* @return zero for success, -errno for failure
*/
int fuse_lowlevel_notify_inval_entry(struct fuse_chan *ch, fuse_ino_t parent,
- const char *name, size_t namelen);
+ const char *name, size_t namelen);
/**
* Notify to invalidate parent attributes and delete the dentry matching
@@ -1702,14 +1702,6 @@ int fuse_session_loop_mt(struct fuse_session *se);
* ----------------------------------------------------------- */
/**
- * Query the file descriptor of the channel
- *
- * @param ch the channel
- * @return the file descriptor passed to fuse_chan_new()
- */
-int fuse_chan_fd(struct fuse_chan *ch);
-
-/**
* Obtain counted reference to the channel
*
* @param ch the channel
diff --git a/lib/fuse.c b/lib/fuse.c
index 2f1c85e..a879ffa 100644
--- a/lib/fuse.c
+++ b/lib/fuse.c
@@ -118,11 +118,11 @@ struct node_table {
};
#define container_of(ptr, type, member) ({ \
- const typeof( ((type *)0)->member ) *__mptr = (ptr); \
- (type *)( (char *)__mptr - offsetof(type,member) );})
+ const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+ (type *)( (char *)__mptr - offsetof(type,member) );})
#define list_entry(ptr, type, member) \
- container_of(ptr, type, member)
+ container_of(ptr, type, member)
struct list_head {
struct list_head *next;
@@ -4320,7 +4320,7 @@ static int fuse_session_loop_remember(struct fuse *f)
time_t next_clean;
struct fuse_chan *ch = fuse_session_chan(se);
struct pollfd fds = {
- .fd = fuse_chan_fd(ch),
+ .fd = ch->fd,
.events = POLLIN
};
struct fuse_buf fbuf = {
@@ -4831,4 +4831,3 @@ void fuse_destroy(struct fuse *f)
free(f);
fuse_delete_context_key();
}
-
diff --git a/lib/fuse_loop_mt.c b/lib/fuse_loop_mt.c
index 6d7f051..036f75c 100755
--- a/lib/fuse_loop_mt.c
+++ b/lib/fuse_loop_mt.c
@@ -193,7 +193,7 @@ static struct fuse_chan *fuse_clone_chan(struct fuse_mt *mt)
}
fcntl(clonefd, F_SETFD, FD_CLOEXEC);
- masterfd = fuse_chan_fd(mt->prevch);
+ masterfd = mt->prevch->fd;
res = ioctl(clonefd, FUSE_DEV_IOC_CLONE, &masterfd);
if (res == -1) {
fprintf(stderr, "fuse: failed to clone device fd: %s\n",
diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c
index 542d7ae..91568b1 100755
--- a/lib/fuse_lowlevel.c
+++ b/lib/fuse_lowlevel.c
@@ -158,80 +158,11 @@ static struct fuse_req *fuse_ll_alloc_req(struct fuse_ll *f)
return req;
}
-static int fuse_chan_recv(struct fuse_session *se, struct fuse_buf *buf,
- struct fuse_chan *ch)
-{
- struct fuse_ll *f = se->f;
- int err;
- ssize_t res;
-
- if (!buf->mem) {
- buf->mem = malloc(f->bufsize);
- if (!buf->mem) {
- fprintf(stderr,
- "fuse: failed to allocate read buffer\n");
- return -ENOMEM;
- }
- }
-
-restart:
- res = read(fuse_chan_fd(ch), buf->mem, f->bufsize);
- err = errno;
-
- if (fuse_session_exited(se))
- return 0;
- if (res == -1) {
- /* ENOENT means the operation was interrupted, it's safe
- to restart */
- if (err == ENOENT)
- goto restart;
-
- if (err == ENODEV) {
- fuse_session_exit(se);
- return 0;
- }
- /* Errors occurring during normal operation: EINTR (read
- interrupted), EAGAIN (nonblocking I/O), ENODEV (filesystem
- umounted) */
- if (err != EINTR && err != EAGAIN)
- perror("fuse: reading device");
- return -err;
- }
- if ((size_t) res < sizeof(struct fuse_in_header)) {
- fprintf(stderr, "short read on fuse device\n");
- return -EIO;
- }
-
- buf->size = res;
-
- return res;
-}
-
-static int fuse_chan_send(struct fuse_chan *ch, const struct iovec iov[],
- size_t count)
-{
- ssize_t res = writev(fuse_chan_fd(ch), iov, count);
- int err = errno;
-
- if (res == -1) {
- struct fuse_session *se = fuse_chan_session(ch);
-
- assert(se != NULL);
-
- /* ENOENT means the operation was interrupted */
- if (!fuse_session_exited(se) && err != ENOENT)
- perror("fuse: writing device");
- return -err;
- }
-
- return 0;
-}
-
void fuse_chan_close(struct fuse_chan *ch)
{
- int fd = fuse_chan_fd(ch);
+ int fd = ch->fd;
if (fd != -1)
- close(fd);
+ close(fd);
}
@@ -257,9 +188,24 @@ static int fuse_send_msg(struct fuse_ll *f, struct fuse_chan *ch,
}
}
- return fuse_chan_send(ch, iov, count);
+ ssize_t res = writev(ch->fd, iov, count);
+ int err = errno;
+
+ if (res == -1) {
+ struct fuse_session *se = fuse_chan_session(ch);
+
+ assert(se != NULL);
+
+ /* ENOENT means the operation was interrupted */
+ if (!fuse_session_exited(se) && err != ENOENT)
+ perror("fuse: writing device");
+ return -err;
+ }
+
+ return 0;
}
+
int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
int count)
{
@@ -831,7 +777,7 @@ static int fuse_send_data_iov(struct fuse_ll *f, struct fuse_chan *ch,
splice_flags |= SPLICE_F_MOVE;
res = splice(llp->pipe[0], NULL,
- fuse_chan_fd(ch), NULL, out->len, splice_flags);
+ ch->fd, NULL, out->len, splice_flags);
if (res == -1) {
res = -errno;
perror("fuse: splice from pipe");
@@ -2051,7 +1997,7 @@ static void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
fprintf(stderr, " max_background=%i\n",
outarg.max_background);
fprintf(stderr, " congestion_threshold=%i\n",
- outarg.congestion_threshold);
+ outarg.congestion_threshold);
fprintf(stderr, " time_gran=%u\n",
outarg.time_gran);
}
@@ -2156,7 +2102,7 @@ int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
}
int fuse_lowlevel_notify_inval_inode(struct fuse_chan *ch, fuse_ino_t ino,
- off_t off, off_t len)
+ off_t off, off_t len)
{
struct fuse_notify_inval_inode_out outarg;
struct fuse_ll *f;
@@ -2180,7 +2126,7 @@ int fuse_lowlevel_notify_inval_inode(struct fuse_chan *ch, fuse_ino_t ino,
}
int fuse_lowlevel_notify_inval_entry(struct fuse_chan *ch, fuse_ino_t parent,
- const char *name, size_t namelen)
+ const char *name, size_t namelen)
{
struct fuse_notify_inval_entry_out outarg;
struct fuse_ll *f;
@@ -2751,16 +2697,16 @@ static void fuse_ll_pipe_destructor(void *data)
fuse_ll_pipe_free(llp);
}
-#ifdef HAVE_SPLICE
int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf,
struct fuse_chan *ch)
{
struct fuse_ll *f = se->f;
+ int err;
+ ssize_t res;
+#ifdef HAVE_SPLICE
size_t bufsize = f->bufsize;
struct fuse_ll_pipe *llp;
struct fuse_buf tmpbuf;
- int err;
- int res;
if (f->conn.proto_minor < 14 || !(f->conn.want & FUSE_CAP_SPLICE_READ))
goto fallback;
@@ -2782,7 +2728,7 @@ int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf,
goto fallback;
}
- res = splice(fuse_chan_fd(ch), NULL, llp->pipe[1], NULL, bufsize, 0);
+ res = splice(ch->fd, NULL, llp->pipe[1], NULL, bufsize, 0);
err = errno;
if (fuse_session_exited(se))
@@ -2855,15 +2801,48 @@ int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf,
return res;
fallback:
- return fuse_chan_recv(se, buf, ch);
-}
-#else
-int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf,
- struct fuse_chan *ch)
-{
- return fuse_chan_recv(se, buf, ch);
-}
#endif
+ if (!buf->mem) {
+ buf->mem = malloc(f->bufsize);
+ if (!buf->mem) {
+ fprintf(stderr,
+ "fuse: failed to allocate read buffer\n");
+ return -ENOMEM;
+ }
+ }
+
+restart:
+ res = read(ch->fd, buf->mem, f->bufsize);
+ err = errno;
+
+ if (fuse_session_exited(se))
+ return 0;
+ if (res == -1) {
+ /* ENOENT means the operation was interrupted, it's safe
+ to restart */
+ if (err == ENOENT)
+ goto restart;
+
+ if (err == ENODEV) {
+ fuse_session_exit(se);
+ return 0;
+ }
+ /* Errors occurring during normal operation: EINTR (read
+ interrupted), EAGAIN (nonblocking I/O), ENODEV (filesystem
+ umounted) */
+ if (err != EINTR && err != EAGAIN)
+ perror("fuse: reading device");
+ return -err;
+ }
+ if ((size_t) res < sizeof(struct fuse_in_header)) {
+ fprintf(stderr, "short read on fuse device\n");
+ return -EIO;
+ }
+
+ buf->size = res;
+
+ return res;
+}
#define MIN_BUFSIZE 0x21000
diff --git a/lib/fuse_session.c b/lib/fuse_session.c
index 42fe5c3..6b54e43 100644
--- a/lib/fuse_session.c
+++ b/lib/fuse_session.c
@@ -90,11 +90,6 @@ struct fuse_chan *fuse_chan_new(int fd)
return ch;
}
-int fuse_chan_fd(struct fuse_chan *ch)
-{
- return ch->fd;
-}
-
struct fuse_session *fuse_chan_session(struct fuse_chan *ch)
{
return ch->se;
diff --git a/lib/fuse_signals.c b/lib/fuse_signals.c
index b992b71..9fa787c 100755
--- a/lib/fuse_signals.c
+++ b/lib/fuse_signals.c
@@ -72,4 +72,3 @@ void fuse_remove_signal_handlers(struct fuse_session *se)
set_one_signal_handler(SIGTERM, exit_handler, 1);
set_one_signal_handler(SIGPIPE, SIG_IGN, 1);
}
-
diff --git a/lib/mount.c b/lib/mount.c
index de4ae74..7be7b25 100644
--- a/lib/mount.c
+++ b/lib/mount.c
@@ -28,12 +28,12 @@
#ifdef __NetBSD__
#include <perfuse.h>
-#define MS_RDONLY MNT_RDONLY
-#define MS_NOSUID MNT_NOSUID
-#define MS_NODEV MNT_NODEV
-#define MS_NOEXEC MNT_NOEXEC
-#define MS_SYNCHRONOUS MNT_SYNCHRONOUS
-#define MS_NOATIME MNT_NOATIME
+#define MS_RDONLY MNT_RDONLY
+#define MS_NOSUID MNT_NOSUID
+#define MS_NODEV MNT_NODEV
+#define MS_NOEXEC MNT_NOEXEC
+#define MS_SYNCHRONOUS MNT_SYNCHRONOUS
+#define MS_NOATIME MNT_NOATIME
#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0)
#endif
@@ -398,7 +398,7 @@ static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo,
rv = receive_fd(fds[1]);
if (!mo->auto_unmount) {
- /* with auto_unmount option fusermount will not exit until
+ /* with auto_unmount option fusermount will not exit until
this socket is closed */
close(fds[1]);
waitpid(pid, NULL, 0); /* bury zombie */
diff --git a/lib/mount_bsd.c b/lib/mount_bsd.c
index 8f3acf0..6a7c958 100644
--- a/lib/mount_bsd.c
+++ b/lib/mount_bsd.c
@@ -43,7 +43,7 @@ struct mount_opts {
char *kernel_opts;
};
-#define FUSE_DUAL_OPT_KEY(templ, key) \
+#define FUSE_DUAL_OPT_KEY(templ, key) \
FUSE_OPT_KEY(templ, key), FUSE_OPT_KEY("no" templ, key)
static const struct fuse_opt fuse_mount_opts[] = {
diff --git a/test/.gitignore b/test/.gitignore
index 7b95378..b7041be 100644
--- a/test/.gitignore
+++ b/test/.gitignore
@@ -1,2 +1,2 @@
-!Makefile
test
+__pycache__/
diff --git a/test/Makefile b/test/Makefile
deleted file mode 100644
index 738dd53..0000000
--- a/test/Makefile
+++ /dev/null
@@ -1,7 +0,0 @@
-CC=gcc
-CFLAGS=-Wall -W
-
-all: test
-
-clean:
- rm -f *.o test
diff --git a/test/Makefile.am b/test/Makefile.am
new file mode 100644
index 0000000..55d6950
--- /dev/null
+++ b/test/Makefile.am
@@ -0,0 +1,2 @@
+bin_PROGRAMS = test
+test_SOURCES = test.c
diff --git a/test/conftest.py b/test/conftest.py
new file mode 100644
index 0000000..d14350d
--- /dev/null
+++ b/test/conftest.py
@@ -0,0 +1,85 @@
+import sys
+import pytest
+import time
+import re
+
+# If a test fails, wait a moment before retrieving the captured
+# stdout/stderr. When using a server process, this makes sure that we capture
+# any potential output of the server that comes *after* a test has failed. For
+# example, if a request handler raises an exception, the server first signals an
+# error to FUSE (causing the test to fail), and then logs the exception. Without
+# the extra delay, the exception will go into nowhere.
+@pytest.mark.hookwrapper
+def pytest_pyfunc_call(pyfuncitem):
+ outcome = yield
+ failed = outcome.excinfo is not None
+ if failed:
+ time.sleep(1)
+
+@pytest.fixture()
+def pass_capfd(request, capfd):
+ '''Provide capfd object to UnitTest instances'''
+ request.instance.capfd = capfd
+
+def check_test_output(capfd):
+ (stdout, stderr) = capfd.readouterr()
+
+ # Write back what we've read (so that it will still be printed.
+ sys.stdout.write(stdout)
+ sys.stderr.write(stderr)
+
+ # Strip out false positives
+ for (pattern, flags, count) in capfd.false_positives:
+ cp = re.compile(pattern, flags)
+ (stdout, cnt) = cp.subn('', stdout, count=count)
+ if count == 0 or count - cnt > 0:
+ stderr = cp.sub('', stderr, count=count - cnt)
+
+ for pattern in ('exception', 'error', 'warning', 'fatal',
+ 'fault', 'crash(?:ed)?', 'abort(?:ed)'):
+ cp = re.compile(r'\b{}\b'.format(pattern), re.IGNORECASE | re.MULTILINE)
+ hit = cp.search(stderr)
+ if hit:
+ raise AssertionError('Suspicious output to stderr (matched "%s")' % hit.group(0))
+ hit = cp.search(stdout)
+ if hit:
+ raise AssertionError('Suspicious output to stdout (matched "%s")' % hit.group(0))
+
+def register_output(self, pattern, count=1, flags=re.MULTILINE):
+ '''Register *pattern* as false positive for output checking
+
+ This prevents the test from failing because the output otherwise
+ appears suspicious.
+ '''
+
+ self.false_positives.append((pattern, flags, count))
+
+# This is a terrible hack that allows us to access the fixtures from the
+# pytest_runtest_call hook. Among a lot of other hidden assumptions, it probably
+# relies on tests running sequential (i.e., don't dare to use e.g. the xdist
+# plugin)
+current_capfd = None
+@pytest.yield_fixture(autouse=True)
+def save_cap_fixtures(request, capfd):
+ global current_capfd
+ capfd.false_positives = []
+
+ # Monkeypatch in a function to register false positives
+ type(capfd).register_output = register_output
+
+ if request.config.getoption('capture') == 'no':
+ capfd = None
+ current_capfd = capfd
+ bak = current_capfd
+ yield
+
+ # Try to catch problems with this hack (e.g. when running tests
+ # simultaneously)
+ assert bak is current_capfd
+ current_capfd = None
+
+@pytest.hookimpl(trylast=True)
+def pytest_runtest_call(item):
+ capfd = current_capfd
+ if capfd is not None:
+ check_test_output(capfd)
diff --git a/test/pytest.ini b/test/pytest.ini
new file mode 100644
index 0000000..bc4af36
--- /dev/null
+++ b/test/pytest.ini
@@ -0,0 +1,2 @@
+[pytest]
+addopts = --verbose --assert=rewrite --tb=native -x
diff --git a/test/test.c b/test/test.c
index 5d5750d..2b38bb2 100644
--- a/test/test.c
+++ b/test/test.c
@@ -993,7 +993,7 @@ static int do_test_open_acc(int flags, const char *flags_str, int mode, int err)
int res;
int fd;
- start_test("open_acc(%s) mode: 0%03o error: '%s'", flags_str, mode,
+ start_test("open_acc(%s) mode: 0%03o message: '%s'", flags_str, mode,
strerror(err));
unlink(testfile);
res = create_file(testfile, data, datalen);
diff --git a/test/test_examples.py b/test/test_examples.py
new file mode 100755
index 0000000..02d3409
--- /dev/null
+++ b/test/test_examples.py
@@ -0,0 +1,321 @@
+#!/usr/bin/env python3
+
+if __name__ == '__main__':
+ import pytest
+ import sys
+ sys.exit(pytest.main([__file__] + sys.argv[1:]))
+
+import subprocess
+import os
+import sys
+import pytest
+import stat
+import shutil
+import filecmp
+import errno
+from tempfile import NamedTemporaryFile
+from util import wait_for_mount, umount, cleanup
+
+basename = os.path.join(os.path.dirname(__file__), '..')
+TEST_FILE = __file__
+
+with open(TEST_FILE, 'rb') as fh:
+ TEST_DATA = fh.read()
+
+def name_generator(__ctr=[0]):
+ __ctr[0] += 1
+ return 'testfile_%d' % __ctr[0]
+
+@pytest.mark.parametrize("name", ('hello', 'hello_ll'))
+def test_hello(tmpdir, name):
+ mnt_dir = str(tmpdir)
+ cmdline = [os.path.join(basename, 'example', name),
+ '-f', mnt_dir ]
+ mount_process = subprocess.Popen(cmdline)
+ try:
+ wait_for_mount(mount_process, mnt_dir)
+ assert os.listdir(mnt_dir) == [ 'hello' ]
+ filename = os.path.join(mnt_dir, 'hello')
+ with open(filename, 'r') as fh:
+ assert fh.read() == 'Hello World!\n'
+ with pytest.raises(IOError) as exc_info:
+ open(filename, 'r+')
+ assert exc_info.value.errno == errno.EACCES
+ with pytest.raises(IOError) as exc_info:
+ open(filename + 'does-not-exist', 'r+')
+ assert exc_info.value.errno == errno.ENOENT
+ except:
+ cleanup(mnt_dir)
+ raise
+ else:
+ umount(mount_process, mnt_dir)
+
+@pytest.mark.parametrize("name", ('fusexmp', 'fusexmp_fh'))
+def test_fusexmp_fh(tmpdir, name):
+ mnt_dir = str(tmpdir.mkdir('mnt'))
+ src_dir = str(tmpdir.mkdir('src'))
+
+ cmdline = [os.path.join(basename, 'example', name),
+ '-f', '-o' , 'use_ino,readdir_ino,kernel_cache',
+ mnt_dir ]
+ mount_process = subprocess.Popen(cmdline)
+ try:
+ wait_for_mount(mount_process, mnt_dir)
+ work_dir = os.path.join(mnt_dir, src_dir)
+ tst_write(work_dir)
+ tst_mkdir(work_dir)
+ tst_symlink(work_dir)
+ tst_mknod(work_dir)
+ if os.getuid() == 0:
+ tst_chown(work_dir)
+ # Underlying fs may not have full nanosecond resolution
+ tst_utimens(work_dir, ns_tol=1000)
+ tst_link(work_dir)
+ tst_readdir(work_dir)
+ tst_statvfs(work_dir)
+ tst_truncate_path(work_dir)
+ tst_truncate_fd(work_dir)
+ tst_unlink(work_dir)
+ tst_passthrough(src_dir, work_dir)
+ except:
+ cleanup(mnt_dir)
+ raise
+ else:
+ umount(mount_process, mnt_dir)
+
+def test_fsel(tmpdir):
+ mnt_dir = str(tmpdir)
+ cmdline = [os.path.join(basename, 'example', 'fsel'),
+ '-f', mnt_dir ]
+ mount_process = subprocess.Popen(cmdline)
+ try:
+ wait_for_mount(mount_process, mnt_dir)
+ cmdline = [ os.path.join(basename, 'example', 'fselclient') ]
+ subprocess.check_call(cmdline, cwd=mnt_dir)
+ except:
+ cleanup(mnt_dir)
+ raise
+ else:
+ umount(mount_process, mnt_dir)
+
+def checked_unlink(filename, path, isdir=False):
+ fullname = os.path.join(path, filename)
+ if isdir:
+ os.rmdir(fullname)
+ else:
+ os.unlink(fullname)
+ with pytest.raises(OSError) as exc_info:
+ os.stat(fullname)
+ assert exc_info.value.errno == errno.ENOENT
+ assert filename not in os.listdir(path)
+
+def tst_mkdir(mnt_dir):
+ dirname = name_generator()
+ fullname = mnt_dir + "/" + dirname
+ os.mkdir(fullname)
+ fstat = os.stat(fullname)
+ assert stat.S_ISDIR(fstat.st_mode)
+ assert os.listdir(fullname) == []
+ assert fstat.st_nlink in (1,2)
+ assert dirname in os.listdir(mnt_dir)
+ checked_unlink(dirname, mnt_dir, isdir=True)
+
+def tst_symlink(mnt_dir):
+ linkname = name_generator()
+ fullname = mnt_dir + "/" + linkname
+ os.symlink("/imaginary/dest", fullname)
+ fstat = os.lstat(fullname)
+ assert stat.S_ISLNK(fstat.st_mode)
+ assert os.readlink(fullname) == "/imaginary/dest"
+ assert fstat.st_nlink == 1
+ assert linkname in os.listdir(mnt_dir)
+ checked_unlink(linkname, mnt_dir)
+
+def tst_mknod(mnt_dir):
+ filename = os.path.join(mnt_dir, name_generator())
+ shutil.copyfile(TEST_FILE, filename)
+ fstat = os.lstat(filename)
+ assert stat.S_ISREG(fstat.st_mode)
+ assert fstat.st_nlink == 1
+ assert os.path.basename(filename) in os.listdir(mnt_dir)
+ assert filecmp.cmp(TEST_FILE, filename, False)
+ checked_unlink(filename, mnt_dir)
+
+def tst_chown(mnt_dir):
+ filename = os.path.join(mnt_dir, name_generator())
+ os.mkdir(filename)
+ fstat = os.lstat(filename)
+ uid = fstat.st_uid
+ gid = fstat.st_gid
+
+ uid_new = uid + 1
+ os.chown(filename, uid_new, -1)
+ fstat = os.lstat(filename)
+ assert fstat.st_uid == uid_new
+ assert fstat.st_gid == gid
+
+ gid_new = gid + 1
+ os.chown(filename, -1, gid_new)
+ fstat = os.lstat(filename)
+ assert fstat.st_uid == uid_new
+ assert fstat.st_gid == gid_new
+
+ checked_unlink(filename, mnt_dir, isdir=True)
+
+def tst_write(mnt_dir):
+ name = os.path.join(mnt_dir, name_generator())
+ shutil.copyfile(TEST_FILE, name)
+ assert filecmp.cmp(name, TEST_FILE, False)
+ checked_unlink(name, mnt_dir)
+
+def tst_unlink(mnt_dir):
+ name = os.path.join(mnt_dir, name_generator())
+ data1 = b'foo'
+ data2 = b'bar'
+
+ with open(os.path.join(mnt_dir, name), 'wb+', buffering=0) as fh:
+ fh.write(data1)
+ checked_unlink(name, mnt_dir)
+ fh.write(data2)
+ fh.seek(0)
+ assert fh.read() == data1+data2
+
+def tst_statvfs(mnt_dir):
+ os.statvfs(mnt_dir)
+
+def tst_link(mnt_dir):
+ name1 = os.path.join(mnt_dir, name_generator())
+ name2 = os.path.join(mnt_dir, name_generator())
+ shutil.copyfile(TEST_FILE, name1)
+ assert filecmp.cmp(name1, TEST_FILE, False)
+ os.link(name1, name2)
+
+ fstat1 = os.lstat(name1)
+ fstat2 = os.lstat(name2)
+
+ assert fstat1 == fstat2
+ assert fstat1.st_nlink == 2
+
+ assert os.path.basename(name2) in os.listdir(mnt_dir)
+ assert filecmp.cmp(name1, name2, False)
+ os.unlink(name2)
+ fstat1 = os.lstat(name1)
+ assert fstat1.st_nlink == 1
+ os.unlink(name1)
+
+def tst_readdir(mnt_dir):
+ dir_ = os.path.join(mnt_dir, name_generator())
+ file_ = dir_ + "/" + name_generator()
+ subdir = dir_ + "/" + name_generator()
+ subfile = subdir + "/" + name_generator()
+
+ os.mkdir(dir_)
+ shutil.copyfile(TEST_FILE, file_)
+ os.mkdir(subdir)
+ shutil.copyfile(TEST_FILE, subfile)
+
+ listdir_is = os.listdir(dir_)
+ listdir_is.sort()
+ listdir_should = [ os.path.basename(file_), os.path.basename(subdir) ]
+ listdir_should.sort()
+ assert listdir_is == listdir_should
+
+ os.unlink(file_)
+ os.unlink(subfile)
+ os.rmdir(subdir)
+ os.rmdir(dir_)
+
+def tst_truncate_path(mnt_dir):
+ assert len(TEST_DATA) > 1024
+
+ filename = os.path.join(mnt_dir, name_generator())
+ with open(filename, 'wb') as fh:
+ fh.write(TEST_DATA)
+
+ fstat = os.stat(filename)
+ size = fstat.st_size
+ assert size == len(TEST_DATA)
+
+ # Add zeros at the end
+ os.truncate(filename, size + 1024)
+ assert os.stat(filename).st_size == size + 1024
+ with open(filename, 'rb') as fh:
+ assert fh.read(size) == TEST_DATA
+ assert fh.read(1025) == b'\0' * 1024
+
+ # Truncate data
+ os.truncate(filename, size - 1024)
+ assert os.stat(filename).st_size == size - 1024
+ with open(filename, 'rb') as fh:
+ assert fh.read(size) == TEST_DATA[:size-1024]
+
+ os.unlink(filename)
+
+def tst_truncate_fd(mnt_dir):
+ assert len(TEST_DATA) > 1024
+ with NamedTemporaryFile('w+b', 0, dir=mnt_dir) as fh:
+ fd = fh.fileno()
+ fh.write(TEST_DATA)
+ fstat = os.fstat(fd)
+ size = fstat.st_size
+ assert size == len(TEST_DATA)
+
+ # Add zeros at the end
+ os.ftruncate(fd, size + 1024)
+ assert os.fstat(fd).st_size == size + 1024
+ fh.seek(0)
+ assert fh.read(size) == TEST_DATA
+ assert fh.read(1025) == b'\0' * 1024
+
+ # Truncate data
+ os.ftruncate(fd, size - 1024)
+ assert os.fstat(fd).st_size == size - 1024
+ fh.seek(0)
+ assert fh.read(size) == TEST_DATA[:size-1024]
+
+def tst_utimens(mnt_dir, ns_tol=0):
+ filename = os.path.join(mnt_dir, name_generator())
+ os.mkdir(filename)
+ fstat = os.lstat(filename)
+
+ atime = fstat.st_atime + 42.28
+ mtime = fstat.st_mtime - 42.23
+ if sys.version_info < (3,3):
+ os.utime(filename, (atime, mtime))
+ else:
+ atime_ns = fstat.st_atime_ns + int(42.28*1e9)
+ mtime_ns = fstat.st_mtime_ns - int(42.23*1e9)
+ os.utime(filename, None, ns=(atime_ns, mtime_ns))
+
+ fstat = os.lstat(filename)
+
+ assert abs(fstat.st_atime - atime) < 1e-3
+ assert abs(fstat.st_mtime - mtime) < 1e-3
+ if sys.version_info >= (3,3):
+ assert abs(fstat.st_atime_ns - atime_ns) <= ns_tol
+ assert abs(fstat.st_mtime_ns - mtime_ns) <= ns_tol
+
+ checked_unlink(filename, mnt_dir, isdir=True)
+
+def tst_passthrough(src_dir, mnt_dir):
+ name = name_generator()
+ src_name = os.path.join(src_dir, name)
+ mnt_name = os.path.join(src_dir, name)
+ assert name not in os.listdir(src_dir)
+ assert name not in os.listdir(mnt_dir)
+ with open(src_name, 'w') as fh:
+ fh.write('Hello, world')
+ assert name in os.listdir(src_dir)
+ assert name in os.listdir(mnt_dir)
+ assert os.stat(src_name) == os.stat(mnt_name)
+
+ name = name_generator()
+ src_name = os.path.join(src_dir, name)
+ mnt_name = os.path.join(src_dir, name)
+ assert name not in os.listdir(src_dir)
+ assert name not in os.listdir(mnt_dir)
+ with open(mnt_name, 'w') as fh:
+ fh.write('Hello, world')
+ assert name in os.listdir(src_dir)
+ assert name in os.listdir(mnt_dir)
+ assert os.stat(src_name) == os.stat(mnt_name)
diff --git a/test/test_fuse.py b/test/test_fuse.py
new file mode 100755
index 0000000..bbba6e0
--- /dev/null
+++ b/test/test_fuse.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python3
+import pytest
+import sys
+
+if __name__ == '__main__':
+ sys.exit(pytest.main([__file__] + sys.argv[1:]))
+
+import subprocess
+import os
+from util import wait_for_mount, umount, cleanup
+
+basename = os.path.join(os.path.dirname(__file__), '..')
+
+def test_fuse(tmpdir):
+ mnt_dir = str(tmpdir.mkdir('mnt'))
+ src_dir = str(tmpdir.mkdir('src'))
+
+ cmdline = [ os.path.join(basename, 'example', 'fusexmp_fh'),
+ '-f', '-o' , 'use_ino,readdir_ino,kernel_cache',
+ mnt_dir ]
+ mount_process = subprocess.Popen(cmdline)
+ try:
+ wait_for_mount(mount_process, mnt_dir)
+ cmdline = [ os.path.join(basename, 'test', 'test'),
+ os.path.join(mnt_dir, src_dir),
+ ':' + src_dir ]
+ subprocess.check_call(cmdline)
+ except:
+ cleanup(mnt_dir)
+ raise
+ else:
+ umount(mount_process, mnt_dir)
diff --git a/test/util.py b/test/util.py
new file mode 100644
index 0000000..48ec995
--- /dev/null
+++ b/test/util.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+import subprocess
+import pytest
+import os
+import time
+
+def wait_for_mount(mount_process, mnt_dir):
+ elapsed = 0
+ while elapsed < 30:
+ if os.path.ismount(mnt_dir):
+ return True
+ if mount_process.poll() is not None:
+ pytest.fail('file system process terminated prematurely')
+ time.sleep(0.1)
+ elapsed += 0.1
+ pytest.fail("mountpoint failed to come up")
+
+def cleanup(mnt_dir):
+ subprocess.call(['fusermount', '-z', '-u', mnt_dir],
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.STDOUT)
+
+def umount(mount_process, mnt_dir):
+ subprocess.check_call(['fusermount', '-z', '-u', mnt_dir])
+ assert not os.path.ismount(mnt_dir)
+
+ # Give mount process a little while to terminate. Popen.wait(timeout)
+ # was only added in 3.3...
+ elapsed = 0
+ while elapsed < 30:
+ code = mount_process.poll()
+ if code is not None:
+ if code == 0:
+ return
+ pytest.fail('file system process terminated with code %s' % (code,))
+ time.sleep(0.1)
+ elapsed += 0.1
+ pytest.fail('mount process did not terminate')