diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d5083ddb..a0e1756b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -69,6 +69,16 @@ ADD_EXECUTABLE(jfjoch_test XDSPluginTest.cpp ) -target_link_libraries(jfjoch_test Catch2WithMain JFJochBroker JFJochReceiver JFJochReader JFJochWriter JFJochImageAnalysis JFJochCommon JFJochHLSSimulation JFJochPreview - jfjoch_xds_plugin) +target_link_libraries(jfjoch_test Catch2WithMain JFJochBroker JFJochReceiver JFJochReader JFJochWriter + JFJochImageAnalysis JFJochCommon JFJochHLSSimulation JFJochPreview + jfjoch_xds_plugin) target_include_directories(jfjoch_test PRIVATE .) + +find_package(Threads REQUIRED) + +add_library(enospc_shim MODULE enospc_shim.c) +target_link_libraries(enospc_shim PRIVATE Threads::Threads dl) +set_target_properties(enospc_shim PROPERTIES PREFIX "" POSITION_INDEPENDENT_CODE ON) # remove "lib" + +ADD_EXECUTABLE(jfjoch_hdf5_enospc_test jfjoch_hdf5_enospc_test.cpp) +target_link_libraries(jfjoch_hdf5_enospc_test Catch2WithMain JFJochWriter) \ No newline at end of file diff --git a/tests/enospc_shim.c b/tests/enospc_shim.c new file mode 100644 index 00000000..fec12797 --- /dev/null +++ b/tests/enospc_shim.c @@ -0,0 +1,153 @@ +// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +// Function pointers to real syscalls +static ssize_t (*real_pwrite64) (int, const void *, size_t, __off64_t) = NULL; + +// State +static size_t total_written = 0; +static size_t fail_after = 0; + +// Thread safety (important in real tests) +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + +// Initialize real symbols and config +__attribute__((constructor)) +static void init(void) { + real_pwrite64 = dlsym(RTLD_NEXT, "pwrite64"); + + const char* env = getenv("ENOSPC_AFTER"); + if (env) { + fail_after = strtoull(env, NULL, 10); + } else { + fail_after = 10ULL * 1024 * 1024; // default: 10 MB + } + + printf("ENOSPC shim loaded with limit %lu bytes\n", fail_after); +} +/* +// Common helper +static int should_fail(size_t upcoming) { + if (fail_after == 0) return 0; + + if (total_written >= fail_after) return 1; + + if (total_written + upcoming > fail_after) { + // Simulate partial write exhaustion: + // allow some bytes, then next call fails + return 0; + } + + return 0; +} + +// ---- write ---- +ssize_t write(int fd, const void *buf, size_t count) { + pthread_mutex_lock(&lock); + + printf("write %lu bytes\n", count); + if (total_written >= fail_after) { + pthread_mutex_unlock(&lock); + errno = ENOSPC; + return -1; + } + + pthread_mutex_unlock(&lock); + + ssize_t ret = real_write(fd, buf, count); + + if (ret > 0) { + pthread_mutex_lock(&lock); + total_written += ret; + pthread_mutex_unlock(&lock); + } + + return ret; +} + +// ---- pwrite ---- +ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset) { + pthread_mutex_lock(&lock); + + printf("pwrite %lu bytes\n", count); + if (total_written >= fail_after) { + pthread_mutex_unlock(&lock); + errno = ENOSPC; + return -1; + } + + pthread_mutex_unlock(&lock); + + ssize_t ret = real_pwrite(fd, buf, count, offset); + + if (ret > 0) { + pthread_mutex_lock(&lock); + total_written += ret; + pthread_mutex_unlock(&lock); + } + + return ret; +} + +// ---- writev ---- +ssize_t writev(int fd, const struct iovec *iov, int iovcnt) { + size_t total = 0; + for (int i = 0; i < iovcnt; i++) { + total += iov[i].iov_len; + } + + printf("writev %lu bytes\n", total); + + pthread_mutex_lock(&lock); + + if (total_written >= fail_after) { + pthread_mutex_unlock(&lock); + errno = ENOSPC; + return -1; + } + + pthread_mutex_unlock(&lock); + + ssize_t ret = real_writev(fd, iov, iovcnt); + + if (ret > 0) { + pthread_mutex_lock(&lock); + total_written += ret; + pthread_mutex_unlock(&lock); + } + pwrite64() + return ret; +} */ + +ssize_t pwrite64(int fd, const void *buf, size_t count, off_t offset) { + pthread_mutex_lock(&lock); + + printf("pwrite64 %lu bytes\n", count); + + if (total_written >= fail_after) { + pthread_mutex_unlock(&lock); + errno = ENOSPC; + return -1; + } + + pthread_mutex_unlock(&lock); + + ssize_t ret = real_pwrite64(fd, buf, count, offset); + + if (ret > 0) { + pthread_mutex_lock(&lock); + total_written += ret; + pthread_mutex_unlock(&lock); + } + + return ret; +} \ No newline at end of file diff --git a/tests/jfjoch_hdf5_enospc_test.cpp b/tests/jfjoch_hdf5_enospc_test.cpp new file mode 100644 index 00000000..2b167dba --- /dev/null +++ b/tests/jfjoch_hdf5_enospc_test.cpp @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +// Run with LD_PRELOAD=enospc_shim.so ./jfjoch_hdf5_enospc_test + +#include +#include "../writer/HDF5Objects.h" +#include "../writer/FileWriter.h" +#include "../common/DiffractionExperiment.h" + +TEST_CASE("HDF5File_enospc") { + auto file = std::make_unique("enospc_test.h5"); + + std::vector small_vector(2056), large_vector(40 * 1024 * 1024); + REQUIRE_NOTHROW(file->SaveVector("/small", small_vector)); + REQUIRE_NOTHROW(file->SaveVector("/large1", large_vector)); + REQUIRE_THROWS(file->SaveVector("/large2", large_vector)); + REQUIRE_NOTHROW(file.reset()); +} + +TEST_CASE("FileWriter_enospc") { + { + RegisterHDF5Filter(); + DiffractionExperiment x(DetJF4M()); + + x.FilePrefix("test02_1p10").ImagesPerTrigger(5).ImagesPerFile(2).Compression(CompressionAlgorithm::NO_COMPRESSION); + StartMessage start_message; + x.FillMessage(start_message); + + FileWriter file_set(start_message); + std::vector image(x.GetPixelsNum()); + + DataMessage message{}; + message.image = CompressedImage(image, x.GetXPixelsNum(), x.GetYPixelsNum()); + message.number = 0; + REQUIRE_NOTHROW(file_set.Write(message)); + message.number = 1; + REQUIRE_THROWS(file_set.Write(message)); + message.number = 2; + REQUIRE_THROWS(file_set.Write(message)); + } + // No leftover HDF5 objects + REQUIRE (H5Fget_obj_count(H5F_OBJ_ALL, H5F_OBJ_ALL) == 0); +}