mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-18 19:00:46 +00:00
Merge eff02faba9 into dddbaa4bf3
This commit is contained in:
commit
691d9b8a8f
4 changed files with 67 additions and 6 deletions
|
|
@ -18,11 +18,13 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
"github.com/ethereum/go-ethereum/console"
|
"github.com/ethereum/go-ethereum/console"
|
||||||
|
"github.com/mattn/go-isatty"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -141,11 +143,30 @@ func remoteConsole(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise print the welcome screen and enter interactive mode
|
// Otherwise print the welcome screen and enter interactive mode
|
||||||
|
if err := requireInteractiveTTY(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
console.Welcome()
|
console.Welcome()
|
||||||
console.Interactive()
|
console.Interactive()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// requireInteractiveTTY ensures stdin is a terminal or an open pipe when starting
|
||||||
|
// an interactive attach session. Piped stdin is allowed for scripting and tests;
|
||||||
|
// closed or non-interactive sources such as /dev/null are rejected early.
|
||||||
|
func requireInteractiveTTY() error {
|
||||||
|
if isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd()) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fi, err := os.Stdin.Stat()
|
||||||
|
if err == nil && fi.Mode()&os.ModeNamedPipe != 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf(`Cannot start interactive console: input closed or not a terminal.
|
||||||
|
Hint: use 'geth attach --exec "eth.blockNumber" <endpoint>' for non-interactive use,
|
||||||
|
or run attach inside a TTY (e.g. screen -dmS name bash -lc 'geth attach ...').`)
|
||||||
|
}
|
||||||
|
|
||||||
// ephemeralConsole starts a new geth node, attaches an ephemeral JavaScript
|
// ephemeralConsole starts a new geth node, attaches an ephemeral JavaScript
|
||||||
// console to it, executes each of the files specified as arguments and tears
|
// console to it, executes each of the files specified as arguments and tears
|
||||||
// everything down.
|
// everything down.
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,8 @@ func TestAttachWelcome(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAttachWelcome(t *testing.T, geth *testgeth, endpoint, apis string) {
|
func testAttachWelcome(t *testing.T, geth *testgeth, endpoint, apis string) {
|
||||||
// Attach to a running geth node and terminate immediately
|
// Attach to a running geth node with stdin closed, as happens when
|
||||||
|
// launched without a TTY (e.g. via GNU screen -X screen).
|
||||||
attach := runGeth(t, "attach", endpoint)
|
attach := runGeth(t, "attach", endpoint)
|
||||||
defer attach.ExpectExit()
|
defer attach.ExpectExit()
|
||||||
attach.CloseStdin()
|
attach.CloseStdin()
|
||||||
|
|
@ -136,7 +137,7 @@ func testAttachWelcome(t *testing.T, geth *testgeth, endpoint, apis string) {
|
||||||
attach.SetTemplateFunc("datadir", func() string { return geth.Datadir })
|
attach.SetTemplateFunc("datadir", func() string { return geth.Datadir })
|
||||||
attach.SetTemplateFunc("apis", func() string { return apis })
|
attach.SetTemplateFunc("apis", func() string { return apis })
|
||||||
|
|
||||||
// Verify the actual welcome message to the required template
|
// Verify the welcome message and non-TTY hint are printed.
|
||||||
attach.Expect(`
|
attach.Expect(`
|
||||||
Welcome to the Geth JavaScript console!
|
Welcome to the Geth JavaScript console!
|
||||||
|
|
||||||
|
|
@ -146,9 +147,11 @@ at block: 0 ({{niltime}}){{if ipc}}
|
||||||
modules: {{apis}}
|
modules: {{apis}}
|
||||||
|
|
||||||
To exit, press ctrl-d or type exit
|
To exit, press ctrl-d or type exit
|
||||||
> {{.InputLine "exit" }}
|
>
|
||||||
|
Cannot start interactive console: input closed or not a terminal.
|
||||||
|
Hint: use 'geth attach --exec "eth.blockNumber" <endpoint>' for non-interactive use,
|
||||||
|
or run attach inside a TTY (e.g. screen -dmS name bash -lc 'geth attach ...').
|
||||||
`)
|
`)
|
||||||
attach.ExpectExit()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// trulyRandInt generates a crypto random integer used by the console tests to
|
// trulyRandInt generates a crypto random integer used by the console tests to
|
||||||
|
|
|
||||||
|
|
@ -429,6 +429,11 @@ func (c *Console) Interactive() {
|
||||||
prompt, indents, input = c.prompt, 0, ""
|
prompt, indents, input = c.prompt, 0, ""
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
fmt.Fprintln(c.printer, "Cannot start interactive console: input closed or not a terminal.")
|
||||||
|
fmt.Fprintln(c.printer, "Hint: use 'geth attach --exec \"eth.blockNumber\" <endpoint>' for non-interactive use,")
|
||||||
|
fmt.Fprintln(c.printer, "or run attach inside a TTY (e.g. screen -dmS name bash -lc 'geth attach ...').")
|
||||||
|
}
|
||||||
return
|
return
|
||||||
|
|
||||||
case line := <-inputLine:
|
case line := <-inputLine:
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -72,6 +73,17 @@ func (p *hookedPrompter) AppendHistory(command string) {}
|
||||||
func (p *hookedPrompter) ClearHistory() {}
|
func (p *hookedPrompter) ClearHistory() {}
|
||||||
func (p *hookedPrompter) SetWordCompleter(completer prompt.WordCompleter) {}
|
func (p *hookedPrompter) SetWordCompleter(completer prompt.WordCompleter) {}
|
||||||
|
|
||||||
|
// eofPrompter simulates a closed or non-terminal stdin by returning io.EOF.
|
||||||
|
type eofPrompter struct{}
|
||||||
|
|
||||||
|
func (eofPrompter) PromptInput(string) (string, error) { return "", io.EOF }
|
||||||
|
func (eofPrompter) PromptPassword(string) (string, error) { return "", io.EOF }
|
||||||
|
func (eofPrompter) PromptConfirm(string) (bool, error) { return false, io.EOF }
|
||||||
|
func (eofPrompter) SetHistory([]string) {}
|
||||||
|
func (eofPrompter) AppendHistory(string) {}
|
||||||
|
func (eofPrompter) ClearHistory() {}
|
||||||
|
func (eofPrompter) SetWordCompleter(completer prompt.WordCompleter) {}
|
||||||
|
|
||||||
// tester is a console test environment for the console tests to operate on.
|
// tester is a console test environment for the console tests to operate on.
|
||||||
type tester struct {
|
type tester struct {
|
||||||
workspace string
|
workspace string
|
||||||
|
|
@ -85,6 +97,10 @@ type tester struct {
|
||||||
// newTester creates a test environment based on which the console can operate.
|
// newTester creates a test environment based on which the console can operate.
|
||||||
// Please ensure you call Close() on the returned tester to avoid leaks.
|
// Please ensure you call Close() on the returned tester to avoid leaks.
|
||||||
func newTester(t *testing.T, confOverride func(*ethconfig.Config)) *tester {
|
func newTester(t *testing.T, confOverride func(*ethconfig.Config)) *tester {
|
||||||
|
return newTesterWithPrompter(t, &hookedPrompter{scheduler: make(chan string)}, confOverride)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTesterWithPrompter(t *testing.T, prompter prompt.UserPrompter, confOverride func(*ethconfig.Config)) *tester {
|
||||||
// Create a temporary storage for the node keys and initialize it
|
// Create a temporary storage for the node keys and initialize it
|
||||||
workspace := t.TempDir()
|
workspace := t.TempDir()
|
||||||
|
|
||||||
|
|
@ -115,7 +131,6 @@ func newTester(t *testing.T, confOverride func(*ethconfig.Config)) *tester {
|
||||||
client.Close()
|
client.Close()
|
||||||
})
|
})
|
||||||
|
|
||||||
prompter := &hookedPrompter{scheduler: make(chan string)}
|
|
||||||
printer := new(bytes.Buffer)
|
printer := new(bytes.Buffer)
|
||||||
|
|
||||||
console, err := New(Config{
|
console, err := New(Config{
|
||||||
|
|
@ -130,12 +145,16 @@ func newTester(t *testing.T, confOverride func(*ethconfig.Config)) *tester {
|
||||||
t.Fatalf("failed to create JavaScript console: %v", err)
|
t.Fatalf("failed to create JavaScript console: %v", err)
|
||||||
}
|
}
|
||||||
// Create the final tester and return
|
// Create the final tester and return
|
||||||
|
var input *hookedPrompter
|
||||||
|
if hp, ok := prompter.(*hookedPrompter); ok {
|
||||||
|
input = hp
|
||||||
|
}
|
||||||
return &tester{
|
return &tester{
|
||||||
workspace: workspace,
|
workspace: workspace,
|
||||||
stack: stack,
|
stack: stack,
|
||||||
ethereum: ethBackend,
|
ethereum: ethBackend,
|
||||||
console: console,
|
console: console,
|
||||||
input: prompter,
|
input: input,
|
||||||
output: printer,
|
output: printer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -188,6 +207,19 @@ func TestEvaluate(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests that Interactive exits with a helpful message when stdin is closed.
|
||||||
|
func TestInteractiveEOF(t *testing.T) {
|
||||||
|
tester := newTesterWithPrompter(t, eofPrompter{}, nil)
|
||||||
|
defer tester.Close(t)
|
||||||
|
|
||||||
|
tester.console.Interactive()
|
||||||
|
|
||||||
|
output := tester.output.String()
|
||||||
|
if want := "not a terminal"; !strings.Contains(output, want) {
|
||||||
|
t.Fatalf("console output missing EOF hint: have\n%s\nwant substring %s", output, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Tests that the console can be used in interactive mode.
|
// Tests that the console can be used in interactive mode.
|
||||||
func TestInteractive(t *testing.T) {
|
func TestInteractive(t *testing.T) {
|
||||||
// Create a tester and run an interactive console in the background
|
// Create a tester and run an interactive console in the background
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue