diff options
Diffstat (limited to 'cmd')
-rw-r--r-- | cmd/geth/accountcmd_test.go | 72 | ||||
-rw-r--r-- | cmd/geth/consolecmd_test.go | 54 | ||||
-rw-r--r-- | cmd/geth/dao_test.go | 4 | ||||
-rw-r--r-- | cmd/geth/genesis_test.go | 6 | ||||
-rw-r--r-- | cmd/geth/run_test.go | 250 | ||||
-rw-r--r-- | cmd/swarm/run_test.go | 255 | ||||
-rw-r--r-- | cmd/swarm/upload_test.go | 76 |
7 files changed, 424 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) + } +} |