author | Alberto Bertogli
<albertito@blitiri.com.ar> 2020-06-13 03:01:22 UTC |
committer | Alberto Bertogli
<albertito@blitiri.com.ar> 2020-06-13 12:14:28 UTC |
parent | 60c4f659d7bc71cc1548db694cdbf42a8dc099ee |
Makefile | +11 | -1 |
coverage_test.go | +39 | -0 |
test/util/cover-report.sh | +30 | -0 |
test/util/exp.go | +3 | -0 |
test/util/gocovcat.go | +91 | -0 |
diff --git a/Makefile b/Makefile index 332bb80..e4818ec 100644 --- a/Makefile +++ b/Makefile @@ -24,4 +24,14 @@ test: vet go test ./... setsid -w ./test/test.sh -.PHONY: gofer vet test +cover: + rm -r .cover/ + mkdir .cover/ + 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/ ./test/util/cover-report.sh + +.PHONY: gofer vet test cover diff --git a/coverage_test.go b/coverage_test.go new file mode 100644 index 0000000..1ca53b4 --- /dev/null +++ b/coverage_test.go @@ -0,0 +1,39 @@ +// 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/test/util/cover-report.sh b/test/util/cover-report.sh new file mode 100755 index 0000000..32995a8 --- /dev/null +++ b/test/util/cover-report.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +set -e + +if [ "$V" == "1" ]; then + set -v +fi + +UTILDIR="$( realpath `dirname "${0}"` )/" + +# Run from the repo root. +cd "$(realpath `dirname ${0}`)/../../" + +echo "## Coverage" + +# Merge all coverage output into a single file. +# Ignore protocol buffer-generated files, as they are not relevant. +go run "${UTILDIR}/gocovcat.go" "${COVER_DIR}"/*.out \ +> "${COVER_DIR}/all.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" + +TOTAL=$(cat .cover/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 diff --git a/test/util/exp.go b/test/util/exp.go index f6daa1d..802c8c2 100644 --- a/test/util/exp.go +++ b/test/util/exp.go @@ -1,3 +1,6 @@ +// +build ignore + +// Fetch an URL, and check if the response matches what we expect. package main import ( diff --git a/test/util/gocovcat.go b/test/util/gocovcat.go new file mode 100755 index 0000000..5b3e759 --- /dev/null +++ b/test/util/gocovcat.go @@ -0,0 +1,91 @@ +//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) + } +}