pax_global_header00006660000000000000000000000064137642260170014522gustar00rootroot0000000000000052 comment=a2d493ab5ad8cd71ee36d8304e8ea917cb7aeb23 bincover-0.2.0/000077500000000000000000000000001376422601700133305ustar00rootroot00000000000000bincover-0.2.0/.gitignore000066400000000000000000000005101376422601700153140ustar00rootroot00000000000000# Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # GoLand files .idea/ # Example instrumented binary examples/echo-arg/instr_bin # golanci-lint installation directory .golangci-bin/ bincover-0.2.0/LICENSE000066400000000000000000000020571376422601700143410ustar00rootroot00000000000000MIT License Copyright (c) 2020 Confluent Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. bincover-0.2.0/Makefile000066400000000000000000000017431376422601700147750ustar00rootroot00000000000000SHELL := /bin/bash ALL_SRC := $(shell find . -name "*.go" | grep -v -e vendor) GIT_REMOTE_NAME ?= origin MASTER_BRANCH ?= master GO ?= go ifdef TF_BUILD CI := on endif GOLANGCI_LINT_VERSION := v1.31.0 all: test .golangci-bin: @echo "===> Installing Golangci-lint <===" @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $@ $(GOLANGCI_LINT_VERSION) .PHONY: deps deps: .golangci-bin .PHONY: fmt fmt: @gofmt -e -s -l -w $(ALL_SRC) .PHONY: lint-go lint-go: .golangci-bin @GO111MODULE=on .golangci-bin/golangci-lint run --timeout=10m --skip-files="test_bin/set_covermode.go" .PHONY: lint lint: lint-go .PHONY: test-go test-go: ifdef CI @# Run unit tests with coverage. @GO111MODULE=on $(GO) test ./... -v -coverpkg=github.com/confluentinc/bincover -coverprofile=coverage.out else @GO111MODULE=on $(GO) test ./... -v endif .PHONY: test test: lint test-go .PHONY: clean clean: @rm -rf .golangci-bin bincover-0.2.0/README.md000066400000000000000000000013221376422601700146050ustar00rootroot00000000000000# bincover [![Build Status](https://dev.azure.com/confluentinc/bincover/_apis/build/status/confluentinc.bincover?branchName=master)](https://dev.azure.com/confluentinc/bincover/_build/latest?definitionId=2&branchName=master) [![codecov](https://codecov.io/gh/confluentinc/bincover/branch/master/graph/badge.svg?token=r79qa1cDeJ)](https://codecov.io/gh/confluentinc/bincover) [![GoDoc](https://godoc.org/github.com/confluentinc/bincover?status.svg)](https://godoc.org/github.com/confluentinc/bincover) Easily measure code coverage of Golang binaries Check out [this](https://www.confluent.io/blog/measure-go-code-coverage-with-bincover/) blog post to see an example of Bincover is used, and how it works under the hood. bincover-0.2.0/azure-pipelines.yml000066400000000000000000000014651376422601700171750ustar00rootroot00000000000000# Go # Build your Go project. # Add steps that test, save build artifacts, deploy, and more: # https://docs.microsoft.com/azure/devops/pipelines/languages/go trigger: - master pool: vmImage: 'ubuntu-latest' steps: - task: GoTool@0 inputs: version: '1.12' - script: | make deps workingDirectory: '$(System.DefaultWorkingDirectory)' displayName: 'make deps' - script: | make test workingDirectory: '$(System.DefaultWorkingDirectory)' displayName: 'make test' - script: | ./ci/check-tidy.sh workingDirectory: '$(System.DefaultWorkingDirectory)' displayName: 'check-tidy' - script: | bash <(curl -s https://codecov.io/bash) -t '$(CODECOV_TOKEN)' workingDirectory: '$(System.DefaultWorkingDirectory)' displayName: 'Generate Codecov report' bincover-0.2.0/ci/000077500000000000000000000000001376422601700137235ustar00rootroot00000000000000bincover-0.2.0/ci/check-tidy.sh000077500000000000000000000006501376422601700163070ustar00rootroot00000000000000#!/usr/bin/env bash set -e : "${GO:=go}" goversion=$($GO version) $GO mod tidy rc=0 diff=$(git diff --exit-code -- go.mod go.sum) || rc=$? if [ $rc -ne 0 ]; then echo "Found some differences when running 'go mod tidy'" echo "**********" echo "$diff" echo "**********" echo "Please ensure you are using the correct version of go ($goversion), run 'go mod tidy', and commit the changes" fi exit $rc bincover-0.2.0/doc.go000066400000000000000000000003041376422601700144210ustar00rootroot00000000000000/* Package bincover provides an easy way to measure code coverage of Golang binaries. See examples/ to see how bincover is used to measure coverage of a simple Go application. */ package bincover bincover-0.2.0/examples/000077500000000000000000000000001376422601700151465ustar00rootroot00000000000000bincover-0.2.0/examples/echo-arg/000077500000000000000000000000001376422601700166335ustar00rootroot00000000000000bincover-0.2.0/examples/echo-arg/bincover_run_main_test.go000066400000000000000000000002451376422601700237210ustar00rootroot00000000000000// +build testbincover package main import ( "testing" "github.com/confluentinc/bincover" ) func TestBincoverRunMain(t *testing.T) { bincover.RunTest(main) } bincover-0.2.0/examples/echo-arg/build_instr_bin.sh000077500000000000000000000002361376422601700223410ustar00rootroot00000000000000#!/usr/bin/env bash go test . -tags testbincover -coverpkg=./... -c -o instr_bin -ldflags="-X github.com/confluentinc/bincover/examples/echo-arg.isTest=true" bincover-0.2.0/examples/echo-arg/main.go000066400000000000000000000012111376422601700201010ustar00rootroot00000000000000package main import ( "fmt" "os" "strconv" "github.com/confluentinc/bincover" ) var ( // Injected from linker flags like `go build -ldflags "-X main.version=$VERSION" -X ...` isTest = "false" ) func main() { isTest, err := strconv.ParseBool(isTest) if err != nil { fmt.Println(err) os.Exit(1) } exitCode := 0 switch len(os.Args) { case 2: fmt.Printf("Your argument is \"%s\"\n", os.Args[1]) case 1: fmt.Println("Please provide an argument") exitCode = 1 default: panic("More than 2 arguments provided! Ahh!") } if exitCode != 0 { if isTest { bincover.ExitCode = exitCode } else { os.Exit(exitCode) } } } bincover-0.2.0/examples/echo-arg/main_test.go000066400000000000000000000033301376422601700211440ustar00rootroot00000000000000package main import ( "fmt" "log" "os" "os/exec" "regexp" "testing" "github.com/stretchr/testify/require" "github.com/confluentinc/bincover" ) func TestMainMethod(t *testing.T) { const binPath = "./instr_bin" buildTestCmd := exec.Command("./build_instr_bin.sh") output, err := buildTestCmd.CombinedOutput() if err != nil { log.Println(output) panic(err) } collector := bincover.NewCoverageCollector("echo_arg_coverage.out", true) err = collector.Setup() require.NoError(t, err) defer func() { err := collector.TearDown() if err != nil { panic(err) } err = os.Remove(binPath) if err != nil { panic(err) } }() tests := []struct { name string args []string wantOutput string outputPattern *regexp.Regexp wantExitCode int }{ { name: "succeed running main with one arg", args: []string{"hello"}, wantOutput: "Your argument is \"hello\"\n", wantExitCode: 0, }, { name: "fail running main with two args", args: []string{"hello", "world"}, wantOutput: "", outputPattern: regexp.MustCompile(".*panic.*More than 2 arguments provided!"), wantExitCode: 1, }, { name: "fail running main with no args", args: []string{""}, wantOutput: "Please provide an argument\n", wantExitCode: 1, }, } for _, tt := range tests { fmt.Println(tt.name) output, exitCode, err := collector.RunBinary(binPath, "TestBincoverRunMain", []string{}, tt.args) require.NoError(t, err) if tt.outputPattern != nil { require.Regexp(t, tt.outputPattern, output) } else { require.Equal(t, tt.wantOutput, output) } require.Equal(t, tt.wantExitCode, exitCode) } require.NoError(t, err) } bincover-0.2.0/go.mod000066400000000000000000000004771376422601700144460ustar00rootroot00000000000000module github.com/confluentinc/bincover go 1.12 require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/kr/pretty v0.1.0 // indirect github.com/pkg/errors v0.8.1 github.com/stretchr/testify v1.4.0 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/yaml.v2 v2.2.7 // indirect ) bincover-0.2.0/go.sum000066400000000000000000000036411376422601700144670ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= bincover-0.2.0/instrument_bin.go000066400000000000000000000041321376422601700167170ustar00rootroot00000000000000package bincover import ( "encoding/json" "flag" "fmt" "io/ioutil" "os" "runtime/debug" "strings" "testing" ) var ( argsFilename = flag.String("args-file", "", "custom args file, newline separated") ExitCode = 0 ) const ( startOfMetadataMarker = "START_BINCOVER_METADATA" endOfMetadataMarker = "END_BINCOVER_METADATA" ) func parseCustomArgs() ([]string, error) { buf, err := ioutil.ReadFile(*argsFilename) if err != nil { return nil, err } rawArgs := strings.Split(string(buf), "\n") var parsedCustomArgs []string for _, arg := range rawArgs { arg = strings.TrimSpace(arg) if len(arg) > 0 { parsedCustomArgs = append(parsedCustomArgs, arg) } } return parsedCustomArgs, nil } type testMetadata struct { CoverMode string `json:"cover_mode"` ExitCode int `json:"exit_code"` } func printMetadata(metadata *testMetadata) { fmt.Println(startOfMetadataMarker) b, err := json.Marshal(metadata) if err != nil { panic(err) } fmt.Println(string(b)) fmt.Println(endOfMetadataMarker) } // RunTest runs function f (usually main), with arguments specified by the flag "args-file", a file of newline-separated args. // When f runs to completion (success or failure), RunTest prints (newline-separated): // 1. f's output, // 2. startOfMetadataMarker // 3. a testMetadata struct // 4. endOfMetadataMarker // // Otherwise, if an unexpected error is encountered during execution, RunTest panics. func RunTest(f func()) { if !flag.Parsed() { flag.Parse() } var parsedArgs []string for _, arg := range os.Args { if !strings.HasPrefix(arg, "-test.") && !strings.HasPrefix(arg, "-args-file") { parsedArgs = append(parsedArgs, arg) } } if len(*argsFilename) > 0 { customArgs, err := parseCustomArgs() if err != nil { panic(err) } parsedArgs = append(parsedArgs, customArgs...) } os.Args = parsedArgs // Catch panicking binaries. defer func() { if r := recover(); r != nil { fmt.Printf("panic: %s\n%s", r, debug.Stack()) ExitCode = 1 } metadata := &testMetadata{ CoverMode: testing.CoverMode(), ExitCode: ExitCode, } printMetadata(metadata) }() f() } bincover-0.2.0/instrument_bin_test.go000066400000000000000000000105631376422601700177630ustar00rootroot00000000000000package bincover import ( "fmt" "io/ioutil" "os" "reflect" "testing" "github.com/stretchr/testify/require" ) func TestRunTest(t *testing.T) { type args struct { f func() } tests := []struct { name string args args argsFile *os.File wantOutput string wantArgs []string wantPanic bool wantOutputPattern string }{ { name: "succeed running test", args: args{f: func() { fmt.Println("The worst thing about prison was the Dementors") }}, argsFile: func() *os.File { f := tempFileWithContent(t, "first\nsecond\nthird\n") return f }(), wantArgs: []string{"first", "second", "third"}, wantOutput: "The worst thing about prison was the Dementors\n" + startOfMetadataMarker + "\n{\"cover_mode\":\"" + testing.CoverMode() + "\",\"exit_code\":0}\n" + endOfMetadataMarker + "\n", }, { name: "fail running test when error parsing args file", args: args{f: func() { fmt.Println("Well, well, well, how the turntables") }}, argsFile: func() *os.File { f := removedTempFile(t) return f }(), wantArgs: []string{}, wantPanic: true, }, { name: "succeed running panicking binary", args: args{f: func() { panic("I am Beyonce, always") }}, argsFile: func() *os.File { return tempFile(t) }(), wantOutputPattern: "panic: I am Beyonce, always\ngoroutine [\\d]+[\\s\\S]+" + startOfMetadataMarker + "\n{\"cover_mode\":\"" + testing.CoverMode() + "\",\"exit_code\":1}\n" + endOfMetadataMarker + "\n", wantArgs: []string{}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { defer os.Remove(tt.argsFile.Name()) s := tt.argsFile.Name() argsFilename = &s resetArgsFileName := func() { var empty string argsFilename = &empty } defer resetArgsFileName() oldStdout := os.Stdout defer func() { os.Stdout = oldStdout }() tempStdout := tempFile(t) defer os.Remove(tempStdout.Name()) os.Stdout = tempStdout if tt.wantPanic { require.Panics(t, func() { RunTest(tt.args.f) }) } else { RunTest(tt.args.f) } _, err := tempStdout.Seek(0, 0) require.NoError(t, err) buf, err := ioutil.ReadAll(tempStdout) require.NoError(t, err) if tt.wantOutputPattern != "" { require.Regexp(t, tt.wantOutputPattern, string(buf)) } else { require.Equal(t, tt.wantOutput, string(buf)) } require.Equal(t, tt.wantArgs, os.Args[len(os.Args)-len(tt.wantArgs):]) }) } } func Test_parseCustomArgs(t *testing.T) { tests := []struct { name string argsFile *os.File want []string wantErr bool }{ { name: "succeed parsing args", argsFile: func() *os.File { return tempFileWithContent(t, "first\nsecond\nthird\n") }(), want: []string{"first", "second", "third"}, wantErr: false, }, { name: "fail parsing args when error reading from args file", argsFile: func() *os.File { return removedTempFile(t) }(), wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { defer os.Remove(tt.argsFile.Name()) s := tt.argsFile.Name() argsFilename = &s resetArgsFileName := func() { var empty string argsFilename = &empty } defer resetArgsFileName() got, err := parseCustomArgs() if (err != nil) != tt.wantErr { t.Errorf("parseCustomArgs() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("parseCustomArgs() got = %v, want %v", got, tt.want) } }) } } func Test_printMetadata(t *testing.T) { type args struct { metadata *testMetadata } tests := []struct { name string args args wantOutput string }{ { name: "succeed printing metadata", args: args{ metadata: &testMetadata{ CoverMode: "set", ExitCode: 0, }}, wantOutput: startOfMetadataMarker + "\n{\"cover_mode\":\"set\",\"exit_code\":0}\n" + endOfMetadataMarker + "\n", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { oldStdout := os.Stdout defer func() { os.Stdout = oldStdout }() tempStdout := tempFile(t) defer os.Remove(tempStdout.Name()) os.Stdout = tempStdout printMetadata(tt.args.metadata) _, err := tempStdout.Seek(0, 0) require.NoError(t, err) buf, err := ioutil.ReadAll(tempStdout) require.NoError(t, err) require.Equal(t, tt.wantOutput, string(buf)) }) } } bincover-0.2.0/run_bin.go000066400000000000000000000172251376422601700153220ustar00rootroot00000000000000package bincover import ( "encoding/json" "fmt" "io/ioutil" "log" "os" "os/exec" "strings" "github.com/pkg/errors" ) const ( set = "set" count = "count" atomic = "atomic" defaultTmpArgsFilePrefix = "integ_args" defaultTmpCoverageFilePrefix = "temp_coverage" ) type CoverageCollector struct { MergedCoverageFilename string CollectCoverage bool tmpArgsFile *os.File coverMode string tmpCoverageFiles []*os.File setupFinished bool preCmdFuncs []PreCmdFunc postCmdFuncs []PostCmdFunc } type CoverageCollectorOption func(collector *CoverageCollector) type PreCmdFunc func(cmd *exec.Cmd) error type PostCmdFunc func(cmd *exec.Cmd, output string, err error) error // NewCoverageCollector initializes a CoverageCollector with the specified // merged coverage filename. CollectCoverage can be set to true to collect coverage, // or set to false to skip coverage collection. This is provided in order to enable reuse of CoverageCollector // for tests where coverage measurement is not needed. func NewCoverageCollector(mergedCoverageFilename string, collectCoverage bool) *CoverageCollector { return &CoverageCollector{ MergedCoverageFilename: mergedCoverageFilename, CollectCoverage: collectCoverage, } } func (c *CoverageCollector) Setup() error { if c.MergedCoverageFilename == "" && c.CollectCoverage { return errors.New("merged coverage profile filename cannot be empty when CollectCoverage is true") } var err error c.tmpArgsFile, err = ioutil.TempFile("", defaultTmpArgsFilePrefix) if err != nil { return errors.Wrap(err, "error creating temporary args file") } c.setupFinished = true return nil } // TearDown merges the coverage profiles collecting from repeated runs of RunBinary. // It must be called at the teardown stage of the test suite, otherwise no merged coverage profile will be created. func (c *CoverageCollector) TearDown() error { if len(c.tmpCoverageFiles) == 0 { return nil } defer c.removeTempFiles() header := fmt.Sprintf("mode: %s", c.coverMode) var parsedProfiles []string for _, file := range c.tmpCoverageFiles { buf, err := ioutil.ReadAll(file) if err != nil { return errors.Wrap(err, "error reading temp coverage profiles") } profile := string(buf) loc := strings.Index(profile, header) if loc == -1 { errMessage := "error parsing coverage profile: missing coverage mode from coverage profile. Maybe the file got corrupted while writing?" return errors.New(errMessage) } parsedProfile := strings.TrimSpace(profile[loc+len(header):]) parsedProfiles = append(parsedProfiles, parsedProfile) } mergedProfile := fmt.Sprintf("%s\n%s", header, strings.Join(parsedProfiles, "\n")) err := ioutil.WriteFile(c.MergedCoverageFilename, []byte(mergedProfile), 0600) if err != nil { return errors.Wrap(err, "error writing merged coverage profile") } return nil } func PreExec(preCmdFuncs ...PreCmdFunc) CoverageCollectorOption { return func(c *CoverageCollector) { c.preCmdFuncs = preCmdFuncs } } func PostExec(postCmdFuncs ...PostCmdFunc) CoverageCollectorOption { return func(c *CoverageCollector) { c.postCmdFuncs = postCmdFuncs } } // RunBinary runs the instrumented binary at binPath with env environment variables, executing only the test with mainTestName with the specified args. func (c *CoverageCollector) RunBinary(binPath string, mainTestName string, env []string, args []string, options ...CoverageCollectorOption) (output string, exitCode int, err error) { if !c.setupFinished { panic("RunBinary called before Setup") } err = c.writeArgs(args) if err != nil { return "", -1, err } for _, option := range options { option(c) } var binArgs string var tempCovFile *os.File if c.CollectCoverage { tempCovFile, err = ioutil.TempFile("", defaultTmpCoverageFilePrefix) if err != nil { return "", -1, err } binArgs = fmt.Sprintf("-test.run=^%s$ -test.coverprofile=%s -args-file=%s", mainTestName, tempCovFile.Name(), c.tmpArgsFile.Name()) } else { binArgs = fmt.Sprintf("-test.run=^%s$ -args-file=%s", mainTestName, c.tmpArgsFile.Name()) } cmd := exec.Command(binPath, strings.Split(binArgs, " ")...) cmd.Env = append(os.Environ(), env...) for _, cmdFunc := range c.preCmdFuncs { if err := cmdFunc(cmd); err != nil { return "", -1, err } } combinedOutput, err := cmd.CombinedOutput() binOutput := string(combinedOutput) if err != nil { if tempCovFile != nil { removeTempCoverageFile(tempCovFile.Name()) } // This exit code testing requires 1.12 - https://stackoverflow.com/a/55055100/337735. if exitError, ok := err.(*exec.ExitError); ok { binExitCode := exitError.ExitCode() format := "unsuccessful exit by command \"%s\"\nExit code: %d\nOutput:\n%s" return "", binExitCode, errors.Wrapf(exitError, format, binPath, binExitCode, binOutput) } else { format := "unexpected error running command \"%s\"" return "", -1, errors.Wrapf(err, format, binPath) } } haveTestsToRun := haveTestsToRun(binOutput) if !haveTestsToRun { return "", -1, errors.New(binOutput) } if tempCovFile != nil { c.tmpCoverageFiles = append(c.tmpCoverageFiles, tempCovFile) } cmdOutput, coverMode, exitCode := parseCommandOutput(string(combinedOutput)) for _, cmdFunc := range c.postCmdFuncs { if e := cmdFunc(cmd, cmdOutput, err); e != nil { return "", -1, e } } if c.CollectCoverage { if c.coverMode == "" { c.coverMode = coverMode } if c.coverMode == "" { panic("coverage mode cannot be empty. test coverage must be enabled when CollectCoverage is set to true") } // https://github.com/wadey/gocovmerge/blob/b5bfa59ec0adc420475f97f89b58045c721d761c/gocovmerge.go#L18 if c.coverMode != coverMode { panic("cannot merge profiles with different coverage modes") } if c.coverMode != set && c.coverMode != count && c.coverMode != atomic { log.Panicf("unexpected coverage mode \"%s\" encountered. Coverage mode must be set, count, or atomic", c.coverMode) } } return cmdOutput, exitCode, err } func (c *CoverageCollector) writeArgs(args []string) error { err := c.tmpArgsFile.Truncate(0) if err != nil { return err } _, err = c.tmpArgsFile.Seek(0, 0) if err != nil { return err } argStr := strings.Join(args, "\n") _, err = c.tmpArgsFile.WriteAt([]byte(argStr), 0) if err != nil { return err } return err } func parseCommandOutput(output string) (cmdOutput string, coverMode string, exitCode int) { startIndex := strings.Index(output, startOfMetadataMarker) if startIndex == -1 { panic("metadata start marker is unexpectedly missing") } endIndex := strings.Index(output, endOfMetadataMarker) if endIndex == -1 { panic("metadata end marker is unexpectedly missing") } cmdOutput = output[:startIndex] tail := output[startIndex+len(startOfMetadataMarker) : endIndex] // Trim extra newline after cmd output. metadataStr := strings.TrimSpace(tail) var metadata testMetadata err := json.Unmarshal([]byte(metadataStr), &metadata) if err != nil { panic("error unmarshalling testMetadata struct from RunTest") } return cmdOutput, metadata.CoverMode, metadata.ExitCode } func (c *CoverageCollector) removeTempFiles() { for _, file := range c.tmpCoverageFiles { removeTempCoverageFile(file.Name()) } if c.tmpArgsFile != nil { err := os.Remove(c.tmpArgsFile.Name()) if err != nil { log.Printf("error removing temp arg file: %s\n", err) } } } func removeTempCoverageFile(name string) { err := os.Remove(name) if err != nil { log.Printf("error removing temp coverage file: %s\n", err) } } func haveTestsToRun(output string) bool { prefix := "testing: warning: no tests to run" return !strings.HasPrefix(output, prefix) } bincover-0.2.0/run_bin_test.go000066400000000000000000000555221376422601700163630ustar00rootroot00000000000000package bincover import ( "bytes" "errors" "io" "io/ioutil" "log" "os" "os/exec" "reflect" "testing" "github.com/stretchr/testify/require" ) const ( ohNoErrMsg = "oh no!" preFuncHelloMsg = "Hello from prefunc\n" helloWorldOutput = "Hello world\n" ) func TestMain(m *testing.M) { // Build necessary binaries before executing unit tests. buildTestCmd := exec.Command("go", []string{"test", "./test_bins", "-tags", "testrunmain", "-coverpkg=./...", "-c", "-o", "set_covermode"}...) output, err := buildTestCmd.CombinedOutput() if err != nil { log.Println(output) panic(err) } exitCode := m.Run() err = os.Remove("set_covermode") if err != nil { panic(err) } os.Exit(exitCode) } func TestCoverageCollector_Setup(t *testing.T) { type fields struct { MergedCoverageFilename string CollectCoverage bool setupFinished bool tmpArgsFilePrefix string } tests := []struct { name string fields fields wantErr bool errMessage string }{ { name: "succeed setting up", fields: fields{ MergedCoverageFilename: "test-file.out", CollectCoverage: false, setupFinished: true, tmpArgsFilePrefix: defaultTmpArgsFilePrefix, }, wantErr: false, }, { name: "fail setting up with empty filename", fields: fields{ MergedCoverageFilename: "", CollectCoverage: true, setupFinished: false, tmpArgsFilePrefix: defaultTmpArgsFilePrefix, }, wantErr: true, errMessage: "merged coverage profile filename cannot be empty when CollectCoverage is true", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &CoverageCollector{ MergedCoverageFilename: tt.fields.MergedCoverageFilename, CollectCoverage: tt.fields.CollectCoverage, } if err := c.Setup(); (err != nil) != tt.wantErr { t.Errorf("Setup() error = %v, wantErr %v", err, tt.wantErr) } else if tt.wantErr && tt.errMessage != "" { require.EqualError(t, err, tt.errMessage) } require.Equal(t, tt.fields.setupFinished, c.setupFinished) }) } } func TestCoverageCollector_TearDown(t *testing.T) { type fields struct { MergedCoverageFilename string CollectCoverage bool coverMode string tmpCoverageFiles []*os.File } tests := []struct { name string fields fields wantErr bool wantPanic bool panicMessage string errMessageContains string errMessage string mergedFileContents string }{ { name: "succeed tearing down with no tests", fields: fields{}, wantErr: false, }, { name: "succeed tearing down with tests", fields: fields{ MergedCoverageFilename: "temp_merged.out", CollectCoverage: true, tmpCoverageFiles: func() []*os.File { f1 := tempFileWithContent(t, "mode: set\nfirst file\n") f2 := tempFileWithContent(t, "mode: set\nsecond file\n") f3 := tempFileWithContent(t, "mode: set\nthird file\n") return []*os.File{f1, f2, f3} }(), coverMode: "set", }, mergedFileContents: "mode: set\nfirst file\nsecond file\nthird file", wantErr: false, }, { name: "fail tearing down with missing coverage mode", fields: fields{ MergedCoverageFilename: "temp_merged.out", CollectCoverage: true, tmpCoverageFiles: func() []*os.File { f1 := tempFileWithContent(t, "mode: set\nfirst file\n") missingHeaderFile := tempFileWithContent(t, "second file\n") return []*os.File{f1, missingHeaderFile} }(), }, wantErr: true, errMessage: "error parsing coverage profile: missing coverage mode from coverage profile. Maybe the file got corrupted while writing?", }, { name: "fail tearing down with missing temp coverage profiles", fields: fields{ MergedCoverageFilename: "temp_merged.out", CollectCoverage: true, tmpCoverageFiles: func() []*os.File { f := tempFile(t) require.NoError(t, f.Close()) return []*os.File{f} }(), }, wantErr: true, errMessageContains: "error reading temp coverage profiles", }, { name: "fail tearing down with invalid merged coverage filename", fields: fields{ MergedCoverageFilename: "inval?df!l3Nam3/.%", CollectCoverage: true, tmpCoverageFiles: func() []*os.File { f1 := tempFileWithContent(t, "mode: set\nfirst file\n") f2 := tempFileWithContent(t, "mode: set\nsecond file\n") f3 := tempFileWithContent(t, "mode: set\nthird file\n") return []*os.File{f1, f2, f3} }(), }, wantErr: true, errMessageContains: "error writing merged coverage profile", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &CoverageCollector{ MergedCoverageFilename: tt.fields.MergedCoverageFilename, CollectCoverage: tt.fields.CollectCoverage, coverMode: tt.fields.coverMode, tmpCoverageFiles: tt.fields.tmpCoverageFiles, } if tt.wantPanic { require.PanicsWithValue(t, tt.panicMessage, func() { _ = c.TearDown() }) return } if err := c.TearDown(); (err != nil) != tt.wantErr { t.Errorf("TearDown() error = %v, wantErr %v", err, tt.wantErr) } else if err != nil && tt.errMessageContains != "" { require.Contains(t, err.Error(), tt.errMessageContains) } else if err != nil && tt.errMessage != "" { require.EqualError(t, err, tt.errMessage) } if c.MergedCoverageFilename != "" && !tt.wantErr { defer os.Remove(c.MergedCoverageFilename) buf, err := ioutil.ReadFile(c.MergedCoverageFilename) require.NoError(t, err) contents := string(buf) require.Equal(t, tt.mergedFileContents, contents) } }) } } func TestCoverageCollector_removeTempFiles(t *testing.T) { tests := []struct { name string tmpCoverageFiles []*os.File tmpArgsFile *os.File stdErrOutputFmt string }{ { name: "succeed removing temp files", tmpCoverageFiles: func() []*os.File { return []*os.File{tempFile(t)} }(), tmpArgsFile: func() *os.File { return tempFile(t) }(), stdErrOutputFmt: "", }, { name: "fail silently removing nonexistent temp files", tmpCoverageFiles: func() []*os.File { f := removedTempFile(t) return []*os.File{f} }(), tmpArgsFile: func() *os.File { return removedTempFile(t) }(), stdErrOutputFmt: ".*error removing.*\n.*error removing temp arg file.*", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &CoverageCollector{} c.tmpCoverageFiles = tt.tmpCoverageFiles c.tmpArgsFile = tt.tmpArgsFile var buf bytes.Buffer log.SetOutput(&buf) c.removeTempFiles() log.SetOutput(os.Stderr) require.Regexp(t, tt.stdErrOutputFmt, buf.String()) for _, file := range c.tmpCoverageFiles { _, err := os.Stat(file.Name()) require.True(t, os.IsNotExist(err)) } _, err := os.Stat(c.tmpArgsFile.Name()) require.True(t, os.IsNotExist(err)) }) } } func TestNewCoverageCollector(t *testing.T) { type args struct { mergedCoverageFilename string collectCoverage bool } tests := []struct { name string args args want *CoverageCollector }{ { name: "succeed creating CoverageCollector instance", args: args{ mergedCoverageFilename: "fake.file", collectCoverage: false, }, want: &CoverageCollector{ MergedCoverageFilename: "fake.file", CollectCoverage: false, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := NewCoverageCollector(tt.args.mergedCoverageFilename, tt.args.collectCoverage); !reflect.DeepEqual(got, tt.want) { t.Errorf("NewCoverageCollector() = %v, want %v", got, tt.want) } }) } } func Test_parseCommandOutput(t *testing.T) { type args struct { output string } tests := []struct { name string args args wantCmdOutput string wantCoverMode string wantExitCode int wantPanic bool panicMessage string }{ { name: "panic if metadata start marker is missing", args: args{output: ""}, wantPanic: true, panicMessage: "metadata start marker is unexpectedly missing", }, { name: "panic if metadata end marker is missing", args: args{output: startOfMetadataMarker}, wantPanic: true, panicMessage: "metadata end marker is unexpectedly missing", }, { name: "panic if error occurs while unmarshalling testMetadata", args: args{output: startOfMetadataMarker + "invalid" + endOfMetadataMarker}, wantPanic: true, panicMessage: "error unmarshalling testMetadata struct from RunTest", }, { name: "succeed parsing command output", args: args{ output: "test output" + startOfMetadataMarker + "\n{\"cover_mode\":\"set\",\"exit_code\":1}\n" + endOfMetadataMarker + "\n", }, wantCmdOutput: "test output", wantCoverMode: "set", wantExitCode: 1, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.wantPanic { require.PanicsWithValue(t, tt.panicMessage, func() { parseCommandOutput(tt.args.output) }) return } gotCmdOutput, gotCoverMode, gotExitCode := parseCommandOutput(tt.args.output) if gotCmdOutput != tt.wantCmdOutput { t.Errorf("parseCommandOutput() gotCmdOutput = %v, want %v", gotCmdOutput, tt.wantCmdOutput) } if gotCoverMode != tt.wantCoverMode { t.Errorf("parseCommandOutput() gotCoverMode = %v, want %v", gotCoverMode, tt.wantCoverMode) } if gotExitCode != tt.wantExitCode { t.Errorf("parseCommandOutput() gotExitCode = %v, want %v", gotExitCode, tt.wantExitCode) } }) } } func TestCoverageCollector_writeArgs(t *testing.T) { type fields struct { MergedCoverageFilename string CollectCoverage bool tmpArgsFile *os.File coverMode string tmpCoverageFiles []*os.File setupFinished bool } type args struct { args []string } tests := []struct { name string fields fields args args wantErr bool wantArgFileContent string }{ { name: "fail when writing args to closed file", fields: fields{ tmpArgsFile: func() *os.File { f := tempFile(t) require.NoError(t, f.Close()) return f }(), }, wantErr: true, }, { name: "succeed writing args", fields: fields{ tmpArgsFile: func() *os.File { return tempFile(t) }(), }, args: args{args: []string{"first", "second", "third"}}, wantArgFileContent: "first\nsecond\nthird", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { defer os.Remove(tt.fields.tmpArgsFile.Name()) c := &CoverageCollector{ MergedCoverageFilename: tt.fields.MergedCoverageFilename, CollectCoverage: tt.fields.CollectCoverage, tmpArgsFile: tt.fields.tmpArgsFile, coverMode: tt.fields.coverMode, tmpCoverageFiles: tt.fields.tmpCoverageFiles, setupFinished: tt.fields.setupFinished, } if err := c.writeArgs(tt.args.args); (err != nil) != tt.wantErr { t.Errorf("writeArgs() error = %v, wantErr %v", err, tt.wantErr) } if !tt.wantErr { buf, err := ioutil.ReadAll(c.tmpArgsFile) require.NoError(t, err) require.Equal(t, tt.wantArgFileContent, string(buf)) } }) } } func TestPreExec(t *testing.T) { type args struct { binPath string mainTestName string env []string args []string } var ( buffer = new(bytes.Buffer) ) tests := []struct { name string preCmdFuncs []PreCmdFunc expected string args args wantErr bool wantErrMsg string buffer *bytes.Buffer }{ { name: "test running two prefuncs", args: args{ binPath: "./test_bins/read_stdin.sh", mainTestName: "TestRunMain", }, buffer: buffer, preCmdFuncs: []PreCmdFunc{printToBufferPreFunc(buffer, preFuncHelloMsg), printToBufferPreFunc(buffer, preFuncHelloMsg)}, expected: preFuncHelloMsg + preFuncHelloMsg, }, { name: "test error in prefuncs", args: args{ binPath: "./test_bins/read_stdin.sh", mainTestName: "TestRunMain", }, preCmdFuncs: []PreCmdFunc{errPreCmdFunc(ohNoErrMsg)}, wantErr: true, wantErrMsg: ohNoErrMsg, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := NewCoverageCollector("", false) _ = c.Setup() _, _, err := c.RunBinary(tt.args.binPath, tt.args.mainTestName, tt.args.env, tt.args.args, PreExec(tt.preCmdFuncs...)) if tt.wantErr { require.Error(t, err) require.Equal(t, tt.wantErrMsg, err.Error()) } else { require.Equal(t, tt.expected, tt.buffer.String()) require.NoError(t, err) } }) } } func TestPostExec(t *testing.T) { type args struct { binPath string mainTestName string env []string args []string } var ( buffer = new(bytes.Buffer) ) tests := []struct { name string postCmdFuncs []PostCmdFunc expected string args args wantErr bool wantErrMsg string buffer *bytes.Buffer }{ { name: "test running two prefuncs", args: args{ binPath: "./set_covermode", mainTestName: "TestRunMain", }, buffer: buffer, postCmdFuncs: []PostCmdFunc{printCommandOutputToBuffer(buffer), printCommandOutputToBuffer(buffer)}, expected: helloWorldOutput + helloWorldOutput, }, { name: "test error in prefuncs", args: args{ binPath: "./set_covermode", mainTestName: "TestRunMain", }, postCmdFuncs: []PostCmdFunc{errPostCmdFunc(ohNoErrMsg)}, wantErr: true, wantErrMsg: ohNoErrMsg, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := NewCoverageCollector("", false) _ = c.Setup() _, _, err := c.RunBinary(tt.args.binPath, tt.args.mainTestName, tt.args.env, tt.args.args, PostExec(tt.postCmdFuncs...)) if tt.wantErr { require.Error(t, err) require.Equal(t, tt.wantErrMsg, err.Error()) } else { require.Equal(t, tt.expected, tt.buffer.String()) require.NoError(t, err) } }) } } func TestCoverageCollector_RunBinary(t *testing.T) { type fields struct { MergedCoverageFilename string CollectCoverage bool coverMode string } type args struct { binPath string mainTestName string env []string args []string } tests := []struct { name string fields fields args args wantOutput string wantExitCode int wantErr bool errMessage string wantPanic bool panicMessage string skipSetup bool cmdFuncs []CoverageCollectorOption }{ { name: "panic if Setup not called", wantPanic: true, panicMessage: "RunBinary called before Setup", skipSetup: true, }, { name: "fail if error exists and is not an ExitError when executing command at 'binPath'", wantErr: true, errMessage: "unexpected error running command \"invalid.exec\": exec: \"invalid.exec\": executable file not found in $PATH", args: args{ binPath: "invalid.exec", mainTestName: "", env: nil, args: nil, }, wantExitCode: -1, }, { name: "fail if error exists and is an ExitError when executing command at 'binPath'", wantErr: true, errMessage: "unsuccessful exit by command \"./test_bins/exit_1.sh\"\nExit code: 1\nOutput:\nHello world\n: exit status 1", args: args{ binPath: "./test_bins/exit_1.sh", mainTestName: "", env: nil, args: nil, }, wantExitCode: 1, }, { name: "succeed running binary when coverage is disabled", args: args{ binPath: "./set_covermode", mainTestName: "TestRunMain", env: nil, args: nil, }, fields: fields{ MergedCoverageFilename: "", CollectCoverage: false, }, wantOutput: "Hello world\n", wantExitCode: 1, }, { name: "succeed running binary when coverage is enabled", args: args{ binPath: "./set_covermode", mainTestName: "TestRunMain", env: nil, args: nil, }, fields: fields{ MergedCoverageFilename: "temp_coverage.out", CollectCoverage: true, }, wantOutput: "Hello world\n", wantExitCode: 1, }, { name: "panic running binary which outputs empty coverage mode", args: args{ binPath: "./test_bins/empty_covermode.sh", mainTestName: "", env: nil, args: nil, }, fields: fields{ MergedCoverageFilename: "temp_coverage.out", CollectCoverage: true, }, wantPanic: true, panicMessage: "coverage mode cannot be empty. test coverage must be enabled when CollectCoverage is set to true", }, { name: "panic running binary which outputs different coverage mode", args: args{ binPath: "./set_covermode", mainTestName: "TestRunMain", env: nil, args: nil, }, fields: fields{ MergedCoverageFilename: "temp_coverage.out", CollectCoverage: true, coverMode: "atomic", }, wantPanic: true, panicMessage: "cannot merge profiles with different coverage modes", }, { name: "panic running binary which outputs unexpected coverage mode", args: args{ binPath: "./test_bins/unexpected_covermode.sh", mainTestName: "", env: nil, args: nil, }, fields: fields{ MergedCoverageFilename: "temp_coverage.out", CollectCoverage: true, }, wantPanic: true, panicMessage: "unexpected coverage mode \"evil\" encountered. Coverage mode must be set, count, or atomic", }, { name: "fail running binary if there are no tests to run", args: args{ binPath: "./test_bins/no_tests.sh", mainTestName: "", env: nil, args: nil, }, wantErr: true, errMessage: "testing: warning: no tests to run\n", wantExitCode: -1, }, { name: "succeed running binary with stdin pipe preCmdFunc", args: args{ binPath: "./test_bins/read_stdin.sh", mainTestName: "", }, fields: fields{ MergedCoverageFilename: "temp_coverage.out", CollectCoverage: false, }, wantOutput: "Hello from prefunc\n", wantExitCode: 1, wantErr: false, cmdFuncs: []CoverageCollectorOption{stdinPipePreFuncCovCollectorOption()}, }, { name: "fail running binary with error in preCmdFunc", args: args{ binPath: "./test_bins/read_stdin.sh", mainTestName: "", }, fields: fields{ MergedCoverageFilename: "temp_coverage.out", CollectCoverage: false, }, errMessage: ohNoErrMsg, wantExitCode: -1, wantErr: true, cmdFuncs: []CoverageCollectorOption{errPreCmdFuncCovCollectorOption(ohNoErrMsg)}, }, { name: "fail running binary with error in postCmdFunc", args: args{ binPath: "./test_bins/read_stdin.sh", mainTestName: "", env: nil, args: nil, }, fields: fields{ MergedCoverageFilename: "temp_coverage.out", CollectCoverage: false, }, errMessage: ohNoErrMsg, wantExitCode: -1, wantErr: true, cmdFuncs: []CoverageCollectorOption{errPostCmdFuncCovCollectorOption(ohNoErrMsg)}, }, { name: "succeed running binary with pre and post cmdFuncs", args: args{ binPath: "./set_covermode", mainTestName: "TestRunMain", env: nil, args: nil, }, fields: fields{ MergedCoverageFilename: "temp_coverage.out", CollectCoverage: true, }, wantOutput: "Hello world\n", wantExitCode: 1, wantErr: false, cmdFuncs: []CoverageCollectorOption{nilPreCmdFunc(), nilPostCmdFunc()}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := NewCoverageCollector(tt.fields.MergedCoverageFilename, tt.fields.CollectCoverage) c.coverMode = tt.fields.coverMode if !tt.skipSetup { require.NoError(t, c.Setup()) } if tt.wantPanic { require.PanicsWithValue(t, tt.panicMessage, func() { _, _, _ = c.RunBinary(tt.args.binPath, tt.args.mainTestName, tt.args.env, tt.args.args, tt.cmdFuncs...) }, ) return } gotOutput, gotExitCode, err := c.RunBinary(tt.args.binPath, tt.args.mainTestName, tt.args.env, tt.args.args, tt.cmdFuncs...) if (err != nil) != tt.wantErr { t.Errorf("RunBinary() error = %v, wantErr %v", err, tt.wantErr) return } else if err != nil && tt.errMessage != err.Error() { t.Errorf("RunBinary() error =\n%v\nerrMessage =\n%v", err, tt.errMessage) } if tt.fields.CollectCoverage { require.Equal(t, 1, len(c.tmpCoverageFiles)) buf, err := ioutil.ReadAll(c.tmpCoverageFiles[0]) require.NoError(t, err) require.NotZero(t, len(buf)) } else { require.Zero(t, len(c.tmpCoverageFiles)) } if gotOutput != tt.wantOutput { t.Errorf("RunBinary() gotOutput = %v, want %v", gotOutput, tt.wantOutput) } if gotExitCode != tt.wantExitCode { t.Errorf("RunBinary() gotExitCode = %v, want %v", gotExitCode, tt.wantExitCode) } }) } } func stdinPipePreFuncCovCollectorOption() CoverageCollectorOption { f := PreCmdFunc(func(cmd *exec.Cmd) error { writer, _ := cmd.StdinPipe() _, err := writer.Write([]byte("Hello from prefunc\n")) writer.Close() return err }) return PreExec(f) } func errPreCmdFuncCovCollectorOption(errMsg string) CoverageCollectorOption { return PreExec(errPreCmdFunc(errMsg)) } func errPostCmdFuncCovCollectorOption(errMsg string) CoverageCollectorOption { return PostExec(errPostCmdFunc(errMsg)) } func nilPreCmdFunc() CoverageCollectorOption { f := PreCmdFunc(func(cmd *exec.Cmd) error { return nil }) return PreExec(f) } func printToBufferPreFunc(w io.Writer, toWrite string) PreCmdFunc { f := PreCmdFunc(func(cmd *exec.Cmd) error { _, err := w.Write([]byte(toWrite)) return err }) return f } func errPreCmdFunc(errMsg string) PreCmdFunc { f := PreCmdFunc(func(cmd *exec.Cmd) error { return errors.New(errMsg) }) return f } func printCommandOutputToBuffer(w io.Writer) PostCmdFunc { f := PostCmdFunc(func(cmd *exec.Cmd, output string, err error) error { _, writeErr := w.Write([]byte(output)) return writeErr }) return f } func errPostCmdFunc(errMsg string) PostCmdFunc { f := PostCmdFunc(func(cmd *exec.Cmd, output string, err error) error { return errors.New(errMsg) }) return f } func nilPostCmdFunc() CoverageCollectorOption { f := PostCmdFunc(func(cmd *exec.Cmd, output string, err error) error { return nil }) return PostExec(f) } func tempFile(t *testing.T) *os.File { f, err := ioutil.TempFile("", "") require.NoError(t, err) return f } func removedTempFile(t *testing.T) *os.File { f := tempFile(t) require.NoError(t, os.Remove(f.Name())) return f } func tempFileWithContent(t *testing.T, content string) *os.File { f := tempFile(t) _, err := f.WriteString(content) require.NoError(t, err) _, err = f.Seek(0, 0) require.NoError(t, err) return f } bincover-0.2.0/test_bins/000077500000000000000000000000001376422601700153225ustar00rootroot00000000000000bincover-0.2.0/test_bins/empty_covermode.sh000077500000000000000000000002121376422601700210550ustar00rootroot00000000000000#!/usr/bin/env bash echo Hello world echo START_BINCOVER_METADATA echo "{\"cover_mode\":\"\",\"exit_code\":1}" echo END_BINCOVER_METADATA bincover-0.2.0/test_bins/exit_1.sh000077500000000000000000000000541376422601700170510ustar00rootroot00000000000000#!/usr/bin/env bash echo Hello world exit 1 bincover-0.2.0/test_bins/no_tests.sh000077500000000000000000000000731376422601700175170ustar00rootroot00000000000000#!/usr/bin/env bash echo testing: warning: no tests to run bincover-0.2.0/test_bins/read_stdin.sh000077500000000000000000000002551376422601700177770ustar00rootroot00000000000000#!/usr/bin/env bash while read line do echo "$line" done < /dev/stdin echo START_BINCOVER_METADATA echo "{\"cover_mode\":\"\",\"exit_code\":1}" echo END_BINCOVER_METADATA bincover-0.2.0/test_bins/set_covermode.go000066400000000000000000000002111376422601700205010ustar00rootroot00000000000000package main import ( "fmt" "github.com/confluentinc/bincover" ) func main() { fmt.Println("Hello world") bincover.ExitCode = 1 } bincover-0.2.0/test_bins/set_covermode_test.go000066400000000000000000000002341376422601700215450ustar00rootroot00000000000000// +build testrunmain package main import ( "testing" "github.com/confluentinc/bincover" ) func TestRunMain(t *testing.T) { bincover.RunTest(main) } bincover-0.2.0/test_bins/unexpected_covermode.sh000077500000000000000000000002161376422601700220670ustar00rootroot00000000000000#!/usr/bin/env bash echo Hello world echo START_BINCOVER_METADATA echo "{\"cover_mode\":\"evil\",\"exit_code\":1}" echo END_BINCOVER_METADATA