I have a production golang code and functional tests for it written not in golang. Functional tests run compiled binary. Very simplified version of my production code is here: main.go
:
package main
import (
"fmt"
"math/rand"
"os"
"time"
)
func main() {
rand.Seed(time.Now().UTC().UnixNano())
for {
i := rand.Int()
fmt.Println(i)
if i%3 == 0 {
os.Exit(0)
}
if i%2 == 0 {
os.Exit(1)
}
time.Sleep(time.Second)
}
}
I want to build coverage profile for my functional tests. In order to do it I add main_test.go
file with content:
package main
import (
"os"
"testing"
)
var exitCode int
func Test_main(t *testing.T) {
go main()
exitCode = <-exitCh
}
func TestMain(m *testing.M) {
m.Run()
// can exit because cover profile is already written
os.Exit(exitCode)
}
And modify main.go
:
package main
import (
"flag"
"fmt"
"math/rand"
"os"
"runtime"
"time"
)
var exitCh chan int = make(chan int)
func main() {
rand.Seed(time.Now().UTC().UnixNano())
for {
i := rand.Int()
fmt.Println(i)
if i%3 == 0 {
exit(0)
}
if i%2 == 0 {
fmt.Println("status 1")
exit(1)
}
time.Sleep(time.Second)
}
}
func exit(code int) {
if flag.Lookup("test.coverprofile") != nil {
exitCh <- code
runtime.Goexit()
} else {
os.Exit(code)
}
}
Then I build coverage binary:
go test -c -coverpkg=. -o myProgram
Then my functional tests run this coverage binary, like this:
./myProgram -test.coverprofile=/tmp/profile
6507374435908599516
PASS
coverage: 64.3% of statements in .
And I build HTML output showing coverage:
$ go tool cover -html /tmp/profile -o /tmp/profile.html
$ open /tmp/profile.html
Problem
Method exit
will never show 100% coverage because of condition if flag.Lookup("test.coverprofile") != nil
. So line os.Exit(code)
is kinda blind spot for my coverage results, although, in fact, functional tests go on this line and this line should be shown as green.
On the other hand, if I remove condition if flag.Lookup("test.coverprofile") != nil
, the line os.Exit(code)
will terminate my binary without building coverage profile.
How to rewrite exit()
and maybe main_test.go
to show coverage without blind spots?
The first solution that comes into mind is time.Sleep()
:
func exit(code int) {
exitCh <- code
time.Sleep(time.Second) // wait some time to let coverprofile be written
os.Exit(code)
}
}
But it's not very good because will cause production code slow down before exit.