Use bufio.Scanner in BasicUi.Ask so we can read whole lines

The previous version used fmt.Fscanln which sounds like it should scan
for a whole line, but it actually errors if there is more than a single
word/token given. You can see this here https://play.golang.org/p/1RYXdtPSbk

And looking at the only usage of this function in core it won't break
anything to change it to actually return whole lines instead.

    line, err := ui.Ask("[c] Clean up and exit, [a] abort without cleanup, or [r] retry step (build may fail even if retry succeeds)?")

Closes #4474
This commit is contained in:
Ash Berlin 2017-01-29 14:10:14 +00:00
parent 60a0d21d37
commit 242ed49ecb
2 changed files with 56 additions and 3 deletions

View File

@ -1,6 +1,7 @@
package packer
import (
"bufio"
"bytes"
"errors"
"fmt"
@ -64,6 +65,7 @@ type BasicUi struct {
ErrorWriter io.Writer
l sync.Mutex
interrupted bool
scanner *bufio.Scanner
}
// MachineReadableUi is a UI that only outputs machine-readable output
@ -174,6 +176,9 @@ func (rw *BasicUi) Ask(query string) (string, error) {
return "", errors.New("interrupted")
}
if rw.scanner == nil {
rw.scanner = bufio.NewScanner(rw.Reader)
}
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt)
defer signal.Stop(sigCh)
@ -188,10 +193,13 @@ func (rw *BasicUi) Ask(query string) (string, error) {
result := make(chan string, 1)
go func() {
var line string
if _, err := fmt.Fscanln(rw.Reader, &line); err != nil {
log.Printf("ui: scan err: %s", err)
if rw.scanner.Scan() {
line = rw.scanner.Text()
}
if err := rw.scanner.Err(); err != nil {
log.Printf("ui: scan err: %s", err)
return
}
result <- line
}()

View File

@ -16,6 +16,12 @@ func readWriter(ui *BasicUi) (result string) {
return
}
// Reset the input Reader than add some input to it.
func writeReader(ui *BasicUi, input string) {
buffer := ui.Reader.(*bytes.Buffer)
buffer.WriteString(input)
}
func readErrorWriter(ui *BasicUi) (result string) {
buffer := ui.ErrorWriter.(*bytes.Buffer)
result = buffer.String()
@ -192,6 +198,45 @@ func TestBasicUi_Say(t *testing.T) {
}
}
func TestBasicUi_Ask(t *testing.T) {
var actual, expected string
var err error
var testCases = []struct {
Prompt, Input, Answer string
}{
{"[c]ontinue or [a]bort", "c\n", "c"},
{"[c]ontinue or [a]bort", "c", "c"},
// Empty input shouldn't give an error
{"Name", "Joe Bloggs\n", "Joe Bloggs"},
{"Name", "Joe Bloggs", "Joe Bloggs"},
{"Name", "\n", ""},
}
for _, testCase := range testCases {
// Because of the internal bufio we can't eaily reset the input, so create a new one each time
bufferUi := testUi()
writeReader(bufferUi, testCase.Input)
actual, err = bufferUi.Ask(testCase.Prompt)
if err != nil {
t.Fatal(err)
}
if actual != testCase.Answer {
t.Fatalf("bad answer: %#v", actual)
}
actual = readWriter(bufferUi)
expected = testCase.Prompt + " "
if actual != expected {
t.Fatalf("bad prompt: %#v", actual)
}
}
}
func TestMachineReadableUi_ImplUi(t *testing.T) {
var raw interface{}
raw = &MachineReadableUi{}