git » gofer » commit 296774a

test: Update coverage tests to Go 1.20

author Alberto Bertogli
2023-01-28 20:26:58 UTC
committer Alberto Bertogli
2023-02-01 21:14:45 UTC
parent 2d656b812a50e4abe2c7281c1a1720801906c813

test: Update coverage tests to Go 1.20

Go 1.20 finally includes proper support for instrumenting binaries for
coverage. This allows us to drop quite a few hacks and workarounds that
we used for it, and we can now also test exiting cases.

The downside is that coverage tests now require Go 1.20, but it is an
acceptable price to pay for the more accurate results.

Normal integration tests are unchanged.

This patch updates the coverage testing infrastructure to make use of
the new Go 1.20 features.

No new tests are added, those will come in future patches.

.github/workflows/tests.yaml +2 -2
Makefile +5 -5
coverage_test.go +0 -40
gofer.go +4 -2
test/test.sh +1 -1
test/util/cover-report.sh +13 -9
test/util/gocovcat/gocovcat.go +0 -90
test/util/lib.sh +6 -17

diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index 61ae50f..a9086f8 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -17,7 +17,7 @@ jobs:
       - uses: actions/checkout@v3
       - uses: actions/setup-go@v3
         with:
-          go-version: ">=1.19"
+          go-version: ">=1.20"
       - name: install cue
         run: go install cuelang.org/go/cmd/cue@latest
       - name: install goveralls
@@ -31,7 +31,7 @@ jobs:
         run: make cover
         timeout-minutes: 2
       - name: upload coverage
-        run: goveralls -coverprofile=.cover/all.out -repotoken=${{ secrets.COVERALLS_TOKEN }}
+        run: goveralls -coverprofile=.cover/merged.out -repotoken=${{ secrets.COVERALLS_TOKEN }}
 
       # Upload the contents of test/ if there are failures, for
       # troubleshooting. Note we need to tar them first because github has
diff --git a/Makefile b/Makefile
index 023c5a5..0329401 100644
--- a/Makefile
+++ b/Makefile
@@ -22,13 +22,13 @@ test: vet
 
 cover:
 	rm -rf .cover/
-	mkdir .cover/
+	mkdir -p .cover/go .cover/sh .cover/all
 	go test -tags coverage \
 		-covermode=count \
-		-coverprofile=".cover/pkg-tests.out"\
-		-coverpkg=./... ./...
-	COVER_DIR=$$PWD/.cover/ setsid -w ./test/test.sh
-	COVER_DIR=$$PWD/.cover/ setsid -w ./test/util/cover-report.sh
+		-coverpkg=./... ./... \
+		-args -test.gocoverdir=$$PWD/.cover/go/
+	GOCOVERDIR=$$PWD/.cover/sh/ setsid -w ./test/test.sh
+	setsid -w ./test/util/cover-report.sh $$PWD/.cover/
 
 install: gofer
 	install -D -b -p gofer /usr/local/bin
diff --git a/coverage_test.go b/coverage_test.go
deleted file mode 100644
index bc6cce9..0000000
--- a/coverage_test.go
+++ /dev/null
@@ -1,40 +0,0 @@
-// Test file used to build a coverage-enabled binary.
-//
-// Go lacks support for properly building a coverage binary, it can only build
-// coverage test binaries.  As a workaround, we have a test that just runs
-// main. We then build a binary of this test, which we use instead of the
-// normal binary in integration tests.
-//
-// This is hacky and horrible.
-//
-// The test has a build label so it's not accidentally executed during normal
-// "go test" invocations.
-//go:build coveragebin
-// +build coveragebin
-
-package main
-
-import (
-	"os"
-	"os/signal"
-	"syscall"
-	"testing"
-)
-
-func TestRunMain(t *testing.T) {
-	done := make(chan bool)
-
-	signals := make(chan os.Signal, 1)
-	go func() {
-		<-signals
-		done <- true
-	}()
-	signal.Notify(signals, os.Interrupt, os.Kill, syscall.SIGTERM)
-
-	go func() {
-		main()
-		done <- true
-	}()
-
-	<-done
-}
diff --git a/gofer.go b/gofer.go
index 2e9e37a..6a2ccbc 100644
--- a/gofer.go
+++ b/gofer.go
@@ -98,7 +98,7 @@ func signalHandler() {
 	var err error
 
 	signals := make(chan os.Signal, 1)
-	signal.Notify(signals, syscall.SIGHUP)
+	signal.Notify(signals, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGINT)
 
 	for {
 		switch sig := <-signals; sig {
@@ -111,8 +111,10 @@ func signalHandler() {
 			}
 
 			reqlog.ReopenAll()
+		case syscall.SIGTERM, syscall.SIGINT:
+			log.Fatalf("Got signal to exit: %v", sig)
 		default:
-			log.Errorf("Unexpected signal %v", sig)
+			log.Errorf("Unexpected signal: %v", sig)
 		}
 	}
 }
diff --git a/test/test.sh b/test/test.sh
index 23b2848..aa540f1 100755
--- a/test/test.sh
+++ b/test/test.sh
@@ -54,7 +54,7 @@ fi
 
 # Use gofer to print the parsed config. Strip out the coverage summary output
 # (if present), which unfortunately cannot be disabled.
-../gofer $COVER_ARGS -configfile=01-be.yaml -configprint \
+../gofer -configfile=01-be.yaml -configprint \
 	| grep -v -E '^(PASS|coverage: .* of statements in .*)$' \
 	> .be-print-conf
 if ! diff -q testdata/expected-printed-01-be-config .be-print-conf; then
diff --git a/test/util/cover-report.sh b/test/util/cover-report.sh
index ee8067b..95a2973 100755
--- a/test/util/cover-report.sh
+++ b/test/util/cover-report.sh
@@ -8,21 +8,25 @@ init
 # Run from the repo root.
 cd ../
 
+# Cover dir is given as the only arg.
+COVERDIR="${1}"
+
 echo "## Coverage"
 
-# Merge all coverage output into a single file.
-# Ignore protocol buffer-generated files, as they are not relevant.
-go run "${UTILDIR}/gocovcat/gocovcat.go" "${COVER_DIR}"/*.out \
-	| grep -v "blitiri.com.ar/go/gofer/test/util/" \
-	> "${COVER_DIR}/all.out"
+# Merge the reports.
+go tool covdata merge -i "${COVERDIR}/go,${COVERDIR}/sh" -o "${COVERDIR}/all"
+
+# Export to the old format.
+go tool covdata textfmt -i "${COVERDIR}/all" -o "${COVERDIR}/merged.out"
 
 # Generate reports based on the merged output.
-go tool cover -func="$COVER_DIR/all.out" | sort -k 3 -n > "$COVER_DIR/func.txt"
-go tool cover -html="$COVER_DIR/all.out" -o "$COVER_DIR/coverage.html"
+go tool cover -func="${COVERDIR}/merged.out" | sort -k 3 -n \
+	> "${COVERDIR}/func.txt"
+go tool cover -html="${COVERDIR}/merged.out" -o "${COVERDIR}/coverage.html"
 
-TOTAL=$(cat .cover/func.txt | grep "total:" | awk '{print $3}')
+TOTAL=$(cat ${COVERDIR}/func.txt | grep "total:" | awk '{print $3}')
 echo
 echo "Total:" $TOTAL
 echo
 echo "Coverage report can be found in:"
-echo file://$COVER_DIR/coverage.html
+echo file://${COVERDIR}/coverage.html
diff --git a/test/util/gocovcat/gocovcat.go b/test/util/gocovcat/gocovcat.go
deleted file mode 100755
index ecb63db..0000000
--- a/test/util/gocovcat/gocovcat.go
+++ /dev/null
@@ -1,90 +0,0 @@
-//usr/bin/env go run "$0" "$@"; exit $?
-//
-// From: https://git.lukeshu.com/go/cmd/gocovcat/
-//
-
-// Copyright 2017 Luke Shumaker <lukeshu@parabola.nu>
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-// Command gocovcat combines multiple go cover runs, and prints the
-// result on stdout.
-package main
-
-import (
-	"bufio"
-	"fmt"
-	"os"
-	"sort"
-	"strconv"
-	"strings"
-)
-
-func handleErr(err error) {
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "%v\n", err)
-		os.Exit(1)
-	}
-}
-
-func main() {
-	modeBool := false
-	blocks := map[string]int{}
-	for _, filename := range os.Args[1:] {
-		file, err := os.Open(filename)
-		handleErr(err)
-		buf := bufio.NewScanner(file)
-		for buf.Scan() {
-			line := buf.Text()
-
-			if strings.HasPrefix(line, "mode: ") {
-				m := strings.TrimPrefix(line, "mode: ")
-				switch m {
-				case "set":
-					modeBool = true
-				case "count", "atomic":
-					// do nothing
-				default:
-					fmt.Fprintf(os.Stderr, "Unrecognized mode: %s\n", m)
-					os.Exit(1)
-				}
-			} else {
-				sp := strings.LastIndexByte(line, ' ')
-				block := line[:sp]
-				cntStr := line[sp+1:]
-				cnt, err := strconv.Atoi(cntStr)
-				handleErr(err)
-				blocks[block] += cnt
-			}
-		}
-		handleErr(buf.Err())
-	}
-	keys := make([]string, 0, len(blocks))
-	for key := range blocks {
-		keys = append(keys, key)
-	}
-	sort.Strings(keys)
-	modeStr := "count"
-	if modeBool {
-		modeStr = "set"
-	}
-	fmt.Printf("mode: %s\n", modeStr)
-	for _, block := range keys {
-		cnt := blocks[block]
-		if modeBool && cnt > 1 {
-			cnt = 1
-		}
-		fmt.Printf("%s %d\n", block, cnt)
-	}
-}
diff --git a/test/util/lib.sh b/test/util/lib.sh
index 4a90893..6f89c36 100644
--- a/test/util/lib.sh
+++ b/test/util/lib.sh
@@ -12,15 +12,15 @@ function init() {
 	trap ":" TERM      # Avoid the EXIT handler from killing bash.
 	trap "exit 2" INT  # Ctrl-C, make sure we fail in that case.
 	trap "kill 0" EXIT # Kill children on exit.
+
+	export GOCOVERDIR
 }
 
 function build() {
-	if [ "$COVER_DIR" != "" ]; then
+	if [ "$GOCOVERDIR" != "" ]; then
 		(
 			cd ..
-			go test -covermode=count -coverpkg=./... \
-				-c -tags coveragebin
-			mv gofer.test gofer
+			go build -cover -covermode=count -o gofer .
 		)
 	else
 		( cd ..; make )
@@ -28,26 +28,15 @@ function build() {
 	( cd util/exp; go build )
 }
 
-function set_cover() {
-	# Set the coverage arguments each time, as we don't want the different
-	# runs to override the generated profile.
-	if [ "$COVER_DIR" != "" ]; then
-		COVER_ARGS="-test.run=^TestRunMain$ \
-			-test.coverprofile=$COVER_DIR/it-`date +%s.%N`.out"
-	fi
-}
-
 function gofer() {
-	set_cover
-	../gofer $COVER_ARGS  "$@"  >> .out.log 2>&1
+	../gofer "$@" >> .out.log 2>&1
 }
 
 # Run gofer in the background (sets $PID to its process id).
 function gofer_bg() {
 	# Duplicate gofer() because if we put the function in the background,
 	# the pid will be of bash, not the subprocess.
-	set_cover
-	../gofer $COVER_ARGS  "$@"  >> .out.log 2>&1 &
+	../gofer "$@" >> .out.log 2>&1 &
 	PID=$!
 }