From 242ed49ecbbde9ce9aa23886d2c89c2baffe6296 Mon Sep 17 00:00:00 2001 From: Ash Berlin Date: Sun, 29 Jan 2017 14:10:14 +0000 Subject: [PATCH] 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 --- packer/ui.go | 14 +++++++++++--- packer/ui_test.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/packer/ui.go b/packer/ui.go index afd21a488..7616eb7ac 100644 --- a/packer/ui.go +++ b/packer/ui.go @@ -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 }() diff --git a/packer/ui_test.go b/packer/ui_test.go index a42cd3033..e65a7f3db 100644 --- a/packer/ui_test.go +++ b/packer/ui_test.go @@ -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{}