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
126 changes: 124 additions & 2 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,22 @@ libocispec/libocispec.la:

libcrun_la_SOURCES = $(libcrun_SOURCES)
libcrun_la_CFLAGS = -I $(abs_top_builddir)/libocispec/src -I $(abs_top_srcdir)/libocispec/src -fvisibility=hidden
libcrun_la_LIBADD = libocispec/libocispec.la $(FOUND_LIBS) $(maybe_libyajl.la)
if ENABLE_COVERAGE
libcrun_la_CFLAGS += $(COVERAGE_CFLAGS)
libcrun_la_LDFLAGS = -Wl,--version-script=$(abs_top_srcdir)/libcrun.lds $(COVERAGE_LDFLAGS)
else
libcrun_la_LDFLAGS = -Wl,--version-script=$(abs_top_srcdir)/libcrun.lds
endif
libcrun_la_LIBADD = libocispec/libocispec.la $(FOUND_LIBS) $(maybe_libyajl.la)

# build a version with all the symbols visible for testing
if BUILD_TESTS
libcrun_testing_la_SOURCES = $(libcrun_SOURCES)
libcrun_testing_la_CFLAGS = -I $(abs_top_builddir)/libocispec/src -I $(abs_top_srcdir)/libocispec/src -fvisibility=default
if ENABLE_COVERAGE
libcrun_testing_la_CFLAGS += $(COVERAGE_CFLAGS)
libcrun_testing_la_LDFLAGS = $(COVERAGE_LDFLAGS)
endif
libcrun_testing_la_LIBADD = libocispec/libocispec.la $(maybe_libyajl.la)
endif

Expand Down Expand Up @@ -137,16 +146,27 @@ dist-luarock: $(LUACRUN_ROCK)
endif

crun_CFLAGS = -I $(abs_top_builddir)/libocispec/src -I $(abs_top_srcdir)/libocispec/src -D CRUN_LIBDIR="\"$(CRUN_LIBDIR)\""
if ENABLE_COVERAGE
crun_CFLAGS += $(COVERAGE_CFLAGS)
endif
crun_SOURCES = src/crun.c src/run.c src/delete.c src/kill.c src/pause.c src/unpause.c src/oci_features.c src/spec.c \
src/exec.c src/list.c src/create.c src/start.c src/state.c src/update.c src/ps.c \
src/checkpoint.c src/restore.c src/mounts.c src/run_create.c

if DYNLOAD_LIBCRUN
if ENABLE_COVERAGE
crun_LDFLAGS = -Wl,--unresolved-symbols=ignore-all $(CRUN_LDFLAGS) $(COVERAGE_LDFLAGS)
else
crun_LDFLAGS = -Wl,--unresolved-symbols=ignore-all $(CRUN_LDFLAGS)
endif
else
crun_LDADD = libcrun.la $(FOUND_LIBS) $(maybe_libyajl.la)
if ENABLE_COVERAGE
crun_LDFLAGS = $(CRUN_LDFLAGS) $(COVERAGE_LDFLAGS)
else
crun_LDFLAGS = $(CRUN_LDFLAGS)
endif
endif

EXTRA_DIST = COPYING COPYING.libcrun README.md NEWS SECURITY.md rpm/crun.spec autogen.sh \
src/libcrun/blake3/blake3_impl.h src/libcrun/blake3/blake3.h \
Expand Down Expand Up @@ -187,6 +207,7 @@ TESTS_LDADD = libcrun_testing.la $(FOUND_LIBS) $(maybe_libyajl.la)

tests_init_LDADD =
tests_init_LDFLAGS = -static-libgcc -all-static
tests_init_CFLAGS = -g -O2
tests_init_SOURCES = tests/init.c

tests_tests_libcrun_utils_CFLAGS = -I $(abs_top_builddir)/libocispec/src -I $(abs_top_srcdir)/libocispec/src -I $(abs_top_builddir)/src -I $(abs_top_srcdir)/src
Expand Down Expand Up @@ -338,4 +359,105 @@ clang-format:
shellcheck:
shellcheck autogen.sh build-aux/release.sh tests/run_all_tests.sh tests/*/*.sh contrib/*.sh

.PHONY: coverity sync generate-rust-bindings generate-signals.c generate-mount_flags.c clang-format shellcheck
# Code coverage targets
if ENABLE_COVERAGE

# Clean coverage data
coverage-clean:
@rm -rf coverage-html coverage.info coverage.xml
@find . -name "*.gcda" -delete
@find . -name "*.gcno" -delete

# Reset coverage counters
coverage-reset:
@if test -n "$(LCOV)"; then \
$(LCOV) --zerocounters --directory .; \
fi

# Run tests and collect coverage data
coverage-check: coverage-reset
@echo "Running tests for coverage (single-threaded to avoid race conditions)..."
$(MAKE) -j1 check
@echo "Collecting coverage data..."

# Generate HTML coverage report (preferred method with lcov)
coverage-html: coverage-check
@if test -n "$(LCOV)"; then \
echo "Generating coverage report with lcov..."; \
$(LCOV) --capture --directory . --output-file coverage.info; \
$(LCOV) --remove coverage.info '/usr/*' --output-file coverage.info; \
$(LCOV) --remove coverage.info '*/libocispec/*' --output-file coverage.info; \
$(LCOV) --remove coverage.info '*/tests/test_*.py*' --output-file coverage.info; \
$(LCOV) --remove coverage.info '*/tests/init*' --output-file coverage.info; \
genhtml coverage.info --output-directory coverage-html; \
echo "Coverage report generated in coverage-html/index.html"; \
elif test -n "$(GCOVR)"; then \
echo "Generating coverage report with gcovr..."; \
$(GCOVR) --html --html-details -o coverage.html \
--exclude '/usr/.*' --exclude '.*/libocispec/.*' --exclude '.*/tests/test_.*\.py.*' --exclude '.*/tests/init.*'; \
echo "Coverage report generated in coverage.html"; \
else \
echo "Generating coverage report with gcov..."; \
mkdir -p coverage-html; \
for src in $(libcrun_SOURCES) $(crun_SOURCES); do \
if test -f "$${src}.gcno"; then \
$(GCOV) -o . $$src || true; \
fi; \
done; \
mv *.gcov coverage-html/ 2>/dev/null || true; \
echo "Coverage files generated in coverage-html/"; \
fi

# Generate XML coverage report (for CI tools)
coverage-xml: coverage-check
@if test -n "$(GCOVR)"; then \
echo "Generating XML coverage report with gcovr..."; \
$(GCOVR) --xml -o coverage.xml \
--exclude '/usr/.*' --exclude '.*/libocispec/.*' --exclude '.*/tests/test_.*\.py.*' --exclude '.*/tests/init.*'; \
echo "Coverage report generated in coverage.xml"; \
elif test -n "$(LCOV)"; then \
echo "Generating XML coverage report with lcov..."; \
$(LCOV) --capture --directory . --output-file coverage.info; \
$(LCOV) --remove coverage.info '/usr/*' --output-file coverage.info; \
$(LCOV) --remove coverage.info '*/libocispec/*' --output-file coverage.info; \
$(LCOV) --remove coverage.info '*/tests/test_*.py*' --output-file coverage.info; \
$(LCOV) --remove coverage.info '*/tests/init*' --output-file coverage.info; \
echo "Coverage data collected in coverage.info"; \
else \
echo "XML coverage requires gcovr or lcov"; \
exit 1; \
fi

# Generate coverage summary
coverage-summary: coverage-check
@if test -n "$(LCOV)"; then \
echo "Coverage summary (lcov):"; \
$(LCOV) --capture --directory . --output-file coverage.info; \
$(LCOV) --remove coverage.info '/usr/*' --output-file coverage.info; \
$(LCOV) --remove coverage.info '*/libocispec/*' --output-file coverage.info; \
$(LCOV) --remove coverage.info '*/tests/test_*.py*' --output-file coverage.info; \
$(LCOV) --remove coverage.info '*/tests/init*' --output-file coverage.info; \
$(LCOV) --summary coverage.info; \
elif test -n "$(GCOVR)"; then \
echo "Coverage summary (gcovr):"; \
$(GCOVR) --exclude '/usr/.*' --exclude '.*/libocispec/.*' --exclude '.*/tests/test_.*\.py.*' --exclude '.*/tests/init.*'; \
else \
echo "Coverage summary requires lcov or gcovr"; \
fi

else

coverage-clean:
@echo "Coverage support not enabled. Reconfigure with --enable-coverage"

coverage-reset coverage-check coverage-html coverage-xml coverage-summary:
@echo "Coverage support not enabled. Reconfigure with --enable-coverage"

endif

clean-local: coverage-clean

# Coverage targets must not run in parallel due to race conditions in .gcda file writes
.NOTPARALLEL: coverage-reset coverage-check coverage-html coverage-xml coverage-summary

.PHONY: coverity sync generate-rust-bindings generate-signals.c generate-mount_flags.c clang-format shellcheck coverage-clean coverage-reset coverage-check coverage-html coverage-xml coverage-summary
39 changes: 39 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,45 @@ if test -z "$GPERF"; then
AC_MSG_NOTICE(gperf not found - cannot rebuild signal parser code)
fi

dnl code coverage
AC_ARG_ENABLE([coverage],
AS_HELP_STRING([--enable-coverage], [Enable code coverage support]),
[enable_coverage=$enableval], [enable_coverage=no])

AS_IF([test "x$enable_coverage" = "xyes"], [
AC_CHECK_TOOL([GCOV], [gcov])
if test -z "$GCOV"; then
AC_MSG_ERROR([gcov is required for code coverage])
fi

AC_CHECK_TOOL([LCOV], [lcov])
AC_CHECK_TOOL([GCOVR], [gcovr])

# Choose the best available coverage tool
if test -n "$LCOV"; then
coverage_tool=lcov
elif test -n "$GCOVR"; then
coverage_tool=gcovr
else
coverage_tool=gcov
fi

AC_MSG_NOTICE([Using $coverage_tool for code coverage reporting])

# Add coverage flags
COVERAGE_CFLAGS="--coverage -g -O0 -fno-inline -fno-inline-small-functions -fno-default-inline"
COVERAGE_LDFLAGS="--coverage"

AC_SUBST([COVERAGE_CFLAGS])
AC_SUBST([COVERAGE_LDFLAGS])
AC_SUBST([GCOV])
AC_SUBST([LCOV])
AC_SUBST([GCOVR])
AC_SUBST([coverage_tool], [$coverage_tool])
])

AM_CONDITIONAL([ENABLE_COVERAGE], [test "x$enable_coverage" = "xyes"])

AC_SEARCH_LIBS([argp_parse], [argp], [], [AC_MSG_ERROR([*** argp functions not found - install libargp or argp_standalone])])

AM_CONDITIONAL([PYTHON_BINDINGS], [test "x$with_python_bindings" = "xyes"])
Expand Down
7 changes: 5 additions & 2 deletions maint.mk
Original file line number Diff line number Diff line change
Expand Up @@ -1581,7 +1581,7 @@ init-coverage:
lcov --directory . --zerocounters

COVERAGE_CCOPTS ?= "-g --coverage"
COVERAGE_OUT ?= doc/coverage
COVERAGE_OUT ?= docs/coverage

build-coverage:
$(MAKE) $(AM_MAKEFLAGS) CFLAGS=$(COVERAGE_CCOPTS) CXXFLAGS=$(COVERAGE_CCOPTS)
Expand All @@ -1594,7 +1594,10 @@ gen-coverage:
genhtml --output-directory $(COVERAGE_OUT) \
$(COVERAGE_OUT)/$(PACKAGE).info \
--highlight --frames --legend \
--title "$(PACKAGE_NAME)"
--title "$(PACKAGE_NAME)" \
--ignore-errors unmapped

.NOTPARALLEL: coverage init-coverage build-coverage gen-coverage

coverage:
$(MAKE) init-coverage
Expand Down
24 changes: 12 additions & 12 deletions tests/test_bpf_devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,27 +47,27 @@ def check_bpf_prerequisites():
"""Check all prerequisites for BPF device tests. Returns 77 (skip) if not met, 0 if OK"""
# Skip if not root
if is_rootless():
return 77
return (77, "requires root privileges")

# Skip if not cgroup v2
if not is_cgroup_v2_unified():
return 77
return (77, "requires cgroup v2")

# Skip if systemd not available
if 'SYSTEMD' not in get_crun_feature_string():
return 77
return (77, "systemd support not compiled in")

# Skip if not running on systemd
if not running_on_systemd():
return 77
return (77, "not running on systemd")

# Skip if no BPF support
if not has_bpf_fs():
return 77
return (77, "BPF filesystem not available")

# Skip if systemd doesn't support BPFProgram
if not systemd_supports_bpf_program():
return 77
return (77, "systemd BPFProgram not supported")

return 0

Expand All @@ -86,7 +86,7 @@ def test_bpf_devices_systemd():
bpf_path = None
try:
# Run container with systemd cgroup manager.
_, cid = run_and_get_output(conf, command='run', detach=True, cgroup_manager="systemd")
_, cid = run_and_get_output(conf, hide_stderr=True, command='run', detach=True, cgroup_manager="systemd")

# Get systemd scope.
state = run_crun_command(['state', cid])
Expand All @@ -96,20 +96,20 @@ def test_bpf_devices_systemd():

output = subprocess.check_output(['systemctl', 'show', '-PBPFProgram', scope], close_fds=False).decode().strip()
if output == "":
sys.stderr.write("# BPFProgram property not found or empty\n")
logger.info("BPFProgram property not found or empty")
return -1

# Should look like "device:/sys/fs/bpf/crun/crun-xxx_scope".
if "device:/sys/fs/bpf/crun/" not in output:
sys.stderr.write("# Bad BPFProgram property value: `%s`\n" % output)
logger.info("Bad BPFProgram property value: `%s`", prop_value)
return -1

# Test 2: Check that BPF program file was created.

# Extract the path.
bpf_path = output.split("device:", 1)[1]
if not os.path.exists(bpf_path):
sys.stderr.write("# BPF program file `%s` not found\n" % bpf_path)
logger.info("BPF program file `%s` not found", prog_file)
return -1

# Test 3: Check that BPF program is cleaned up.
Expand All @@ -118,13 +118,13 @@ def test_bpf_devices_systemd():
run_crun_command(["delete", "-f", cid])
cid = None
if os.path.exists(bpf_path):
sys.stderr.write("# BPF program `%s` still exist after crun delete\n" % bpf_path)
logger.info("BPF program `%s` still exist after crun delete", prog_file)
return -1

return 0

except Exception as e:
sys.stderr.write("# Test failed with exception: %s\n" % str(e))
logger.info("Test failed with exception: %s", e)
return -1
finally:
if cid is not None:
Expand Down
Loading
Loading