From baee123265fac01693aa3af6446a4c9977205080 Mon Sep 17 00:00:00 2001 From: Stefan Junker Date: Mon, 27 Jul 2015 15:26:48 +0200 Subject: [PATCH 01/14] Add function ExpectRegexFindWithOutput This function returns the output of the child, which might be of interest for the caller. --- gexpect.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/gexpect.go b/gexpect.go index 211d045..54a2abc 100644 --- a/gexpect.go +++ b/gexpect.go @@ -182,10 +182,10 @@ func (expect *ExpectSubprocess) ExpectRegex(regex string) (bool, error) { return regexp.MatchReader(regex, expect.buf) } -func (expect *ExpectSubprocess) ExpectRegexFind(regex string) ([]string, error) { +func (expect *ExpectSubprocess) expectRegexFind(regex string, output bool) ([]string, string, error) { re, err := regexp.Compile(regex) if err != nil { - return nil, err + return nil, "", err } expect.buf.StartCollecting() pairs := re.FindReaderSubmatchIndex(expect.buf) @@ -197,7 +197,16 @@ func (expect *ExpectSubprocess) ExpectRegexFind(regex string) ([]string, error) result[i] = stringIndexedInto[pairs[i*2]:pairs[i*2+1]] } // convert indexes to strings - return result, nil + return result, stringIndexedInto, nil +} + +func (expect *ExpectSubprocess) ExpectRegexFind(regex string) ([]string, error) { + result, _, err := expect.expectRegexFind(regex, false) + return result, err +} + +func (expect *ExpectSubprocess) ExpectRegexFindWithOutput(regex string) ([]string, string, error) { + return expect.expectRegexFind(regex, true) } func buildKMPTable(searchString string) []int { From 2879eb1c66f3a44b3d1dc3e5d16a42716daa32b9 Mon Sep 17 00:00:00 2001 From: Stefan Junker Date: Fri, 31 Jul 2015 18:04:08 +0200 Subject: [PATCH 02/14] Split std anad non-std imports --- gexpect.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gexpect.go b/gexpect.go index 54a2abc..a9b5a1b 100644 --- a/gexpect.go +++ b/gexpect.go @@ -3,14 +3,15 @@ package gexpect import ( "bytes" "errors" - shell "github.com/kballard/go-shellquote" - "github.com/kr/pty" "io" "os" "os/exec" "regexp" "time" "unicode/utf8" + + shell "github.com/kballard/go-shellquote" + "github.com/kr/pty" ) type ExpectSubprocess struct { From 42d1f7f6595d2789358240da905c45ad879581ee Mon Sep 17 00:00:00 2001 From: Stefan Junker Date: Mon, 27 Jul 2015 15:26:48 +0200 Subject: [PATCH 03/14] Add function ExpectTimeoutRegexFindWithOutput - New function that returns the output of the child, which might be of interest for the caller. Also offers to timeout after a specified time. - Introduces tests for regex related functions. --- gexpect.go | 30 ++++++++++++++++++- gexpect_test.go | 78 +++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 104 insertions(+), 4 deletions(-) diff --git a/gexpect.go b/gexpect.go index a9b5a1b..8bd2eb3 100644 --- a/gexpect.go +++ b/gexpect.go @@ -198,7 +198,26 @@ func (expect *ExpectSubprocess) expectRegexFind(regex string, output bool) ([]st result[i] = stringIndexedInto[pairs[i*2]:pairs[i*2+1]] } // convert indexes to strings - return result, stringIndexedInto, nil + + if len(result) == 0 { + err = errors.New("ExpectRegex didn't find regex") + } + return result, stringIndexedInto, err +} + +func (expect *ExpectSubprocess) expectTimeoutRegexFind(regex string, timeout time.Duration) (result []string, out string, err error) { + t := make(chan bool) + go func() { + result, out, err = expect.ExpectRegexFindWithOutput(regex) + t <- false + }() + go func() { + time.Sleep(timeout) + err = errors.New("ExpectRegex timed out") + t <- true + }() + <-t + return result, out, err } func (expect *ExpectSubprocess) ExpectRegexFind(regex string) ([]string, error) { @@ -206,10 +225,19 @@ func (expect *ExpectSubprocess) ExpectRegexFind(regex string) ([]string, error) return result, err } +func (expect *ExpectSubprocess) ExpectTimeoutRegexFind(regex string, timeout time.Duration) ([]string, error) { + result, _, err := expect.expectTimeoutRegexFind(regex, timeout) + return result, err +} + func (expect *ExpectSubprocess) ExpectRegexFindWithOutput(regex string) ([]string, string, error) { return expect.expectRegexFind(regex, true) } +func (expect *ExpectSubprocess) ExpectTimeoutRegexFindWithOutput(regex string, timeout time.Duration) ([]string, string, error) { + return expect.expectTimeoutRegexFind(regex, timeout) +} + func buildKMPTable(searchString string) []int { pos := 2 cnd := 0 diff --git a/gexpect_test.go b/gexpect_test.go index 544ceb2..8ba6ac9 100644 --- a/gexpect_test.go +++ b/gexpect_test.go @@ -1,8 +1,10 @@ package gexpect import ( + "fmt" "strings" "testing" + "time" ) func TestHelloWorld(t *testing.T) { @@ -51,6 +53,7 @@ func TestHelloWorldFailureCase(t *testing.T) { } func TestBiChannel(t *testing.T) { + t.Logf("Testing BiChannel screen... ") child, err := Spawn("cat") if err != nil { @@ -68,12 +71,14 @@ func TestBiChannel(t *testing.T) { } } } - sender <- "echo\n" + + endlChar := fmt.Sprintln("")[0] + sender <- fmt.Sprintf("echo%c", endlChar) wait("echo") - sender <- "echo2" + sender <- fmt.Sprintf("echo2%c", endlChar) wait("echo2") child.Close() - // child.Wait() + child.Wait() } func TestCommandStart(t *testing.T) { @@ -164,3 +169,70 @@ func TestRegexFind(t *testing.T) { } } } + +func TestRegexWithOutput(t *testing.T) { + t.Logf("Testing Regular Expression search with output...") + + s := "You will not find me" + p, err := Spawn("echo -n " + s) + if err != nil { + t.Fatalf("Cannot exec rkt: %v", err) + } + searchPattern := `I should not find you` + result, out, err := p.ExpectRegexFindWithOutput(searchPattern) + if err == nil { + t.Fatalf("Shouldn't have found `%v` in `%v`", searchPattern, out) + } + if s != out { + t.Fatalf("Child output didn't match: %s", out) + } + + err = p.Wait() + if err != nil { + t.Fatalf("Child didn't terminate correctly: %v", err) + } + + p, err = Spawn("echo You will find me") + if err != nil { + t.Fatalf("Cannot exec rkt: %v", err) + } + searchPattern = `.*(You will).*` + result, out, err = p.ExpectRegexFindWithOutput(searchPattern) + if err != nil || result[1] != "You will" { + t.Fatalf("Did not find pattern `%v` in `%v'\n", searchPattern, out) + } + err = p.Wait() + if err != nil { + t.Fatalf("Child didn't terminate correctly: %v", err) + } +} + +func TestRegexTimeoutWithOutput(t *testing.T) { + t.Logf("Testing Regular Expression search with output...") + + seconds := 2 + timeout := time.Duration(seconds-1) * time.Second + + p, err := Spawn(fmt.Sprintf("sh -c 'sleep %d && echo You find me'", seconds)) + if err != nil { + t.Fatalf("Cannot exec rkt: %v", err) + } + searchPattern := `find me` + result, out, err := p.ExpectTimeoutRegexFindWithOutput(searchPattern, timeout) + if err == nil { + t.Fatalf("Shouldn't finished call with result: %v", result) + } + + seconds = 2 + timeout = time.Duration(seconds+1) * time.Second + + p, err = Spawn(fmt.Sprintf("sh -c 'sleep %d && echo You find me'", seconds)) + if err != nil { + t.Fatalf("Cannot exec rkt: %v", err) + } + searchPattern = `find me` + result, out, err = p.ExpectTimeoutRegexFindWithOutput(searchPattern, timeout) + if err != nil { + t.Fatalf("Didn't find %v in output: %v", searchPattern, out) + } +} From f1c747ff8ab93361b9f209d5472d622f2387b083 Mon Sep 17 00:00:00 2001 From: Stefan Junker Date: Fri, 31 Jul 2015 18:26:20 +0200 Subject: [PATCH 04/14] Rewrite imports due to fork --- examples/ftp.go | 2 +- examples/ping.go | 2 +- examples/python.go | 2 +- examples/screen.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/ftp.go b/examples/ftp.go index 8700e6a..1680e14 100644 --- a/examples/ftp.go +++ b/examples/ftp.go @@ -1,6 +1,6 @@ package main -import gexpect "github.com/ThomasRooney/gexpect" +import gexpect "github.com/steveeJ/gexpect" import "log" func main() { diff --git a/examples/ping.go b/examples/ping.go index 78a635a..60d4070 100644 --- a/examples/ping.go +++ b/examples/ping.go @@ -1,6 +1,6 @@ package main -import gexpect "github.com/ThomasRooney/gexpect" +import gexpect "github.com/steveeJ/gexpect" import "log" func main() { diff --git a/examples/python.go b/examples/python.go index ea1208a..47d11db 100644 --- a/examples/python.go +++ b/examples/python.go @@ -1,6 +1,6 @@ package main -import "github.com/ThomasRooney/gexpect" +import "github.com/steveeJ/gexpect" import "fmt" func main() { diff --git a/examples/screen.go b/examples/screen.go index c94aab2..a9b4e0e 100644 --- a/examples/screen.go +++ b/examples/screen.go @@ -1,6 +1,6 @@ package main -import "github.com/ThomasRooney/gexpect" +import "github.com/steveeJ/gexpect" import "fmt" import "strings" From 17b075372944c747dfb3156db3a22b02e1863529 Mon Sep 17 00:00:00 2001 From: Stefan Junker Date: Fri, 31 Jul 2015 18:57:32 +0200 Subject: [PATCH 05/14] tests: minor fixes --- gexpect_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gexpect_test.go b/gexpect_test.go index 8ba6ac9..87bd907 100644 --- a/gexpect_test.go +++ b/gexpect_test.go @@ -72,10 +72,10 @@ func TestBiChannel(t *testing.T) { } } - endlChar := fmt.Sprintln("")[0] - sender <- fmt.Sprintf("echo%c", endlChar) + endlChar := fmt.Sprintln("") + sender <- fmt.Sprintf("echo%v", endlChar) wait("echo") - sender <- fmt.Sprintf("echo2%c", endlChar) + sender <- fmt.Sprintf("echo2%v", endlChar) wait("echo2") child.Close() child.Wait() @@ -208,7 +208,7 @@ func TestRegexWithOutput(t *testing.T) { } func TestRegexTimeoutWithOutput(t *testing.T) { - t.Logf("Testing Regular Expression search with output...") + t.Logf("Testing Regular Expression search with timeout and output...") seconds := 2 timeout := time.Duration(seconds-1) * time.Second From cbae588c260f35c9d80a36cf2c57319bf1eab314 Mon Sep 17 00:00:00 2001 From: Stefan Junker Date: Sat, 1 Aug 2015 17:42:36 +0200 Subject: [PATCH 06/14] Increased error message detail --- gexpect.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gexpect.go b/gexpect.go index 8bd2eb3..9b8d620 100644 --- a/gexpect.go +++ b/gexpect.go @@ -3,6 +3,7 @@ package gexpect import ( "bytes" "errors" + "fmt" "io" "os" "os/exec" @@ -200,7 +201,7 @@ func (expect *ExpectSubprocess) expectRegexFind(regex string, output bool) ([]st // convert indexes to strings if len(result) == 0 { - err = errors.New("ExpectRegex didn't find regex") + err = fmt.Errorf("ExpectRegex didn't find regex '%v'.", regex) } return result, stringIndexedInto, err } @@ -213,7 +214,7 @@ func (expect *ExpectSubprocess) expectTimeoutRegexFind(regex string, timeout tim }() go func() { time.Sleep(timeout) - err = errors.New("ExpectRegex timed out") + err = fmt.Errorf("ExpectRegex timed out after %v finding '%v'.", timeout, regex) t <- true }() <-t @@ -275,7 +276,7 @@ func (expect *ExpectSubprocess) ExpectTimeout(searchString string, timeout time. select { case e = <-result: case <-time.After(timeout): - e = errors.New("Expect timed out.") + e = fmt.Errorf("Expect timed out after %v waiting for '%v'.", timeout, searchString) } return e } From d56b2c62c0a0ba2733c18b27024a961195f10a17 Mon Sep 17 00:00:00 2001 From: Stefan Junker Date: Sat, 1 Aug 2015 17:42:59 +0200 Subject: [PATCH 07/14] tests: fix typo in error message --- gexpect_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gexpect_test.go b/gexpect_test.go index 87bd907..4d4f4b2 100644 --- a/gexpect_test.go +++ b/gexpect_test.go @@ -220,7 +220,7 @@ func TestRegexTimeoutWithOutput(t *testing.T) { searchPattern := `find me` result, out, err := p.ExpectTimeoutRegexFindWithOutput(searchPattern, timeout) if err == nil { - t.Fatalf("Shouldn't finished call with result: %v", result) + t.Fatalf("Shouldn't have finished call with result: %v", result) } seconds = 2 From b96dabc830dcaadd563d164516326d0d88530093 Mon Sep 17 00:00:00 2001 From: Alban Crequy Date: Thu, 12 Nov 2015 16:17:51 +0100 Subject: [PATCH 08/14] include output in the error when a timeout occurs --- gexpect.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gexpect.go b/gexpect.go index 9b8d620..ab6287b 100644 --- a/gexpect.go +++ b/gexpect.go @@ -214,7 +214,7 @@ func (expect *ExpectSubprocess) expectTimeoutRegexFind(regex string, timeout tim }() go func() { time.Sleep(timeout) - err = fmt.Errorf("ExpectRegex timed out after %v finding '%v'.", timeout, regex) + err = fmt.Errorf("ExpectRegex timed out after %v finding '%v'.\nOutput:\n%s", timeout, regex, expect.Collect()) t <- true }() <-t @@ -276,7 +276,7 @@ func (expect *ExpectSubprocess) ExpectTimeout(searchString string, timeout time. select { case e = <-result: case <-time.After(timeout): - e = fmt.Errorf("Expect timed out after %v waiting for '%v'.", timeout, searchString) + e = fmt.Errorf("Expect timed out after %v waiting for '%v'.\nOutput:\n%s", timeout, searchString, expect.Collect()) } return e } From 730eeb6f55a562a07ebeddc142d2e97d9e0d92c1 Mon Sep 17 00:00:00 2001 From: Jonathan Boulle Date: Wed, 25 Nov 2015 11:33:29 -0800 Subject: [PATCH 09/14] examples: fix import references --- examples/ftp.go | 2 +- examples/ping.go | 2 +- examples/python.go | 2 +- examples/screen.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/ftp.go b/examples/ftp.go index 1680e14..28fbbae 100644 --- a/examples/ftp.go +++ b/examples/ftp.go @@ -1,6 +1,6 @@ package main -import gexpect "github.com/steveeJ/gexpect" +import gexpect "github.com/coreos/gexpect" import "log" func main() { diff --git a/examples/ping.go b/examples/ping.go index 60d4070..a39dd68 100644 --- a/examples/ping.go +++ b/examples/ping.go @@ -1,6 +1,6 @@ package main -import gexpect "github.com/steveeJ/gexpect" +import gexpect "github.com/coreos/gexpect" import "log" func main() { diff --git a/examples/python.go b/examples/python.go index 47d11db..ef8be7c 100644 --- a/examples/python.go +++ b/examples/python.go @@ -1,6 +1,6 @@ package main -import "github.com/steveeJ/gexpect" +import "github.com/coreos/gexpect" import "fmt" func main() { diff --git a/examples/screen.go b/examples/screen.go index a9b4e0e..dfdd747 100644 --- a/examples/screen.go +++ b/examples/screen.go @@ -1,6 +1,6 @@ package main -import "github.com/steveeJ/gexpect" +import "github.com/coreos/gexpect" import "fmt" import "strings" From 59e31a76465b06db93eaa7dd37a7921526d87b90 Mon Sep 17 00:00:00 2001 From: Krzesimir Nowak Date: Wed, 13 Jan 2016 13:12:02 +0100 Subject: [PATCH 10/14] Put the excess unmatched characters back into the buffer The expectRegexOutput uses the regexp.FindReaderSubmatchIndex function to do the matching. As its docs say, it may read more characters than it is necessary from the stream. This may be problematic if the regex we use can match chunks of the stream that come right after another. This is because the function might match the first chunk and read into the middle of the second chunk, so the subsequent call to the function will match the third chunk, not the second. For example: data: "one two three" regexp: "\w{3,}" The first call to the function may return "one" as a match and "one tw" as an output. We can see that the output has the beginning of the second word which comes after the matched first word. This part is not available anymore in the stream, thus the following call to the function will return "three" as a match (instead of "two") and "o three" as an output. To fix it, we put the excess characters coming after the whole match back into the stream, so the subsequent calls to the function may start their matching from the place when last whole match ended. --- gexpect.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/gexpect.go b/gexpect.go index ab6287b..5f72233 100644 --- a/gexpect.go +++ b/gexpect.go @@ -202,6 +202,15 @@ func (expect *ExpectSubprocess) expectRegexFind(regex string, output bool) ([]st if len(result) == 0 { err = fmt.Errorf("ExpectRegex didn't find regex '%v'.", regex) + } else { + // The number in pairs[1] is an index of a first + // character outside the whole match + putBackIdx := pairs[1] + if len(stringIndexedInto) > putBackIdx { + stringToPutBack := stringIndexedInto[putBackIdx:] + stringIndexedInto = stringIndexedInto[:putBackIdx] + expect.buf.PutBack([]byte(stringToPutBack)) + } } return result, stringIndexedInto, err } From 3bc350a56660aa1f1400cefa3a5cca78c9060538 Mon Sep 17 00:00:00 2001 From: Krzesimir Nowak Date: Wed, 13 Jan 2016 13:42:05 +0100 Subject: [PATCH 11/14] Add a test for putting the excess characters back into the buffer --- gexpect_test.go | 85 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/gexpect_test.go b/gexpect_test.go index 4d4f4b2..b78263a 100644 --- a/gexpect_test.go +++ b/gexpect_test.go @@ -236,3 +236,88 @@ func TestRegexTimeoutWithOutput(t *testing.T) { t.Fatalf("Didn't find %v in output: %v", searchPattern, out) } } + +func TestRegexFindNoExcessBytes(t *testing.T) { + t.Logf("Testing Regular Expressions returning output with no excess strings") + repeats := 100 + tests := []struct { + desc string + loopBody string + searchPattern string + expectFullTmpl string + unmatchedData string + }{ + { + desc: `matching lines line by line with $ at the end of the regexp`, + loopBody: `echo "prefix: ${i} line"`, + searchPattern: `(?m)^prefix:\s+(\d+) line\s??$`, + expectFullTmpl: `prefix: %d line`, + unmatchedData: "\n", + // the "$" char at the end of regexp does not + // match the \n, so it is left as an unmatched + // data + }, + { + desc: `matching lines line by line with \n at the end of the regexp`, + loopBody: `echo "prefix: ${i} line"`, + searchPattern: `(?m)^prefix:\s+(\d+) line\s??\n`, + expectFullTmpl: `prefix: %d line`, + unmatchedData: "", + }, + { + desc: `matching chunks in single line chunk by chunk`, + loopBody: `echo -n "a ${i} b"`, + searchPattern: `a\s+(\d+)\s+b`, + expectFullTmpl: `a %d b`, + unmatchedData: "", + }, + } + seqCmd := fmt.Sprintf("`seq 1 %d`", repeats) + shCmdTmpl := fmt.Sprintf(`sh -c 'for i in %s; do %%s; done'`, seqCmd) + for _, tt := range tests { + t.Logf("Test: %s", tt.desc) + shCmd := fmt.Sprintf(shCmdTmpl, tt.loopBody) + t.Logf("Running command: %s", shCmd) + p, err := Spawn(shCmd) + if err != nil { + t.Fatalf("Cannot exec shell script: %v", err) + } + defer func() { + if err := p.Wait(); err != nil { + t.Fatalf("shell script didn't terminate correctly: %v", err) + } + }() + for i := 1; i <= repeats; i++ { + matches, output, err := p.ExpectRegexFindWithOutput(tt.searchPattern) + if err != nil { + t.Fatalf("Failed to get the match number %d: %v", i, err) + } + if len(matches) != 2 { + t.Fatalf("Expected only 2 matches, got %d", len(matches)) + } + full := strings.TrimSpace(matches[0]) + expFull := fmt.Sprintf(tt.expectFullTmpl, i) + partial := matches[1] + expPartial := fmt.Sprintf("%d", i) + if full != expFull { + t.Fatalf("Did not the expected full match %q, got %q", expFull, full) + } + if partial != expPartial { + t.Fatalf("Did not the expected partial match %q, got %q", expPartial, partial) + } + // The output variable usually contains the + // unmatched data followed by the whole match. + // The first line is special as it has no data + // preceding it. + var expectedOutput string + if i == 1 || tt.unmatchedData == "" { + expectedOutput = matches[0] + } else { + expectedOutput = fmt.Sprintf("%s%s", tt.unmatchedData, matches[0]) + } + if output != expectedOutput { + t.Fatalf("The collected output %q should be the same as the whole match %q", output, expectedOutput) + } + } + } +} From 17aee1a1e0dd00c0e282bcbaa646961c3cf7892a Mon Sep 17 00:00:00 2001 From: Pawel Palucki Date: Thu, 18 Feb 2016 17:30:20 +0100 Subject: [PATCH 12/14] fix buffer.ReadRune for multibyte unicode characters In buffer.ReadRune the utf.FullRune checks given slice if there is enough bytes for multibyte utf8 encoded unicode character, but because chunk is always UTFMax long check is always true, without actually getting remaining bytes to decode rune correctly This fix passes only slice of actual (according the n for buffer and l for file) read bytes to FullRune. Includes unittest for ReadRune with partially filled buffer.b. --- gexpect.go | 4 ++-- gexpect_test.go | 57 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/gexpect.go b/gexpect.go index 63bbf00..12ccb53 100644 --- a/gexpect.go +++ b/gexpect.go @@ -71,7 +71,7 @@ func (buf *buffer) ReadRune() (r rune, size int, err error) { if err != nil { return 0, 0, err } - if utf8.FullRune(chunk) { + if utf8.FullRune(chunk[:n]) { r, rL := utf8.DecodeRune(chunk) if n > rL { buf.PutBack(chunk[rL:n]) @@ -90,7 +90,7 @@ func (buf *buffer) ReadRune() (r rune, size int, err error) { } l = l + fn - if utf8.FullRune(chunk) { + if utf8.FullRune(chunk[:l]) { r, rL := utf8.DecodeRune(chunk) if buf.collect { buf.collection.WriteRune(r) diff --git a/gexpect_test.go b/gexpect_test.go index 7166d31..523d1c5 100644 --- a/gexpect_test.go +++ b/gexpect_test.go @@ -3,7 +3,9 @@ package gexpect import ( + "bytes" "fmt" + "io/ioutil" "strings" "testing" "time" @@ -117,6 +119,7 @@ var regexMatchTests = []struct { {`a+hello`, `aaaahello`, `bhello`}, {`(hello|world)`, `hello`, `unknown`}, {`(hello|world)`, `world`, `unknown`}, + {"\u00a9", "\u00a9", `unknown`}, // 2 bytes long unicode character "copyright sign" } func TestRegexMatch(t *testing.T) { @@ -153,6 +156,7 @@ var regexFindTests = []struct { {`so.. (hello|world)`, `so.. hello`, []string{"so.. hello", "hello"}}, {`(a+)hello`, `aaaahello`, []string{"aaaahello", "aaaa"}}, {`\d+ (\d+) (\d+)`, `123 456 789`, []string{"123 456 789", "456", "789"}}, + {`\d+ (\d+) (\d+)`, "\u00a9 123 456 789 \u00a9", []string{"123 456 789", "456", "789"}}, // check unicode characters } func TestRegexFind(t *testing.T) { @@ -335,3 +339,56 @@ func TestRegexFindNoExcessBytes(t *testing.T) { } } } + +func TestBufferReadRune(t *testing.T) { + tests := []struct { + bufferContent []byte + fileContent []byte + expectedRune rune + }{ + // unicode "copyright char" is \u00a9 is encoded as two bytes in utf8 0xc2 0xa9 + {[]byte{0xc2, 0xa9}, []byte{}, '\u00a9'}, // whole rune is already in buffer.b + {[]byte{0xc2}, []byte{0xa9}, '\u00a9'}, // half of is in the buffer.b and another half still in buffer.f (file) + {[]byte{}, []byte{0xc2, 0xa9}, '\u00a9'}, // whole rune is the file + // some random noise in the end of file + {[]byte{0xc2, 0xa9}, []byte{0x20, 0x20, 0x20, 0x20}, '\u00a9'}, + {[]byte{0xc2}, []byte{0xa9, 0x20, 0x20, 0x20, 0x20}, '\u00a9'}, + {[]byte{}, []byte{0xc2, 0xa9, 0x20, 0x20, 0x20, 0x20}, '\u00a9'}, + } + + for i, tt := range tests { + + // prepare tmp file with fileContent + f, err := ioutil.TempFile("", "") + if err != nil { + t.Fatal(err) + } + n, err := f.Write(tt.fileContent) + if err != nil { + t.Fatal(err) + } + if n != len(tt.fileContent) { + t.Fatal("expected fileContent written to temp file") + } + _, err = f.Seek(0, 0) + if err != nil { + t.Fatal(err) + } + + // new buffer + buf := buffer{f: f, b: *bytes.NewBuffer(tt.bufferContent)} + + // call ReadRune + r, size, err := buf.ReadRune() + + if r != tt.expectedRune { + t.Fatalf("#%d: expected rune %+q but go is %+q", i, tt.expectedRune, r) + } + + if size != len(string(tt.expectedRune)) { + t.Fatalf("#%d: expected rune %d bytes long but got just %d bytes long", i, len(string(tt.expectedRune)), size) + } + + } + +} From 6e0175037a89fc36996328ab8861a8a3e5755ce5 Mon Sep 17 00:00:00 2001 From: Luca Bruno Date: Thu, 8 Dec 2016 16:41:34 +0000 Subject: [PATCH 13/14] expect: do not prematurely return error on last read This commit changes Expect() to not return early errors on final reads (`EOF` but `n != 0`) in order to avoid discarding the last bytes from the source buffer. According to Reader interface next loop iteration will get a (0, EOF) causing this method to return. --- gexpect.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gexpect.go b/gexpect.go index 4ea620e..6e918cb 100644 --- a/gexpect.go +++ b/gexpect.go @@ -318,7 +318,7 @@ func (expect *ExpectSubprocess) Expect(searchString string) (e error) { for { n, err := expect.buf.Read(chunk) - if err != nil { + if n == 0 && err != nil { return err } if expect.outputBuffer != nil { From 24be02b5bbf8cb24e2032291a8fca8d8f3480323 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Mon, 26 Dec 2016 10:32:59 +1000 Subject: [PATCH 14/14] Fixes to let me copy&paste examples Signed-off-by: Sven Dowideit --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2f9a8b7..c30e0e2 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ It makes it simpler to create and control other terminal applications. `ReadLine`, `ReadUntil` and `SendLine` send strings from/to `stdout/stdin` respectively - child := gexpect.Spawn("cat") + child, _ := gexpect.Spawn("cat") child.SendLine("echoing process_stdin") // SendLine(command string) (error) msg, _ := child.ReadLine() // msg = echoing process_stdin @@ -34,24 +34,24 @@ It makes it simpler to create and control other terminal applications. `AsyncInteractChannels` spawns two go routines to pipe into and from `stdout`/`stdin`, allowing for some usecases to be a little simpler. - child := gexpect.spawn("sh") + child, _ := gexpect.Spawn("sh") sender, receiver := child.AsyncInteractChannels() sender <- "echo Hello World\n" // Send to stdin line, open := <- receiver // Recieve a line from stdout/stderr // When the subprocess stops (e.g. with child.Close()) , receiver is closed if open { - fmt.Printf("Received %s", line)] + fmt.Printf("Received %s", line) } `ExpectRegex` uses golang's internal regex engine to wait until a match from the process with the given regular expression (or an error on process termination with no match). - child := gexpect.Spawn("echo accb") + child, _ := gexpect.Spawn("echo accb") match, _ := child.ExpectRegex("a..b") // (match=true) `ExpectRegexFind` allows for groups to be extracted from process stdout. The first element is an array of containing the total matched text, followed by each subexpression group match. - child := gexpect.Spawn("echo 123 456 789") + child, _ := gexpect.Spawn("echo 123 456 789") result, _ := child.ExpectRegexFind("\d+ (\d+) (\d+)") // result = []string{"123 456 789", "456", "789"} @@ -61,4 +61,4 @@ See `gexpect_test.go` and the `examples` folder for full syntax github.com/kballard/go-shellquote github.com/kr/pty - KMP Algorithm: "http://blog.databigbang.com/searching-for-substrings-in-streams-a-slight-modification-of-the-knuth-morris-pratt-algorithm-in-haxe/" \ No newline at end of file + KMP Algorithm: "http://blog.databigbang.com/searching-for-substrings-in-streams-a-slight-modification-of-the-knuth-morris-pratt-algorithm-in-haxe/"