Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Test Suite

on: [push, pull_request]

jobs:
test:
name: Build and test
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
fuse: [fuse3, fuse2]

steps:
- name: Set up Git repository
uses: actions/checkout@v6
with:
submodules: recursive

- name: Install dependencies
run: |
sudo apt-get -q update
sudo apt-get -q -y install --no-install-recommends \
build-essential automake autoconf libtool pkg-config \
icu-devtools libicu-dev libxml2-dev uuid-dev libsnmp-dev \
libfuse-dev libfuse3-dev attr

- name: Build
run: |
./autogen.sh
./configure ${{ matrix.fuse == 'fuse2' && '--with-fuse2' || '' }}
make -j"$(nproc)"

- name: Run tests
run: make check VERBOSE=1
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,8 @@ GTAGS
TAGS
tags
*~
# Files generated by make check
tests/t/*.log
tests/t/*.trs
tests/test-suite.log
tests/helpers/fsops_helper
2 changes: 1 addition & 1 deletion Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ ACLOCAL_AMFLAGS = -I m4
nobase_pkginclude_HEADERS = config.h
EXTRA_DIST = VERSION

SUBDIRS = messages src conf init.d man
SUBDIRS = messages src conf init.d man tests

install-data-local: ltfs.pc
if [ ! -d $(DESTDIR)$(libdir)/pkgconfig ]; then \
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,12 @@ make install
| 8.0 | i386 | OK |
| 7.2 | amd64 | OK |

## Running the test suite

`make check` runs the integration tests. They mount LTFS through FUSE on a tape that the file backend emulates in a local directory, so no tape hardware is needed.

The tests need Linux with `/dev/fuse` and are skipped on other systems. On macOS, `tests/run-in-docker.sh [configure-options...]` builds LTFS and runs the tests in an Ubuntu container.

## Contributing

Please read [CONTRIBUTING.md](.github/CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us.
Expand Down
6 changes: 6 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,11 @@ dnl
dnl Output files
dnl
AC_CONFIG_HEADERS([config.h])
dnl Compute the integration test list at configure time so branches can
dnl add tests/t/*.sh scripts without editing the build files.
TESTS_LIST=`cd "$srcdir/tests" 2>/dev/null && ls t/*.sh 2>/dev/null | sort | tr '\n' ' '`
AC_SUBST([TESTS_LIST])

AC_CONFIG_FILES([
Makefile
messages/Makefile
Expand All @@ -637,6 +642,7 @@ AC_CONFIG_FILES([
src/iosched/Makefile
src/kmi/Makefile
src/utils/Makefile
tests/Makefile
ltfs.pc:ltfs.pc.in
])

Expand Down
2 changes: 1 addition & 1 deletion src/libltfs/arch/ltfs_arch_ops.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ extern "C" {

#define arch_sscanf sscanf

#define arch_open( descriptor_ptr, filename_ptr, open_flg, share_flg, unused) do{ *descriptor_ptr = open(filename_ptr, open_flg, share_flg); }while(0)
#define arch_open( descriptor_ptr, filename_ptr, open_flg, share_flg, perm) do{ *descriptor_ptr = open(filename_ptr, open_flg, perm); }while(0)

#define arch_fopen(file, mode, file_ptr) do {file_ptr = fopen(file, mode);}while(0)

Expand Down
62 changes: 62 additions & 0 deletions tests/Makefile.am
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#
#
# OO_Copyright_BEGIN
#
#
# Copyright 2010, 2020 IBM Corp. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# OO_Copyright_END
#

#
# Integration tests for LTFS using the file tape backend.
#
# Run with: make check
# The tests need a Linux host with /dev/fuse; elsewhere they are skipped.
# On macOS use tests/run-in-docker.sh to run them in a Linux container.
#

TEST_EXTENSIONS = .sh
SH_LOG_COMPILER = $(SHELL)

AM_TESTS_ENVIRONMENT = \
top_builddir='$(abs_top_builddir)' top_srcdir='$(abs_top_srcdir)'; \
export top_builddir top_srcdir;

check_PROGRAMS = helpers/fsops_helper
helpers_fsops_helper_SOURCES = helpers/fsops_helper.c

# TESTS_LIST is set by configure from tests/t/*.sh; GNU make functions
# cannot be used because automake runs with -Wall -Werror.
TESTS = $(TESTS_LIST)

EXTRA_DIST = \
lib/harness.sh \
$(TESTS) \
docker/Dockerfile \
run-in-docker.sh
12 changes: 12 additions & 0 deletions tests/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM ubuntu:24.04

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get -q update && apt-get -q -y install --no-install-recommends \
build-essential automake autoconf libtool pkg-config \
icu-devtools libicu-dev libxml2-dev uuid-dev libsnmp-dev \
libfuse-dev fuse3 libfuse3-dev \
attr file procps util-linux tar \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /build
158 changes: 158 additions & 0 deletions tests/helpers/fsops_helper.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
**
** OO_Copyright_BEGIN
**
**
** Copyright 2010, 2025 IBM Corp. All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. Neither the name of the copyright holder nor the names of its
** contributors may be used to endorse or promote products derived from
** this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
** POSSIBILITY OF SUCH DAMAGE.
**
**
** OO_Copyright_END
*/

/* Test helper exercising syscalls that shell utilities do not reach
* directly: renameat2() flags, ftruncate() on an open descriptor, mmap()
* and utimensat() with UTIME_OMIT.
*
* Usage:
* fsops_helper noreplace <old> <new> renameat2 with RENAME_NOREPLACE
* fsops_helper exchange <a> <b> renameat2 with RENAME_EXCHANGE
* fsops_helper ftruncate <file> <len> ftruncate an open fd, print new size
* fsops_helper mmap <file> <len> map the file shared and writable
* fsops_helper atime <file> <sec> set atime, UTIME_OMIT for mtime
* fsops_helper mtime <file> <sec> set mtime, UTIME_OMIT for atime
*
* Exit codes: 0 = success, 2 = syscall failed (strerror printed on stdout),
* 3 = usage, 4 = not supported on this platform.
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>

static int usage(const char *prog)
{
fprintf(stderr, "usage: %s noreplace|exchange|ftruncate|mmap|atime|mtime <arg> <arg>\n",
prog);
return 3;
}

static int parse_len(const char *arg, long long *len)
{
char *end;

*len = strtoll(arg, &end, 10);
return end != arg && *end == '\0';
}

/* Print the error before close() can change errno. */
static int syscall_failed(int fd)
{
printf("%s\n", strerror(errno));
if (fd >= 0)
close(fd);
return 2;
}

int main(int argc, char **argv)
{
if (argc != 4)
return usage(argv[0]);

if (!strcmp(argv[1], "noreplace") || !strcmp(argv[1], "exchange")) {
/* renameat2() and RENAME_* need glibc 2.28 or later. */
#if defined(__linux__) && defined(RENAME_NOREPLACE)
unsigned int flags = !strcmp(argv[1], "noreplace") ?
RENAME_NOREPLACE : RENAME_EXCHANGE;

if (renameat2(AT_FDCWD, argv[2], AT_FDCWD, argv[3], flags) < 0)
return syscall_failed(-1);
return 0;
#else
fprintf(stderr, "rename flags are not supported on this platform\n");
return 4;
#endif
}

if (!strcmp(argv[1], "ftruncate")) {
struct stat st;
long long len;
int fd;

if (!parse_len(argv[3], &len))
return usage(argv[0]);
fd = open(argv[2], O_RDWR);
if (fd < 0)
return syscall_failed(-1);
if (ftruncate(fd, len) < 0 || fstat(fd, &st) < 0)
return syscall_failed(fd);
printf("%lld\n", (long long)st.st_size);
close(fd);
return 0;
}

if (!strcmp(argv[1], "mmap")) {
long long len;
void *addr;
int fd;

if (!parse_len(argv[3], &len))
return usage(argv[0]);
fd = open(argv[2], O_RDWR);
if (fd < 0)
return syscall_failed(-1);
addr = mmap(NULL, (size_t)len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED)
return syscall_failed(fd);
munmap(addr, (size_t)len);
close(fd);
return 0;
}

if (!strcmp(argv[1], "atime") || !strcmp(argv[1], "mtime")) {
struct timespec ts[2] = { { 0, UTIME_OMIT }, { 0, UTIME_OMIT } };
int i = !strcmp(argv[1], "mtime");
long long sec;

if (!parse_len(argv[3], &sec))
return usage(argv[0]);
ts[i].tv_sec = (time_t)sec;
ts[i].tv_nsec = 0;
if (utimensat(AT_FDCWD, argv[2], ts, 0) < 0)
return syscall_failed(-1);
return 0;
}

fprintf(stderr, "unknown command: %s\n", argv[1]);
return 3;
}
Loading
Loading