diff options
Diffstat (limited to 'cmd/geth')
-rw-r--r-- | cmd/geth/accountcmd.go | 7 | ||||
-rw-r--r-- | cmd/geth/accountcmd_test.go | 221 | ||||
-rw-r--r-- | cmd/geth/run_test.go | 282 | ||||
-rw-r--r-- | cmd/geth/testdata/empty.js | 1 | ||||
-rw-r--r-- | cmd/geth/testdata/guswallet.json | 6 | ||||
-rw-r--r-- | cmd/geth/testdata/passwords.txt | 3 | ||||
-rw-r--r-- | cmd/geth/testdata/wrong-passwords.txt | 3 |
7 files changed, 522 insertions, 1 deletions
diff --git a/cmd/geth/accountcmd.go b/cmd/geth/accountcmd.go index b4c37cb86..18265f251 100644 --- a/cmd/geth/accountcmd.go +++ b/cmd/geth/accountcmd.go @@ -23,6 +23,8 @@ import ( "github.com/codegangsta/cli" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" ) var ( @@ -180,6 +182,7 @@ func unlockAccount(ctx *cli.Context, accman *accounts.Manager, address string, i prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3) password := getPassPhrase(prompt, false, i, passwords) if err := accman.Unlock(account, password); err == nil { + glog.V(logger.Info).Infof("Unlocked account %x", account.Address) return account, password } } @@ -199,7 +202,9 @@ func getPassPhrase(prompt string, confirmation bool, i int, passwords []string) return passwords[len(passwords)-1] } // Otherwise prompt the user for the password - fmt.Println(prompt) + if prompt != "" { + fmt.Println(prompt) + } password, err := utils.Stdin.PasswordPrompt("Passphrase: ") if err != nil { utils.Fatalf("Failed to read passphrase: %v", err) diff --git a/cmd/geth/accountcmd_test.go b/cmd/geth/accountcmd_test.go new file mode 100644 index 000000000..4b8a80855 --- /dev/null +++ b/cmd/geth/accountcmd_test.go @@ -0,0 +1,221 @@ +// 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/ioutil" + "path/filepath" + "strings" + "testing" + + "github.com/cespare/cp" +) + +// These tests are 'smoke tests' for the account related +// subcommands and flags. +// +// For most tests, the test files from package accounts +// are copied into a temporary keystore directory. + +func tmpDatadirWithKeystore(t *testing.T) string { + datadir := tmpdir(t) + keystore := filepath.Join(datadir, "keystore") + source := filepath.Join("..", "..", "accounts", "testdata", "keystore") + if err := cp.CopyAll(keystore, source); err != nil { + t.Fatal(err) + } + return datadir +} + +func TestAccountListEmpty(t *testing.T) { + geth := runGeth(t, "account") + geth.expectExit() +} + +func TestAccountList(t *testing.T) { + datadir := tmpDatadirWithKeystore(t) + geth := runGeth(t, "--datadir", datadir, "account") + defer geth.expectExit() + geth.expect(` +Account #0: {7ef5a6135f1fd6a02593eedc869c6d41d934aef8} +Account #1: {f466859ead1932d743d622cb74fc058882e8648a} +Account #2: {289d485d9771714cce91d3393d764e1311907acc} +`) +} + +func TestAccountNew(t *testing.T) { + geth := runGeth(t, "--lightkdf", "account", "new") + 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`) +} + +func TestAccountNewBadRepeat(t *testing.T) { + geth := runGeth(t, "--lightkdf", "account", "new") + 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"}} +Repeat passphrase: {{.InputLine "something else"}} +Fatal: Passphrases do not match +`) +} + +func TestAccountUpdate(t *testing.T) { + datadir := tmpDatadirWithKeystore(t) + geth := runGeth(t, + "--datadir", datadir, "--lightkdf", + "account", "update", "f466859ead1932d743d622cb74fc058882e8648a") + defer geth.expectExit() + geth.expect(` +Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3 +!! Unsupported terminal, password will be echoed. +Passphrase: {{.InputLine "foobar"}} +Please give a new password. Do not forget this password. +Passphrase: {{.InputLine "foobar2"}} +Repeat passphrase: {{.InputLine "foobar2"}} +`) +} + +func TestWalletImport(t *testing.T) { + geth := runGeth(t, "--lightkdf", "wallet", "import", "testdata/guswallet.json") + defer geth.expectExit() + geth.expect(` +!! Unsupported terminal, password will be echoed. +Passphrase: {{.InputLine "foo"}} +Address: {d4584b5f6229b7be90727b0fc8c6b91bb427821f} +`) + + files, err := ioutil.ReadDir(filepath.Join(geth.Datadir, "keystore")) + if len(files) != 1 { + t.Errorf("expected one key file in keystore directory, found %d files (error: %v)", len(files), err) + } +} + +func TestWalletImportBadPassword(t *testing.T) { + geth := runGeth(t, "--lightkdf", "wallet", "import", "testdata/guswallet.json") + defer geth.expectExit() + geth.expect(` +!! Unsupported terminal, password will be echoed. +Passphrase: {{.InputLine "wrong"}} +Fatal: Could not create the account: Decryption failed: PKCS7Unpad failed after AES decryption +`) +} + +func TestUnlockFlag(t *testing.T) { + datadir := tmpDatadirWithKeystore(t) + geth := runGeth(t, + "--datadir", datadir, "--nat", "none", "--nodiscover", "--dev", + "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", + "js", "testdata/empty.js") + geth.expect(` +Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3 +!! Unsupported terminal, password will be echoed. +Passphrase: {{.InputLine "foobar"}} +`) + geth.expectExit() + + wantMessages := []string{ + "Unlocked account f466859ead1932d743d622cb74fc058882e8648a", + } + for _, m := range wantMessages { + if strings.Index(geth.stderrText(), m) == -1 { + t.Errorf("stderr text does not contain %q", m) + } + } +} + +func TestUnlockFlagWrongPassword(t *testing.T) { + datadir := tmpDatadirWithKeystore(t) + geth := runGeth(t, + "--datadir", datadir, "--nat", "none", "--nodiscover", "--dev", + "--unlock", "f466859ead1932d743d622cb74fc058882e8648a") + defer geth.expectExit() + geth.expect(` +Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3 +!! Unsupported terminal, password will be echoed. +Passphrase: {{.InputLine "wrong1"}} +Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 2/3 +Passphrase: {{.InputLine "wrong2"}} +Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 3/3 +Passphrase: {{.InputLine "wrong3"}} +Fatal: Failed to unlock account: f466859ead1932d743d622cb74fc058882e8648a +`) +} + +// https://github.com/ethereum/go-ethereum/issues/1785 +func TestUnlockFlagMultiIndex(t *testing.T) { + datadir := tmpDatadirWithKeystore(t) + geth := runGeth(t, + "--datadir", datadir, "--nat", "none", "--nodiscover", "--dev", + "--unlock", "0,2", + "js", "testdata/empty.js") + 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() + + wantMessages := []string{ + "Unlocked account 7ef5a6135f1fd6a02593eedc869c6d41d934aef8", + "Unlocked account 289d485d9771714cce91d3393d764e1311907acc", + } + for _, m := range wantMessages { + if strings.Index(geth.stderrText(), m) == -1 { + t.Errorf("stderr text does not contain %q", m) + } + } +} + +func TestUnlockFlagPasswordFile(t *testing.T) { + datadir := tmpDatadirWithKeystore(t) + geth := runGeth(t, + "--datadir", datadir, "--nat", "none", "--nodiscover", "--dev", + "--password", "testdata/passwords.txt", "--unlock", "0,2", + "js", "testdata/empty.js") + geth.expectExit() + + wantMessages := []string{ + "Unlocked account 7ef5a6135f1fd6a02593eedc869c6d41d934aef8", + "Unlocked account 289d485d9771714cce91d3393d764e1311907acc", + } + for _, m := range wantMessages { + if strings.Index(geth.stderrText(), m) == -1 { + t.Errorf("stderr text does not contain %q", m) + } + } +} + +func TestUnlockFlagPasswordFileWrongPassword(t *testing.T) { + datadir := tmpDatadirWithKeystore(t) + geth := runGeth(t, + "--datadir", datadir, "--nat", "none", "--nodiscover", "--dev", + "--password", "testdata/wrong-passwords.txt", "--unlock", "0,2") + defer geth.expectExit() + geth.expect(` +Fatal: Failed to unlock account: 0 +`) +} diff --git a/cmd/geth/run_test.go b/cmd/geth/run_test.go new file mode 100644 index 000000000..a15d0bf28 --- /dev/null +++ b/cmd/geth/run_test.go @@ -0,0 +1,282 @@ +// 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 ( + "bufio" + "bytes" + "fmt" + "html/template" + "io" + "io/ioutil" + "os" + "os/exec" + "regexp" + "sync" + "testing" + "time" +) + +func tmpdir(t *testing.T) string { + dir, err := ioutil.TempDir("", "geth-test") + if err != nil { + t.Fatal(err) + } + return dir +} + +type testgeth struct { + // For total convenience, all testing methods are available. + *testing.T + // template variables for expect + Datadir string + Executable string + + removeDatadir bool + cmd *exec.Cmd + stdout *bufio.Reader + stdin io.WriteCloser + stderr *testlogger +} + +func init() { + // Run the app if we're the child process for runGeth. + if os.Getenv("GETH_TEST_CHILD") != "" { + app.RunAndExitOnError() + os.Exit(0) + } +} + +// 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]} + for i, arg := range args { + if arg == "-datadir" || arg == "--datadir" { + if i < len(args)-1 { + tt.Datadir = args[i+1] + } + break + } + } + if tt.Datadir == "" { + tt.Datadir = tmpdir(t) + tt.removeDatadir = true + args = append([]string{"-datadir", tt.Datadir}, args...) + // Remove the temporary datadir if something fails below. + defer func() { + if t.Failed() { + os.RemoveAll(tt.Datadir) + } + }() + } + + // 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 "" +} + +// 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("").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/geth/testdata/empty.js b/cmd/geth/testdata/empty.js new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/cmd/geth/testdata/empty.js @@ -0,0 +1 @@ + diff --git a/cmd/geth/testdata/guswallet.json b/cmd/geth/testdata/guswallet.json new file mode 100644 index 000000000..e8ea4f332 --- /dev/null +++ b/cmd/geth/testdata/guswallet.json @@ -0,0 +1,6 @@ +{ + "encseed": "26d87f5f2bf9835f9a47eefae571bc09f9107bb13d54ff12a4ec095d01f83897494cf34f7bed2ed34126ecba9db7b62de56c9d7cd136520a0427bfb11b8954ba7ac39b90d4650d3448e31185affcd74226a68f1e94b1108e6e0a4a91cdd83eba", + "ethaddr": "d4584b5f6229b7be90727b0fc8c6b91bb427821f", + "email": "gustav.simonsson@gmail.com", + "btcaddr": "1EVknXyFC68kKNLkh6YnKzW41svSRoaAcx" +} diff --git a/cmd/geth/testdata/passwords.txt b/cmd/geth/testdata/passwords.txt new file mode 100644 index 000000000..96f98c7f4 --- /dev/null +++ b/cmd/geth/testdata/passwords.txt @@ -0,0 +1,3 @@ +foobar +foobar +foobar diff --git a/cmd/geth/testdata/wrong-passwords.txt b/cmd/geth/testdata/wrong-passwords.txt new file mode 100644 index 000000000..7d1e338bb --- /dev/null +++ b/cmd/geth/testdata/wrong-passwords.txt @@ -0,0 +1,3 @@ +wrong +wrong +wrong |