At the last Seattle Go Programmers Meetup there was some interest expressed in work I had done using Jenkins to handle continuous integration for a couple of projects. I’ve captured details below along with a Dockerfile that lets you get up and running quickly with a worked example that builds the Hugo project.
Setting up the demo within Docker
A docker file and job config is checked into GitHub that shows these ideas in action.
On a 64 bit Linux system running a recent version of docker perform the following steps.
Workspace Setup
Jenkins creates a workspace directory for each job that contains all source and generated artifacts. This makes a convenient GOPATH root; ensuring that all dependencies are known and accounted for.
Setup
- Install the EnvInject Jenkins plugins
- Under Build Environment, select ‘Inject environment variables to the build process’ and enter 'GOPATH=$WORKSPACE’ under Properties Contents
- All subsequent 'go get’ or other go tools calls will operate within the context of the Jenkins workspace directory for this job.
Git configuration
A useful configuration option when building projects from a Git repository is adding a Git reference that specifies the commit to be built by the job. The value can be defaulted to 'refs/heads/master’ which will build from master during automatic or ad-hoc execution if not overridden. However, if a one-off job run is desired for a particular tag, branch or commit; this can easily be specified as a parameter. This also makes it very easy to go back and run prior versions of a project, in order to see historical trends for test pass rates, benchmarks and warnings counts.
Setup
- Install the Git Client Jenkins plugin
- Configure a job parameter of COMMIT that defaults to 'refs/heads/master’
- In the job’s Git Client setup, set the 'Branch Specifier’ to $COMMIT
Running Tests
While there is no native support for ingesting 'go test’ output, Jenkins does have excellent handling of JUnit output. With the go2xunit utility, output from 'go test’ and go check can be transformed into a format that the JUnit Jenkins plugin can process. This makes possible capture and visualization of test output.
Setup
- Add a job step that installs go2xunit: 'go get bitbucket.org/tebeka/go2xunit’
- Add a job step that runs tests with the ’-v’ option and capture the output to a file: 'go test -v github.com/spf13/hugo/… > test.output’
- Add a job step that converts the go test output to JUnit xml: 'cat test.output | “./bin/go2xunit” -output tests.xml’
- Add a Post Build Action to Publish JUnit test results and specify 'tests.xml’
Visualizing Benchmarks
Go has great support for adding benchmarks. I’ve written a tiny utility gobench2plot that extracts benchmark output and transforms it into an XML format that can be plotted. This format can then be the existing Plot Jenkins plugin.
Setup
- Install the Plot Jenkins plugin
- Add a job step that installs gobench2plot: 'go get github.com/ryancox/gobench2plot’
- Add a job step that runs tests and benchmarks: 'go test -bench=Bench* -test.benchmem -v github.com/spf13/hugo/… > ${WORKSPACE}/test.output’
- Add a job step that transforms benchmark values in test output to xml: 'cat test.output | gobench2plot > benchmarks.xml’
- Add a post build job step that does a line style plot of the values; specifying benchmarks.xml and an appropriate XPath query: ’/Benchmarks/AllocsPerOp/*’
Static Analysis
The popular Jenkin plugin 'warnings’ parses and captures output of static analysis tools like lint and findbugs. There was no support for Golint and Go vet. So I added this capability and submitted a pull request to the project. This PR has been merged into master.
Note: At the time of writing, a new release has not been created for 'warnings’ and pushed to the Jenkins plugin server. So until then, you will need to build from source or use the binary in the example project by manually uploading the HPI file into Jenkins via the UI or just copying it to your plugins directory.
Setup
- Install the Warnings Jenkins plugin
- Add build step that installs glint or go vet: 'go get github.com/golang/lint/golint’
- Add build step that runs glint or go vet and saves output: ’./bin/golint github.com/spf13/hugo/… > lint.txt’
- Add a 'Scan for Compiler Warnings’ post build action that specifies the output file and Go Lint or Go Vet Parser.
Testing Multiple Go Versions
Package owners may want to test support and performance across several different versions of the Go. This can be accomplished using the Jenkins multi-configuration job type. This job type allow for various 'axis’ of execution to be defined. A new Jenkins build is created for each axis combination. A single axis named GOVERSION, for example, can be created that contains values such as 'go1.1.2 go1.2 go1.3 go tip’. In the shell execute steps, $GOVERSION would be used in place of the normal 'go’ command. This scheme requires that the system upon which Jenkins is executing has multiple Go. This can be accomplished by installing the binaries in a location that is accessible system wide ( e.g. /usr/local/go-1.1.2 and /usr/local/go-1.3 ), then creating symlinks in /usr/local/bin/go-1.1.2 and /usr/local/go-1.3 that point back to the binaries in these locations ). It can be useful to keep a current build from the tip of master installed and accessible as 'gotip’. This allows for testing of packages against the most recent changes to Go. TODO: Do I need the matrix plugin for this?
Setup
- Create build job as 'Build multi-configuration project’ rather than a 'Freestyle’ job type
- Under Configuration Matrix, define a User defined Axis of 'GOVERSION’ containing values 'go1.1.2 go1.2 go1.3’
- Replace calls to 'go’ command with $GOVERSION
- Configure host system with multiple versions of Go such that 'go1.1.2, go1.2 and go1.3 map to the respective versions of the tools
Note: To keep the example simple, this is not included in the accompanying Dockerfile
Special thanks to Dylan Meissner for his review of this post