mirror of
https://github.com/RsyncProject/rsync.git
synced 2026-05-24 23:05:52 -04:00
build: add gcov coverage and --disable-openat2 knobs for the test suite
Two test-coverage build knobs (both behaviour-neutral by default):
--enable-coverage appends '--coverage -fprofile-update=atomic -O0' and adds
a 'make coverage' target (whole suite, run serially, then
gcovr HTML with branch + decision coverage). rsync forks
and its children exit without running the gcov atexit
flush -- the generator via its SIGUSR1 handler
(_exit_cleanup) and the receiver via the SIGUSR2 handler
-- so under GCOV_COVERAGE we call __gcov_dump() at both, or
receiver.c/generator.c record no coverage at all.
--disable-openat2 gates the Linux openat2(RESOLVE_BENEATH) sites in syscall.c
on HAVE_OPENAT2 (defined by default), so disabling it forces
the portable per-component O_NOFOLLOW resolver to run as the
primary on ordinary Linux -- exercising and
coverage-counting that fallback tier without a pre-5.6
kernel. NOTE: coordinate with the parallel syscall.c
path-resolution restructure.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
34
Makefile.in
34
Makefile.in
@@ -280,6 +280,8 @@ clean: cleantests
|
||||
rm -f *~ $(OBJS) $(CHECK_PROGS) $(CHECK_OBJS) $(CHECK_SYMLINKS) @MAKE_RRSYNC@ \
|
||||
git-version.h rounding rounding.h *.old rsync*.1 rsync*.5 @MAKE_RRSYNC_1@ \
|
||||
*.html daemon-parm.h help-*.h default-*.h proto.h proto.h-tstamp
|
||||
rm -f *.gcno *.gcda lib/*.gcno lib/*.gcda zlib/*.gcno zlib/*.gcda popt/*.gcno popt/*.gcda
|
||||
rm -rf coverage
|
||||
|
||||
.PHONY: cleantests
|
||||
cleantests:
|
||||
@@ -324,6 +326,15 @@ test: check
|
||||
# `make check CHECK_J=1` (serial) or any other value.
|
||||
CHECK_J = 8
|
||||
|
||||
# Parallelism for `make coverage`. Defaults to the same as CHECK_J: the
|
||||
# coverage build sets -fprofile-update=atomic (atomic in-memory counters) and
|
||||
# gcc's libgcov serializes the per-source .gcda read-modify-write merge with a
|
||||
# file lock, so concurrent rsync processes (incl. the forked sender/generator/
|
||||
# receiver) accumulate exactly -- verified by a count-linearity check (a hot
|
||||
# line accumulates identically at -j1 and -P16). Override with
|
||||
# `make coverage COVERAGE_J=1` if your libgcov does not lock .gcda merges.
|
||||
COVERAGE_J = $(CHECK_J)
|
||||
|
||||
.PHONY: check
|
||||
check: all $(CHECK_PROGS) $(CHECK_SYMLINKS)
|
||||
$(srcdir)/runtests.py --rsync-bin=`pwd`/rsync$(EXEEXT) -j $(CHECK_J)
|
||||
@@ -336,6 +347,29 @@ check29: all $(CHECK_PROGS) $(CHECK_SYMLINKS)
|
||||
check30: all $(CHECK_PROGS) $(CHECK_SYMLINKS)
|
||||
$(srcdir)/runtests.py --rsync-bin=`pwd`/rsync$(EXEEXT) -j $(CHECK_J) --protocol=30
|
||||
|
||||
# Whole-suite gcov coverage report (HTML, with branch + decision coverage).
|
||||
# Requires a build configured with --enable-coverage and the `gcovr` tool
|
||||
# (pip install gcovr). Runs the suite in parallel (COVERAGE_J, default CHECK_J):
|
||||
# this is safe because the coverage build uses -fprofile-update=atomic and
|
||||
# libgcov locks the per-source .gcda during its merge, so concurrent rsync
|
||||
# processes accumulate exactly (see COVERAGE_J above). Use COVERAGE_J=1 if your
|
||||
# toolchain's libgcov does not lock .gcda merges.
|
||||
.PHONY: coverage
|
||||
coverage: all $(CHECK_PROGS) $(CHECK_SYMLINKS)
|
||||
@case '$(CFLAGS)' in *--coverage*) ;; \
|
||||
*) echo "*** not a coverage build; reconfigure with --enable-coverage"; exit 1 ;; esac
|
||||
@command -v gcovr >/dev/null 2>&1 || { echo "*** gcovr not found (pip install gcovr)"; exit 1; }
|
||||
find . -name '*.gcda' -delete
|
||||
@rc=0; $(srcdir)/runtests.py --rsync-bin=`pwd`/rsync$(EXEEXT) -j $(COVERAGE_J) || rc=$$?; \
|
||||
rm -rf coverage && mkdir -p coverage; \
|
||||
gcovr --root $(srcdir) --branches --decisions --print-summary \
|
||||
--html-details -o coverage/index.html . || exit $$?; \
|
||||
echo "Coverage report written to coverage/index.html"; \
|
||||
if test $$rc != 0; then \
|
||||
echo "*** test suite FAILED (status $$rc) -- coverage report still written above"; \
|
||||
fi; \
|
||||
exit $$rc
|
||||
|
||||
wildtest.o: wildtest.c t_stub.o lib/wildmatch.c rsync.h config.h
|
||||
wildtest$(EXEEXT): wildtest.o lib/compat.o lib/snprintf.o @BUILD_POPT@
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ wildtest.o lib/compat.o lib/snprintf.o @BUILD_POPT@ $(LIBS)
|
||||
|
||||
10
cleanup.c
10
cleanup.c
@@ -269,8 +269,16 @@ NORETURN void _exit_cleanup(int code, const char *file, int line)
|
||||
break;
|
||||
}
|
||||
|
||||
if (called_from_signal_handler)
|
||||
if (called_from_signal_handler) {
|
||||
#ifdef GCOV_COVERAGE
|
||||
/* _exit() bypasses the gcov atexit flush; rsync's generator (and
|
||||
* other processes) normally finish via the signal handler, so
|
||||
* without this they would write no .gcda. Harmless otherwise. */
|
||||
extern void __gcov_dump(void);
|
||||
__gcov_dump();
|
||||
#endif
|
||||
_exit(exit_code);
|
||||
}
|
||||
exit(exit_code);
|
||||
}
|
||||
|
||||
|
||||
26
configure.ac
26
configure.ac
@@ -82,6 +82,32 @@ if test x"$enable_profile" = x"yes"; then
|
||||
CFLAGS="$CFLAGS -pg"
|
||||
fi
|
||||
|
||||
dnl Coverage build (gcov) for `make coverage`. NOTE: --enable-profile above is
|
||||
dnl gprof (-pg) and is NOT coverage. -O0 keeps branch coverage meaningful;
|
||||
dnl -fprofile-update=atomic keeps the shared .gcda counters correct while the
|
||||
dnl suite runs many rsync processes in parallel.
|
||||
AC_ARG_ENABLE(coverage,
|
||||
AS_HELP_STRING([--enable-coverage],[build with gcov instrumentation for `make coverage`]))
|
||||
if test x"$enable_coverage" = x"yes"; then
|
||||
CFLAGS="$CFLAGS --coverage -fprofile-update=atomic -O0"
|
||||
CXXFLAGS="$CXXFLAGS --coverage -fprofile-update=atomic -O0"
|
||||
LDFLAGS="$LDFLAGS --coverage"
|
||||
AC_DEFINE([GCOV_COVERAGE], 1,
|
||||
[Flush gcov counters at exit_cleanup: rsync's children exit via _exit(), which bypasses the gcov atexit handler, so without this no .gcda is written for the receiver/generator/daemon-worker processes.])
|
||||
fi
|
||||
|
||||
dnl openat2(RESOLVE_BENEATH) is used on Linux 5.6+ for the secure resolver.
|
||||
dnl --disable-openat2 forces the portable per-component O_NOFOLLOW fallback to
|
||||
dnl run as the primary resolver on ordinary Linux, so that tier is exercised
|
||||
dnl (and coverage-counted) without needing a pre-5.6 kernel. Behaviour-neutral
|
||||
dnl by default (the knob only REMOVES a tier when explicitly disabled).
|
||||
AC_ARG_ENABLE(openat2,
|
||||
AS_HELP_STRING([--disable-openat2],[do not use Linux openat2(RESOLVE_BENEATH); force the portable resolver (for exercising the fallback tier)]))
|
||||
if test x"$enable_openat2" != x"no"; then
|
||||
AC_DEFINE([HAVE_OPENAT2], 1,
|
||||
[Define to use Linux openat2(RESOLVE_BENEATH) in secure_relative_open where available.])
|
||||
fi
|
||||
|
||||
AC_MSG_CHECKING([if md2man can create manpages])
|
||||
if test x"$ac_cv_path_PYTHON3" = x; then
|
||||
AC_MSG_RESULT(no - python3 not found)
|
||||
|
||||
5
main.c
5
main.c
@@ -1618,6 +1618,11 @@ static void sigusr2_handler(UNUSED(int val))
|
||||
if (!am_server)
|
||||
output_summary();
|
||||
close_all();
|
||||
#ifdef GCOV_COVERAGE
|
||||
/* The receiver child is killed here via SIGUSR2 and exits with _exit(),
|
||||
* bypassing the gcov atexit flush; without this it writes no .gcda. */
|
||||
{ extern void __gcov_dump(void); __gcov_dump(); }
|
||||
#endif
|
||||
if (got_xfer_error)
|
||||
_exit(RERR_PARTIAL);
|
||||
_exit(0);
|
||||
|
||||
10
syscall.c
10
syscall.c
@@ -33,7 +33,7 @@
|
||||
#include <sys/syscall.h>
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
#if defined(__linux__) && defined(HAVE_OPENAT2)
|
||||
#include <sys/syscall.h>
|
||||
#include <linux/openat2.h>
|
||||
#endif
|
||||
@@ -1691,7 +1691,7 @@ static int path_has_dotdot_component(const char *path)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
#if defined(__linux__) && defined(HAVE_OPENAT2)
|
||||
static int secure_relative_open_linux(const char *basedir, const char *relpath, int flags, mode_t mode)
|
||||
{
|
||||
struct open_how how;
|
||||
@@ -1791,11 +1791,13 @@ int secure_relative_open(const char *basedir, const char *relpath, int flags, mo
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
#if defined(__linux__) && defined(HAVE_OPENAT2)
|
||||
{
|
||||
int fd = secure_relative_open_linux(basedir, relpath, flags, mode);
|
||||
/* ENOSYS = kernel < 5.6 doesn't have the syscall even though
|
||||
* glibc/kernel-headers do; fall through to the portable path. */
|
||||
* glibc/kernel-headers do; fall through to the portable path.
|
||||
* (Built unconditionally unless --disable-openat2, which forces
|
||||
* the portable resolver below so that tier is exercised.) */
|
||||
if (fd != -1 || errno != ENOSYS)
|
||||
return fd;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user