pax_global_header00006660000000000000000000000064140664032030014510gustar00rootroot0000000000000052 comment=3fdce262e5fbc2ca457a4399442c5f2af447afbb golang-github-thomasrooney-gexpect-0.0~git20161231.5482f03/000077500000000000000000000000001406640320300227145ustar00rootroot00000000000000golang-github-thomasrooney-gexpect-0.0~git20161231.5482f03/LICENCE000066400000000000000000000020401406640320300236750ustar00rootroot00000000000000Copyright (C) 2014 Thomas Rooney Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.golang-github-thomasrooney-gexpect-0.0~git20161231.5482f03/README.md000066400000000000000000000044461406640320300242030ustar00rootroot00000000000000# Gexpect Gexpect is a pure golang expect-like module. It makes it simpler to create and control other terminal applications. child, err := gexpect.Spawn("python") if err != nil { panic(err) } child.Expect(">>>") child.SendLine("print 'Hello World'") child.Interact() child.Close() ## Examples `Spawn` handles the argument parsing from a string child.Spawn("/bin/sh -c 'echo \"my complicated command\" | tee log | cat > log2'") child.ReadLine() // ReadLine() (string, error) child.ReadUntil(' ') // ReadUntil(delim byte) ([]byte, error) `ReadLine`, `ReadUntil` and `SendLine` send strings from/to `stdout/stdin` respectively child, _ := gexpect.Spawn("cat") child.SendLine("echoing process_stdin") // SendLine(command string) (error) msg, _ := child.ReadLine() // msg = echoing process_stdin `Wait` and `Close` allow for graceful and ungraceful termination. child.Wait() // Waits until the child terminates naturally. child.Close() // Sends a kill command `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") 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) } `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") 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") result, _ := child.ExpectRegexFind("\d+ (\d+) (\d+)") // result = []string{"123 456 789", "456", "789"} See `gexpect_test.go` and the `examples` folder for full syntax ## Credits 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/" golang-github-thomasrooney-gexpect-0.0~git20161231.5482f03/examples/000077500000000000000000000000001406640320300245325ustar00rootroot00000000000000golang-github-thomasrooney-gexpect-0.0~git20161231.5482f03/examples/ftp.go000066400000000000000000000011231406640320300256470ustar00rootroot00000000000000package main import gexpect "github.com/ThomasRooney/gexpect" import "log" func main() { log.Printf("Testing Ftp... ") child, err := gexpect.Spawn("ftp ftp.openbsd.org") if err != nil { panic(err) } child.Expect("Name") child.SendLine("anonymous") child.Expect("Password") child.SendLine("pexpect@sourceforge.net") child.Expect("ftp> ") child.SendLine("cd /pub/OpenBSD/3.7/packages/i386") child.Expect("ftp> ") child.SendLine("bin") child.Expect("ftp> ") child.SendLine("prompt") child.Expect("ftp> ") child.SendLine("pwd") child.Expect("ftp> ") log.Printf("Success\n") } golang-github-thomasrooney-gexpect-0.0~git20161231.5482f03/examples/ping.go000066400000000000000000000004101406640320300260110ustar00rootroot00000000000000package main import gexpect "github.com/ThomasRooney/gexpect" import "log" func main() { log.Printf("Testing Ping interact... \n") child, err := gexpect.Spawn("ping -c8 127.0.0.1") if err != nil { panic(err) } child.Interact() log.Printf("Success\n") } golang-github-thomasrooney-gexpect-0.0~git20161231.5482f03/examples/python.go000066400000000000000000000006641406640320300264100ustar00rootroot00000000000000package main import "github.com/ThomasRooney/gexpect" import "fmt" func main() { fmt.Printf("Starting python.. \n") child, err := gexpect.Spawn("python") if err != nil { panic(err) } fmt.Printf("Expecting >>>.. \n") child.Expect(">>>") fmt.Printf("print 'Hello World'..\n") child.SendLine("print 'Hello World'") child.Expect(">>>") fmt.Printf("Interacting.. \n") child.Interact() fmt.Printf("Done \n") child.Close() } golang-github-thomasrooney-gexpect-0.0~git20161231.5482f03/examples/screen.go000066400000000000000000000016471406640320300263500ustar00rootroot00000000000000package main import "github.com/ThomasRooney/gexpect" import "fmt" import "strings" func main() { waitChan := make(chan string) fmt.Printf("Starting screen.. \n") child, err := gexpect.Spawn("screen") if err != nil { panic(err) } sender, reciever := child.AsyncInteractChannels() go func() { waitString := "" count := 0 for { select { case waitString = <-waitChan: count++ case msg, open := <-reciever: if !open { return } fmt.Printf("Recieved: %s\n", msg) if strings.Contains(msg, waitString) { if count >= 1 { waitChan <- msg count -= 1 } } } } }() wait := func(str string) { waitChan <- str <-waitChan } fmt.Printf("Waiting until started.. \n") wait(" ") fmt.Printf("Sending Enter.. \n") sender <- "\n" wait("$") fmt.Printf("Sending echo.. \n") sender <- "echo Hello World\n" wait("Hello World") fmt.Printf("Received echo. \n") } golang-github-thomasrooney-gexpect-0.0~git20161231.5482f03/gexpect.go000066400000000000000000000224301406640320300247030ustar00rootroot00000000000000// +build !windows package gexpect import ( "bytes" "errors" "fmt" "io" "os" "os/exec" "regexp" "time" "unicode/utf8" shell "github.com/kballard/go-shellquote" "github.com/kr/pty" ) var ( ErrEmptySearch = errors.New("empty search string") ) type ExpectSubprocess struct { Cmd *exec.Cmd buf *buffer outputBuffer []byte } type buffer struct { f *os.File b bytes.Buffer collect bool collection bytes.Buffer } func (buf *buffer) StartCollecting() { buf.collect = true } func (buf *buffer) StopCollecting() (result string) { result = string(buf.collection.Bytes()) buf.collect = false buf.collection.Reset() return result } func (buf *buffer) Read(chunk []byte) (int, error) { nread := 0 if buf.b.Len() > 0 { n, err := buf.b.Read(chunk) if err != nil { return n, err } if n == len(chunk) { return n, nil } nread = n } fn, err := buf.f.Read(chunk[nread:]) return fn + nread, err } func (buf *buffer) ReadRune() (r rune, size int, err error) { l := buf.b.Len() chunk := make([]byte, utf8.UTFMax) if l > 0 { n, err := buf.b.Read(chunk) if err != nil { return 0, 0, err } if utf8.FullRune(chunk[:n]) { r, rL := utf8.DecodeRune(chunk) if n > rL { buf.PutBack(chunk[rL:n]) } if buf.collect { buf.collection.WriteRune(r) } return r, rL, nil } } // else add bytes from the file, then try that for l < utf8.UTFMax { fn, err := buf.f.Read(chunk[l : l+1]) if err != nil { return 0, 0, err } l = l + fn if utf8.FullRune(chunk[:l]) { r, rL := utf8.DecodeRune(chunk) if buf.collect { buf.collection.WriteRune(r) } return r, rL, nil } } return 0, 0, errors.New("File is not a valid UTF=8 encoding") } func (buf *buffer) PutBack(chunk []byte) { if len(chunk) == 0 { return } if buf.b.Len() == 0 { buf.b.Write(chunk) return } d := make([]byte, 0, len(chunk)+buf.b.Len()) d = append(d, chunk...) d = append(d, buf.b.Bytes()...) buf.b.Reset() buf.b.Write(d) } func SpawnAtDirectory(command string, directory string) (*ExpectSubprocess, error) { expect, err := _spawn(command) if err != nil { return nil, err } expect.Cmd.Dir = directory return _start(expect) } func Command(command string) (*ExpectSubprocess, error) { expect, err := _spawn(command) if err != nil { return nil, err } return expect, nil } func (expect *ExpectSubprocess) Start() error { _, err := _start(expect) return err } func Spawn(command string) (*ExpectSubprocess, error) { expect, err := _spawn(command) if err != nil { return nil, err } return _start(expect) } func (expect *ExpectSubprocess) Close() error { if err := expect.Cmd.Process.Kill(); err != nil { return err } if err := expect.buf.f.Close(); err != nil { return err } return nil } func (expect *ExpectSubprocess) AsyncInteractChannels() (send chan string, receive chan string) { receive = make(chan string) send = make(chan string) go func() { for { str, err := expect.ReadLine() if err != nil { close(receive) return } receive <- str } }() go func() { for { select { case sendCommand, exists := <-send: { if !exists { return } err := expect.Send(sendCommand) if err != nil { receive <- "gexpect Error: " + err.Error() return } } } } }() return } func (expect *ExpectSubprocess) ExpectRegex(regex string) (bool, error) { return regexp.MatchReader(regex, expect.buf) } func (expect *ExpectSubprocess) expectRegexFind(regex string, output bool) ([]string, string, error) { re, err := regexp.Compile(regex) if err != nil { return nil, "", err } expect.buf.StartCollecting() pairs := re.FindReaderSubmatchIndex(expect.buf) stringIndexedInto := expect.buf.StopCollecting() l := len(pairs) numPairs := l / 2 result := make([]string, numPairs) for i := 0; i < numPairs; i += 1 { result[i] = stringIndexedInto[pairs[i*2]:pairs[i*2+1]] } // convert indexes to strings 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 } 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 = fmt.Errorf("ExpectRegex timed out after %v finding '%v'.\nOutput:\n%s", timeout, regex, expect.Collect()) t <- true }() <-t return result, out, err } func (expect *ExpectSubprocess) ExpectRegexFind(regex string) ([]string, error) { result, _, err := expect.expectRegexFind(regex, false) 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 length := len(searchString) var table []int if length < 2 { length = 2 } table = make([]int, length) table[0] = -1 table[1] = 0 for pos < len(searchString) { if searchString[pos-1] == searchString[cnd] { cnd += 1 table[pos] = cnd pos += 1 } else if cnd > 0 { cnd = table[cnd] } else { table[pos] = 0 pos += 1 } } return table } func (expect *ExpectSubprocess) ExpectTimeout(searchString string, timeout time.Duration) (e error) { result := make(chan error) go func() { result <- expect.Expect(searchString) }() select { case e = <-result: case <-time.After(timeout): e = fmt.Errorf("Expect timed out after %v waiting for '%v'.\nOutput:\n%s", timeout, searchString, expect.Collect()) } return e } func (expect *ExpectSubprocess) Expect(searchString string) (e error) { target := len(searchString) if target < 1 { return ErrEmptySearch } chunk := make([]byte, target*2) if expect.outputBuffer != nil { expect.outputBuffer = expect.outputBuffer[0:] } m := 0 i := 0 // Build KMP Table table := buildKMPTable(searchString) for { n, err := expect.buf.Read(chunk) if n == 0 && err != nil { return err } if expect.outputBuffer != nil { expect.outputBuffer = append(expect.outputBuffer, chunk[:n]...) } offset := m + i for m+i-offset < n { if searchString[i] == chunk[m+i-offset] { i += 1 if i == target { unreadIndex := m + i - offset if len(chunk) > unreadIndex { expect.buf.PutBack(chunk[unreadIndex:n]) } return nil } } else { m += i - table[i] if table[i] > -1 { i = table[i] } else { i = 0 } } } } } func (expect *ExpectSubprocess) Send(command string) error { _, err := io.WriteString(expect.buf.f, command) return err } func (expect *ExpectSubprocess) Capture() { if expect.outputBuffer == nil { expect.outputBuffer = make([]byte, 0) } } func (expect *ExpectSubprocess) Collect() []byte { collectOutput := make([]byte, len(expect.outputBuffer)) copy(collectOutput, expect.outputBuffer) expect.outputBuffer = nil return collectOutput } func (expect *ExpectSubprocess) SendLine(command string) error { _, err := io.WriteString(expect.buf.f, command+"\r\n") return err } func (expect *ExpectSubprocess) Interact() { defer expect.Cmd.Wait() io.Copy(os.Stdout, &expect.buf.b) go io.Copy(os.Stdout, expect.buf.f) go io.Copy(expect.buf.f, os.Stdin) } func (expect *ExpectSubprocess) ReadUntil(delim byte) ([]byte, error) { join := make([]byte, 0, 512) chunk := make([]byte, 255) for { n, err := expect.buf.Read(chunk) for i := 0; i < n; i++ { if chunk[i] == delim { if len(chunk) > i+1 { expect.buf.PutBack(chunk[i+1:n]) } return join, nil } else { join = append(join, chunk[i]) } } if err != nil { return join, err } } } func (expect *ExpectSubprocess) Wait() error { return expect.Cmd.Wait() } func (expect *ExpectSubprocess) ReadLine() (string, error) { str, err := expect.ReadUntil('\n') return string(str), err } func _start(expect *ExpectSubprocess) (*ExpectSubprocess, error) { f, err := pty.Start(expect.Cmd) if err != nil { return nil, err } expect.buf.f = f return expect, nil } func _spawn(command string) (*ExpectSubprocess, error) { wrapper := new(ExpectSubprocess) wrapper.outputBuffer = nil splitArgs, err := shell.Split(command) if err != nil { return nil, err } numArguments := len(splitArgs) - 1 if numArguments < 0 { return nil, errors.New("gexpect: No command given to spawn") } path, err := exec.LookPath(splitArgs[0]) if err != nil { return nil, err } if numArguments >= 1 { wrapper.Cmd = exec.Command(path, splitArgs[1:]...) } else { wrapper.Cmd = exec.Command(path) } wrapper.buf = new(buffer) return wrapper, nil } golang-github-thomasrooney-gexpect-0.0~git20161231.5482f03/gexpect_test.go000066400000000000000000000253761406640320300257560ustar00rootroot00000000000000// +build !windows package gexpect import ( "bytes" "fmt" "io/ioutil" "strings" "testing" "time" ) func TestEmptySearchString(t *testing.T) { t.Logf("Testing empty search string...") child, err := Spawn("echo Hello World") if err != nil { t.Fatal(err) } err = child.Expect("") if err != ErrEmptySearch { t.Fatalf("Expected empty search error, got %v", err) } } func TestHelloWorld(t *testing.T) { t.Logf("Testing Hello World... ") child, err := Spawn("echo \"Hello World\"") if err != nil { t.Fatal(err) } err = child.Expect("Hello World") if err != nil { t.Fatal(err) } } func TestDoubleHelloWorld(t *testing.T) { t.Logf("Testing Double Hello World... ") child, err := Spawn(`sh -c "echo Hello World ; echo Hello ; echo Hi"`) if err != nil { t.Fatal(err) } err = child.Expect("Hello World") if err != nil { t.Fatal(err) } err = child.Expect("Hello") if err != nil { t.Fatal(err) } err = child.Expect("Hi") if err != nil { t.Fatal(err) } } func TestHelloWorldFailureCase(t *testing.T) { t.Logf("Testing Hello World Failure case... ") child, err := Spawn("echo \"Hello World\"") if err != nil { t.Fatal(err) } err = child.Expect("YOU WILL NEVER FIND ME") if err != nil { return } t.Fatal("Expected an error for TestHelloWorldFailureCase") } func TestBiChannel(t *testing.T) { t.Logf("Testing BiChannel screen... ") child, err := Spawn("cat") if err != nil { t.Fatal(err) } sender, receiver := child.AsyncInteractChannels() wait := func(str string) { for { msg, open := <-receiver if !open { return } if strings.Contains(msg, str) { return } } } endlChar := fmt.Sprintln("") sender <- fmt.Sprintf("echo%v", endlChar) wait("echo") sender <- fmt.Sprintf("echo2%v", endlChar) wait("echo2") child.Close() child.Wait() } func TestCommandStart(t *testing.T) { t.Logf("Testing Command... ") // Doing this allows you to modify the cmd struct prior to execution, for example to add environment variables child, err := Command("echo 'Hello World'") if err != nil { t.Fatal(err) } child.Start() child.Expect("Hello World") } var regexMatchTests = []struct { re string good string bad string }{ {`a`, `a`, `b`}, {`.b`, `ab`, `ac`}, {`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) { t.Logf("Testing Regular Expression Matching... ") for _, tt := range regexMatchTests { runTest := func(input string) bool { var match bool child, err := Spawn("echo \"" + input + "\"") if err != nil { t.Fatal(err) } match, err = child.ExpectRegex(tt.re) if err != nil { t.Fatal(err) } return match } if !runTest(tt.good) { t.Errorf("Regex Not matching [%#q] with pattern [%#q]", tt.good, tt.re) } if runTest(tt.bad) { t.Errorf("Regex Matching [%#q] with pattern [%#q]", tt.bad, tt.re) } } } var regexFindTests = []struct { re string input string matches []string }{ {`he(l)lo wo(r)ld`, `hello world`, []string{"hello world", "l", "r"}}, {`(a)`, `a`, []string{"a", "a"}}, {`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) { t.Logf("Testing Regular Expression Search... ") for _, tt := range regexFindTests { runTest := func(input string) []string { child, err := Spawn("echo \"" + input + "\"") if err != nil { t.Fatal(err) } matches, err := child.ExpectRegexFind(tt.re) if err != nil { t.Fatal(err) } return matches } matches := runTest(tt.input) if len(matches) != len(tt.matches) { t.Fatalf("Regex not producing the expected number of patterns.. got[%d] ([%s]) expected[%d] ([%s])", len(matches), strings.Join(matches, ","), len(tt.matches), strings.Join(tt.matches, ",")) } for i, _ := range matches { if matches[i] != tt.matches[i] { t.Errorf("Regex Expected group [%s] and got group [%s] with pattern [%#q] and input [%s]", tt.matches[i], matches[i], tt.re, tt.input) } } } } func TestReadLine(t *testing.T) { t.Logf("Testing ReadLine...") child, err := Spawn("echo \"foo\nbar\"") if err != nil { t.Fatal(err) } s, err := child.ReadLine() if err != nil { t.Fatal(err) } if s != "foo\r" { t.Fatalf("expected 'foo\\r', got '%s'", s) } s, err = child.ReadLine() if err != nil { t.Fatal(err) } if s != "bar\r" { t.Fatalf("expected 'bar\\r', got '%s'", s) } } 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 timeout and 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 have 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) } } func TestRegexFindNoExcessBytes(t *testing.T) { t.Logf("Testing Regular Expressions returning output with no excess strings") repeats := 50 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: `printf "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) } } } } 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) } } }