Skip to content

Commit f570deb

Browse files
committed
Add tests for opening the editor program
1 parent bcfe176 commit f570deb

File tree

3 files changed

+194
-41
lines changed

3 files changed

+194
-41
lines changed

pkg/surveyext/editor.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ type GhEditor struct {
3535
*survey.Editor
3636
EditorCommand string
3737
BlankAllowed bool
38+
39+
lookPath func(string) ([]string, []string, error)
3840
}
3941

4042
func (e *GhEditor) editorCommand() string {
@@ -136,7 +138,11 @@ func (e *GhEditor) prompt(initialValue string, config *survey.PromptConfig) (int
136138
}
137139

138140
stdio := e.Stdio()
139-
text, err := Edit(e.editorCommand(), e.FileName, initialValue, stdio.In, stdio.Out, stdio.Err, cursor)
141+
lookPath := e.lookPath
142+
if lookPath == nil {
143+
lookPath = defaultLookPath
144+
}
145+
text, err := edit(e.editorCommand(), e.FileName, initialValue, stdio.In, stdio.Out, stdio.Err, cursor, lookPath)
140146
if err != nil {
141147
return "", err
142148
}

pkg/surveyext/editor_manual.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@ type showable interface {
1616
}
1717

1818
func Edit(editorCommand, fn, initialValue string, stdin io.Reader, stdout io.Writer, stderr io.Writer, cursor showable) (string, error) {
19+
return edit(editorCommand, fn, initialValue, stdin, stdout, stderr, cursor, defaultLookPath)
20+
}
21+
22+
func defaultLookPath(name string) ([]string, []string, error) {
23+
exe, err := safeexec.LookPath(name)
24+
if err != nil {
25+
return nil, nil, err
26+
}
27+
return []string{exe}, nil, nil
28+
}
29+
30+
func edit(editorCommand, fn, initialValue string, stdin io.Reader, stdout io.Writer, stderr io.Writer, cursor showable, lookPath func(string) ([]string, []string, error)) (string, error) {
1931
// prepare the temp file
2032
pattern := fn
2133
if pattern == "" {
@@ -56,12 +68,14 @@ func Edit(editorCommand, fn, initialValue string, stdin io.Reader, stdout io.Wri
5668
}
5769
args = append(args, f.Name())
5870

59-
editorExe, err := safeexec.LookPath(args[0])
71+
editorExe, env, err := lookPath(args[0])
6072
if err != nil {
6173
return "", err
6274
}
75+
args = append(editorExe, args[1:]...)
6376

64-
cmd := exec.Command(editorExe, args[1:]...)
77+
cmd := exec.Command(args[0], args[1:]...)
78+
cmd.Env = env
6579
cmd.Stdin = stdin
6680
cmd.Stdout = stdout
6781
cmd.Stderr = stderr

pkg/surveyext/editor_test.go

Lines changed: 171 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,66 +14,137 @@ import (
1414
"github.com/AlecAivazis/survey/v2/terminal"
1515
pseudotty "github.com/creack/pty"
1616
"github.com/stretchr/testify/assert"
17-
"github.com/stretchr/testify/require"
1817
)
1918

20-
func Test_GhEditor_Prompt(t *testing.T) {
19+
func testLookPath(s string) ([]string, []string, error) {
20+
return []string{os.Args[0], "-test.run=TestHelperProcess", "--", s}, []string{"GH_WANT_HELPER_PROCESS=1"}, nil
21+
}
22+
23+
func TestHelperProcess(t *testing.T) {
24+
if os.Getenv("GH_WANT_HELPER_PROCESS") != "1" {
25+
return
26+
}
27+
if err := func(args []string) error {
28+
switch args[0] {
29+
// "vim" appends a message to the file
30+
case "vim":
31+
f, err := os.OpenFile(args[1], os.O_APPEND|os.O_WRONLY, 0)
32+
if err != nil {
33+
return err
34+
}
35+
defer f.Close()
36+
_, err = f.WriteString(" - added by vim")
37+
return err
38+
// "nano" truncates the contents of the file
39+
case "nano":
40+
f, err := os.OpenFile(args[1], os.O_TRUNC|os.O_WRONLY, 0)
41+
if err != nil {
42+
return err
43+
}
44+
return f.Close()
45+
default:
46+
return fmt.Errorf("unrecognized arguments: %#v\n", args)
47+
}
48+
}(os.Args[3:]); err != nil {
49+
fmt.Fprintln(os.Stderr, err)
50+
os.Exit(1)
51+
}
52+
os.Exit(0)
53+
}
54+
55+
func Test_GhEditor_Prompt_skip(t *testing.T) {
56+
pty := newTerminal(t)
57+
2158
e := &GhEditor{
2259
BlankAllowed: true,
23-
EditorCommand: "false",
60+
EditorCommand: "vim",
2461
Editor: &survey.Editor{
2562
Message: "Body",
2663
FileName: "*.md",
2764
Default: "initial value",
2865
HideDefault: true,
2966
AppendDefault: true,
3067
},
68+
lookPath: func(s string) ([]string, []string, error) {
69+
return nil, nil, errors.New("no editor allowed")
70+
},
3171
}
72+
e.WithStdio(pty.Stdio())
3273

33-
pty, tty, err := pseudotty.Open()
34-
if errors.Is(err, pseudotty.ErrUnsupported) {
35-
return
36-
}
37-
require.NoError(t, err)
38-
defer pty.Close()
39-
defer tty.Close()
40-
41-
err = pseudotty.Setsize(tty, &pseudotty.Winsize{Cols: 72, Rows: 30})
42-
require.NoError(t, err)
43-
44-
out := teeWriter{File: tty}
45-
e.WithStdio(terminal.Stdio{
46-
In: tty,
47-
Out: &out,
48-
Err: tty,
49-
})
74+
// wait until the prompt is rendered and send the Enter key
75+
go func() {
76+
pty.WaitForOutput("Body")
77+
assert.Equal(t, "\x1b[0G\x1b[2K\x1b[0;1;92m? \x1b[0m\x1b[0;1;99mBody \x1b[0m\x1b[0;36m[(e) to launch vim, enter to skip] \x1b[0m\x1b[?25l", pty.Output())
78+
pty.ResetOutput()
79+
assert.NoError(t, pty.SendKey('\n'))
80+
}()
81+
82+
res, err := e.Prompt(defaultPromptConfig())
83+
assert.NoError(t, err)
84+
assert.Equal(t, "initial value", res)
85+
assert.Equal(t, "\x1b[?25h", pty.Output())
86+
}
87+
88+
func Test_GhEditor_Prompt_editorAppend(t *testing.T) {
89+
pty := newTerminal(t)
5090

51-
var res string
52-
errc := make(chan error)
91+
e := &GhEditor{
92+
BlankAllowed: true,
93+
EditorCommand: "vim",
94+
Editor: &survey.Editor{
95+
Message: "Body",
96+
FileName: "*.md",
97+
Default: "initial value",
98+
HideDefault: true,
99+
AppendDefault: true,
100+
},
101+
lookPath: testLookPath,
102+
}
103+
e.WithStdio(pty.Stdio())
53104

105+
// wait until the prompt is rendered and send the 'e' key
54106
go func() {
55-
r, err := e.Prompt(defaultPromptConfig())
56-
if r != nil {
57-
res = r.(string)
58-
}
59-
errc <- err
107+
pty.WaitForOutput("Body")
108+
assert.Equal(t, "\x1b[0G\x1b[2K\x1b[0;1;92m? \x1b[0m\x1b[0;1;99mBody \x1b[0m\x1b[0;36m[(e) to launch vim, enter to skip] \x1b[0m\x1b[?25l", pty.Output())
109+
pty.ResetOutput()
110+
assert.NoError(t, pty.SendKey('e'))
60111
}()
61112

62-
for {
63-
time.Sleep(time.Millisecond)
64-
if strings.Contains(out.String(), "Body") {
65-
break
66-
}
113+
res, err := e.Prompt(defaultPromptConfig())
114+
assert.NoError(t, err)
115+
assert.Equal(t, "initial value - added by vim", res)
116+
assert.Equal(t, "\x1b[?25h\x1b[?25h", pty.Output())
117+
}
118+
119+
func Test_GhEditor_Prompt_editorTruncate(t *testing.T) {
120+
pty := newTerminal(t)
121+
122+
e := &GhEditor{
123+
BlankAllowed: true,
124+
EditorCommand: "nano",
125+
Editor: &survey.Editor{
126+
Message: "Body",
127+
FileName: "*.md",
128+
Default: "initial value",
129+
HideDefault: true,
130+
AppendDefault: true,
131+
},
132+
lookPath: testLookPath,
67133
}
134+
e.WithStdio(pty.Stdio())
68135

69-
assert.Equal(t, "\x1b[0G\x1b[2K\x1b[0;1;92m? \x1b[0m\x1b[0;1;99mBody \x1b[0m\x1b[0;36m[(e) to launch false, enter to skip] \x1b[0m\x1b[?25l", out.String())
70-
out.Reset()
71-
fmt.Fprint(pty, "\n") // send Enter key
136+
// wait until the prompt is rendered and send the 'e' key
137+
go func() {
138+
pty.WaitForOutput("Body")
139+
assert.Equal(t, "\x1b[0G\x1b[2K\x1b[0;1;92m? \x1b[0m\x1b[0;1;99mBody \x1b[0m\x1b[0;36m[(e) to launch nano, enter to skip] \x1b[0m\x1b[?25l", pty.Output())
140+
pty.ResetOutput()
141+
assert.NoError(t, pty.SendKey('e'))
142+
}()
72143

73-
err = <-errc
144+
res, err := e.Prompt(defaultPromptConfig())
74145
assert.NoError(t, err)
75-
assert.Equal(t, "initial value", res)
76-
assert.Equal(t, "\x1b[?25h", out.String())
146+
assert.Equal(t, "", res)
147+
assert.Equal(t, "\x1b[?25h\x1b[?25h", pty.Output())
77148
}
78149

79150
// survey doesn't expose this
@@ -116,6 +187,68 @@ func defaultPromptConfig() *survey.PromptConfig {
116187
}
117188
}
118189

190+
type testTerminal struct {
191+
pty *os.File
192+
tty *os.File
193+
stdout *teeWriter
194+
stderr *teeWriter
195+
}
196+
197+
func newTerminal(t *testing.T) *testTerminal {
198+
t.Helper()
199+
200+
pty, tty, err := pseudotty.Open()
201+
if errors.Is(err, pseudotty.ErrUnsupported) {
202+
t.SkipNow()
203+
return nil
204+
}
205+
if err != nil {
206+
t.Fatal(err)
207+
}
208+
t.Cleanup(func() {
209+
pty.Close()
210+
tty.Close()
211+
})
212+
213+
if err := pseudotty.Setsize(tty, &pseudotty.Winsize{Cols: 72, Rows: 30}); err != nil {
214+
t.Fatal(err)
215+
}
216+
217+
return &testTerminal{pty: pty, tty: tty}
218+
}
219+
220+
func (t *testTerminal) SendKey(c rune) error {
221+
_, err := t.pty.WriteString(string(c))
222+
return err
223+
}
224+
225+
func (t *testTerminal) WaitForOutput(s string) {
226+
for {
227+
time.Sleep(time.Millisecond)
228+
if strings.Contains(t.stdout.String(), s) {
229+
return
230+
}
231+
}
232+
}
233+
234+
func (t *testTerminal) Output() string {
235+
return t.stdout.String()
236+
}
237+
238+
func (t *testTerminal) ResetOutput() {
239+
t.stdout.Reset()
240+
}
241+
242+
func (t *testTerminal) Stdio() terminal.Stdio {
243+
t.stdout = &teeWriter{File: t.tty}
244+
t.stderr = t.stdout
245+
return terminal.Stdio{
246+
In: t.tty,
247+
Out: t.stdout,
248+
Err: t.stderr,
249+
}
250+
}
251+
119252
// teeWriter is a writer that duplicates all writes to a file into a buffer
120253
type teeWriter struct {
121254
*os.File

0 commit comments

Comments
 (0)