Skip to content
Merged
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
28 changes: 7 additions & 21 deletions ci/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -61,29 +61,15 @@ cmake -S . -B "build-${DISTRO}" -G Ninja \

cmake --build "build-${DISTRO}" -j"$(nproc)"

# ctest exit code is non-zero when test cases throw exceptions (e.g.
# missing D-Bus in containers) even if all doctest assertions pass.
# We parse the doctest assertion summary instead of trusting the exit code.
# ctest runs everything for console diagnostics; its exit code is
# informational only because test cases aborted by environment exceptions
# (e.g. missing D-Bus in containers) are tolerated.
ctest --test-dir "build-${DISTRO}" --output-on-failure 2>&1 | tee "/tmp/ctest-${DISTRO}.txt" || true

assertion_line=$(grep -F "assertions:" "/tmp/ctest-${DISTRO}.txt" | tail -1)
if [ -z "${assertion_line}" ]; then
echo "No doctest assertion summary found in ctest output for ${DISTRO}."
exit 1
fi

failed_assertions=$(echo "${assertion_line}" | sed -n 's/.*| *\([0-9]*\) failed.*/\1/p')
if [ -z "${failed_assertions}" ]; then
echo "Could not parse assertion failure count from: ${assertion_line}"
exit 1
fi

if [ "${failed_assertions}" -ne 0 ]; then
echo "Tests failed on ${DISTRO}: ${failed_assertions} assertion failure(s)."
exit 1
fi

echo "All ${assertion_line}"
# Gate on doctest's machine-readable JUnit report instead of scraping the
# console summary (issue #60).
./ci/check-doctest-junit.sh \
"build-${DISTRO}/test/OpenCATTUS-tests" "/tmp/doctest-${DISTRO}-junit.xml"

if [[ "${DISTRO}" == el* || "${DISTRO}" == ubi* ]]; then
# Repo publishing (ciPublishRepo) expects out/rpm/<distro>/<arch>/*.rpm.
Expand Down
52 changes: 52 additions & 0 deletions ci/check-doctest-junit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
# Gate CI on doctest results through the machine-readable JUnit reporter
# instead of scraping the human-oriented console summary, which is not a
# stable interface across doctest versions (issue #60).
#
# Policy (unchanged from the summary-scraping days): assertion failures fail
# the gate; test cases aborted by environment exceptions (missing D-Bus,
# single-NIC containers, ...) are tolerated and only reported. JUnit keeps
# the two apart natively: assertion failures arrive as <failure> elements,
# exceptions as <error> elements.
#
# Usage: check-doctest-junit.sh <test-binary> <junit-xml-path>
# Must be invoked from the repository root: the test binary expects the
# source tree fixtures (test/sample, repos/) relative to its working
# directory.
set -euo pipefail

test_binary=$1
junit_xml=$2

rm -f "${junit_xml}"
# The exit code reflects exceptions as well as failures, so it is not the
# gate; the parsed report below is.
"${test_binary}" --test-case-exclude='*slow*' \
--reporters=junit --out="${junit_xml}" || true

python3 - "${junit_xml}" <<'EOF'
import sys
import xml.etree.ElementTree as ET

path = sys.argv[1]
try:
root = ET.parse(path).getroot()
except (OSError, ET.ParseError) as error:
print(f"Unable to read doctest JUnit report {path}: {error}")
sys.exit(1)

suites = [root] if root.tag == "testsuite" else list(root.iter("testsuite"))
tests = sum(int(suite.get("tests", 0)) for suite in suites)
failures = sum(int(suite.get("failures", 0)) for suite in suites)
errors = sum(int(suite.get("errors", 0)) for suite in suites)

print(f"doctest JUnit report: {tests} reported test(s), "
f"{failures} assertion failure(s), "
f"{errors} environment error(s) tolerated")

if tests == 0:
print("No tests recorded; refusing to pass an empty report.")
sys.exit(1)

sys.exit(1 if failures else 0)
EOF
34 changes: 8 additions & 26 deletions ci/preflight.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,32 +36,14 @@ export DBUS_SESSION_BUS_ADDRESS
ip link add eth0 type dummy 2>/dev/null && ip addr add 10.99.0.1/24 dev eth0 && ip link set eth0 up || true
ip link add eth1 type dummy 2>/dev/null && ip addr add 10.99.1.1/24 dev eth1 && ip link set eth1 up || true

# Run all ctest targets. No test exclusions — the assertion-count
# approach below handles tests that throw environment exceptions
# Run all ctest targets for console diagnostics. No test exclusions — the
# structured gate below handles tests that throw environment exceptions
# (e.g. presenter_tui needing real NICs, cli_dump_answerfile needing
# multiple interfaces) without needing per-test skip lists.
#
# ctest exit code is non-zero when test cases throw exceptions even
# when all doctest assertions pass, so we parse the summary instead.
# multiple interfaces) without needing per-test skip lists, so the ctest
# exit code is informational only.
ctest --test-dir build-preflight --output-on-failure 2>&1 | tee /tmp/ctest-output.txt || true

# Extract the assertion failure count from doctest output.
# Format: "[doctest] assertions: 1202 | 1202 passed | 0 failed |"
assertion_line=$(grep -F "assertions:" /tmp/ctest-output.txt | tail -1)
if [ -z "${assertion_line}" ]; then
echo "No doctest assertion summary found in ctest output."
exit 1
fi

failed_assertions=$(echo "${assertion_line}" | sed -n 's/.*| *\([0-9]*\) failed.*/\1/p')
if [ -z "${failed_assertions}" ]; then
echo "Could not parse assertion failure count from: ${assertion_line}"
exit 1
fi

if [ "${failed_assertions}" -ne 0 ]; then
echo "doctest reported ${failed_assertions} assertion failure(s)."
exit 1
fi

echo "All ${assertion_line}"
# Gate on doctest's machine-readable JUnit report instead of scraping the
# console summary (issue #60).
./ci/check-doctest-junit.sh \
build-preflight/test/OpenCATTUS-tests /tmp/doctest-preflight-junit.xml
5 changes: 4 additions & 1 deletion conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ def requirements(self):
self.requires("boost/[>=1.83.0 <1.84.0]")
self.requires("magic_enum/[>=0.9.0 <0.10.0]")
self.requires("gsl-lite/[>=0.41.0 <0.42.0]")
self.requires("doctest/[>=2.4.0 <2.5.0]")
# Floor 2.4.11: older JUnit reporters omit FAIL() from <failure>
# counts (doctest/doctest#755), which would false-green the CI gate
# in ci/check-doctest-junit.sh.
self.requires("doctest/[>=2.4.11 <2.5.0]")
self.requires("sdbus-cpp/[>=2.0.0 <2.1.0]")

# We're using host's glibmm
Expand Down