aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLewis Marshall <lewis@lmars.net>2017-06-21 20:54:23 +0800
committerFelix Lange <fjl@users.noreply.github.com>2017-06-21 20:54:23 +0800
commita1f3878ec50c8d9ce410d9715fa4c7ee998a3639 (patch)
tree101d29006107167fdfef99f13b741eb9c3d082b7
parenta20a02ce0bc4af67275eba418ff385f00bc87068 (diff)
downloaddexon-a1f3878ec50c8d9ce410d9715fa4c7ee998a3639.tar
dexon-a1f3878ec50c8d9ce410d9715fa4c7ee998a3639.tar.gz
dexon-a1f3878ec50c8d9ce410d9715fa4c7ee998a3639.tar.bz2
dexon-a1f3878ec50c8d9ce410d9715fa4c7ee998a3639.tar.lz
dexon-a1f3878ec50c8d9ce410d9715fa4c7ee998a3639.tar.xz
dexon-a1f3878ec50c8d9ce410d9715fa4c7ee998a3639.tar.zst
dexon-a1f3878ec50c8d9ce410d9715fa4c7ee998a3639.zip
swarm/test: add integration test for 'swarm up' (#14353)
-rw-r--r--cmd/geth/accountcmd_test.go72
-rw-r--r--cmd/geth/consolecmd_test.go54
-rw-r--r--cmd/geth/dao_test.go4
-rw-r--r--cmd/geth/genesis_test.go6
-rw-r--r--cmd/geth/run_test.go250
-rw-r--r--cmd/swarm/run_test.go255
-rw-r--r--cmd/swarm/upload_test.go76
-rw-r--r--internal/cmdtest/test_cmd.go270
-rw-r--r--vendor/github.com/docker/docker/LICENSE191
-rw-r--r--vendor/github.com/docker/docker/NOTICE19
-rw-r--r--vendor/github.com/docker/docker/pkg/reexec/README.md5
-rw-r--r--vendor/github.com/docker/docker/pkg/reexec/command_linux.go28
-rw-r--r--vendor/github.com/docker/docker/pkg/reexec/command_unix.go23
-rw-r--r--vendor/github.com/docker/docker/pkg/reexec/command_unsupported.go12
-rw-r--r--vendor/github.com/docker/docker/pkg/reexec/command_windows.go23
-rw-r--r--vendor/github.com/docker/docker/pkg/reexec/reexec.go47
-rw-r--r--vendor/vendor.json6
17 files changed, 1048 insertions, 293 deletions
diff --git a/cmd/geth/accountcmd_test.go b/cmd/geth/accountcmd_test.go
index 5f9f67677..e146323ee 100644
--- a/cmd/geth/accountcmd_test.go
+++ b/cmd/geth/accountcmd_test.go
@@ -44,21 +44,21 @@ func tmpDatadirWithKeystore(t *testing.T) string {
func TestAccountListEmpty(t *testing.T) {
geth := runGeth(t, "account", "list")
- geth.expectExit()
+ geth.ExpectExit()
}
func TestAccountList(t *testing.T) {
datadir := tmpDatadirWithKeystore(t)
geth := runGeth(t, "account", "list", "--datadir", datadir)
- defer geth.expectExit()
+ defer geth.ExpectExit()
if runtime.GOOS == "windows" {
- geth.expect(`
+ geth.Expect(`
Account #0: {7ef5a6135f1fd6a02593eedc869c6d41d934aef8} keystore://{{.Datadir}}\keystore\UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8
Account #1: {f466859ead1932d743d622cb74fc058882e8648a} keystore://{{.Datadir}}\keystore\aaa
Account #2: {289d485d9771714cce91d3393d764e1311907acc} keystore://{{.Datadir}}\keystore\zzz
`)
} else {
- geth.expect(`
+ geth.Expect(`
Account #0: {7ef5a6135f1fd6a02593eedc869c6d41d934aef8} keystore://{{.Datadir}}/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8
Account #1: {f466859ead1932d743d622cb74fc058882e8648a} keystore://{{.Datadir}}/keystore/aaa
Account #2: {289d485d9771714cce91d3393d764e1311907acc} keystore://{{.Datadir}}/keystore/zzz
@@ -68,20 +68,20 @@ Account #2: {289d485d9771714cce91d3393d764e1311907acc} keystore://{{.Datadir}}/k
func TestAccountNew(t *testing.T) {
geth := runGeth(t, "account", "new", "--lightkdf")
- defer geth.expectExit()
- geth.expect(`
+ defer geth.ExpectExit()
+ geth.Expect(`
Your new account is locked with a password. Please give a password. Do not forget this password.
!! Unsupported terminal, password will be echoed.
Passphrase: {{.InputLine "foobar"}}
Repeat passphrase: {{.InputLine "foobar"}}
`)
- geth.expectRegexp(`Address: \{[0-9a-f]{40}\}\n`)
+ geth.ExpectRegexp(`Address: \{[0-9a-f]{40}\}\n`)
}
func TestAccountNewBadRepeat(t *testing.T) {
geth := runGeth(t, "account", "new", "--lightkdf")
- defer geth.expectExit()
- geth.expect(`
+ defer geth.ExpectExit()
+ geth.Expect(`
Your new account is locked with a password. Please give a password. Do not forget this password.
!! Unsupported terminal, password will be echoed.
Passphrase: {{.InputLine "something"}}
@@ -95,8 +95,8 @@ func TestAccountUpdate(t *testing.T) {
geth := runGeth(t, "account", "update",
"--datadir", datadir, "--lightkdf",
"f466859ead1932d743d622cb74fc058882e8648a")
- defer geth.expectExit()
- geth.expect(`
+ defer geth.ExpectExit()
+ geth.Expect(`
Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3
!! Unsupported terminal, password will be echoed.
Passphrase: {{.InputLine "foobar"}}
@@ -108,8 +108,8 @@ Repeat passphrase: {{.InputLine "foobar2"}}
func TestWalletImport(t *testing.T) {
geth := runGeth(t, "wallet", "import", "--lightkdf", "testdata/guswallet.json")
- defer geth.expectExit()
- geth.expect(`
+ defer geth.ExpectExit()
+ geth.Expect(`
!! Unsupported terminal, password will be echoed.
Passphrase: {{.InputLine "foo"}}
Address: {d4584b5f6229b7be90727b0fc8c6b91bb427821f}
@@ -123,8 +123,8 @@ Address: {d4584b5f6229b7be90727b0fc8c6b91bb427821f}
func TestWalletImportBadPassword(t *testing.T) {
geth := runGeth(t, "wallet", "import", "--lightkdf", "testdata/guswallet.json")
- defer geth.expectExit()
- geth.expect(`
+ defer geth.ExpectExit()
+ geth.Expect(`
!! Unsupported terminal, password will be echoed.
Passphrase: {{.InputLine "wrong"}}
Fatal: could not decrypt key with given passphrase
@@ -137,19 +137,19 @@ func TestUnlockFlag(t *testing.T) {
"--datadir", datadir, "--nat", "none", "--nodiscover", "--dev",
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a",
"js", "testdata/empty.js")
- geth.expect(`
+ geth.Expect(`
Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3
!! Unsupported terminal, password will be echoed.
Passphrase: {{.InputLine "foobar"}}
`)
- geth.expectExit()
+ geth.ExpectExit()
wantMessages := []string{
"Unlocked account",
"=0xf466859ead1932d743d622cb74fc058882e8648a",
}
for _, m := range wantMessages {
- if !strings.Contains(geth.stderrText(), m) {
+ if !strings.Contains(geth.StderrText(), m) {
t.Errorf("stderr text does not contain %q", m)
}
}
@@ -160,8 +160,8 @@ func TestUnlockFlagWrongPassword(t *testing.T) {
geth := runGeth(t,
"--datadir", datadir, "--nat", "none", "--nodiscover", "--dev",
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a")
- defer geth.expectExit()
- geth.expect(`
+ defer geth.ExpectExit()
+ geth.Expect(`
Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3
!! Unsupported terminal, password will be echoed.
Passphrase: {{.InputLine "wrong1"}}
@@ -180,14 +180,14 @@ func TestUnlockFlagMultiIndex(t *testing.T) {
"--datadir", datadir, "--nat", "none", "--nodiscover", "--dev",
"--unlock", "0,2",
"js", "testdata/empty.js")
- geth.expect(`
+ geth.Expect(`
Unlocking account 0 | Attempt 1/3
!! Unsupported terminal, password will be echoed.
Passphrase: {{.InputLine "foobar"}}
Unlocking account 2 | Attempt 1/3
Passphrase: {{.InputLine "foobar"}}
`)
- geth.expectExit()
+ geth.ExpectExit()
wantMessages := []string{
"Unlocked account",
@@ -195,7 +195,7 @@ Passphrase: {{.InputLine "foobar"}}
"=0x289d485d9771714cce91d3393d764e1311907acc",
}
for _, m := range wantMessages {
- if !strings.Contains(geth.stderrText(), m) {
+ if !strings.Contains(geth.StderrText(), m) {
t.Errorf("stderr text does not contain %q", m)
}
}
@@ -207,7 +207,7 @@ func TestUnlockFlagPasswordFile(t *testing.T) {
"--datadir", datadir, "--nat", "none", "--nodiscover", "--dev",
"--password", "testdata/passwords.txt", "--unlock", "0,2",
"js", "testdata/empty.js")
- geth.expectExit()
+ geth.ExpectExit()
wantMessages := []string{
"Unlocked account",
@@ -215,7 +215,7 @@ func TestUnlockFlagPasswordFile(t *testing.T) {
"=0x289d485d9771714cce91d3393d764e1311907acc",
}
for _, m := range wantMessages {
- if !strings.Contains(geth.stderrText(), m) {
+ if !strings.Contains(geth.StderrText(), m) {
t.Errorf("stderr text does not contain %q", m)
}
}
@@ -226,8 +226,8 @@ func TestUnlockFlagPasswordFileWrongPassword(t *testing.T) {
geth := runGeth(t,
"--datadir", datadir, "--nat", "none", "--nodiscover", "--dev",
"--password", "testdata/wrong-passwords.txt", "--unlock", "0,2")
- defer geth.expectExit()
- geth.expect(`
+ defer geth.ExpectExit()
+ geth.Expect(`
Fatal: Failed to unlock account 0 (could not decrypt key with given passphrase)
`)
}
@@ -238,14 +238,14 @@ func TestUnlockFlagAmbiguous(t *testing.T) {
"--keystore", store, "--nat", "none", "--nodiscover", "--dev",
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a",
"js", "testdata/empty.js")
- defer geth.expectExit()
+ defer geth.ExpectExit()
// Helper for the expect template, returns absolute keystore path.
- geth.setTemplateFunc("keypath", func(file string) string {
+ geth.SetTemplateFunc("keypath", func(file string) string {
abs, _ := filepath.Abs(filepath.Join(store, file))
return abs
})
- geth.expect(`
+ geth.Expect(`
Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3
!! Unsupported terminal, password will be echoed.
Passphrase: {{.InputLine "foobar"}}
@@ -257,14 +257,14 @@ Your passphrase unlocked keystore://{{keypath "1"}}
In order to avoid this warning, you need to remove the following duplicate key files:
keystore://{{keypath "2"}}
`)
- geth.expectExit()
+ geth.ExpectExit()
wantMessages := []string{
"Unlocked account",
"=0xf466859ead1932d743d622cb74fc058882e8648a",
}
for _, m := range wantMessages {
- if !strings.Contains(geth.stderrText(), m) {
+ if !strings.Contains(geth.StderrText(), m) {
t.Errorf("stderr text does not contain %q", m)
}
}
@@ -275,14 +275,14 @@ func TestUnlockFlagAmbiguousWrongPassword(t *testing.T) {
geth := runGeth(t,
"--keystore", store, "--nat", "none", "--nodiscover", "--dev",
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a")
- defer geth.expectExit()
+ defer geth.ExpectExit()
// Helper for the expect template, returns absolute keystore path.
- geth.setTemplateFunc("keypath", func(file string) string {
+ geth.SetTemplateFunc("keypath", func(file string) string {
abs, _ := filepath.Abs(filepath.Join(store, file))
return abs
})
- geth.expect(`
+ geth.Expect(`
Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3
!! Unsupported terminal, password will be echoed.
Passphrase: {{.InputLine "wrong"}}
@@ -292,5 +292,5 @@ Multiple key files exist for address f466859ead1932d743d622cb74fc058882e8648a:
Testing your passphrase against all of them...
Fatal: None of the listed files could be unlocked.
`)
- geth.expectExit()
+ geth.ExpectExit()
}
diff --git a/cmd/geth/consolecmd_test.go b/cmd/geth/consolecmd_test.go
index e5472836c..258b9e6dd 100644
--- a/cmd/geth/consolecmd_test.go
+++ b/cmd/geth/consolecmd_test.go
@@ -47,15 +47,15 @@ func TestConsoleWelcome(t *testing.T) {
"console")
// Gather all the infos the welcome message needs to contain
- geth.setTemplateFunc("goos", func() string { return runtime.GOOS })
- geth.setTemplateFunc("goarch", func() string { return runtime.GOARCH })
- geth.setTemplateFunc("gover", runtime.Version)
- geth.setTemplateFunc("gethver", func() string { return params.Version })
- geth.setTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format(time.RFC1123) })
- geth.setTemplateFunc("apis", func() string { return ipcAPIs })
+ geth.SetTemplateFunc("goos", func() string { return runtime.GOOS })
+ geth.SetTemplateFunc("goarch", func() string { return runtime.GOARCH })
+ geth.SetTemplateFunc("gover", runtime.Version)
+ geth.SetTemplateFunc("gethver", func() string { return params.Version })
+ geth.SetTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format(time.RFC1123) })
+ geth.SetTemplateFunc("apis", func() string { return ipcAPIs })
// Verify the actual welcome message to the required template
- geth.expect(`
+ geth.Expect(`
Welcome to the Geth JavaScript console!
instance: Geth/v{{gethver}}/{{goos}}-{{goarch}}/{{gover}}
@@ -66,7 +66,7 @@ at block: 0 ({{niltime}})
> {{.InputLine "exit"}}
`)
- geth.expectExit()
+ geth.ExpectExit()
}
// Tests that a console can be attached to a running node via various means.
@@ -90,8 +90,8 @@ func TestIPCAttachWelcome(t *testing.T) {
time.Sleep(2 * time.Second) // Simple way to wait for the RPC endpoint to open
testAttachWelcome(t, geth, "ipc:"+ipc, ipcAPIs)
- geth.interrupt()
- geth.expectExit()
+ geth.Interrupt()
+ geth.ExpectExit()
}
func TestHTTPAttachWelcome(t *testing.T) {
@@ -104,8 +104,8 @@ func TestHTTPAttachWelcome(t *testing.T) {
time.Sleep(2 * time.Second) // Simple way to wait for the RPC endpoint to open
testAttachWelcome(t, geth, "http://localhost:"+port, httpAPIs)
- geth.interrupt()
- geth.expectExit()
+ geth.Interrupt()
+ geth.ExpectExit()
}
func TestWSAttachWelcome(t *testing.T) {
@@ -119,29 +119,29 @@ func TestWSAttachWelcome(t *testing.T) {
time.Sleep(2 * time.Second) // Simple way to wait for the RPC endpoint to open
testAttachWelcome(t, geth, "ws://localhost:"+port, httpAPIs)
- geth.interrupt()
- geth.expectExit()
+ geth.Interrupt()
+ geth.ExpectExit()
}
func testAttachWelcome(t *testing.T, geth *testgeth, endpoint, apis string) {
// Attach to a running geth note and terminate immediately
attach := runGeth(t, "attach", endpoint)
- defer attach.expectExit()
- attach.stdin.Close()
+ defer attach.ExpectExit()
+ attach.CloseStdin()
// Gather all the infos the welcome message needs to contain
- attach.setTemplateFunc("goos", func() string { return runtime.GOOS })
- attach.setTemplateFunc("goarch", func() string { return runtime.GOARCH })
- attach.setTemplateFunc("gover", runtime.Version)
- attach.setTemplateFunc("gethver", func() string { return params.Version })
- attach.setTemplateFunc("etherbase", func() string { return geth.Etherbase })
- attach.setTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format(time.RFC1123) })
- attach.setTemplateFunc("ipc", func() bool { return strings.HasPrefix(endpoint, "ipc") })
- attach.setTemplateFunc("datadir", func() string { return geth.Datadir })
- attach.setTemplateFunc("apis", func() string { return apis })
+ attach.SetTemplateFunc("goos", func() string { return runtime.GOOS })
+ attach.SetTemplateFunc("goarch", func() string { return runtime.GOARCH })
+ attach.SetTemplateFunc("gover", runtime.Version)
+ attach.SetTemplateFunc("gethver", func() string { return params.Version })
+ attach.SetTemplateFunc("etherbase", func() string { return geth.Etherbase })
+ attach.SetTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format(time.RFC1123) })
+ attach.SetTemplateFunc("ipc", func() bool { return strings.HasPrefix(endpoint, "ipc") })
+ attach.SetTemplateFunc("datadir", func() string { return geth.Datadir })
+ attach.SetTemplateFunc("apis", func() string { return apis })
// Verify the actual welcome message to the required template
- attach.expect(`
+ attach.Expect(`
Welcome to the Geth JavaScript console!
instance: Geth/v{{gethver}}/{{goos}}-{{goarch}}/{{gover}}
@@ -152,7 +152,7 @@ at block: 0 ({{niltime}}){{if ipc}}
> {{.InputLine "exit" }}
`)
- attach.expectExit()
+ attach.ExpectExit()
}
// trulyRandInt generates a crypto random integer used by the console tests to
diff --git a/cmd/geth/dao_test.go b/cmd/geth/dao_test.go
index ec7802ada..8cc66aabf 100644
--- a/cmd/geth/dao_test.go
+++ b/cmd/geth/dao_test.go
@@ -112,12 +112,12 @@ func testDAOForkBlockNewChain(t *testing.T, test int, genesis string, expectBloc
if err := ioutil.WriteFile(json, []byte(genesis), 0600); err != nil {
t.Fatalf("test %d: failed to write genesis file: %v", test, err)
}
- runGeth(t, "--datadir", datadir, "init", json).cmd.Wait()
+ runGeth(t, "--datadir", datadir, "init", json).WaitExit()
} else {
// Force chain initialization
args := []string{"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--datadir", datadir}
geth := runGeth(t, append(args, []string{"--exec", "2+2", "console"}...)...)
- geth.cmd.Wait()
+ geth.WaitExit()
}
// Retrieve the DAO config flag from the database
path := filepath.Join(datadir, "geth", "chaindata")
diff --git a/cmd/geth/genesis_test.go b/cmd/geth/genesis_test.go
index 6c3ca7298..a00ae00c1 100644
--- a/cmd/geth/genesis_test.go
+++ b/cmd/geth/genesis_test.go
@@ -97,14 +97,14 @@ func TestCustomGenesis(t *testing.T) {
if err := ioutil.WriteFile(json, []byte(tt.genesis), 0600); err != nil {
t.Fatalf("test %d: failed to write genesis file: %v", i, err)
}
- runGeth(t, "--datadir", datadir, "init", json).cmd.Wait()
+ runGeth(t, "--datadir", datadir, "init", json).WaitExit()
// Query the custom genesis block
geth := runGeth(t,
"--datadir", datadir, "--maxpeers", "0", "--port", "0",
"--nodiscover", "--nat", "none", "--ipcdisable",
"--exec", tt.query, "console")
- geth.expectRegexp(tt.result)
- geth.expectExit()
+ geth.ExpectRegexp(tt.result)
+ geth.ExpectExit()
}
}
diff --git a/cmd/geth/run_test.go b/cmd/geth/run_test.go
index e26b4509a..da82facac 100644
--- a/cmd/geth/run_test.go
+++ b/cmd/geth/run_test.go
@@ -17,18 +17,13 @@
package main
import (
- "bufio"
- "bytes"
"fmt"
- "io"
"io/ioutil"
"os"
- "os/exec"
- "regexp"
- "sync"
"testing"
- "text/template"
- "time"
+
+ "github.com/docker/docker/pkg/reexec"
+ "github.com/ethereum/go-ethereum/internal/cmdtest"
)
func tmpdir(t *testing.T) string {
@@ -40,36 +35,37 @@ func tmpdir(t *testing.T) string {
}
type testgeth struct {
- // For total convenience, all testing methods are available.
- *testing.T
- // template variables for expect
- Datadir string
- Executable string
- Etherbase string
- Func template.FuncMap
+ *cmdtest.TestCmd
- removeDatadir bool
- cmd *exec.Cmd
- stdout *bufio.Reader
- stdin io.WriteCloser
- stderr *testlogger
+ // template variables for expect
+ Datadir string
+ Etherbase string
}
func init() {
- // Run the app if we're the child process for runGeth.
- if os.Getenv("GETH_TEST_CHILD") != "" {
+ // Run the app if we've been exec'd as "geth-test" in runGeth.
+ reexec.Register("geth-test", func() {
if err := app.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
os.Exit(0)
+ })
+}
+
+func TestMain(m *testing.M) {
+ // check if we have been reexec'd
+ if reexec.Init() {
+ return
}
+ os.Exit(m.Run())
}
// spawns geth with the given command line args. If the args don't set --datadir, the
// child g gets a temporary data directory.
func runGeth(t *testing.T, args ...string) *testgeth {
- tt := &testgeth{T: t, Executable: os.Args[0]}
+ tt := &testgeth{}
+ tt.TestCmd = cmdtest.NewTestCmd(t, tt)
for i, arg := range args {
switch {
case arg == "-datadir" || arg == "--datadir":
@@ -84,215 +80,19 @@ func runGeth(t *testing.T, args ...string) *testgeth {
}
if tt.Datadir == "" {
tt.Datadir = tmpdir(t)
- tt.removeDatadir = true
+ tt.Cleanup = func() { os.RemoveAll(tt.Datadir) }
args = append([]string{"-datadir", tt.Datadir}, args...)
// Remove the temporary datadir if something fails below.
defer func() {
if t.Failed() {
- os.RemoveAll(tt.Datadir)
+ tt.Cleanup()
}
}()
}
- // Boot "geth". This actually runs the test binary but the init function
- // will prevent any tests from running.
- tt.stderr = &testlogger{t: t}
- tt.cmd = exec.Command(os.Args[0], args...)
- tt.cmd.Env = append(os.Environ(), "GETH_TEST_CHILD=1")
- tt.cmd.Stderr = tt.stderr
- stdout, err := tt.cmd.StdoutPipe()
- if err != nil {
- t.Fatal(err)
- }
- tt.stdout = bufio.NewReader(stdout)
- if tt.stdin, err = tt.cmd.StdinPipe(); err != nil {
- t.Fatal(err)
- }
- if err := tt.cmd.Start(); err != nil {
- t.Fatal(err)
- }
- return tt
-}
-
-// InputLine writes the given text to the childs stdin.
-// This method can also be called from an expect template, e.g.:
-//
-// geth.expect(`Passphrase: {{.InputLine "password"}}`)
-func (tt *testgeth) InputLine(s string) string {
- io.WriteString(tt.stdin, s+"\n")
- return ""
-}
-
-func (tt *testgeth) setTemplateFunc(name string, fn interface{}) {
- if tt.Func == nil {
- tt.Func = make(map[string]interface{})
- }
- tt.Func[name] = fn
-}
-
-// expect runs its argument as a template, then expects the
-// child process to output the result of the template within 5s.
-//
-// If the template starts with a newline, the newline is removed
-// before matching.
-func (tt *testgeth) expect(tplsource string) {
- // Generate the expected output by running the template.
- tpl := template.Must(template.New("").Funcs(tt.Func).Parse(tplsource))
- wantbuf := new(bytes.Buffer)
- if err := tpl.Execute(wantbuf, tt); err != nil {
- panic(err)
- }
- // Trim exactly one newline at the beginning. This makes tests look
- // much nicer because all expect strings are at column 0.
- want := bytes.TrimPrefix(wantbuf.Bytes(), []byte("\n"))
- if err := tt.matchExactOutput(want); err != nil {
- tt.Fatal(err)
- }
- tt.Logf("Matched stdout text:\n%s", want)
-}
-
-func (tt *testgeth) matchExactOutput(want []byte) error {
- buf := make([]byte, len(want))
- n := 0
- tt.withKillTimeout(func() { n, _ = io.ReadFull(tt.stdout, buf) })
- buf = buf[:n]
- if n < len(want) || !bytes.Equal(buf, want) {
- // Grab any additional buffered output in case of mismatch
- // because it might help with debugging.
- buf = append(buf, make([]byte, tt.stdout.Buffered())...)
- tt.stdout.Read(buf[n:])
- // Find the mismatch position.
- for i := 0; i < n; i++ {
- if want[i] != buf[i] {
- return fmt.Errorf("Output mismatch at ◊:\n---------------- (stdout text)\n%s◊%s\n---------------- (expected text)\n%s",
- buf[:i], buf[i:n], want)
- }
- }
- if n < len(want) {
- return fmt.Errorf("Not enough output, got until ◊:\n---------------- (stdout text)\n%s\n---------------- (expected text)\n%s◊%s",
- buf, want[:n], want[n:])
- }
- }
- return nil
-}
-
-// expectRegexp expects the child process to output text matching the
-// given regular expression within 5s.
-//
-// Note that an arbitrary amount of output may be consumed by the
-// regular expression. This usually means that expect cannot be used
-// after expectRegexp.
-func (tt *testgeth) expectRegexp(resource string) (*regexp.Regexp, []string) {
- var (
- re = regexp.MustCompile(resource)
- rtee = &runeTee{in: tt.stdout}
- matches []int
- )
- tt.withKillTimeout(func() { matches = re.FindReaderSubmatchIndex(rtee) })
- output := rtee.buf.Bytes()
- if matches == nil {
- tt.Fatalf("Output did not match:\n---------------- (stdout text)\n%s\n---------------- (regular expression)\n%s",
- output, resource)
- return re, nil
- }
- tt.Logf("Matched stdout text:\n%s", output)
- var submatch []string
- for i := 0; i < len(matches); i += 2 {
- submatch = append(submatch, string(output[i:i+1]))
- }
- return re, submatch
-}
-
-// expectExit expects the child process to exit within 5s without
-// printing any additional text on stdout.
-func (tt *testgeth) expectExit() {
- var output []byte
- tt.withKillTimeout(func() {
- output, _ = ioutil.ReadAll(tt.stdout)
- })
- tt.cmd.Wait()
- if tt.removeDatadir {
- os.RemoveAll(tt.Datadir)
- }
- if len(output) > 0 {
- tt.Errorf("Unmatched stdout text:\n%s", output)
- }
-}
-
-func (tt *testgeth) interrupt() {
- tt.cmd.Process.Signal(os.Interrupt)
-}
-
-// stderrText returns any stderr output written so far.
-// The returned text holds all log lines after expectExit has
-// returned.
-func (tt *testgeth) stderrText() string {
- tt.stderr.mu.Lock()
- defer tt.stderr.mu.Unlock()
- return tt.stderr.buf.String()
-}
-
-func (tt *testgeth) withKillTimeout(fn func()) {
- timeout := time.AfterFunc(5*time.Second, func() {
- tt.Log("killing the child process (timeout)")
- tt.cmd.Process.Kill()
- if tt.removeDatadir {
- os.RemoveAll(tt.Datadir)
- }
- })
- defer timeout.Stop()
- fn()
-}
+ // Boot "geth". This actually runs the test binary but the TestMain
+ // function will prevent any tests from running.
+ tt.Run("geth-test", args...)
-// testlogger logs all written lines via t.Log and also
-// collects them for later inspection.
-type testlogger struct {
- t *testing.T
- mu sync.Mutex
- buf bytes.Buffer
-}
-
-func (tl *testlogger) Write(b []byte) (n int, err error) {
- lines := bytes.Split(b, []byte("\n"))
- for _, line := range lines {
- if len(line) > 0 {
- tl.t.Logf("(stderr) %s", line)
- }
- }
- tl.mu.Lock()
- tl.buf.Write(b)
- tl.mu.Unlock()
- return len(b), err
-}
-
-// runeTee collects text read through it into buf.
-type runeTee struct {
- in interface {
- io.Reader
- io.ByteReader
- io.RuneReader
- }
- buf bytes.Buffer
-}
-
-func (rtee *runeTee) Read(b []byte) (n int, err error) {
- n, err = rtee.in.Read(b)
- rtee.buf.Write(b[:n])
- return n, err
-}
-
-func (rtee *runeTee) ReadRune() (r rune, size int, err error) {
- r, size, err = rtee.in.ReadRune()
- if err == nil {
- rtee.buf.WriteRune(r)
- }
- return r, size, err
-}
-
-func (rtee *runeTee) ReadByte() (b byte, err error) {
- b, err = rtee.in.ReadByte()
- if err == nil {
- rtee.buf.WriteByte(b)
- }
- return b, err
+ return tt
}
diff --git a/cmd/swarm/run_test.go b/cmd/swarm/run_test.go
new file mode 100644
index 000000000..2d32a51c8
--- /dev/null
+++ b/cmd/swarm/run_test.go
@@ -0,0 +1,255 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of go-ethereum.
+//
+// go-ethereum is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// go-ethereum is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
+
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "net"
+ "os"
+ "path/filepath"
+ "runtime"
+ "testing"
+ "time"
+
+ "github.com/docker/docker/pkg/reexec"
+ "github.com/ethereum/go-ethereum/accounts/keystore"
+ "github.com/ethereum/go-ethereum/internal/cmdtest"
+ "github.com/ethereum/go-ethereum/node"
+ "github.com/ethereum/go-ethereum/p2p"
+ "github.com/ethereum/go-ethereum/rpc"
+ "github.com/ethereum/go-ethereum/swarm"
+)
+
+func init() {
+ // Run the app if we've been exec'd as "swarm-test" in runSwarm.
+ reexec.Register("swarm-test", func() {
+ if err := app.Run(os.Args); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ os.Exit(0)
+ })
+}
+
+func TestMain(m *testing.M) {
+ // check if we have been reexec'd
+ if reexec.Init() {
+ return
+ }
+ os.Exit(m.Run())
+}
+
+func runSwarm(t *testing.T, args ...string) *cmdtest.TestCmd {
+ tt := cmdtest.NewTestCmd(t, nil)
+
+ // Boot "swarm". This actually runs the test binary but the TestMain
+ // function will prevent any tests from running.
+ tt.Run("swarm-test", args...)
+
+ return tt
+}
+
+type testCluster struct {
+ Nodes []*testNode
+ TmpDir string
+}
+
+// newTestCluster starts a test swarm cluster of the given size.
+//
+// A temporary directory is created and each node gets a data directory inside
+// it.
+//
+// Each node listens on 127.0.0.1 with random ports for both the HTTP and p2p
+// ports (assigned by first listening on 127.0.0.1:0 and then passing the ports
+// as flags).
+//
+// When starting more than one node, they are connected together using the
+// admin SetPeer RPC method.
+func newTestCluster(t *testing.T, size int) *testCluster {
+ cluster := &testCluster{}
+ defer func() {
+ if t.Failed() {
+ cluster.Shutdown()
+ }
+ }()
+
+ tmpdir, err := ioutil.TempDir("", "swarm-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ cluster.TmpDir = tmpdir
+
+ // start the nodes
+ cluster.Nodes = make([]*testNode, 0, size)
+ for i := 0; i < size; i++ {
+ dir := filepath.Join(cluster.TmpDir, fmt.Sprintf("swarm%02d", i))
+ if err := os.Mkdir(dir, 0700); err != nil {
+ t.Fatal(err)
+ }
+
+ node := newTestNode(t, dir)
+ node.Name = fmt.Sprintf("swarm%02d", i)
+
+ cluster.Nodes = append(cluster.Nodes, node)
+ }
+
+ if size == 1 {
+ return cluster
+ }
+
+ // connect the nodes together
+ for _, node := range cluster.Nodes {
+ if err := node.Client.Call(nil, "admin_addPeer", cluster.Nodes[0].Enode); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ // wait until all nodes have the correct number of peers
+outer:
+ for _, node := range cluster.Nodes {
+ var peers []*p2p.PeerInfo
+ for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(50 * time.Millisecond) {
+ if err := node.Client.Call(&peers, "admin_peers"); err != nil {
+ t.Fatal(err)
+ }
+ if len(peers) == len(cluster.Nodes)-1 {
+ continue outer
+ }
+ }
+ t.Fatalf("%s only has %d / %d peers", node.Name, len(peers), len(cluster.Nodes)-1)
+ }
+
+ return cluster
+}
+
+func (c *testCluster) Shutdown() {
+ for _, node := range c.Nodes {
+ node.Shutdown()
+ }
+ os.RemoveAll(c.TmpDir)
+}
+
+type testNode struct {
+ Name string
+ Addr string
+ URL string
+ Enode string
+ Dir string
+ Client *rpc.Client
+ Cmd *cmdtest.TestCmd
+}
+
+const testPassphrase = "swarm-test-passphrase"
+
+func newTestNode(t *testing.T, dir string) *testNode {
+ // create key
+ conf := &node.Config{
+ DataDir: dir,
+ IPCPath: "bzzd.ipc",
+ }
+ n, err := node.New(conf)
+ if err != nil {
+ t.Fatal(err)
+ }
+ account, err := n.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore).NewAccount(testPassphrase)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ node := &testNode{Dir: dir}
+
+ // use a unique IPCPath when running tests on Windows
+ if runtime.GOOS == "windows" {
+ conf.IPCPath = fmt.Sprintf("bzzd-%s.ipc", account.Address.String())
+ }
+
+ // assign ports
+ httpPort, err := assignTCPPort()
+ if err != nil {
+ t.Fatal(err)
+ }
+ p2pPort, err := assignTCPPort()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // start the node
+ node.Cmd = runSwarm(t,
+ "--port", p2pPort,
+ "--nodiscover",
+ "--datadir", dir,
+ "--ipcpath", conf.IPCPath,
+ "--ethapi", "",
+ "--bzzaccount", account.Address.String(),
+ "--bzznetworkid", "321",
+ "--bzzport", httpPort,
+ "--verbosity", "6",
+ )
+ node.Cmd.InputLine(testPassphrase)
+ defer func() {
+ if t.Failed() {
+ node.Shutdown()
+ }
+ }()
+
+ // wait for the node to start
+ for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
+ node.Client, err = rpc.Dial(conf.IPCEndpoint())
+ if err == nil {
+ break
+ }
+ }
+ if node.Client == nil {
+ t.Fatal(err)
+ }
+
+ // load info
+ var info swarm.Info
+ if err := node.Client.Call(&info, "bzz_info"); err != nil {
+ t.Fatal(err)
+ }
+ node.Addr = net.JoinHostPort("127.0.0.1", info.Port)
+ node.URL = "http://" + node.Addr
+
+ var nodeInfo p2p.NodeInfo
+ if err := node.Client.Call(&nodeInfo, "admin_nodeInfo"); err != nil {
+ t.Fatal(err)
+ }
+ node.Enode = fmt.Sprintf("enode://%s@127.0.0.1:%s", nodeInfo.ID, p2pPort)
+
+ return node
+}
+
+func (n *testNode) Shutdown() {
+ if n.Cmd != nil {
+ n.Cmd.Kill()
+ }
+}
+
+func assignTCPPort() (string, error) {
+ l, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ return "", err
+ }
+ l.Close()
+ _, port, err := net.SplitHostPort(l.Addr().String())
+ if err != nil {
+ return "", err
+ }
+ return port, nil
+}
diff --git a/cmd/swarm/upload_test.go b/cmd/swarm/upload_test.go
new file mode 100644
index 000000000..5656186e1
--- /dev/null
+++ b/cmd/swarm/upload_test.go
@@ -0,0 +1,76 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of go-ethereum.
+//
+// go-ethereum is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// go-ethereum is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
+
+package main
+
+import (
+ "io"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "testing"
+)
+
+// TestCLISwarmUp tests that running 'swarm up' makes the resulting file
+// available from all nodes via the HTTP API
+func TestCLISwarmUp(t *testing.T) {
+ // start 3 node cluster
+ t.Log("starting 3 node cluster")
+ cluster := newTestCluster(t, 3)
+ defer cluster.Shutdown()
+
+ // create a tmp file
+ tmp, err := ioutil.TempFile("", "swarm-test")
+ assertNil(t, err)
+ defer tmp.Close()
+ defer os.Remove(tmp.Name())
+ _, err = io.WriteString(tmp, "data")
+ assertNil(t, err)
+
+ // upload the file with 'swarm up' and expect a hash
+ t.Log("uploading file with 'swarm up'")
+ up := runSwarm(t, "--bzzapi", cluster.Nodes[0].URL, "up", tmp.Name())
+ _, matches := up.ExpectRegexp(`[a-f\d]{64}`)
+ up.ExpectExit()
+ hash := matches[0]
+ t.Logf("file uploaded with hash %s", hash)
+
+ // get the file from the HTTP API of each node
+ for _, node := range cluster.Nodes {
+ t.Logf("getting file from %s", node.Name)
+ res, err := http.Get(node.URL + "/bzz:/" + hash)
+ assertNil(t, err)
+ assertHTTPResponse(t, res, http.StatusOK, "data")
+ }
+}
+
+func assertNil(t *testing.T, err error) {
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func assertHTTPResponse(t *testing.T, res *http.Response, expectedStatus int, expectedBody string) {
+ defer res.Body.Close()
+ if res.StatusCode != expectedStatus {
+ t.Fatalf("expected HTTP status %d, got %s", expectedStatus, res.Status)
+ }
+ data, err := ioutil.ReadAll(res.Body)
+ assertNil(t, err)
+ if string(data) != expectedBody {
+ t.Fatalf("expected HTTP body %q, got %q", expectedBody, data)
+ }
+}
diff --git a/internal/cmdtest/test_cmd.go b/internal/cmdtest/test_cmd.go
new file mode 100644
index 000000000..541e51c4c
--- /dev/null
+++ b/internal/cmdtest/test_cmd.go
@@ -0,0 +1,270 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of go-ethereum.
+//
+// go-ethereum is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// go-ethereum is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
+
+package cmdtest
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "regexp"
+ "sync"
+ "testing"
+ "text/template"
+ "time"
+
+ "github.com/docker/docker/pkg/reexec"
+)
+
+func NewTestCmd(t *testing.T, data interface{}) *TestCmd {
+ return &TestCmd{T: t, Data: data}
+}
+
+type TestCmd struct {
+ // For total convenience, all testing methods are available.
+ *testing.T
+
+ Func template.FuncMap
+ Data interface{}
+ Cleanup func()
+
+ cmd *exec.Cmd
+ stdout *bufio.Reader
+ stdin io.WriteCloser
+ stderr *testlogger
+}
+
+// Run exec's the current binary using name as argv[0] which will trigger the
+// reexec init function for that name (e.g. "geth-test" in cmd/geth/run_test.go)
+func (tt *TestCmd) Run(name string, args ...string) {
+ tt.stderr = &testlogger{t: tt.T}
+ tt.cmd = &exec.Cmd{
+ Path: reexec.Self(),
+ Args: append([]string{name}, args...),
+ Stderr: tt.stderr,
+ }
+ stdout, err := tt.cmd.StdoutPipe()
+ if err != nil {
+ tt.Fatal(err)
+ }
+ tt.stdout = bufio.NewReader(stdout)
+ if tt.stdin, err = tt.cmd.StdinPipe(); err != nil {
+ tt.Fatal(err)
+ }
+ if err := tt.cmd.Start(); err != nil {
+ tt.Fatal(err)
+ }
+}
+
+// InputLine writes the given text to the childs stdin.
+// This method can also be called from an expect template, e.g.:
+//
+// geth.expect(`Passphrase: {{.InputLine "password"}}`)
+func (tt *TestCmd) InputLine(s string) string {
+ io.WriteString(tt.stdin, s+"\n")
+ return ""
+}
+
+func (tt *TestCmd) SetTemplateFunc(name string, fn interface{}) {
+ if tt.Func == nil {
+ tt.Func = make(map[string]interface{})
+ }
+ tt.Func[name] = fn
+}
+
+// Expect runs its argument as a template, then expects the
+// child process to output the result of the template within 5s.
+//
+// If the template starts with a newline, the newline is removed
+// before matching.
+func (tt *TestCmd) Expect(tplsource string) {
+ // Generate the expected output by running the template.
+ tpl := template.Must(template.New("").Funcs(tt.Func).Parse(tplsource))
+ wantbuf := new(bytes.Buffer)
+ if err := tpl.Execute(wantbuf, tt.Data); err != nil {
+ panic(err)
+ }
+ // Trim exactly one newline at the beginning. This makes tests look
+ // much nicer because all expect strings are at column 0.
+ want := bytes.TrimPrefix(wantbuf.Bytes(), []byte("\n"))
+ if err := tt.matchExactOutput(want); err != nil {
+ tt.Fatal(err)
+ }
+ tt.Logf("Matched stdout text:\n%s", want)
+}
+
+func (tt *TestCmd) matchExactOutput(want []byte) error {
+ buf := make([]byte, len(want))
+ n := 0
+ tt.withKillTimeout(func() { n, _ = io.ReadFull(tt.stdout, buf) })
+ buf = buf[:n]
+ if n < len(want) || !bytes.Equal(buf, want) {
+ // Grab any additional buffered output in case of mismatch
+ // because it might help with debugging.
+ buf = append(buf, make([]byte, tt.stdout.Buffered())...)
+ tt.stdout.Read(buf[n:])
+ // Find the mismatch position.
+ for i := 0; i < n; i++ {
+ if want[i] != buf[i] {
+ return fmt.Errorf("Output mismatch at ◊:\n---------------- (stdout text)\n%s◊%s\n---------------- (expected text)\n%s",
+ buf[:i], buf[i:n], want)
+ }
+ }
+ if n < len(want) {
+ return fmt.Errorf("Not enough output, got until ◊:\n---------------- (stdout text)\n%s\n---------------- (expected text)\n%s◊%s",
+ buf, want[:n], want[n:])
+ }
+ }
+ return nil
+}
+
+// ExpectRegexp expects the child process to output text matching the
+// given regular expression within 5s.
+//
+// Note that an arbitrary amount of output may be consumed by the
+// regular expression. This usually means that expect cannot be used
+// after ExpectRegexp.
+func (tt *TestCmd) ExpectRegexp(resource string) (*regexp.Regexp, []string) {
+ var (
+ re = regexp.MustCompile(resource)
+ rtee = &runeTee{in: tt.stdout}
+ matches []int
+ )
+ tt.withKillTimeout(func() { matches = re.FindReaderSubmatchIndex(rtee) })
+ output := rtee.buf.Bytes()
+ if matches == nil {
+ tt.Fatalf("Output did not match:\n---------------- (stdout text)\n%s\n---------------- (regular expression)\n%s",
+ output, resource)
+ return re, nil
+ }
+ tt.Logf("Matched stdout text:\n%s", output)
+ var submatches []string
+ for i := 0; i < len(matches); i += 2 {
+ submatch := string(output[matches[i]:matches[i+1]])
+ submatches = append(submatches, submatch)
+ }
+ return re, submatches
+}
+
+// ExpectExit expects the child process to exit within 5s without
+// printing any additional text on stdout.
+func (tt *TestCmd) ExpectExit() {
+ var output []byte
+ tt.withKillTimeout(func() {
+ output, _ = ioutil.ReadAll(tt.stdout)
+ })
+ tt.WaitExit()
+ if tt.Cleanup != nil {
+ tt.Cleanup()
+ }
+ if len(output) > 0 {
+ tt.Errorf("Unmatched stdout text:\n%s", output)
+ }
+}
+
+func (tt *TestCmd) WaitExit() {
+ tt.cmd.Wait()
+}
+
+func (tt *TestCmd) Interrupt() {
+ tt.cmd.Process.Signal(os.Interrupt)
+}
+
+// StderrText returns any stderr output written so far.
+// The returned text holds all log lines after ExpectExit has
+// returned.
+func (tt *TestCmd) StderrText() string {
+ tt.stderr.mu.Lock()
+ defer tt.stderr.mu.Unlock()
+ return tt.stderr.buf.String()
+}
+
+func (tt *TestCmd) CloseStdin() {
+ tt.stdin.Close()
+}
+
+func (tt *TestCmd) Kill() {
+ tt.cmd.Process.Kill()
+ if tt.Cleanup != nil {
+ tt.Cleanup()
+ }
+}
+
+func (tt *TestCmd) withKillTimeout(fn func()) {
+ timeout := time.AfterFunc(5*time.Second, func() {
+ tt.Log("killing the child process (timeout)")
+ tt.Kill()
+ })
+ defer timeout.Stop()
+ fn()
+}
+
+// testlogger logs all written lines via t.Log and also
+// collects them for later inspection.
+type testlogger struct {
+ t *testing.T
+ mu sync.Mutex
+ buf bytes.Buffer
+}
+
+func (tl *testlogger) Write(b []byte) (n int, err error) {
+ lines := bytes.Split(b, []byte("\n"))
+ for _, line := range lines {
+ if len(line) > 0 {
+ tl.t.Logf("(stderr) %s", line)
+ }
+ }
+ tl.mu.Lock()
+ tl.buf.Write(b)
+ tl.mu.Unlock()
+ return len(b), err
+}
+
+// runeTee collects text read through it into buf.
+type runeTee struct {
+ in interface {
+ io.Reader
+ io.ByteReader
+ io.RuneReader
+ }
+ buf bytes.Buffer
+}
+
+func (rtee *runeTee) Read(b []byte) (n int, err error) {
+ n, err = rtee.in.Read(b)
+ rtee.buf.Write(b[:n])
+ return n, err
+}
+
+func (rtee *runeTee) ReadRune() (r rune, size int, err error) {
+ r, size, err = rtee.in.ReadRune()
+ if err == nil {
+ rtee.buf.WriteRune(r)
+ }
+ return r, size, err
+}
+
+func (rtee *runeTee) ReadByte() (b byte, err error) {
+ b, err = rtee.in.ReadByte()
+ if err == nil {
+ rtee.buf.WriteByte(b)
+ }
+ return b, err
+}
diff --git a/vendor/github.com/docker/docker/LICENSE b/vendor/github.com/docker/docker/LICENSE
new file mode 100644
index 000000000..9c8e20ab8
--- /dev/null
+++ b/vendor/github.com/docker/docker/LICENSE
@@ -0,0 +1,191 @@
+
+ Apache License
+ Version 2.0, January 2004
+ https://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ Copyright 2013-2017 Docker, Inc.
+
+ Licensed 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
+
+ https://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.
diff --git a/vendor/github.com/docker/docker/NOTICE b/vendor/github.com/docker/docker/NOTICE
new file mode 100644
index 000000000..0c74e15b0
--- /dev/null
+++ b/vendor/github.com/docker/docker/NOTICE
@@ -0,0 +1,19 @@
+Docker
+Copyright 2012-2017 Docker, Inc.
+
+This product includes software developed at Docker, Inc. (https://www.docker.com).
+
+This product contains software (https://github.com/kr/pty) developed
+by Keith Rarick, licensed under the MIT License.
+
+The following is courtesy of our legal counsel:
+
+
+Use and transfer of Docker may be subject to certain restrictions by the
+United States and other governments.
+It is your responsibility to ensure that your use and/or transfer does not
+violate applicable laws.
+
+For more information, please see https://www.bis.doc.gov
+
+See also https://www.apache.org/dev/crypto.html and/or seek legal counsel.
diff --git a/vendor/github.com/docker/docker/pkg/reexec/README.md b/vendor/github.com/docker/docker/pkg/reexec/README.md
new file mode 100644
index 000000000..6658f69b6
--- /dev/null
+++ b/vendor/github.com/docker/docker/pkg/reexec/README.md
@@ -0,0 +1,5 @@
+# reexec
+
+The `reexec` package facilitates the busybox style reexec of the docker binary that we require because
+of the forking limitations of using Go. Handlers can be registered with a name and the argv 0 of
+the exec of the binary will be used to find and execute custom init paths.
diff --git a/vendor/github.com/docker/docker/pkg/reexec/command_linux.go b/vendor/github.com/docker/docker/pkg/reexec/command_linux.go
new file mode 100644
index 000000000..34ae2a9dc
--- /dev/null
+++ b/vendor/github.com/docker/docker/pkg/reexec/command_linux.go
@@ -0,0 +1,28 @@
+// +build linux
+
+package reexec
+
+import (
+ "os/exec"
+ "syscall"
+)
+
+// Self returns the path to the current process's binary.
+// Returns "/proc/self/exe".
+func Self() string {
+ return "/proc/self/exe"
+}
+
+// Command returns *exec.Cmd which has Path as current binary. Also it setting
+// SysProcAttr.Pdeathsig to SIGTERM.
+// This will use the in-memory version (/proc/self/exe) of the current binary,
+// it is thus safe to delete or replace the on-disk binary (os.Args[0]).
+func Command(args ...string) *exec.Cmd {
+ return &exec.Cmd{
+ Path: Self(),
+ Args: args,
+ SysProcAttr: &syscall.SysProcAttr{
+ Pdeathsig: syscall.SIGTERM,
+ },
+ }
+}
diff --git a/vendor/github.com/docker/docker/pkg/reexec/command_unix.go b/vendor/github.com/docker/docker/pkg/reexec/command_unix.go
new file mode 100644
index 000000000..778a720e3
--- /dev/null
+++ b/vendor/github.com/docker/docker/pkg/reexec/command_unix.go
@@ -0,0 +1,23 @@
+// +build freebsd solaris darwin
+
+package reexec
+
+import (
+ "os/exec"
+)
+
+// Self returns the path to the current process's binary.
+// Uses os.Args[0].
+func Self() string {
+ return naiveSelf()
+}
+
+// Command returns *exec.Cmd which has Path as current binary.
+// For example if current binary is "docker" at "/usr/bin/", then cmd.Path will
+// be set to "/usr/bin/docker".
+func Command(args ...string) *exec.Cmd {
+ return &exec.Cmd{
+ Path: Self(),
+ Args: args,
+ }
+}
diff --git a/vendor/github.com/docker/docker/pkg/reexec/command_unsupported.go b/vendor/github.com/docker/docker/pkg/reexec/command_unsupported.go
new file mode 100644
index 000000000..76edd8242
--- /dev/null
+++ b/vendor/github.com/docker/docker/pkg/reexec/command_unsupported.go
@@ -0,0 +1,12 @@
+// +build !linux,!windows,!freebsd,!solaris,!darwin
+
+package reexec
+
+import (
+ "os/exec"
+)
+
+// Command is unsupported on operating systems apart from Linux, Windows, Solaris and Darwin.
+func Command(args ...string) *exec.Cmd {
+ return nil
+}
diff --git a/vendor/github.com/docker/docker/pkg/reexec/command_windows.go b/vendor/github.com/docker/docker/pkg/reexec/command_windows.go
new file mode 100644
index 000000000..ca871c422
--- /dev/null
+++ b/vendor/github.com/docker/docker/pkg/reexec/command_windows.go
@@ -0,0 +1,23 @@
+// +build windows
+
+package reexec
+
+import (
+ "os/exec"
+)
+
+// Self returns the path to the current process's binary.
+// Uses os.Args[0].
+func Self() string {
+ return naiveSelf()
+}
+
+// Command returns *exec.Cmd which has Path as current binary.
+// For example if current binary is "docker.exe" at "C:\", then cmd.Path will
+// be set to "C:\docker.exe".
+func Command(args ...string) *exec.Cmd {
+ return &exec.Cmd{
+ Path: Self(),
+ Args: args,
+ }
+}
diff --git a/vendor/github.com/docker/docker/pkg/reexec/reexec.go b/vendor/github.com/docker/docker/pkg/reexec/reexec.go
new file mode 100644
index 000000000..c56671d91
--- /dev/null
+++ b/vendor/github.com/docker/docker/pkg/reexec/reexec.go
@@ -0,0 +1,47 @@
+package reexec
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+)
+
+var registeredInitializers = make(map[string]func())
+
+// Register adds an initialization func under the specified name
+func Register(name string, initializer func()) {
+ if _, exists := registeredInitializers[name]; exists {
+ panic(fmt.Sprintf("reexec func already registered under name %q", name))
+ }
+
+ registeredInitializers[name] = initializer
+}
+
+// Init is called as the first part of the exec process and returns true if an
+// initialization function was called.
+func Init() bool {
+ initializer, exists := registeredInitializers[os.Args[0]]
+ if exists {
+ initializer()
+
+ return true
+ }
+ return false
+}
+
+func naiveSelf() string {
+ name := os.Args[0]
+ if filepath.Base(name) == name {
+ if lp, err := exec.LookPath(name); err == nil {
+ return lp
+ }
+ }
+ // handle conversion of relative paths to absolute
+ if absName, err := filepath.Abs(name); err == nil {
+ return absName
+ }
+ // if we couldn't get absolute name, return original
+ // (NOTE: Go only errors on Abs() if os.Getwd fails)
+ return name
+}
diff --git a/vendor/vendor.json b/vendor/vendor.json
index 607f193a3..1c2c58500 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -75,6 +75,12 @@
"revisionTime": "2017-02-01T22:58:49Z"
},
{
+ "checksumSHA1": "lutCa+IVM60R1OYBm9RtDAW50Ys=",
+ "path": "github.com/docker/docker/pkg/reexec",
+ "revision": "83ee902ecc3790c33c1e2d87334074436056bb49",
+ "revisionTime": "2017-04-22T21:51:12Z"
+ },
+ {
"checksumSHA1": "zYnPsNAVm1/ViwCkN++dX2JQhBo=",
"path": "github.com/edsrzf/mmap-go",
"revision": "935e0e8a636ca4ba70b713f3e38a19e1b77739e8",