8989
9090 integration :
9191 runs-on : ubuntu-22.04
92+ # Ensure integration tests can resolve a locally-exported dalec frontend image.
93+ # The test harness (withDalecInput) uses FRONTEND_REF when provided.
94+ env :
95+ FRONTEND_REF : localhost:5000/dalec/frontend:ci
9296 strategy :
9397 fail-fast : false
9498 matrix :
@@ -137,6 +141,22 @@ jobs:
137141 username : ${{ github.actor }}
138142 password : ${{ secrets.GITHUB_TOKEN }}
139143
144+ - name : Start local registry (for FRONTEND_REF)
145+ run : |
146+ set -eux
147+ docker rm -f local-registry || true
148+ docker run -d --restart=always --net=host --name local-registry registry:2
149+
150+ - name : Setup buildx builder (host network)
151+ run : |
152+ set -eux
153+ if ! docker buildx inspect dalec-ci >/dev/null 2>&1; then
154+ docker buildx create --name dalec-ci --use --driver docker-container --driver-opt network=host
155+ else
156+ docker buildx use dalec-ci
157+ fi
158+ docker buildx inspect --bootstrap
159+
140160 - name : Setup otel-collector
141161 run : |
142162 set -e
@@ -269,7 +289,15 @@ jobs:
269289 run : |
270290 set -eu
271291
272- docker buildx bake frontend
292+ # Build instrumented frontend and PUSH it to the local registry so the
293+ # test harness/BuildKit can always resolve it by FRONTEND_REF.
294+ docker buildx bake frontend \
295+ --set frontend.args.DALEC_FRONTEND_COVERAGE=1 \
296+ --set frontend.tags="${FRONTEND_REF}" \
297+ --push
298+
299+ docker buildx imagetools inspect "${FRONTEND_REF}"
300+
273301 if [ "${TEST_SUITE}" = "other" ]; then
274302 exit 0
275303 fi
@@ -280,22 +308,75 @@ jobs:
280308 worker="windowscross"
281309 fi
282310 export WORKER_TARGET=${worker}/worker
283- docker buildx bake worker
311+ docker buildx bake worker \
312+ --set frontend.args.DALEC_FRONTEND_COVERAGE=1
313+
314+ # Defensive: if building the worker caused any frontend rebuild/tagging,
315+ # re-push instrumented frontend so FRONTEND_REF stays correct.
316+ docker buildx bake frontend \
317+ --set frontend.args.DALEC_FRONTEND_COVERAGE=1 \
318+ --set frontend.tags="${FRONTEND_REF}" \
319+ --push
284320 env :
285321 TEST_SUITE : ${{ matrix.suite }}
286- - name : Run integration tests
322+ - name : Run integration tests (with coverage tracking)
287323 run : |
288324 set -ex
289- if [ -n "${TEST_SUITE}" ] && [ ! "${TEST_SUITE}" = "other" ]; then
325+ mkdir -p coverage
326+
327+ # The frontend covdata files (covmeta/covcounters) are written by the test harness
328+ # (writeFrontendCovdata) on the RUNNER filesystem.
329+ export DALEC_FRONTEND_GOCOVERDIR="${GITHUB_WORKSPACE}/coverage/frontend-${TEST_SUITE}"
330+ mkdir -p "${DALEC_FRONTEND_GOCOVERDIR}"
331+ chmod -R a+rwx "${DALEC_FRONTEND_GOCOVERDIR}"
332+
333+ run=""
334+ skip=""
335+ if [ -n "${TEST_SUITE}" ] && [ "${TEST_SUITE}" != "other" ]; then
290336 run="-run=${TEST_SUITE}"
291337 fi
292338 if [ -n "${TEST_SKIP}" ]; then
293339 skip="-skip=${TEST_SKIP}"
294340 fi
295- go test -timeout=59m -v -json ${run} ${skip} ./test | go run ./cmd/test2json2gha --slow 120s --logdir /tmp/testlogs
341+
342+ go test -timeout=59m -v -json \
343+ -covermode=atomic -coverpkg=./... \
344+ -coverprofile="coverage/integration-${TEST_SUITE}.out" \
345+ ${run} ${skip} ./test \
346+ | go run ./cmd/test2json2gha --slow 120s --logdir /tmp/testlogs
347+
348+ # Convert frontend covdata -> legacy coverprofile
349+ if ! ls "${DALEC_FRONTEND_GOCOVERDIR}"/covmeta.* >/dev/null 2>&1; then
350+ echo "::group::frontend coverage debug"
351+ echo "DALEC_FRONTEND_GOCOVERDIR=${DALEC_FRONTEND_GOCOVERDIR}"
352+ echo "Contents:"
353+ ls -la "${DALEC_FRONTEND_GOCOVERDIR}" || true
354+ echo "Searching workspace for covmeta/covcounters..."
355+ find "${GITHUB_WORKSPACE}" \( -name 'covmeta.*' -o -name 'covcounters.*' \) 2>/dev/null | head -n 200 || true
356+ echo "::endgroup::"
357+ echo "::error::No frontend coverage covmeta.* found in ${DALEC_FRONTEND_GOCOVERDIR} (frontend coverage not collected)"
358+ exit 1
359+ fi
360+
361+ go tool covdata textfmt \
362+ -i="${DALEC_FRONTEND_GOCOVERDIR}" \
363+ -o="coverage/frontend-${TEST_SUITE}.out"
296364 env :
297365 TEST_SUITE : ${{ matrix.suite }}
298366 TEST_SKIP : ${{ matrix.skip }}
367+
368+
369+ - name : Upload integration coverage profile
370+ if : always()
371+ uses : actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
372+ with :
373+ name : coverage-integration-${{ matrix.suite }}
374+ path : |
375+ coverage/integration-${{ matrix.suite }}.out
376+ coverage/frontend-${{ matrix.suite }}.out
377+ if-no-files-found : ignore
378+ retention-days : 7
379+
299380 - name : Get traces
300381 if : always()
301382 run : |
@@ -354,8 +435,27 @@ jobs:
354435 cache : false
355436 - name : download deps
356437 run : go mod download
357- - name : Run unit tests
358- run : go test -v --test.short --json ./... | go run ./cmd/test2json2gha
438+ - name : Run unit tests (with coverage tracking)
439+ run : |
440+ set -eux
441+ mkdir -p coverage
442+
443+ pkgs="$(go list ./... | grep -v '/test$' | grep -v '/test/' )"
444+ go test -v --test.short --json \
445+ -covermode=atomic \
446+ -coverprofile="coverage/unit.out" \
447+ ${pkgs} \
448+ | go run ./cmd/test2json2gha
449+ - name : Upload unit coverage profile
450+ if : always()
451+ uses : actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
452+ with :
453+ name : coverage-unit
454+ path : coverage/unit.out
455+ if-no-files-found : ignore
456+ retention-days : 7
457+
458+
359459
360460 e2e :
361461 runs-on : ubuntu-22.04
@@ -443,3 +543,82 @@ jobs:
443543 path : ${{ steps.dump-logs.outputs.DOCKERD_LOG_PATH }}
444544 retention-days : 1
445545
546+ coverage-report :
547+ runs-on : ubuntu-22.04
548+ needs :
549+ - unit
550+ - integration
551+
552+ steps :
553+ - name : Harden Runner
554+ uses : step-security/harden-runner@e3f713f2d8f53843e71c69a996d56f51aa9adfb9 # v2.14.1
555+ with :
556+ egress-policy : audit
557+
558+ - name : Checkout
559+ uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
560+
561+ - name : Setup Go
562+ uses : actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
563+ with :
564+ go-version : " 1.25"
565+ cache : false
566+
567+ - name : Download deps
568+ run : go mod download
569+
570+ - name : Download unit coverage artifact
571+ uses : actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
572+ with :
573+ name : coverage-unit
574+ path : coverage
575+
576+ - name : Download integration coverage artifacts
577+ uses : actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
578+ with :
579+ path : coverage/_integration
580+
581+ - name : Merge coverage + generate report
582+ run : |
583+ set -eux
584+ go install github.com/wadey/gocovmerge@latest
585+
586+ integration_profiles="$(find coverage/_integration -type f -name 'integration-*.out' | sort | tr '\n' ' ')"
587+ frontend_profiles="$(find coverage/_integration -type f -name 'frontend-*.out' | sort | tr '\n' ' ')"
588+ if [ -z "${integration_profiles}" ]; then
589+ echo "::error::No integration coverage profiles found"
590+ exit 1
591+ fi
592+
593+ if [ -z "${frontend_profiles}" ]; then
594+ echo "::error::No frontend coverage profiles found"
595+ exit 1
596+ fi
597+
598+ if [ ! -f coverage/unit.out ]; then
599+ echo "::error::Unit coverage profile not found (coverage/unit.out)"
600+ exit 1
601+ fi
602+
603+ "$(go env GOPATH)/bin/gocovmerge" coverage/unit.out ${integration_profiles} ${frontend_profiles} > coverage/all.out
604+
605+ go tool cover -func=coverage/all.out | tee coverage/summary.txt
606+ go tool cover -html=coverage/all.out -o coverage/index.html
607+
608+ total="$(tail -n 1 coverage/summary.txt | awk '{print $3}')"
609+ {
610+ echo "## Coverage"
611+ echo
612+ echo "- Total: **${total}**"
613+ echo "- Profiles merged: $(echo "${integration_profiles}" | wc -w) integration + $(echo "${frontend_profiles}" | wc -w) frontend"
614+ } >> "${GITHUB_STEP_SUMMARY}"
615+
616+ - name : Upload merged coverage report
617+ uses : actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
618+ with :
619+ name : coverage-report
620+ path : |
621+ coverage/all.out
622+ coverage/summary.txt
623+ coverage/index.html
624+ retention-days : 14
0 commit comments