Skip to content

Instantly share code, notes, and snippets.

@bhcleek
Last active October 2, 2018 16:42
Show Gist options
  • Save bhcleek/d0b350a2aa7ae0c098ff to your computer and use it in GitHub Desktop.
Save bhcleek/d0b350a2aa7ae0c098ff to your computer and use it in GitHub Desktop.
Go TeamCity Reporter

Go TeamCity Reporter

go build is great for developer builds, CI needs more control of the build process.

make.bash

Calls make all and pipes the output to a sed script that transforms the output that TeamCity can understand in the form of service messages

Makefile

Builds all the packages in the cmd directory, and runs all tests on all packages, generating a cover profile for each package. The cover profiles are then collected into a single cover profile to be analayzed by go tool cover. Finally, output of go tool cover is packaged into a zip file.

teamcity-reporter.sed

Transforms output from go test -v (and the one info line that the Makefile adds before running each package's tests) into TeamCity service messages.

It also publishes the coverage.zip that the Makefile generates as an artifact so that TeamCity will display the code coverage report on the coverage tab for the build.

#! /bin/bash
set -o pipefail
make all | ([[ "$TEAMCITY_PROJECT_NAME" != "" ]] && sed -f teamcity-reporter.sed || cat)
SHELL = /bin/sh
PKGS := $(shell go list ./... | grep -v /vendor/)
CMDS := $(notdir $(shell go list -f '{{if eq .Name "main"}}{{.ImportPath}}{{end}}' ./cmd/...))
GO_FILES := $(shell go list -f '{{$$dir := .Dir}}{{range .GoFiles}}{{print $$dir "/" . "\n"}}{{end}}{{range .TestGoFiles}}{{print $$dir "/" . "\n"}}{{end}}' ./... | grep -v /vendor/)
GO_VENDOR_FILES := $(shell go list -f '{{$$dir := .Dir}}{{range .GoFiles}}{{print $$dir "/" . "\n"}}{{end}}' ./vendor/...)
WORK := $(shell mktemp -d)
CMD_ROOT := __MY_PROJECT__
go_cmd_deps = $(shell go list -f '{{$$dir := .Dir}}{{range .GoFiles}}{{print $$dir "/" . "\n"}}{{end}}' ./cmd/$(1)) $(shell go list -f '{{join .Deps "\n"}}' ./cmd/$(1) | grep -E '^$(CMD_ROOT)(/.*)?$$' | xargs go list -f '{{$$dir := .Dir}}{{range .GoFiles}}{{print $$dir "/" . "\n"}}{{end}}')
all : $(PKGS) $(CMDS) test coverage.zip
test : $(PKGS)
.PHONY : all test $(PKGS)
$(WORK)/coverage :
mkdir -p $(WORK)/coverage
coverage.zip: $(WORK)/coverage/index.html | $(WORK)/coverage
cd $(WORK)/coverage && find . -print | zip ../coverage.zip -@
cp $(WORK)/coverage.zip coverage.zip
$(WORK)/coverage/index.html : $(WORK)/cover.out | $(WORK)/coverage
go tool cover -func=$(WORK)/cover.out -o $(WORK)/coverage/cover.func
go tool cover -html=$(WORK)/cover.out -o $(WORK)/coverage/index.html
$(WORK)/cover.out : test
find $(WORK) -mindepth 2 -name 'cover.out' | head -n 1 | xargs head -n 1 > $(WORK)/cover.out
find $(WORK) -mindepth 2 -name 'cover.out' -print0 | xargs -0 tail -q -n +2 >> $(WORK)/cover.out
$(PKGS) : $(GO_SOURCES) $(GO_VENDOR_FILES) generate.done
$(info TESTING $@)
mkdir -p $(WORK)/$@
godep go test -race -v -coverprofile=$(WORK)/$@/cover.out -covermode=atomic $@
$(CMDS) : $(GO_VENDOR_FILES)
go build $(CMDBUILDFLAGS) ./cmd/$@
.SECONDEXPANSION:
$(CMDS) : $$(call go_cmd_deps,$$@)
#! /usr/bin/sed -f
# Test durations are reported in seconds with a precision of a hundredeth of a
# second and ending with 'seconds' (e.g. '(0.54 seconds)'). Package test durations are reported in seconds with a precision of a
# thousandth of a second and ending with 's' (e.g. '0.542s'), but not wrapped in parentheses. TeamCity expects the
# durations to be be reported in milliseconds.
/^TESTING[[:space:]]*/ s/^TESTING[[:space:]]*\([^[:space:]]*\).*/##teamcity[testSuiteStarted name='\1']/
# The line matching ^=== RUN[[:space:]]* could be used as the trigger to write
# the testStarted message, but when subtests are run, they all print their
# start message before any of their results are reported. Using the "=== RUN"
# line as the signal to write the testStarted message causes the n+1 test to
# implicitly mark the nth test as finished unless flowId is specified.
# Unfortunately, using flowId causes subtests to nest within each other. Just
# punt by deleting the line and write the testStarted message when each test is
# finished.
/^=== RUN[[:space:]]*/ d
/^\( \)\{0,1\}--- PASS:[[:space:]]*/ s/^\( \)\{0,1\}--- PASS:[[:space:]]*\([^[:space:]]*\)[[:space:]]*(\([[:digit:]]*\)\.\([[:digit:]]*\).*/##teamcity[testStarted name='\2' captureStandardOutput='false']\
##teamcity[testFinished name='\2' duration='\3\40']/
/^\( \)\{0,1\}--- FAIL:[[:space:]]*/ s/^\( \)\{0,1\}--- FAIL:[[:space:]]*\([^[:space:]]*\)[[:space:]]*(\([[:digit:]]*\)\.\([[:digit:]]*\).*/##teamcity[testStarted name='\2' captureStandardOutput='false']\
##teamcity[testFailed name='\2']\
##teamcity[testFinished name='\2' duration='\3\40']/
/^ok[[:space:]]*/ s/^ok[[:space:]]*\([^[:space:]]*\)[[:space:]]*\([[:digit:]]*\)\.\([[:digit:]]*\).*/##teamcity[testSuiteFinished name='\1' duration='\2\3']/
/^FAIL[[:space:]]\{1,\}/ s/^FAIL[[:space:]]\{1,\}\([^[:space:]]*\)[[:space:]]*\([[:digit:]]*\)\.\([[:digit:]]*\).*/##teamcity[testSuiteFinished name='\1' duration='\2\3']/
/^?[[:space:]]\{1,\}/ s/^?[[:space:]]\{1,\}\([^[:space:]]*\)[[:space:]]\{1,\}\[no test files\].*/##teamcity[testSuiteFinished name='\1']/
$ a\
##teamcity[publishArtifacts 'coverage.zip']
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment