git » dnss » commit 12c3d35

tests: Update coverage tests to Go 1.20

author Alberto Bertogli
2023-02-01 22:05:39 UTC
committer Alberto Bertogli
2023-02-01 22:05:39 UTC
parent f61e48160f7ce2658e813217d65584dd1c0aa9f8

tests: 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.

coverage_test.go +0 -39
dnss.go +17 -0
tests/coverage.sh +12 -10
tests/external.sh +3 -11
tests/gocovcat.go +0 -91

diff --git a/coverage_test.go b/coverage_test.go
deleted file mode 100644
index 1ca53b4..0000000
--- a/coverage_test.go
+++ /dev/null
@@ -1,39 +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.
-// +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/dnss.go b/dnss.go
index 8f5f685..1ba2d5b 100644
--- a/dnss.go
+++ b/dnss.go
@@ -16,7 +16,10 @@ import (
 	"fmt"
 	"net/http"
 	"net/url"
+	"os"
+	"os/signal"
 	"sync"
+	"syscall"
 
 	"blitiri.com.ar/go/dnss/internal/dnsserver"
 	"blitiri.com.ar/go/dnss/internal/httpresolver"
@@ -79,6 +82,8 @@ func main() {
 	flag.Parse()
 	log.Init()
 
+	go signalHandler()
+
 	if *monitoringListenAddr != "" {
 		launchMonitoringServer(*monitoringListenAddr)
 	}
@@ -143,6 +148,18 @@ func main() {
 	wg.Wait()
 }
 
+func signalHandler() {
+	signals := make(chan os.Signal, 1)
+	signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT)
+
+	for sig := range signals {
+		switch sig {
+		case syscall.SIGTERM, syscall.SIGINT:
+			log.Fatalf("Got signal to exit: %v", sig)
+		}
+	}
+}
+
 func launchMonitoringServer(addr string) {
 	log.Infof("Monitoring HTTP server listening on %s", addr)
 
diff --git a/tests/coverage.sh b/tests/coverage.sh
index 8285192..645b96c 100755
--- a/tests/coverage.sh
+++ b/tests/coverage.sh
@@ -11,25 +11,27 @@ cd "$(realpath `dirname ${0}`)/../"
 # Recreate the coverage output directory, to avoid including stale results
 # from previous runs.
 rm -rf .coverage
-mkdir -p .coverage
+mkdir -p .coverage/sh .coverage/go .coverage/all
 export COVER_DIR="$PWD/.coverage"
 
-
+# Run Go tests in coverage mode.
 go test \
 	-covermode=count \
-	-coverprofile="$COVER_DIR/pkg-tests.out" \
 	-coverpkg=./... \
-	./...
+	./... \
+	-args -test.gocoverdir=${COVER_DIR}/go/
 
-# These will run in coverage mode due to $COVER_DIR being set.
-setsid -w ./tests/external.sh
+# Run external tests in coverage mode. They will run in coverage mode due to
+# $GOCOVERDIR being set.
+GOCOVERDIR="${COVER_DIR}/sh" setsid -w ./tests/external.sh
 
 # Merge all coverage output into a single file.
-go run "tests/gocovcat.go" .coverage/*.out \
-        > .coverage/all.out
+go tool covdata merge -i "${COVER_DIR}/go,${COVER_DIR}/sh" -o "${COVER_DIR}/all"
+go tool covdata textfmt -i "${COVER_DIR}/all" -o "${COVER_DIR}/merged.out"
 
-go tool cover -func=.coverage/all.out | sort -k 3 -n > ".func.txt"
-go tool cover -html=.coverage/all.out -o .coverage/dnss.cover.html
+# Generate reports based on the merged output.
+go tool cover -func=.coverage/merged.out | sort -k 3 -n > ".func.txt"
+go tool cover -html=.coverage/merged.out -o .coverage/dnss.cover.html
 
 grep -i total .func.txt
 echo "file:///$PWD/.coverage/dnss.cover.html"
diff --git a/tests/external.sh b/tests/external.sh
index ba69279..fd4a9d6 100755
--- a/tests/external.sh
+++ b/tests/external.sh
@@ -21,9 +21,8 @@ trap "kill 0" EXIT # Kill children on exit.
 cd "$(realpath `dirname ${0}`)/../"
 
 # Build the dnss binary.
-if [ "$COVER_DIR" != "" ]; then
-	go test -covermode=count -coverpkg=./... -c -tags coveragebin
-	mv dnss.test dnss
+if [ "${GOCOVERDIR}" != "" ]; then
+	go build -cover -covermode=count -o dnss .
 else
 	go build
 fi
@@ -31,14 +30,7 @@ fi
 
 # Run dnss in the background (sets $PID to its process id).
 function dnss() {
-	# 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
-
-	$SYSTEMD_ACTIVATE ./dnss $COVER_ARGS \
+	$SYSTEMD_ACTIVATE ./dnss \
 		-v 3 -monitoring_listen_addr :1900 \
 		"$@" > .dnss.log 2>&1 &
 	PID=$!
diff --git a/tests/gocovcat.go b/tests/gocovcat.go
deleted file mode 100644
index 5b3e759..0000000
--- a/tests/gocovcat.go
+++ /dev/null
@@ -1,91 +0,0 @@
-//usr/bin/env go run "$0" "$@"; exit $?
-//
-// From: https://git.lukeshu.com/go/cmd/gocovcat/
-//
-// +build ignore
-
-// 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)
-	}
-}