Open-source project
qiniu/goc avatar
qiniu/goc

goc: collecting Go coverage from a running service, not just from tests

A Comprehensive Coverage Testing System for The Go Programming Language

873 stars138 forksGoApache-2.0

At a glance

What is it?
goc is a coverage tool for Go that instruments a built binary so coverage can be collected at run time, which is what system and end to end testing needs, with a server, a build wrapper, a profile command and a merge step for restarts. It suits teams whose coverage report is empty because their tests do not run in-process.
Who is it for?
goc is a good fit for a team running system or end to end tests against a real service, since that is the case the standard coverage tools cannot reach, and the build wrapper plus a service registry is the shape that makes it work. It is the wrong choice for a project whose unit tests run in the same process as the code, because the standard tooling already covers that and adds no value here.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 173 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The case standard Go coverage cannot reach

The positioning is about a specific gap rather than a better way to do the same thing. goc is a comprehensive coverage testing system for the Go programming language, especially for some complex scenarios, like system testing code coverage collection and accurate testing. The reason a normal coverage report comes back empty in that situation is not a bug in the coverage tooling, it is the design of the Go toolchain, since the standard approach instruments the test binary, and a system test drives a separate service over a network, so the instrumented code is never the code that runs. goc takes a different route. You build the service with the goc wrapper rather than with go build, the resulting binary reports what it executes back to a service registry, and the coverage is collected from the running process. That is why there is a server, a build command and a profile command, and why the roadmap lists support for code coverage collection for system testing and develop mode towards accurate testing as delivered items. The roadmap also shows the honest remaining item, which is to optimise the performance cost of the coverage counters.

Three commands, and the profile output is standard

The workflow is three steps and the third one produces a file in the standard Go coverage format, which is the detail that makes the tool composable with everything else in the Go ecosystem. First you start the service registry centre with the server command. Then you build the target service with the build command and run the generated binary. Then you ask for the profile:

bash
 ➜  simple-go-server git:(master) ✗ goc profile
 mode: atomic
 enricofoltran/simple-go-server/main.go:30.13,48.33 13 1
 enricofoltran/simple-go-server/main.go:48.33,50.3 1 0
 enricofoltran/simple-go-server/main.go:52.2,65.12 5 1
 enricofoltran/simple-go-server/main.go:65.12,74.46 7 1
 enricofoltran/simple-go-server/main.go:74.46,76.4 1 0

The format is the familiar one, with the file path, the start and end positions of the block, the number of statements and the number of executions, and the mode is reported as atomic. That means the output can be fed to the standard coverage tooling, merged, diffed and rendered without a converter, which is the reason goc is a drop-in for an existing pipeline rather than a parallel one. The readme goes further and annotates what each column means, from the file and the two block positions through the statement count to the execution count, so the output is readable by someone who has not used a coverage profile before.

The random agent port, and the flag that fixes it in containers

The second tip in the readme is the one that will cost you an afternoon if you miss it. By default the covered service listens on a random port in order to communicate with the goc server, and the documentation says plainly that this may not be suitable in a Docker or a Kubernetes environment, since the port must be exposed explicitly in order to be accessible by others in that kind of environment. The fix is the agentport flag, which specifies a fixed port when calling the build or install command. This is a real constraint of the design rather than a rough edge, because the covered service is a separate process that has to call home, and in a container the port it happens to pick is not the port your orchestrator published. The third tip extends the same idea to remote setups, with the center flag to compile the target service against a goc server that is not on the same machine, which is what you need when the service under test runs in a cluster and the registry runs on your laptop.

Coverage across a restart, and how to merge the pieces

The fourth tip covers a case that will appear the moment your service is not perfectly stable. The coverage data is stored on each covered service side, so if one service needs to restart during a test, that service's coverage data is lost. The documented workaround is three steps. Before the service restarts, you collect the coverage to a file. After the service has restarted and the test has finished, you collect again to a second file. Then you merge the two profiles together with the merge command:

bash
goc profile -o a.cov
goc profile -o b.cov
goc merge a.cov b.cov -o merge.cov

The last tip is about naming, and it is smaller but easy to trip over. By default goc uses the artifact's file name as its service name, and you can overwrite it by setting the environment variable for the service name, with a link to the issue that tracks it. That matters in a test suite where several services are built from binaries with unhelpful names, because the name is how you tell the profiles apart when you collect them from several running services at once. The readme also asks that you use the debug flag to understand the execution details of the tool, and mentions that logs like that are welcome with a bug report, which is a small sign of how the project is run.

Installing from the release tarballs, and the Go version split

There is no package manager route documented, and the installation is a set of shell pipelines that pull the latest release archive for your platform and drop the binary into the path. For example for Mac on AMD64:

bash
curl -s https://api.github.com/repos/qiniu/goc/releases/latest | grep "browser_download_url.*-darwin-amd64.tar.gz" | cut -d : -f 2,3 | tr -d \" | xargs -n 1 curl -L | tar -zx && chmod +x goc && mv goc /usr/local/bin

The pattern is the same for Linux on AMD64 and for Linux on 386, with the architecture string in the grep expression changed, and the whole thing is one line that queries the GitHub releases API, extracts the download URL, fetches it, unpacks it and moves the binary. It is a dated approach compared with a package manager, but it has the advantage of not requiring Go at all to install the tool, which matters for a CI image that only needs the binary. On the Go requirement there is a split worth noting: goc supports both GOPATH projects and Go Modules projects with Go 1.11 or newer, but to develop goc itself you need Go 1.13 or newer. The go.mod in the repository asks for Go 1.13, which is consistent with that development requirement rather than with the runtime one.

The dependency list and the shape of the codebase

The go.mod shows what the tool is built from and it is a conventional Go service plus a command line application. The web framework is gin with the julienschmidt router, and the configuration is handled by viper with cobra for the command line and pflag for the flags, with logrus for logging and tablewriter for the terminal output. The retrying HTTP client comes from hashicorp, the JSON handling from the json-iterator library, and the Qiniu API client is a direct dependency, which tells you the project comes from Qiniu and that its own storage and upload features are part of the tool rather than an afterthought. There is also a Kubernetes test infrastructure dependency, which is a strong hint that the project is exercised against a real cluster, and testify for the unit tests. The repository layout follows the same split, with a cmd directory for the command line entry points, a pkg directory for the library parts, a goc.go at the root, a tests directory, a tools directory, a docs directory with images, a ci-build script and a Makefile. The Makefile is small and useful, with the default target installing every package, a test target that runs with coverage and excludes the end to end packages, a format target, a vet target and a clean target that removes the stray coverage and project directories the integration tests leave behind.

Editorial conclusion

goc is a good fit for a team running system or end to end tests against a real service, since that is the case the standard coverage tools cannot reach, and the build wrapper plus a service registry is the shape that makes it work. It is the wrong choice for a project whose unit tests run in the same process as the code, because the standard tooling already covers that and adds no value here. It is also the wrong choice if you cannot run a long-lived service during your test suite, since the whole model depends on the service being alive to report on. Before adopting it, read the tips section rather than only the walkthrough, because the random agent port is the first thing that breaks in a container or a Kubernetes environment, and the fixed port flag exists for exactly that reason. The project is Apache-2.0 licensed, the last push was on 2026-05-03, and the most recent tagged release is v1.4.5 from 2023-03-19.

Frequently asked questions

What is goc and what problem does it solve?

It is a coverage testing system for Go aimed at complex scenarios such as system testing code coverage collection and accurate testing. Because the standard approach instruments the test binary, coverage is empty when a system test drives a separate service over the network, and goc instruments the built service itself so coverage can be collected at run time.

What are the three steps to collect coverage with goc?

Start the service registry centre with the server command, build the target service with the build command and run the generated binary, then ask for the profile with the profile command. The output is in the standard Go coverage format, with the file path, the block positions, the statement count and the execution count, and the mode reported as atomic.

Why does goc need a fixed agent port in Docker or Kubernetes?

By default the covered service listens on a random port to talk to the goc server, and in a container or Kubernetes environment that port has to be exposed explicitly to be reachable. The agentport flag sets a fixed port when calling the build or install command, and the center flag compiles the target service against a goc server on another machine.

How do you keep coverage data when a service restarts during a test?

The coverage data lives on each covered service, so a restart loses it. Collect the coverage to a file before the restart, collect again to a second file after the service has come back and the test has finished, and then merge the two with the merge command.

What Go version does goc need?

Using goc supports both GOPATH projects and Go Modules projects with Go 1.11 or newer, while developing goc itself needs Go 1.13 or newer, which is what the go.mod in the repository asks for. The licence is Apache-2.0, the last push was on 2026-05-03, and the most recent tagged release is v1.4.5 from 2023-03-19.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. qiniu/goc on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/qiniu-goc.svg)](https://hysenlabs.com/projects/qiniu-goc)

Was this useful?