mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-18 10:50:44 +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 (
|
||||
"fmt"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/console"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
|
|
@ -141,11 +143,30 @@ func remoteConsole(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
// Otherwise print the welcome screen and enter interactive mode
|
||||
if err := requireInteractiveTTY(); err != nil {
|
||||
return err
|
||||
}
|
||||
console.Welcome()
|
||||
console.Interactive()
|
||||
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
|
||||
// console to it, executes each of the files specified as arguments and tears
|
||||
// everything down.
|
||||
|
|
|
|||
|
|
@ -119,7 +119,8 @@ func TestAttachWelcome(t *testing.T) {
|
|||
}
|
||||
|
||||
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)
|
||||
defer attach.ExpectExit()
|
||||
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("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(`
|
||||
Welcome to the Geth JavaScript console!
|
||||
|
||||
|
|
@ -146,9 +147,11 @@ at block: 0 ({{niltime}}){{if ipc}}
|
|||
modules: {{apis}}
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -429,6 +429,11 @@ func (c *Console) Interactive() {
|
|||
prompt, indents, input = c.prompt, 0, ""
|
||||
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
|
||||
|
||||
case line := <-inputLine:
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
|
@ -72,6 +73,17 @@ func (p *hookedPrompter) AppendHistory(command string) {}
|
|||
func (p *hookedPrompter) ClearHistory() {}
|
||||
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.
|
||||
type tester struct {
|
||||
workspace string
|
||||
|
|
@ -85,6 +97,10 @@ type tester struct {
|
|||
// newTester creates a test environment based on which the console can operate.
|
||||
// Please ensure you call Close() on the returned tester to avoid leaks.
|
||||
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
|
||||
workspace := t.TempDir()
|
||||
|
||||
|
|
@ -115,7 +131,6 @@ func newTester(t *testing.T, confOverride func(*ethconfig.Config)) *tester {
|
|||
client.Close()
|
||||
})
|
||||
|
||||
prompter := &hookedPrompter{scheduler: make(chan string)}
|
||||
printer := new(bytes.Buffer)
|
||||
|
||||
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)
|
||||
}
|
||||
// Create the final tester and return
|
||||
var input *hookedPrompter
|
||||
if hp, ok := prompter.(*hookedPrompter); ok {
|
||||
input = hp
|
||||
}
|
||||
return &tester{
|
||||
workspace: workspace,
|
||||
stack: stack,
|
||||
ethereum: ethBackend,
|
||||
console: console,
|
||||
input: prompter,
|
||||
input: input,
|
||||
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.
|
||||
func TestInteractive(t *testing.T) {
|
||||
// Create a tester and run an interactive console in the background
|
||||
|
|
|
|||
Loading…
Reference in a new issue