docs/its/tester.go

364 lines
7.6 KiB
Go

package its
import (
"os/exec"
"regexp"
"strings"
"testing"
"io/ioutil"
"os"
"fmt"
"path/filepath"
"runtime"
)
var (
regexpCommandLine = regexp.MustCompile("('[^']*')|(\\S+)")
)
type IntegrationTest interface {
RequireDriver(driverName string)
SkipDriver(driverName string)
SkipDrivers(driverNames ...string)
ForceDriver(driverName string)
Run(description string, action func())
Cmd(commandLine string) IntegrationTest
Machine(commandLine string) IntegrationTest
DriverName() string
Should() Assertions
TearDown()
}
type Assertions interface {
Succeed(messages ...string) Assertions
Fail(errorMessages ...string) Assertions
ContainLines(count int) Assertions
ContainLine(index int, text string) Assertions
MatchLine(index int, template string) Assertions
EqualLine(index int, text string) Assertions
}
func NewTest(t *testing.T) IntegrationTest {
storagePath, _ := ioutil.TempDir("", "docker")
return &dockerMachineTest{
t: t,
storagePath: storagePath,
}
}
type dockerMachineTest struct {
t *testing.T
storagePath string
dockerMachineBinary string
description string
skip bool
rawOutput string
lines []string
err error
fatal bool
failed bool
}
func (dmt *dockerMachineTest) RequireDriver(driverName string) {
dmt.skipIf(dmt.DriverName() != driverName)
}
func (dmt *dockerMachineTest) SkipDriver(driverName string) {
dmt.skipIf(dmt.DriverName() == driverName)
}
func (dmt *dockerMachineTest) SkipDrivers(driverNames ...string) {
for _, driverName := range driverNames {
dmt.skipIf(dmt.DriverName() == driverName)
}
}
func (dmt *dockerMachineTest) ForceDriver(driverName string) {
os.Setenv("DRIVER", driverName)
}
func (dmt *dockerMachineTest) skipIf(condition bool) {
if condition {
dmt.skip = true
}
}
func (dmt *dockerMachineTest) Run(description string, action func()) {
dmt.description = description
dmt.rawOutput = ""
dmt.lines = nil
dmt.err = nil
dmt.failed = false
if dmt.skip {
fmt.Printf("%s %s\n", yellow("[SKIP]"), description)
} else {
fmt.Printf("%s %s", yellow("[..]"), description)
action()
if dmt.fatal || dmt.failed {
fmt.Printf("\r%s %s\n", red("[KO]"), description)
} else {
fmt.Printf("\r%s %s\n", green("[OK]"), description)
}
}
}
func red(message string) string {
if runtime.GOOS == "windows" {
return message
}
return "\033[1;31m" + message + "\033[0m"
}
func green(message string) string {
if runtime.GOOS == "windows" {
return message
}
return "\033[1;32m" + message + "\033[0m"
}
func yellow(message string) string {
if runtime.GOOS == "windows" {
return message
}
return "\033[1;33m" + message + "\033[0m"
}
func (dmt *dockerMachineTest) DriverName() string {
driver := os.Getenv("DRIVER")
if driver != "" {
return driver
}
return "none"
}
func (dmt *dockerMachineTest) Should() Assertions {
return dmt
}
func (dmt *dockerMachineTest) testedBinary() string {
if dmt.dockerMachineBinary != "" {
return dmt.dockerMachineBinary
}
var binary string
if runtime.GOOS == "windows" {
binary = "docker-machine.exe"
} else {
binary = "docker-machine"
}
_, file, _, _ := runtime.Caller(0)
dir := filepath.Dir(file)
for dir != "/" {
path := filepath.Join(dir, "bin", binary)
_, err := os.Stat(path)
if err == nil {
dmt.dockerMachineBinary = path
return path
}
dir = filepath.Dir(dir)
}
if !dmt.fatal {
dmt.fatal = true
dmt.t.Errorf("Binary not found: %s", binary)
}
return ""
}
func (dmt *dockerMachineTest) Cmd(commandLine string) IntegrationTest {
if dmt.fatal {
return dmt
}
commandLine = dmt.replaceDriver(commandLine)
commandLine = dmt.replaceMachinePath(commandLine)
return dmt.cmd("bash", "-c", commandLine)
}
func (dmt *dockerMachineTest) Machine(commandLine string) IntegrationTest {
if dmt.fatal {
return dmt
}
commandLine = dmt.replaceDriver(commandLine)
return dmt.cmd(dmt.testedBinary(), parseFields(commandLine)...)
}
func (dmt *dockerMachineTest) cmd(command string, args ...string) IntegrationTest {
cmd := exec.Command(command, args...)
cmd.Env = append(os.Environ(), "MACHINE_STORAGE_PATH="+dmt.storagePath)
combinedOutput, err := cmd.CombinedOutput()
dmt.rawOutput = string(combinedOutput)
dmt.lines = strings.Split(strings.TrimSpace(dmt.rawOutput), "\n")
dmt.err = err
return dmt
}
func (dmt *dockerMachineTest) replaceMachinePath(commandLine string) string {
return strings.Replace(commandLine, "machine", dmt.testedBinary(), -1)
}
func (dmt *dockerMachineTest) replaceDriver(commandLine string) string {
return strings.Replace(commandLine, "$DRIVER", dmt.DriverName(), -1)
}
func parseFields(commandLine string) []string {
fields := regexpCommandLine.FindAllString(commandLine, -1)
for i := range fields {
if len(fields[i]) > 2 && strings.HasPrefix(fields[i], "'") && strings.HasSuffix(fields[i], "'") {
fields[i] = fields[i][1 : len(fields[i])-1]
}
}
return fields
}
func (dmt *dockerMachineTest) TearDown() {
machines := filepath.Join(dmt.storagePath, "machines")
dirs, _ := ioutil.ReadDir(machines)
for _, dir := range dirs {
dmt.Cmd("machine rm -f " + dir.Name())
}
os.RemoveAll(dmt.storagePath)
}
func (dmt *dockerMachineTest) ContainLines(count int) Assertions {
if dmt.fatal {
return dmt
}
if count != len(dmt.lines) {
return dmt.failExpected("%d lines but got %d\n%s", count, len(dmt.lines), dmt.rawOutput)
}
return dmt
}
func (dmt *dockerMachineTest) ContainLine(index int, text string) Assertions {
if dmt.fatal {
return dmt
}
if index >= len(dmt.lines) {
return dmt.failExpected("at least %d lines\nGot %d", index+1, len(dmt.lines))
}
if !strings.Contains(dmt.lines[index], text) {
return dmt.failExpected("line %d to contain '%s'\nGot '%s'", index, text, dmt.lines[index])
}
return dmt
}
func (dmt *dockerMachineTest) MatchLine(index int, template string) Assertions {
if dmt.fatal {
return dmt
}
if index >= len(dmt.lines) {
return dmt.failExpected("at least %d lines\nGot %d", index+1, len(dmt.lines))
}
if !regexp.MustCompile(template).MatchString(dmt.lines[index]) {
return dmt.failExpected("line %d to match '%s'\nGot '%s'", index, template, dmt.lines[index])
}
return dmt
}
func (dmt *dockerMachineTest) EqualLine(index int, text string) Assertions {
if dmt.fatal {
return dmt
}
if index >= len(dmt.lines) {
return dmt.failExpected("at least %d lines\nGot %d", index+1, len(dmt.lines))
}
if text != dmt.lines[index] {
return dmt.failExpected("line %d to be '%s'\nGot '%s'", index, text, dmt.lines[index])
}
return dmt
}
func (dmt *dockerMachineTest) Succeed(messages ...string) Assertions {
if dmt.fatal {
return dmt
}
if dmt.err != nil {
return dmt.failExpected("to succeed\nFailed with %s\n%s", dmt.err, dmt.rawOutput)
}
for _, message := range messages {
if !strings.Contains(dmt.rawOutput, message) {
return dmt.failExpected("output to contain '%s'\nGot '%s'", message, dmt.rawOutput)
}
}
return dmt
}
func (dmt *dockerMachineTest) Fail(errorMessages ...string) Assertions {
if dmt.fatal {
return dmt
}
if dmt.err == nil {
return dmt.failExpected("to fail\nGot success\n%s", dmt.rawOutput)
}
for _, message := range errorMessages {
if !strings.Contains(dmt.rawOutput, message) {
return dmt.failExpected("output to contain '%s'\nGot '%s'", message, dmt.rawOutput)
}
}
return dmt
}
func (dmt *dockerMachineTest) failExpected(message string, args ...interface{}) Assertions {
allArgs := append([]interface{}{dmt.description}, args...)
dmt.failed = true
dmt.t.Errorf("%s\nExpected "+message, allArgs...)
return dmt
}