How to measure code coverage in Go

Sep 06, 2019
4 min read

Since version 1.2 code coverage is built into Go (Golang). Use go test -cover . to get basic coverage statistics for a single package.

$ go test -cover .
ok      calc        0.001s  coverage: 100.0% of statements

Unfortunately it does not show the total coverage for all your packages within a single project. To get the overall code coverage for multiple packages you have to create a coverprofile and use a second command go tool cover. First of all generate the coverage profile by using the -coverprofile flag instead of the -cover one and tell the command where to store the information.

$ go test ./... -coverprofile cover.out
ok      calc        0.001s  coverage: 100.0% of statements
ok      calc/format 0.001s  coverage: 100.0% of statements

Afterwards use go tool cover with the -func flag and point it to the previously generated file. This will show you the code coverage for every single package withing your project and down at the bottom the total coverage.

$ go tool cover -func cover.out
calc/calc.go:4:              Add             100.0%
calc/calc.go:9:              Subtract        100.0%
calc/calc.go:14:             Multiply        100.0%
calc/format/format.go:8:     Print           100.0%
total:                       (statements)    100.0%

This is great and the last line is the most important one for us. To only print this line use grep and filter for total:. This removes all the noise and returns only the total code coverage.

$ go tool cover -func cover.out | grep total:
total:                       (statements)    100.0%

The whole line is not really useful for automated processing. We only need the last value. By using awk we can easily deal with tabular data and make sure we're only getting the third string.

$ go tool cover -func cover.out | grep total | awk '{print $3}'
100.0%

We're almost done here. However for storing this value in our database we have to remove the percentage %. Luckily awk has substr built in and we can simply remove the last character. We have to keep in mind that the total code coverage could vary between 0% and 100%. Therefore the initial string might be between 2 and 4 characters long. To handle the unknown string length we will use the built in length method.

$ go tool cover -func cover.out | grep total | awk '{print substr($3, 1, length($3)-1)}'
100.0

Now we're done. We've got our total coverage as a plain number. We can take this number and post it to the SeriesCI API using curl and xargs.

$ go tool cover -func cover.out | grep total | awk '{print substr($3, 1, length($3)-1)}' | xargs -I {} curl \
    --header "Authorization: Token MY_TOKEN" \
    --data value="{}" \
    --data sha="MY_SHA" \
    https://seriesci.com/api/repos/:owner/:repo/:series/values

Simply add this little script to your Continuous Integration (CI) and automatically track your code coverage for every single commit.

In case you are using Travis CI copy the following snippet into your .travis.yaml.

after_success:
  - |
    go test ./... -coverprofile cover.out
    go tool cover -func cover.out | grep total | awk '{print substr($3, 1, length($3)-1)}' | xargs -I {} curl \
      --header "Authorization: Token ${TOKEN}" \
      --data value="{}" \
      --data sha="${TRAVIS_COMMIT}" \
      https://seriesci.com/api/repos/:owner/:repo/:series/values

${TOKEN} and ${TRAVIS_COMMIT} are environment variables which are available while your tests are running. ${TRAVIS_COMMIT} is automatically provided whereas ${TOKEN} has to be added manually.

In case you are using CircleCI copy the following snippet into your .circleci/config.yml.

- run:
    name: POST value to SeriesCI
    command: |
      go test ./... -coverprofile cover.out
      go tool cover -func cover.out | grep total | awk '{print substr($3, 1, length($3)-1)}' | xargs -I {} curl \
          --header "Authorization: Token ${TOKEN}" \
          --data value="{}" \
          --data sha="${CIRCLE_SHA1}" \
          https://seriesci.com/api/repos/:owner/:repo/:series/values

Again ${TOKEN} and ${CIRCLE_SHA1} are environment variables. ${CIRCLE_SHA1} automatically available whereas ${TOKEN} has to be set manually at CircleCI.

It's that easy to add cove coverage to your existing Go project. Start today by adding SeriesCI to your repository. To see all of the above in action have a look at our GitHub demo repository github.com/seriesci/calc. In case you are interested in the code coverage visit seriesci.com/seriesci/calc at SeriesCI.