From ac4cbc63997020be9d752fe40f6bf43f2dd58d4e Mon Sep 17 00:00:00 2001 From: Pavel Sturc Date: Wed, 12 Nov 2025 13:46:00 +0100 Subject: [PATCH 1/4] feat: improve acceptance tests * use native Go instrumentation * report test results per flag (one coverage output file per flag --- .github/workflows/checks-codecov.yaml | 9 ++++++++- Makefile | 9 +++++---- acceptance/cli/cli.go | 1 + 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/workflows/checks-codecov.yaml b/.github/workflows/checks-codecov.yaml index 7b09e0130..1a6fc8253 100644 --- a/.github/workflows/checks-codecov.yaml +++ b/.github/workflows/checks-codecov.yaml @@ -91,6 +91,7 @@ jobs: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: files: ./coverage-unit.out + disable_search: true flags: unit - name: Upload generative test coverage report @@ -99,6 +100,7 @@ jobs: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: files: ./coverage-generative.out + disable_search: true flags: generative - name: Upload integration test coverage report @@ -107,6 +109,7 @@ jobs: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: files: ./coverage-integration.out + disable_search: true flags: integration Acceptance: @@ -143,12 +146,16 @@ jobs: run: git log --oneline -n1 - name: Acceptance test - run: make acceptance + id: acceptance_test + run: E2E_INSTRUMENTATION=true make acceptance - name: Upload coverage report uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: files: ./coverage-acceptance.out + disable_search: true flags: acceptance Tools: diff --git a/Makefile b/Makefile index e987b5b3c..8ae640a37 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,7 @@ SHELL=$(if $@,$(info ❱ $@))$(_SHELL) ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) COPY:=The Conforma Contributors COSIGN_VERSION=$(shell go list -f '{{.Version}}' -m github.com/sigstore/cosign/v2) +E2E_INSTRUMENTATION_FLAGS := $(if $(filter $(E2E_INSTRUMENTATION),true),-cover -covermode atomic) ##@ Information @@ -54,7 +55,7 @@ BUILD_BIN_SUFFIX=$(if $(DEBUG_BUILD),_debug,) $(ALL_SUPPORTED_OS_ARCH): generate ## Build binaries for specific platform/architecture, e.g. make dist/ec_linux_amd64 @GOOS=$(word 2,$(subst _, ,$(notdir $@))); \ GOARCH=$(word 3,$(subst _, ,$(notdir $@))); \ - GOOS=$${GOOS} GOARCH=$${GOARCH} CGO_ENABLED=0 go build $(BUILD_TRIMPATH) $(BUILD_GC_FLAGS) -ldflags="$(BUILD_LD_FLAGS) -X github.com/conforma/cli/internal/version.Version=$(VERSION)" -o dist/ec_$${GOOS}_$${GOARCH}$(BUILD_BIN_SUFFIX); \ + GOOS=$${GOOS} GOARCH=$${GOARCH} CGO_ENABLED=0 go build $(E2E_INSTRUMENTATION_FLAGS) $(BUILD_TRIMPATH) $(BUILD_GC_FLAGS) -ldflags="$(BUILD_LD_FLAGS) -X github.com/conforma/cli/internal/version.Version=$(VERSION)" -o dist/ec_$${GOOS}_$${GOARCH}$(BUILD_BIN_SUFFIX); \ sha256sum -b dist/ec_$${GOOS}_$${GOARCH}$(BUILD_BIN_SUFFIX) > dist/ec_$${GOOS}_$${GOARCH}$(BUILD_BIN_SUFFIX).sha256 .PHONY: dist @@ -120,15 +121,15 @@ acceptance: ## Run all acceptance tests cleanup() { \ cp "$${ACCEPTANCE_WORKDIR}"/features/__snapshots__/* "$(ROOT_DIR)"/features/__snapshots__/; \ }; \ + mkdir -p "$${ACCEPTANCE_WORKDIR}/coverage"; \ trap cleanup EXIT; \ cp -R . "$$ACCEPTANCE_WORKDIR"; \ cd "$$ACCEPTANCE_WORKDIR" && \ - go run acceptance/coverage/coverage.go && \ $(MAKE) build && \ export COVERAGE_FILEPATH="$$ACCEPTANCE_WORKDIR"; \ export COVERAGE_FILENAME="-acceptance"; \ - cd acceptance && go test -coverprofile "$$ACCEPTANCE_WORKDIR/coverage-acceptance.out" -timeout $(ACCEPTANCE_TIMEOUT) ./... && \ - go run -modfile "$$ACCEPTANCE_WORKDIR/tools/go.mod" github.com/wadey/gocovmerge "$$ACCEPTANCE_WORKDIR/coverage-acceptance.out" > "$(ROOT_DIR)/coverage-acceptance.out" + export GOCOVERDIR="$${ACCEPTANCE_WORKDIR}/coverage"; \ + cd acceptance && go test ./... ; go tool covdata textfmt -i=$${GOCOVERDIR} -o="$(ROOT_DIR)/coverage-acceptance.out" # Add @focus above the feature you're hacking on to use this # (Mainly for use with the feature-% target below) diff --git a/acceptance/cli/cli.go b/acceptance/cli/cli.go index baf2b93df..44b633e56 100644 --- a/acceptance/cli/cli.go +++ b/acceptance/cli/cli.go @@ -89,6 +89,7 @@ func variables(ctx context.Context) (context.Context, []string, map[string]strin "PATH=" + os.Getenv("PATH"), "COVERAGE_FILEPATH=" + os.Getenv("COVERAGE_FILEPATH"), // where to put the coverage file, $COVERAGE_FILEPATH is provided by the Makefile, if empty it'll be $TMPDIR "COVERAGE_FILENAME=" + os.Getenv("COVERAGE_FILENAME"), // suffix for the coverage file + "GOCOVERDIR=" + os.Getenv("GOCOVERDIR"), // directory where the Go coverage raw data is stored "HOME=/tmp", } From b86bddd22c929fb2f0532c403da45b9045edb3a0 Mon Sep 17 00:00:00 2001 From: Pavel Sturc Date: Wed, 12 Nov 2025 15:03:39 +0100 Subject: [PATCH 2/4] fix: keep original test timeout --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8ae640a37..9f0bcfa0c 100644 --- a/Makefile +++ b/Makefile @@ -129,7 +129,7 @@ acceptance: ## Run all acceptance tests export COVERAGE_FILEPATH="$$ACCEPTANCE_WORKDIR"; \ export COVERAGE_FILENAME="-acceptance"; \ export GOCOVERDIR="$${ACCEPTANCE_WORKDIR}/coverage"; \ - cd acceptance && go test ./... ; go tool covdata textfmt -i=$${GOCOVERDIR} -o="$(ROOT_DIR)/coverage-acceptance.out" + cd acceptance && go test -timeout $(ACCEPTANCE_TIMEOUT) ./... ; go tool covdata textfmt -i=$${GOCOVERDIR} -o="$(ROOT_DIR)/coverage-acceptance.out" # Add @focus above the feature you're hacking on to use this # (Mainly for use with the feature-% target below) From 94b1e83a34fb9285800f31d8d1e38212e7aa0ed4 Mon Sep 17 00:00:00 2001 From: Pavel Sturc Date: Thu, 13 Nov 2025 08:12:45 +0100 Subject: [PATCH 3/4] docs: update readme --- README.md | 7 ++++++- acceptance/README.md | 4 ++-- hack/macos/TROUBLESHOOTING.md | 2 +- hack/macos/setup-podman-machine.sh | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b06b26d4b..79746c43c 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,14 @@ run `make dist` to build for all supported architectures. ## Testing -Run `make test` to run the unit tests, and `make acceptance` to run the +Run `make test` to run the unit tests, and `E2E_INSTRUMENTATION=true make acceptance` to run the acceptance tests. +The purpose of the `E2E_INSTRUMENTATION=true` environment variable is to pass +extra flags to the build command. This instruments the resulting binary, +allowing for the collection of coverage data during the acceptance test run. +(more information here: https://go.dev/doc/build-cover). + ## Linting Run `make lint` to check for linting issues, and `make lint-fix` to fix linting diff --git a/acceptance/README.md b/acceptance/README.md index 4155d1e05..073778143 100644 --- a/acceptance/README.md +++ b/acceptance/README.md @@ -14,7 +14,7 @@ uses the established Go test to launch Godog. To run the acceptance tests either run: - $ make acceptance + $ E2E_INSTRUMENTATION=true make acceptance from the root of the repository. @@ -64,7 +64,7 @@ Docker website how to accomplish that. ## Debugging The acceptance tests execute the `ec` binary during test execution. (For this -reason `make acceptance` builds the binary prior to running the tests.) +reason `E2E_INSTRUMENTATION=true make acceptance` builds the binary prior to running the tests.) To use a debugger, like [delve](https://github.com/go-delve/delve), you must determine what part of the code is being debugged. If it's part of the diff --git a/hack/macos/TROUBLESHOOTING.md b/hack/macos/TROUBLESHOOTING.md index 0a91aae11..a2c794182 100644 --- a/hack/macos/TROUBLESHOOTING.md +++ b/hack/macos/TROUBLESHOOTING.md @@ -102,7 +102,7 @@ podman machine start ```bash # Run all acceptance tests -make acceptance +E2E_INSTRUMENTATION=true make acceptance # Or run tests directly cd acceptance diff --git a/hack/macos/setup-podman-machine.sh b/hack/macos/setup-podman-machine.sh index f451388ba..171f36d4d 100755 --- a/hack/macos/setup-podman-machine.sh +++ b/hack/macos/setup-podman-machine.sh @@ -305,7 +305,7 @@ main() { echo echo "To run acceptance tests:" echo " 1. Source the environment: source setup-test-env.sh" - echo " 2. Run tests: make acceptance" + echo " 2. Run tests: E2E_INSTRUMENTATION=true make acceptance" echo " or: cd acceptance && go test -v ./..." echo echo "Environment variables are saved in: setup-test-env.sh" From 783e7d253a293b2318c29bc652e377f91c52ea62 Mon Sep 17 00:00:00 2001 From: Pavel Sturc Date: Thu, 13 Nov 2025 12:59:20 +0100 Subject: [PATCH 4/4] fix: address PR comment * remove deprecated acceptance/coverage/coverage.go and related env vars --- Makefile | 2 - acceptance/cli/cli.go | 4 +- acceptance/coverage/coverage.go | 453 -------------------------------- 3 files changed, 1 insertion(+), 458 deletions(-) delete mode 100644 acceptance/coverage/coverage.go diff --git a/Makefile b/Makefile index 9f0bcfa0c..4b3ee2362 100644 --- a/Makefile +++ b/Makefile @@ -126,8 +126,6 @@ acceptance: ## Run all acceptance tests cp -R . "$$ACCEPTANCE_WORKDIR"; \ cd "$$ACCEPTANCE_WORKDIR" && \ $(MAKE) build && \ - export COVERAGE_FILEPATH="$$ACCEPTANCE_WORKDIR"; \ - export COVERAGE_FILENAME="-acceptance"; \ export GOCOVERDIR="$${ACCEPTANCE_WORKDIR}/coverage"; \ cd acceptance && go test -timeout $(ACCEPTANCE_TIMEOUT) ./... ; go tool covdata textfmt -i=$${GOCOVERDIR} -o="$(ROOT_DIR)/coverage-acceptance.out" diff --git a/acceptance/cli/cli.go b/acceptance/cli/cli.go index 44b633e56..129e207ac 100644 --- a/acceptance/cli/cli.go +++ b/acceptance/cli/cli.go @@ -87,9 +87,7 @@ func variables(ctx context.Context) (context.Context, []string, map[string]strin // fail if it can't locate the git command environment := []string{ "PATH=" + os.Getenv("PATH"), - "COVERAGE_FILEPATH=" + os.Getenv("COVERAGE_FILEPATH"), // where to put the coverage file, $COVERAGE_FILEPATH is provided by the Makefile, if empty it'll be $TMPDIR - "COVERAGE_FILENAME=" + os.Getenv("COVERAGE_FILENAME"), // suffix for the coverage file - "GOCOVERDIR=" + os.Getenv("GOCOVERDIR"), // directory where the Go coverage raw data is stored + "GOCOVERDIR=" + os.Getenv("GOCOVERDIR"), // directory where the Go coverage raw data is stored "HOME=/tmp", } diff --git a/acceptance/coverage/coverage.go b/acceptance/coverage/coverage.go deleted file mode 100644 index b4fb8b3d3..000000000 --- a/acceptance/coverage/coverage.go +++ /dev/null @@ -1,453 +0,0 @@ -// Copyright 2020 Northern.tech AS -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Environment variables: -// -// - COVERAGE_FILENAME: The suffix given to the coverage file created -// - COVERAGE_FILEPATH: The directory in which to put the coverage file - -package main - -import ( - "bytes" - "encoding/json" - "fmt" - "go/ast" - "go/parser" - "go/printer" - "go/token" - "io" - "os" - "os/exec" - "strconv" - "strings" - "text/template" -) - -// The structure generated by go tool cover -// var GoCover = struct { -// Count [117]uint32 -// Pos [3 * 117]uint32 -// NumStmt [117]uint16 -// } - -// coverInfo holds a map to the names of the cover variables -type coverInfo struct { - Package string - Vars map[string]*CoverVar -} - -// CoverVar is a simple set collecting the GoCover variable name along with its -// source file -type CoverVar struct { - File string - Var string -} - -// ReplaceFilecontents replaces the dst file contents with the contents of src. -func replaceFileContents(src, dst string) error { - in, err := os.Open(src) - if err != nil { - return err - } - defer in.Close() - - out, err := os.Create(dst) - if err != nil { - return err - } - defer out.Close() - - _, err = io.Copy(out, in) - if err != nil { - return err - } - return out.Close() -} - -// Package is for use with `go list -json` -type Package struct { - Dir string // Directory containing the source files - GoFiles []string - ImportPath string - - Imports []string // imports used by this package - ImportMap map[string]string // map from source import to ImportPath (identity entries are omitted) - - Deps []string -} - -func listPackagesImported(packageName string) (packages []string, imports []string, importsMap map[string]string, dir string, err error) { - cmd := exec.Command( - "go", "list", - "-json", - packageName, - ) - buf := bytes.NewBuffer(nil) - cmd.Stdout = buf - if err = cmd.Run(); err != nil { - fmt.Fprintf(os.Stderr, "`go list -json %s failed. Error: %s\n", packageName, err.Error()) - return nil, nil, nil, "", err - } - // The go list command returns a json byte array parse this into the - // appropriate structure, from which we can extract all the Go files present - // in the package - p := &Package{} - if err = json.Unmarshal(buf.Bytes(), p); err != nil { - fmt.Fprintf(os.Stderr, "`go list -json %s failed. Error: %s\n", packageName, err.Error()) - return nil, nil, nil, "", err - } - // Filter all the non-local dependencies, and vendored packages - // i.e., remove all local libraries, and vendored packages - var coverPackages []string - for _, pName := range p.Deps { - if strings.Contains(pName, p.ImportPath) && !strings.Contains(pName, "/vendor/") { - coverPackages = append(coverPackages, pName) - } - } - return coverPackages, p.Imports, p.ImportMap, p.Dir, nil -} - -// getFilesInPackage employs `go list 'packageName'` to extract all the files in -// the given package -func getFilesInPackage(packageName string) (p *Package, err error) { - cmd := exec.Command( - "go", "list", - "-json", - packageName, - ) - buf := bytes.NewBuffer(nil) - cmd.Stdout = buf - if err = cmd.Run(); err != nil { - fmt.Fprintf(os.Stderr, "`go list -json %s failed. Error: %s\n", packageName, err.Error()) - return nil, err - } - // The go list command returns a json byte array parse this into the - // appropriate structure, from which we can extract all the Go files present - // in the package - p = &Package{} - if err = json.Unmarshal(buf.Bytes(), p); err != nil { - fmt.Fprintf(os.Stderr, "`go list -json %s failed. Error: %s\n", packageName, err.Error()) - return nil, err - } - return p, nil -} - -// instrumentFileInPackage runs `go tool cover` on all the go source files in -// the named package -func instrumentFilesInPackage(packageName string) (cInfo *coverInfo, err error) { - tdir, err := os.MkdirTemp("", "instrumentFiles") - if err != nil { - return nil, err - } - defer os.Remove(tdir) - - // Store the package name along with the GoCover variable names - cInfo = &coverInfo{Package: packageName, Vars: make(map[string]*CoverVar)} - - p, err := getFilesInPackage(packageName) - if err != nil { - return nil, err - } - - // covstructName is a function which generates the name of the coverage - // struct, with an integer suffix in order to differentiate amongst them - // globally. - counter := 1 - covStructName := func(fileName string) string { - s := "GoCover" + strconv.Itoa(counter) - counter += 1 - // Add the name of the variable to the coverInfo struct - cInfo.Vars[fileName] = &CoverVar{File: fileName, Var: s} - return s - } - - for _, name := range p.GoFiles { - tname := tdir + name - fname := p.Dir + "/" + name // name with the full path prefixed - rname := p.ImportPath + "/" + name // name with the relative import path for coverage output - // 1) Generate the instrumented source code using the `go tool cover` - // functionality. The instrumented file is created in the temporary dir, - // tdir. - /* #nosec */ - cmd := exec.Command( - "go", "tool", "cover", - "-mode=set", - "-var", covStructName(rname), - "-o", tname, - fname) - buf := bytes.NewBuffer(nil) - cmd.Stderr = buf - if err = cmd.Run(); err != nil { - fmt.Fprintf(os.Stderr, "go tool cover %s, failed. Error: %s\nOutput: %s\n", - fname, err.Error(), buf.String()) - return nil, err - } - // 2) Replace the original source code file, with the instrumented one - // generated above. - if err = replaceFileContents(tname, fname); err != nil { - return nil, err - } - } - return cInfo, nil -} - -func parseMainGoFile(fset *token.FileSet, filePath string) (*ast.File, error) { - // fset := token.NewFileSet() // positions are relative to fset - // Parse src but stop after processing the imports. - f, err := parser.ParseFile(fset, filePath, nil, 0) // Parse all the things - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to parse the file: %s. Error: %s\n", filePath, err.Error()) - return nil, err - } - return f, nil -} - -// mergeASTTrees takes two AST trees, and merges them (if possible) into a -// single unified ast, and returns it. The merging is naive, and does no fancy -// heurestics for resolving conflicts. Conflicts will have to be solved by a -// human. -func mergeASTTrees(fset *token.FileSet, t1 *ast.File, t2 *ast.File) (*bytes.Buffer, error) { - // Merge the imports from both files - ast.Inspect(t1, func(n ast.Node) bool { - switch x := n.(type) { - case *ast.GenDecl: - if x.Tok == token.IMPORT { - // Walk the second tree until we find the import statements - ast.Inspect(t2, func(n ast.Node) bool { - switch y := n.(type) { - case *ast.GenDecl: - if y.Tok == token.IMPORT { - // Add all the children to the t1 tree's import statement - x.Specs = append(x.Specs, y.Specs...) - return false // Stop the iteration - } - } - return true - }) - return false - } - } - return true - }) - - // Merge the declarations from t2 into t1 - for _, decl := range t2.Decls { - if d, isDecl := decl.(*ast.GenDecl); isDecl { - if d.Tok == token.IMPORT { - continue - } - } - t1.Decls = append(t1.Decls, decl) - } - - // modify main to invoke coverReport function - ast.Inspect(t1, func(n ast.Node) bool { - switch n := n.(type) { - case *ast.FuncDecl: - if n.Name.Name == "main" { - n.Body.List = append([]ast.Stmt{&ast.DeferStmt{ - Call: &ast.CallExpr{ - Fun: &ast.Ident{ - Name: "coverReport", - }, - Args: []ast.Expr{}, - }, - }}, n.Body.List...) - } - } - - return true - }) - - // Print the modified AST to buf. - var buf bytes.Buffer - if err := printer.Fprint(&buf, fset, t1); err != nil { - panic(err) - } - - return &buf, nil -} - -// Cover is passed in to the main.go template, and expands all the needed -// GoCover variables, and imports all the packages we are covering. -type Cover struct { - CoverInfo []*coverInfo - Imports []string // The packages the main file imports (generated by go list on the package provided no the CLI) - ImportMap map[string]string // Resolves coverage paths TODO -- how to use this? -} - -func main() { - // Collect all coverage meta-data in the Cover struct. This is needed for the - // template generation of main later on. - cov := Cover{} - // - // Get all the packages imported by main - // - packageList, imports, importMap, dir, err := listPackagesImported(".") - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to list the packages imported by: %s. Error: %s\n", os.Args[1], err.Error()) - os.Exit(1) - } - cov.Imports = imports - cov.ImportMap = importMap - // - // Parse the main.go file - // - fset := token.NewFileSet() // positions are relative to fset - originalMainAST, err := parseMainGoFile(fset, dir+"/main.go") - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to parse main.go\nError: %s\n", err.Error()) - os.Exit(1) - } - // - // Instrument the source files in the given package with coverage functionality - // - for _, pname := range packageList { - cInfo, err := instrumentFilesInPackage(pname) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to instrument the files in package: %s\nError: %s\n", - os.Args[1], err.Error()) - os.Exit(1) - } - cov.CoverInfo = append(cov.CoverInfo, cInfo) - } - // TODO - Merge the syntax trees of the generated template, and the main.go file parsed - generatedMainAST, err := generateMainFromTemplate(fset, &cov) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to generated instrumented main from template: Error: %s\n", err.Error()) - os.Exit(1) - } - - // - // merge the two AST's - // - buf, err := mergeASTTrees(fset, generatedMainAST, originalMainAST) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to merge the generated main file with the main file of the package: Error: %s\n", err.Error()) - os.Exit(1) - } - // - // Replace the main file with the new merged contents - // - f, err := os.OpenFile(dir+"/main.go", os.O_WRONLY, 0644) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to open the main.go file. Error: %s\n", err.Error()) - os.Exit(1) - } - _, err = io.Copy(f, buf) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to replace the contents of main.go. Error: %s\n", err.Error()) - os.Exit(1) - } - os.Exit(0) -} - -func generateMainFromTemplate(fset *token.FileSet, cover *Cover) (*ast.File, error) { - tmpl, err := template.New("Main").Parse(testmainTmplStr) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to parse the main.go template. Error: %s\n", err.Error()) - return nil, err - } - var buf bytes.Buffer - if err := tmpl.Execute(&buf, cover); err != nil { - fmt.Fprintf(os.Stderr, "Failed to execute the main.go template. Error: %s\n", err.Error()) - return nil, err - } - // Parse the template file generated into an AST - f, err := parser.ParseFile(fset, "", buf.String(), 0) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to parse the generated main file. Error: %s\n", err.Error()) - return nil, err - } - return f, nil -} - -var testmainTmplStr = ` -package main - -import ( - "fmt" - "testing" - "os" - - // Import all the GoCover variables from the packages which are coverage instrumented - {{- range $i, $ci := .CoverInfo}} - _cover{{$i}} {{$ci.Package | printf "%q"}} - {{- end}} -) - -var ( - coverCounters = make(map[string][]uint32) - coverBlocks = make(map[string][]testing.CoverBlock) -) - -func init() { - // Register the addresses of all the GoCover variables from all the packages - // to be covered - {{- range $i, $p := .CoverInfo}} - {{- range $file, $cover := $p.Vars}} - coverRegisterFile({{printf "%q" $cover.File}}, _cover{{$i}}.{{$cover.Var}}.Count[:], _cover{{$i}}.{{$cover.Var}}.Pos[:], _cover{{$i}}.{{$cover.Var}}.NumStmt[:]) - {{- end}} - {{- end}} -} - -func coverRegisterFile(fileName string, counter []uint32, pos []uint32, numStmts []uint16) { - if 3*len(counter) != len(pos) || len(counter) != len(numStmts) { - panic("coverage: mismatched sizes") - } - if coverCounters[fileName] != nil { - // Already registered. - return - } - coverCounters[fileName] = counter - block := make([]testing.CoverBlock, len(counter)) - for i := range counter { - block[i] = testing.CoverBlock{ - Line0: pos[3*i+0], - Col0: uint16(pos[3*i+2]), - Line1: pos[3*i+1], - Col1: uint16(pos[3*i+2]>>16), - Stmts: numStmts[i], - } - } - coverBlocks[fileName] = block -} - -func coverReport() { - reportFile, err := os.CreateTemp(os.Getenv("COVERAGE_FILEPATH"), "coverage" + os.Getenv("COVERAGE_FILENAME") + "*.out") - if err != nil { - return - } - - fmt.Fprintf(reportFile, "mode: count\n") - - var active, total int64 - for name, counts := range coverCounters { - blocks := coverBlocks[name] - for i := range counts { - stmts := int64(blocks[i].Stmts) - total += stmts - if counts[i] > 0 { - active += stmts - } - fmt.Fprintf(reportFile, "%s:%d.%d,%d.%d %d %d\n", name, - blocks[i].Line0, blocks[i].Col0, - blocks[i].Line1, blocks[i].Col1, - stmts, - counts[i]) - } - } -} -`