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) - } + // Boot "geth". This actually runs the test binary but the TestMain + // function will prevent any tests from running. + tt.Run("geth-test", args...) + 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() -} - -// 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/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 . + +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 . + +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 . + +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 @@ -74,6 +74,12 @@ "revision": "2268707a8f0843315e2004ee4f1d021dc08baedf", "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",