Write CLI integration tests with Go

Signed-off-by: David Gageot <david@gageot.net>
This commit is contained in:
David Gageot
2016-01-19 16:54:03 +01:00
parent b812fc9e2a
commit 5d95524be6
18 changed files with 807 additions and 517 deletions

79
its/cli/create_rm_test.go Normal file
View File

@@ -0,0 +1,79 @@
package cli
import (
"testing"
"github.com/docker/machine/its"
)
func TestCreateRm(t *testing.T) {
test := its.NewTest(t)
defer test.TearDown()
test.Run("non-existent driver fails", func() {
test.Machine("create -d bogus bogus").Should().Fail(`Driver "bogus" not found. Do you have the plugin binary accessible in your PATH?`)
})
test.Run("non-existent driver fails", func() {
test.Machine("create -d bogus bogus").Should().Fail(`Driver "bogus" not found. Do you have the plugin binary accessible in your PATH?`)
})
test.Run("create with no name fails", func() {
test.Machine("create -d none").Should().Fail(`Error: No machine name specified`)
})
test.Run("create with invalid name fails", func() {
test.Machine("create -d none --url none ∞").Should().Fail(`Error creating machine: Invalid hostname specified. Allowed hostname chars are: 0-9a-zA-Z . -`)
})
test.Run("create with invalid name fails", func() {
test.Machine("create -d none --url none -").Should().Fail(`Error creating machine: Invalid hostname specified. Allowed hostname chars are: 0-9a-zA-Z . -`)
})
test.Run("create with invalid name fails", func() {
test.Machine("create -d none --url none .").Should().Fail(`Error creating machine: Invalid hostname specified. Allowed hostname chars are: 0-9a-zA-Z . -`)
})
test.Run("create with invalid name fails", func() {
test.Machine("create -d none --url none ..").Should().Fail(`Error creating machine: Invalid hostname specified. Allowed hostname chars are: 0-9a-zA-Z . -`)
})
test.Run("create with weird but valid name succeeds", func() {
test.Machine("create -d none --url none a").Should().Succeed()
})
test.Run("fail with extra argument", func() {
test.Machine("create -d none --url none a extra").Should().Fail(`Invalid command line. Found extra arguments [extra]`)
})
test.Run("create with weird but valid name", func() {
test.Machine("create -d none --url none 0").Should().Succeed()
})
test.Run("rm with no name fails", func() {
test.Machine("rm -y").Should().Fail(`Error: Expected to get one or more machine names as arguments`)
})
test.Run("rm non existent machine fails", func() {
test.Machine("rm ∞ -y").Should().Fail(`Error removing host "∞": Host does not exist: "∞"`)
})
test.Run("rm existing machine", func() {
test.Machine("rm 0 -y").Should().Succeed()
})
test.Run("rm ask user confirmation when -y is not provided", func() {
test.Machine("create -d none --url none ba").Should().Succeed()
test.Cmd("echo y | machine rm ba").Should().Succeed()
})
test.Run("rm deny user confirmation when -y is not provided", func() {
test.Machine("create -d none --url none ab").Should().Succeed()
test.Cmd("echo n | machine rm ab").Should().Succeed()
})
test.Run("rm never prompt user confirmation when -f is provided", func() {
test.Machine("create -d none --url none c").Should().Succeed()
test.Machine("rm -f c").Should().Succeed("Successfully removed c")
})
}

View File

@@ -0,0 +1,26 @@
package cli
import (
"testing"
"github.com/docker/machine/its"
)
func TestDriverHelp(t *testing.T) {
test := its.NewTest(t)
defer test.TearDown()
test.SkipDriver("ci-test")
test.Run("no --help flag or command specified", func() {
test.Machine("create -d $DRIVER").Should().Fail("Error: No machine name specified")
})
test.Run("-h flag specified", func() {
test.Machine("create -d $DRIVER -h").Should().Succeed(test.DriverName())
})
test.Run("--help flag specified", func() {
test.Machine("create -d $DRIVER --help").Should().Succeed(test.DriverName())
})
}

96
its/cli/help_test.go Normal file
View File

@@ -0,0 +1,96 @@
package cli
import (
"testing"
"github.com/docker/machine/its"
)
func TestHelp(t *testing.T) {
test := its.NewTest(t)
defer test.TearDown()
test.Run("cli: show info", func() {
test.Machine("").Should().Succeed("Usage:", "Create and manage machines running Docker")
})
test.Run("cli: show active help", func() {
test.Machine("active -h").Should().Succeed("machine active")
})
test.Run("cli: show config help", func() {
test.Machine("config -h").Should().Succeed("machine config")
})
test.Run("cli: show create help", func() {
test.Machine("create -h").Should().Succeed("machine create")
})
test.Run("cli: show env help", func() {
test.Machine("env -h").Should().Succeed("machine env")
})
test.Run("cli: show inspect help", func() {
test.Machine("inspect -h").Should().Succeed("machine inspect")
})
test.Run("cli: show ip help", func() {
test.Machine("ip -h").Should().Succeed("machine ip")
})
test.Run("cli: show kill help", func() {
test.Machine("kill -h").Should().Succeed("machine kill")
})
test.Run("cli: show ls help", func() {
test.Machine("ls -h").Should().Succeed("machine ls")
})
test.Run("cli: show regenerate-certs help", func() {
test.Machine("regenerate-certs -h").Should().Succeed("machine regenerate-certs")
})
test.Run("cli: show restart help", func() {
test.Machine("restart -h").Should().Succeed("machine restart")
})
test.Run("cli: show rm help", func() {
test.Machine("rm -h").Should().Succeed("machine rm")
})
test.Run("cli: show scp help", func() {
test.Machine("scp -h").Should().Succeed("machine scp")
})
test.Run("cli: show ssh help", func() {
test.Machine("ssh -h").Should().Succeed("machine ssh")
})
test.Run("cli: show start help", func() {
test.Machine("start -h").Should().Succeed("machine start")
})
test.Run("cli: show status help", func() {
test.Machine("status -h").Should().Succeed("machine status")
})
test.Run("cli: show stop help", func() {
test.Machine("stop -h").Should().Succeed("machine stop")
})
test.Run("cli: show upgrade help", func() {
test.Machine("upgrade -h").Should().Succeed("machine upgrade")
})
test.Run("cli: show url help", func() {
test.Machine("url -h").Should().Succeed("machine url")
})
test.Run("cli: show version", func() {
test.Machine("-v").Should().Succeed("version")
})
test.Run("cli: show help", func() {
test.Machine("--help").Should().Succeed("Usage:")
})
}

16
its/cli/inspect_test.go Normal file
View File

@@ -0,0 +1,16 @@
package cli
import (
"testing"
"github.com/docker/machine/its"
)
func TestInspect(t *testing.T) {
test := its.NewTest(t)
defer test.TearDown()
test.Run("inspect: show error in case of no args", func() {
test.Machine("inspect").Should().Fail(`Error: No machine name(s) specified and no "default" machine exists.`)
})
}

178
its/cli/ls_test.go Normal file
View File

@@ -0,0 +1,178 @@
package cli
import (
"testing"
"github.com/docker/machine/its"
)
func TestLs(t *testing.T) {
test := its.NewTest(t)
defer test.TearDown()
test.Run("setup", func() {
test.Machine("create -d none --url none --engine-label app=1 testmachine5").Should().Succeed()
test.Machine("create -d none --url none --engine-label foo=bar --engine-label app=1 testmachine4").Should().Succeed()
test.Machine("create -d none --url none testmachine3").Should().Succeed()
test.Machine("create -d none --url none testmachine2").Should().Succeed()
test.Machine("create -d none --url none testmachine").Should().Succeed()
})
test.Run("ls: filter on label", func() {
test.Machine("ls --filter label=foo=bar").Should().Succeed().
ContainLines(2).
ContainLine(0, "NAME").
ContainLine(1, "testmachine4")
})
test.Run("ls: mutiple filters on label", func() {
test.Machine("ls --filter label=foo=bar --filter label=app=1").Should().Succeed().
ContainLines(3).
ContainLine(0, "NAME").
ContainLine(1, "testmachine4").
ContainLine(2, "testmachine5")
})
test.Run("ls: non-existing filter on label", func() {
test.Machine("ls --filter label=invalid=filter").Should().Succeed().
ContainLines(1).
ContainLine(0, "NAME")
})
test.Run("ls: filter on driver", func() {
test.Machine("ls --filter driver=none").Should().Succeed().
ContainLines(6).
ContainLine(0, "NAME").
ContainLine(1, "testmachine").
ContainLine(2, "testmachine2").
ContainLine(3, "testmachine3").
ContainLine(4, "testmachine4").
ContainLine(5, "testmachine5")
})
test.Run("ls: filter on driver", func() {
test.Machine("ls -q --filter driver=none").Should().Succeed().
ContainLines(5).
EqualLine(0, "testmachine").
EqualLine(1, "testmachine2").
EqualLine(2, "testmachine3")
})
test.Run("ls: filter on state", func() {
test.Machine("ls --filter state=Running").Should().Succeed().
ContainLines(6).
ContainLine(0, "NAME").
ContainLine(1, "testmachine").
ContainLine(2, "testmachine2").
ContainLine(3, "testmachine3")
test.Machine("ls -q --filter state=Running").Should().Succeed().
ContainLines(5).
EqualLine(0, "testmachine").
EqualLine(1, "testmachine2").
EqualLine(2, "testmachine3")
test.Machine("ls --filter state=None").Should().Succeed().
ContainLines(1).
ContainLine(0, "NAME")
test.Machine("ls --filter state=Paused").Should().Succeed().
ContainLines(1).
ContainLine(0, "NAME")
test.Machine("ls --filter state=Saved").Should().Succeed().
ContainLines(1).
ContainLine(0, "NAME")
test.Machine("ls --filter state=Stopped").Should().Succeed().
ContainLines(1).
ContainLine(0, "NAME")
test.Machine("ls --filter state=Stopping").Should().Succeed().
ContainLines(1).
ContainLine(0, "NAME")
test.Machine("ls --filter state=Starting").Should().Succeed().
ContainLines(1).
ContainLine(0, "NAME")
test.Machine("ls --filter state=Error").Should().Succeed().
ContainLines(1).
ContainLine(0, "NAME")
})
test.Run("ls: filter on name", func() {
test.Machine("ls --filter name=testmachine2").Should().Succeed().
ContainLines(2).
ContainLine(0, "NAME").
ContainLine(1, "testmachine2")
test.Machine("ls -q --filter name=testmachine3").Should().Succeed().
ContainLines(1).
EqualLine(0, "testmachine3")
})
test.Run("ls: filter on name with regex", func() {
test.Machine("ls --filter name=^t.*e[3-5]").Should().Succeed().
ContainLines(4).
ContainLine(0, "NAME").
ContainLine(1, "testmachine3").
ContainLine(2, "testmachine4").
ContainLine(3, "testmachine5")
test.Machine("ls -q --filter name=^t.*e[45]").Should().Succeed().
ContainLines(2).
EqualLine(0, "testmachine4").
EqualLine(1, "testmachine5")
})
test.Run("setup swarm", func() {
test.Machine("create -d none --url tcp://127.0.0.1:2375 --swarm --swarm-master --swarm-discovery token://deadbeef testswarm").Should().Succeed()
test.Machine("create -d none --url tcp://127.0.0.1:2375 --swarm --swarm-discovery token://deadbeef testswarm2").Should().Succeed()
test.Machine("create -d none --url tcp://127.0.0.1:2375 --swarm --swarm-discovery token://deadbeef testswarm3").Should().Succeed()
})
test.Run("ls: filter on swarm", func() {
test.Machine("ls --filter swarm=testswarm").Should().Succeed().
ContainLines(4).
ContainLine(0, "NAME").
ContainLine(1, "testswarm").
ContainLine(2, "testswarm2").
ContainLine(3, "testswarm3")
})
test.Run("ls: multi filter", func() {
test.Machine("ls -q --filter swarm=testswarm --filter name=^t.*e --filter driver=none --filter state=Running").Should().Succeed().
ContainLines(3).
EqualLine(0, "testswarm").
EqualLine(1, "testswarm2").
EqualLine(2, "testswarm3")
})
test.Run("ls: format on driver", func() {
test.Machine("ls --format {{.DriverName}}").Should().Succeed().
ContainLines(8).
EqualLine(0, "none").
EqualLine(1, "none").
EqualLine(2, "none").
EqualLine(3, "none").
EqualLine(4, "none").
EqualLine(5, "none").
EqualLine(6, "none").
EqualLine(7, "none")
})
test.Run("ls: format on name and driver", func() {
test.Machine("ls --format 'table {{.Name}}: {{.DriverName}}'").Should().Succeed().
ContainLines(9).
ContainLine(0, "NAME").
EqualLine(1, "testmachine: none").
EqualLine(2, "testmachine2: none").
EqualLine(3, "testmachine3: none").
EqualLine(4, "testmachine4: none").
EqualLine(5, "testmachine5: none").
EqualLine(6, "testswarm: none").
EqualLine(7, "testswarm2: none").
EqualLine(8, "testswarm3: none")
})
}

16
its/cli/status_test.go Normal file
View File

@@ -0,0 +1,16 @@
package cli
import (
"testing"
"github.com/docker/machine/its"
)
func TestStatus(t *testing.T) {
test := its.NewTest(t)
defer test.TearDown()
test.Run("status: show error in case of no args", func() {
test.Machine("status").Should().Fail(`Error: No machine name(s) specified and no "default" machine exists.`)
})
}

16
its/cli/url_test.go Normal file
View File

@@ -0,0 +1,16 @@
package cli
import (
"testing"
"github.com/docker/machine/its"
)
func TestUrl(t *testing.T) {
test := its.NewTest(t)
defer test.TearDown()
test.Run("url: show error in case of no args", func() {
test.Machine("url").Should().Fail(`Error: No machine name(s) specified and no "default" machine exists.`)
})
}

345
its/tester.go Normal file
View File

@@ -0,0 +1,345 @@
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
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", count, len(dmt.lines))
}
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) 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
}

34
its/thirdparty/commands_test.go vendored Normal file
View File

@@ -0,0 +1,34 @@
package thirdparty
import (
"testing"
"github.com/docker/machine/its"
)
func TestThirdPartyCompatibility(t *testing.T) {
test := its.NewTest(t)
defer test.TearDown()
test.RequireDriver("ci-test")
test.Run("create", func() {
test.Machine("create -d $DRIVER --url url default").Should().Succeed()
})
test.Run("ls", func() {
test.Machine("ls -q").Should().Succeed().ContainLines(1).EqualLine(0, "default")
})
test.Run("url", func() {
test.Machine("url default").Should().Succeed("url")
})
test.Run("status", func() {
test.Machine("status default").Should().Succeed("Running")
})
test.Run("rm", func() {
test.Machine("rm -y default").Should().Succeed()
})
}