diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 0000000..204c501 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,86 @@ +on: + push: + branches: + - main + pull_request: + +name: Go test coverage + +jobs: + coverage: + name: Measure test coverage (unit + integration tests) + runs-on: ubuntu-latest + steps: + - name: Install golang + uses: actions/setup-go@v5 + with: + go-version: "1.25" + + - name: Check out repository code + uses: actions/checkout@v4 + + - name: Run tests with coverage + run: make test-coverage + + - name: Download base coverage breakdown + id: download-main-breakdown + uses: dawidd6/action-download-artifact@v6 + with: + branch: main + workflow_conclusion: success + name: main.breakdown + if_no_artifact_found: warn + + # continue-on-error: allow workflow to proceed to post PR comment and upload artifacts + - name: Check coverage thresholds + id: coverage + uses: vladopajic/go-test-coverage@v2 + continue-on-error: true + with: + config: .testcoverage.yml + breakdown-file-name: ${{ github.ref_name == 'main' && 'main.breakdown' || '' }} + diff-base-breakdown-file-name: ${{ steps.download-main-breakdown.outputs.found_artifact == 'true' && 'main.breakdown' || '' }} + + - name: Find pull request ID + run: | + PR_DATA=$(curl -s -H "Authorization: token ${{ github.token }}" \ + "https://api.github.com/repos/${{ github.repository }}/pulls?head=${{ github.repository_owner }}:${{ github.head_ref || github.ref_name }}&state=open") + PR_ID=$(echo "$PR_DATA" | jq -r '.[0].number') + if [ "$PR_ID" != "null" ]; then + echo "pull_request_id=$PR_ID" >> "$GITHUB_ENV" + else + echo "No open pull request found for branch ${{ github.head_ref || github.ref_name }}." + fi + + - name: Post coverage report to PR + if: env.pull_request_id + uses: thollander/actions-comment-pull-request@v3 + with: + github-token: ${{ github.token }} + comment-tag: coverage-report + pr-number: ${{ env.pull_request_id }} + message: | + `go-test-coverage` report + ``` + ${{ fromJSON(steps.coverage.outputs.report) }}``` + + - name: Upload coverage breakdown + uses: actions/upload-artifact@v4 + if: github.ref_name == 'main' + with: + name: main.breakdown + path: main.breakdown + if-no-files-found: error + + - name: Upload coverage report + if: always() + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: test/coverage/coverage.html + + # fail the workflow if coverage thresholds are not met, after PR comment and artifacts are posted + - name: Fail if coverage thresholds not met + if: steps.coverage.outcome == 'failure' + shell: bash + run: echo "Coverage thresholds are not met." && exit 1 diff --git a/.gitignore b/.gitignore index b6b93ec..f397d14 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ go.work.sum go-fdo-client go-fdo-client-*.tar.* rpmbuild +test/coverage diff --git a/.testcoverage.yml b/.testcoverage.yml new file mode 100644 index 0000000..141e8f0 --- /dev/null +++ b/.testcoverage.yml @@ -0,0 +1,5 @@ +profile: test/coverage/coverage.out +threshold: + file: 0 + package: 0 + total: 0 diff --git a/Makefile b/Makefile index 945b1ba..c9fe3ae 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,9 @@ SPEC_FILE_NAME := $(PROJECT).spec SPEC_FILE := $(SOURCEDIR)/$(SPEC_FILE_NAME) COMMIT_SHORT := $(shell git rev-parse --short HEAD) VERSION := $(shell grep 'Version:' $(SPEC_FILE) | awk '{printf "%s", $$2}').git$(COMMIT_SHORT) +GOFLAGS ?= +COVERDIR ?= $(CURDIR)/test/coverage +GOCOVERDIR ?= $(COVERDIR)/integration GO_VENDOR_TOOLS_FILE_NAME := go-vendor-tools.toml GO_VENDOR_TOOLS_FILE := $(SOURCEDIR)/$(GO_VENDOR_TOOLS_FILE_NAME) @@ -20,7 +23,7 @@ all: build test .PHONY: build build: tidy fmt vet - go build -ldflags="-X github.com/fido-device-onboard/go-fdo-client/internal/version.VERSION=$(VERSION)" + go build $(GOFLAGS) -ldflags="-X github.com/fido-device-onboard/go-fdo-client/internal/version.VERSION=$(VERSION)" .PHONY: tidy tidy: @@ -38,6 +41,25 @@ vet: test: go test -v ./... +.PHONY: test-coverage +test-coverage: SHELL := /usr/bin/env bash +test-coverage: + rm -rf "$(COVERDIR)" + mkdir -p "$(GOCOVERDIR)" + go test -coverpkg=./... -coverprofile="$(COVERDIR)/unit.out" -covermode=atomic ./... + export GOCOVERDIR="$(GOCOVERDIR)"; \ + set -e; \ + $(MAKE) build GOFLAGS="-cover -covermode=atomic" && sudo install -D -m 755 $(PROJECT) /usr/bin/; \ + source .github/scripts/fdo-utils.sh; \ + generate_certs; \ + docker compose -f .github/compose/servers.yaml up -d --build; \ + trap 'docker compose -f .github/compose/servers.yaml logs; docker compose -f .github/compose/servers.yaml down' EXIT; \ + test_onboarding; \ + go tool covdata textfmt -i="$(GOCOVERDIR)" -o="$(COVERDIR)/integration.out" + go install github.com/wadey/gocovmerge@latest + gocovmerge "$(COVERDIR)/unit.out" "$(COVERDIR)/integration.out" > "$(COVERDIR)/coverage.out" + go tool cover -html="$(COVERDIR)/coverage.out" -o "$(COVERDIR)/coverage.html" + .PHONY: vendor-tarball vendor-tarball: $(VENDOR_TARBALL) diff --git a/README.md b/README.md index 747cad8..e19ae5a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # FIDO Device Onboard - Go Client + `go-fdo-client` is a client implementation of FIDO Device Onboard specification in Go using [FDO GO protocols.](https://github.com/fido-device-onboard/go-fdo)