mirror of https://github.com/wolfSSL/wolfssh.git
CI: add code coverage workflow, misc script updates
parent
17461f2271
commit
581053bcf6
|
|
@ -0,0 +1,180 @@
|
|||
name: Code Coverage
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ 'master', 'main', 'release/**' ]
|
||||
pull_request:
|
||||
branches: [ '*' ]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build_wolfssl:
|
||||
name: Build wolfSSL
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout wolfSSL
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: wolfssl/wolfssl
|
||||
path: wolfssl
|
||||
|
||||
# Match the sshd-test workflow so the cert and ML-DSA paths are built
|
||||
# and measured rather than compiled out.
|
||||
- name: Build wolfSSL
|
||||
working-directory: ./wolfssl
|
||||
run: |
|
||||
./autogen.sh
|
||||
./configure --enable-all --enable-mldsa
|
||||
make -j$(nproc)
|
||||
sudo make install
|
||||
sudo ldconfig
|
||||
|
||||
- name: tar build-dir
|
||||
run: tar -zcf wolfssl-install.tgz /usr/local/lib/libwolfssl* /usr/local/include/wolfssl
|
||||
|
||||
- name: Upload built lib
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: wolfssl-coverage
|
||||
path: wolfssl-install.tgz
|
||||
retention-days: 5
|
||||
|
||||
# Use clang to report line, branch, function and MC/DC coverage in one run.
|
||||
coverage:
|
||||
name: Coverage
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
needs: build_wolfssl
|
||||
steps:
|
||||
- name: Checkout wolfSSH
|
||||
uses: actions/checkout@v6
|
||||
|
||||
# clang 18 is the min: -fcoverage-mcdc does not exist before it.
|
||||
- name: Install clang and LLVM coverage tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y clang-18 llvm-18 libclang-rt-18-dev
|
||||
|
||||
- name: Download wolfSSL
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: wolfssl-coverage
|
||||
|
||||
- name: Install wolfSSL
|
||||
run: |
|
||||
sudo tar -xzf wolfssl-install.tgz -C /
|
||||
sudo ldconfig
|
||||
|
||||
# -O0 keeps line and branch attribution honest; atomic counters are
|
||||
# required because several tests drive client and server on separate
|
||||
# threads, and the default non-atomic updates lose increments.
|
||||
- name: Build wolfSSH
|
||||
run: |
|
||||
./autogen.sh
|
||||
./configure --enable-all --enable-ossh-certs CC=clang-18 \
|
||||
CPPFLAGS="-DMAX_PATH_SZ=120" \
|
||||
CFLAGS="-fprofile-instr-generate -fcoverage-mapping -fcoverage-mcdc -fprofile-update=atomic -O0 -g" \
|
||||
LDFLAGS="-fprofile-instr-generate"
|
||||
make -j$(nproc)
|
||||
|
||||
# %p in the pattern keeps forked servers from overwriting the raw
|
||||
# profile of the client that spawned them.
|
||||
- name: Run tests
|
||||
run: |
|
||||
mkdir -p prof
|
||||
LLVM_PROFILE_FILE="$PWD/prof/%p-%m.profraw" \
|
||||
timeout -k 30 1200 make check
|
||||
|
||||
# 'make check' does not execute wolfsshd, so run it separately
|
||||
- name: Run wolfSSHd tests
|
||||
working-directory: ./apps/wolfsshd/test
|
||||
run: |
|
||||
prof="$GITHUB_WORKSPACE/prof/%p-%m.profraw"
|
||||
sudo LLVM_PROFILE_FILE="$prof" SSHD_ENV="LLVM_PROFILE_FILE=$prof" \
|
||||
./run_all_sshd_tests.sh
|
||||
sudo chown -R "$(id -u):$(id -g)" "$GITHUB_WORKSPACE/prof"
|
||||
|
||||
- name: Report coverage
|
||||
run: |
|
||||
llvm-profdata-18 merge -sparse prof/*.profraw -o wolfssh.profdata
|
||||
# llvm-cov takes one binary positionally and the rest via -object.
|
||||
# Programs linking the shared library are libtool wrapper scripts, so
|
||||
# take the real binary from .libs when one is there. The apps are
|
||||
# optional, so skip whatever this configuration did not build.
|
||||
first=""
|
||||
args=()
|
||||
for t in tests/*.test apps/wolfssh/wolfssh apps/wolfsshd/wolfsshd \
|
||||
apps/wolfsshd/test/test_configuration; do
|
||||
[ -e "$t" ] || continue
|
||||
real="$(dirname "$t")/.libs/$(basename "$t")"
|
||||
[ -x "$real" ] || real="$t"
|
||||
if [ -z "$first" ]; then
|
||||
first="$real"
|
||||
else
|
||||
args+=(-object "$real")
|
||||
fi
|
||||
done
|
||||
if [ -z "$first" ]; then
|
||||
echo "no instrumented binaries found"
|
||||
exit 1
|
||||
fi
|
||||
ignore='(tests|examples)/.*|apps/wolfsshd/test/.*'
|
||||
ignore="$ignore"'|.*/include/wolfssl/.*|.*/wolfssh/.*\.h'
|
||||
llvm-cov-18 report "$first" "${args[@]}" \
|
||||
-instr-profile=wolfssh.profdata \
|
||||
--show-mcdc-summary \
|
||||
--ignore-filename-regex="$ignore" | tee coverage-report.txt
|
||||
llvm-cov-18 show "$first" "${args[@]}" \
|
||||
-instr-profile=wolfssh.profdata \
|
||||
--show-mcdc --format=html --output-dir=coverage-html \
|
||||
--ignore-filename-regex="$ignore"
|
||||
# lcov text for any external dashboard that consumes it.
|
||||
llvm-cov-18 export "$first" "${args[@]}" \
|
||||
-instr-profile=wolfssh.profdata \
|
||||
--format=lcov \
|
||||
--ignore-filename-regex="$ignore" > coverage.lcov
|
||||
{
|
||||
echo '### Coverage'
|
||||
echo '```'
|
||||
cat coverage-report.txt
|
||||
echo '```'
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Upload coverage report
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: coverage-report
|
||||
path: |
|
||||
coverage-report.txt
|
||||
coverage.lcov
|
||||
coverage-html/
|
||||
retention-days: 30
|
||||
|
||||
- name: Show test logs on failure
|
||||
if: failure()
|
||||
run: |
|
||||
echo "=== test-suite.log ==="
|
||||
cat test-suite.log || true
|
||||
for f in tests/*.log scripts/*.log; do
|
||||
[ -f "$f" ] || continue
|
||||
echo ""
|
||||
echo "=== $f ==="
|
||||
cat "$f"
|
||||
done
|
||||
|
||||
- name: Upload failure logs
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: wolfssh-coverage-logs
|
||||
path: |
|
||||
test-suite.log
|
||||
tests/*.log
|
||||
scripts/*.log
|
||||
config.log
|
||||
retention-days: 5
|
||||
|
|
@ -131,6 +131,12 @@ int main(void)
|
|||
printf("FPKI\n");
|
||||
#endif
|
||||
|
||||
/* certman.c's FPKI certificate profile enforcement. Separate from FPKI
|
||||
* above. */
|
||||
#if defined(WOLFSSH_CERTS) && !defined(WOLFSSH_NO_FPKI)
|
||||
printf("FPKI_PROFILE\n");
|
||||
#endif
|
||||
|
||||
/* PQC Options */
|
||||
#ifndef WOLFSSH_NO_MLDSA
|
||||
printf("MLDSA\n");
|
||||
|
|
|
|||
|
|
@ -5,6 +5,15 @@
|
|||
# Not named PWD: the shell rewrites that variable on every cd, so a saved
|
||||
# copy would not survive the cd to the repository root below.
|
||||
TESTDIR=`pwd`
|
||||
. ./wolfssh_options.sh
|
||||
|
||||
# No FPKI profiles exist in keys/, so skip this test which would fail.
|
||||
# Drop this skip once conforming certificates are added.
|
||||
if wolfssh_has FPKI_PROFILE; then
|
||||
echo "wolfSSHd enforces FPKI profiles; test certs meet none, skipping"
|
||||
exit 77
|
||||
fi
|
||||
|
||||
cd ../../..
|
||||
|
||||
if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then
|
||||
|
|
|
|||
|
|
@ -17,6 +17,13 @@ if ! wolfssh_has FPKI; then
|
|||
exit 77
|
||||
fi
|
||||
|
||||
# No FPKI profiles exist in keys/, so skip this test which would fail.
|
||||
# Drop this skip once conforming certificates are added.
|
||||
if wolfssh_has FPKI_PROFILE; then
|
||||
echo "wolfSSHd enforces FPKI profiles; UPN check not reached, skipping"
|
||||
exit 77
|
||||
fi
|
||||
|
||||
# Count existing rejection lines first so a stale match left in the appended
|
||||
# log (start_sshd.sh uses 'wolfsshd -E ./log.txt', which never truncates) is
|
||||
# not mistaken for this run's rejection.
|
||||
|
|
|
|||
|
|
@ -93,10 +93,24 @@ EOF
|
|||
sudo env $SSHD_ENV "$SSHD_BIN" -d -E ./log.txt -f "$CONFIG"
|
||||
|
||||
# The PID of the started sshd is the one present now that was not there
|
||||
# before. The daemon can still die after sudo returns, so guard the same
|
||||
# way and let the caller's empty-PID check report it.
|
||||
NEW_PIDS=`pgrep -x wolfsshd | sort -n` || true
|
||||
PID=`diff <(echo "$CURRENT_PIDS") <(echo "$NEW_PIDS") | sed -n 's/^> *//p' | head -n1`
|
||||
# before. wolfSSHd forks twice while daemonizing, so for a moment its two
|
||||
# short lived parents are listed as well; wait for the new pids to settle
|
||||
# on the single survivor. Recording a parent instead would leave
|
||||
# stop_wolfsshd killing a pid that is already gone while the real daemon
|
||||
# keeps the port. The daemon can also die after sudo returns, so leave PID
|
||||
# empty in that case and let the caller's empty-PID check report it.
|
||||
PID=""
|
||||
for i in $(seq 1 50); do
|
||||
NEW_PIDS=`pgrep -x wolfsshd | sort -n` || true
|
||||
NEW=`diff <(echo "$CURRENT_PIDS") <(echo "$NEW_PIDS") \
|
||||
| sed -n 's/^> *//p'`
|
||||
NEW_COUNT=`printf '%s\n' $NEW | grep -c .` || NEW_COUNT=0
|
||||
if [ "$NEW_COUNT" -eq 1 ]; then
|
||||
PID="$NEW"
|
||||
break
|
||||
fi
|
||||
sleep 0.1
|
||||
done
|
||||
printf "SSHD running on PID $PID\n"
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue