Skip to content

Commit

Permalink
Introduce mage and enable arm testing via docker+qemu (#32)
Browse files Browse the repository at this point in the history
We introduce support for testing other environments here. For now we only add ARM (32bit) support to our tests.

The Travis configs are updated to cross-compile and run the testsuite on ARM, via qemu. I've been able to reproduce the panic on atomic access, which has been fixed in #31 with these tests + some false assumption in an unit test has been fixed as well.

As the build and testing environment has become somewhat more complicated due to the need to cross compile and run/test via qemu, we introduce mage here. We hope for the full test suite and cross-combile/test support to be executable via Windows, Linux, and Darwin. We still target x86_64 dev environments, though.
  • Loading branch information
Steffen Siering authored Jan 24, 2019
1 parent c1b61de commit 6785e86
Show file tree
Hide file tree
Showing 9 changed files with 844 additions and 15 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@

# Project-local glide cache, RE: https:/Masterminds/glide/issues/736
.glide/

# build directory
/build/
42 changes: 37 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,55 @@ env:
jobs:
include:
# try to cross compile to untested OSes
- env: TARGET=openbsd
- name: XBuild OpenBSD
env: TARGET=openbsd
go: $GO_CROSS_VERSION
script: eval $GO_CHECK_CROSS_SCRIPT
- env: TARGET=netbsd
- name: XBuild NetBSD
env: TARGET=netbsd
go: $GO_CROSS_VERSION
script: eval $GO_CHECK_CROSS_SCRIPT
- env: TARGET=freebsd
- name: XBuild FreeBSD
env: TARGET=freebsd
go: $GO_CROSS_VERSION
script: eval $GO_CHECK_CROSS_SCRIPT

- name: 32Bit ARM go1.10
env: [BUILD_OS=linux, BUILD_ARCH=arm]
go: '1.10'
services: [docker]
before_install:
- docker run --rm --privileged multiarch/qemu-user-static:register --reset
- |
go get -u -d github.com/magefile/mage
(cd $GOPATH/src/github.com/magefile/mage; go run bootstrap.go)
script:
- mage -v test

- name: 32Bit ARM go1.11
env: [BUILD_OS=linux, BUILD_ARCH=arm]
go: '1.11'
services: [docker]
before_install:
- docker run --rm --privileged multiarch/qemu-user-static:register --reset
- |
go get -u -d github.com/magefile/mage
(cd $GOPATH/src/github.com/magefile/mage; go run bootstrap.go)
script:
- mage -v test

# Check we're testing the correct commit (Snippet from: https:/travis-ci/travis-ci/issues/7459#issuecomment-287040521)
before_install:
- |
if [[ "$TRAVIS_COMMIT" != "$(git rev-parse HEAD)" ]]; then
echo "Commit $(git rev-parse HEAD) doesn't match expected commit $TRAVIS_COMMIT"
exit 1
fi
- |
# install mage
go get -u -d github.com/magefile/mage
(cd $GOPATH/src/github.com/magefile/mage; go run bootstrap.go)
script: |
go test -cover -v ./...
script:
- go env
- mage -v test
255 changes: 255 additions & 0 deletions dev-tools/lib/mage/gotool/go.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package gotool

import (
"os"
"strings"

"github.com/magefile/mage/mg"
"github.com/magefile/mage/sh"
)

// Args holds parameters, environment variables and flag information used to
// pass to the go tool.
type Args struct {
extra map[string]string // extra flags one can pass to the command
env map[string]string
flags map[string]string
pos []string
}

// ArgOpt is a functional option adding info to Args once executed.
type ArgOpt func(args *Args)

type goTest func(opts ...ArgOpt) error

// Test runs `go test` and provides optionals for adding command line arguments.
var Test goTest = runGoTest

// ListProjectPackages lists all packages in the current project
func ListProjectPackages() ([]string, error) {
return ListPackages("./...")
}

// ListPackages calls `go list` for every package spec given.
func ListPackages(pkgs ...string) ([]string, error) {
return getLines(callGo(nil, "list", pkgs...))
}

// ListTestFiles lists all go and cgo test files available in a package.
func ListTestFiles(pkg string) ([]string, error) {
const tmpl = `{{ range .TestGoFiles }}{{ printf "%s\n" . }}{{ end }}` +
`{{ range .XTestGoFiles }}{{ printf "%s\n" . }}{{ end }}`

return getLines(callGo(nil, "list", "-f", tmpl, pkg))
}

// HasTests returns true if the given package contains test files.
func HasTests(pkg string) (bool, error) {
files, err := ListTestFiles(pkg)
if err != nil {
return false, err
}
return len(files) > 0, nil
}

func (goTest) WithCoverage(to string) ArgOpt {
return combine(flagArg("-cover", ""), flagArgIf("-test.coverprofile", to))
}
func (goTest) Short(b bool) ArgOpt { return flagBoolIf("-test.short", b) }
func (goTest) Use(bin string) ArgOpt { return extraArgIf("use", bin) }
func (goTest) OS(os string) ArgOpt { return envArgIf("GOOS", os) }
func (goTest) ARCH(arch string) ArgOpt { return envArgIf("GOARCH", arch) }
func (goTest) Create() ArgOpt { return flagArg("-c", "") }
func (goTest) Out(path string) ArgOpt { return flagArg("-o", path) }
func (goTest) Package(path string) ArgOpt { return posArg(path) }
func (goTest) Verbose() ArgOpt { return flagArg("-test.v", "") }
func runGoTest(opts ...ArgOpt) error {
args := buildArgs(opts)
if bin := args.Val("use"); bin != "" {
flags := map[string]string{}
for k, v := range args.flags {
if strings.HasPrefix(k, "-test.") {
flags[k] = v
}
}

useArgs := &Args{}
*useArgs = *args
useArgs.flags = flags

_, err := sh.Exec(useArgs.env, os.Stdout, os.Stderr, bin, useArgs.build()...)
return err
}

return runVGo("test", args)
}

func getLines(out string, err error) ([]string, error) {
if err != nil {
return nil, err
}

lines := strings.Split(out, "\n")
res := lines[:0]
for _, line := range lines {
line = strings.TrimSpace(line)
if len(line) > 0 {
res = append(res, line)
}
}

return res, nil
}

func callGo(env map[string]string, cmd string, opts ...string) (string, error) {
args := []string{cmd}
args = append(args, opts...)
return sh.OutputWith(env, mg.GoCmd(), args...)
}

func runVGo(cmd string, args *Args) error {
return execGoWith(func(env map[string]string, cmd string, args ...string) error {
_, err := sh.Exec(env, os.Stdout, os.Stderr, cmd, args...)
return err
}, cmd, args)
}

func runGo(cmd string, args *Args) error {
return execGoWith(sh.RunWith, cmd, args)
}

func execGoWith(
fn func(map[string]string, string, ...string) error,
cmd string, args *Args,
) error {
cliArgs := []string{cmd}
cliArgs = append(cliArgs, args.build()...)
return fn(args.env, mg.GoCmd(), cliArgs...)
}

func posArg(value string) ArgOpt {
return func(a *Args) { a.Add(value) }
}

func extraArg(k, v string) ArgOpt {
return func(a *Args) { a.Extra(k, v) }
}

func extraArgIf(k, v string) ArgOpt {
if v == "" {
return nil
}
return extraArg(k, v)
}

func envArg(k, v string) ArgOpt {
return func(a *Args) { a.Env(k, v) }
}

func envArgIf(k, v string) ArgOpt {
if v == "" {
return nil
}
return envArg(k, v)
}

func flagArg(flag, value string) ArgOpt {
return func(a *Args) { a.Flag(flag, value) }
}

func flagArgIf(flag, value string) ArgOpt {
if value == "" {
return nil
}
return flagArg(flag, value)
}

func flagBoolIf(flag string, b bool) ArgOpt {
if b {
return flagArg(flag, "")
}
return nil
}

func combine(opts ...ArgOpt) ArgOpt {
return func(a *Args) {
for _, opt := range opts {
if opt != nil {
opt(a)
}
}
}
}

func buildArgs(opts []ArgOpt) *Args {
a := &Args{}
combine(opts...)(a)
return a
}

// Extra sets a special k/v pair to be interpreted by the execution function.
func (a *Args) Extra(k, v string) {
if a.extra == nil {
a.extra = map[string]string{}
}
a.extra[k] = v
}

// Val returns a special functions value for a given key.
func (a *Args) Val(k string) string {
if a.extra == nil {
return ""
}
return a.extra[k]
}

// Env sets an environmant variable to be passed to the child process on exec.
func (a *Args) Env(k, v string) {
if a.env == nil {
a.env = map[string]string{}
}
a.env[k] = v
}

// Flag adds a flag to be passed to the child process on exec.
func (a *Args) Flag(flag, value string) {
if a.flags == nil {
a.flags = map[string]string{}
}
a.flags[flag] = value
}

// Add adds a positional argument to be passed to the child process on exec.
func (a *Args) Add(p string) {
a.pos = append(a.pos, p)
}

func (a *Args) build() []string {
args := make([]string, 0, 2*len(a.flags)+len(a.pos))
for k, v := range a.flags {
args = append(args, k)
if v != "" {
args = append(args, v)
}
}

args = append(args, a.pos...)
return args
}
Loading

0 comments on commit 6785e86

Please sign in to comment.