This is quick braindump of getting SnailLife Go building and testing on Go 1.10.

A few days ago I decided to start building and testing SnailLife Go on Go 1.10 RC 1 (now RC 2). It took a bit of wrangling, but after updating my local environment and finding the best image to use for GitLab CI, I now have it building on Go 1.9 and 1.10 rc 2.

One nice thing with Go 1.10 is its ability to easily get coverage for tests across multiple packages and report them to the same coverprofile output file. In my Go 1.9 CI script you can see having to do some ugly wrangling and concatentaton of file contents to get some sort of multi-package coverage information.

Go 1.9.1

for d in $(go list ./... | grep -v vendor); do
    parentdir=`dirname "$d"`
    subdir=`basename "$d"`
    if [[ $subdir == "tests" ]]; then
        parentdir="$parentdir/..."
        go test -race -cover -coverpkg=$parentdir -coverprofile=profile.out $d
    else
        go test -race -cover -coverprofile=profile.out $d
    fi

    if [ -f profile.out ]; then
        echo "Merging coverage file for" $d
        tail -n+2 profile.out >> ../../allcoverage.out
        rm profile.out
    fi
done

For Go 1.10 I’ve been able to shorten this to one line:

Go 1.10

go test -race -coverpkg=./... -coverprofile allcoverage1.10rc.out ./...

If you try to run the above command in Go 1.9, you will get the following error: cannot use test profile flag with multiple packages. Go 1.10 introduces support for this.

Another change is that I am now not running this inside server/…, common/…, and client/… individually. I’m running this from the project root and having it cover the entire project.

While I’ve set up the CI to save the .html output which breaks down coverage for each file in the project (seen here), that wasn’t really enough - I also want to have an overall coverage percentage to refer to for the entire project. To me this seems like something go test would support natively, but I haven’t managed to find a way to get this yet. So at the advice of Tim Heckman in the Gophers Slack, I am now using the Gocov package for this as follows in my .gitlab-ci.yml:

    - gocov convert allcoverage1.10rc.out | gocov report

This provides “Total Coverage” output in the job log, which I can then use in my project’s Gitlab CI settings: ^Total Coverage.*

Now I can display SnailLife Go’s coverage percentage in the README:

coverage report

Not great, as you can see. If you look through the html output broken down per-file above you’ll see that I’ve mostly focused on testing the server infrastructure packages and am severely lacking in tests for the cli and REST API. Before proceeding with further porting from the PHP version I’m going to focus on improving testing in these areas and the bug fixing that comes out of that.

I’m not going to use test coverage percentage as the be-all-end-all number for my testing and I have no “coverage target”, but it is a uesful metric to see obvious gaps in my testing that I need to work on.

Running into Go bugs

While experimenting with the above setup I ran into and reported a couple of bugs:

  • #23694 - cmd/go: Stack overflow on running: go test with -coverpkg=all - I ran into this issue when trying to use the new -coverpkg=all filter on go test in conjunction with -race. The issue was actually a circular dependency related to covermode=atomic, which is always the case when running -race. In the end I chose not to use the “all” filter anyway as you can see above, but the Go team was also really quick to fix the issue once reported.

  • #1118 - could not find .debug_frame section in binary with "plugin" imported on Go 1.10rc1 - I ran into this when trying to debug a package that imports plugin. I was not sure if this was an issue with the Delve debugger not supporting Go 1.10 (ie, was .debug_frame removed on purpose in this release?) or with Go itself, so reported it on the Delve repo where the error originates. In the end it turns out to have been a Go issue (ie, it seems .debug_frame should not be missing), and the Delve maintainer reported it there (#23733). Fortunately I actually removed the dependency on plugin from my package a while back and just hadn’t removed the unneeded code yet so for my purposes this was really quick to get around (ie, stop importing plugin)