misc/ios: timeout and continue waiting for getwd

Split out from cl/8024 for clarity and improved approach.

Rarely, "stop reason = breakpoint" does not appear in the lldb stop
text. However the program is ready to proceed. To be a little more
robust about those cases, we wait for two seconds, and if that text
doesn't appear but a prompt does we continue and hope for the best.
Worst case, this results in a harder to read failure message.

Change-Id: Ib20aa92564cdccefd2b7260417c647cd44122b66
Reviewed-on: https://go-review.googlesource.com/8080
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/misc/ios/go_darwin_arm_exec.go b/misc/ios/go_darwin_arm_exec.go
index eb93005..f81e07e 100644
--- a/misc/ios/go_darwin_arm_exec.go
+++ b/misc/ios/go_darwin_arm_exec.go
@@ -150,8 +150,10 @@
 	// Manage the -test.timeout here, outside of the test. There is a lot
 	// of moving parts in an iOS test harness (notably lldb) that can
 	// swallow useful stdio or cause its own ruckus.
+	brTimeout := 5 * time.Second
 	var timedout chan struct{}
 	if t := parseTimeout(args); t > 1*time.Second {
+		brTimeout = t / 4
 		timedout = make(chan struct{})
 		time.AfterFunc(t-1*time.Second, func() {
 			close(timedout)
@@ -163,7 +165,7 @@
 		exited <- cmd.Wait()
 	}()
 
-	waitFor := func(stage, str string) error {
+	waitFor := func(stage, str string, timeout time.Duration) error {
 		select {
 		case <-timedout:
 			w.printBuf()
@@ -174,20 +176,24 @@
 		case err := <-exited:
 			w.printBuf()
 			return fmt.Errorf("failed (stage %s): %v", stage, err)
-		case i := <-w.find(str):
-			w.clearTo(i + len(str))
+		case i := <-w.find(str, timeout):
+			if i >= 0 {
+				w.clearTo(i + len(str))
+			} else {
+				log.Printf("timed out on stage %s, continuing", stage)
+			}
 			return nil
 		}
 	}
 	do := func(cmd string) {
 		fmt.Fprintln(lldb, cmd)
-		if err := waitFor(fmt.Sprintf("prompt after %q", cmd), "(lldb)"); err != nil {
+		if err := waitFor(fmt.Sprintf("prompt after %q", cmd), "(lldb)", 0); err != nil {
 			panic(waitPanic{err})
 		}
 	}
 
 	// Wait for installation and connection.
-	if err := waitFor("ios-deploy before run", "(lldb)     connect\r\nProcess 0 connected\r\n"); err != nil {
+	if err := waitFor("ios-deploy before run", "(lldb)     connect\r\nProcess 0 connected\r\n", 0); err != nil {
 		return err
 	}
 
@@ -201,10 +207,12 @@
 	do(`breakpoint set -n getwd`) // in runtime/cgo/gcc_darwin_arm.go
 
 	fmt.Fprintln(lldb, `run`)
-	if err := waitFor("br getwd", "stop reason = breakpoint"); err != nil {
+	// Sometimes we don't see "reason = breakpoint", so we time out
+	// and try to continue.
+	if err := waitFor("br getwd", "stop reason = breakpoint", brTimeout); err != nil {
 		return err
 	}
-	if err := waitFor("br getwd prompt", "(lldb)"); err != nil {
+	if err := waitFor("br getwd prompt", "(lldb)", 0); err != nil {
 		return err
 	}
 
@@ -218,11 +226,11 @@
 	// Watch for SIGSEGV. Ideally lldb would never break on SIGSEGV.
 	// http://golang.org/issue/10043
 	go func() {
-		<-w.find("stop reason = EXC_BAD_ACCESS")
+		<-w.find("stop reason = EXC_BAD_ACCESS", 0)
 		// cannot use do here, as the defer/recover is not available
 		// on this goroutine.
 		fmt.Fprintln(lldb, `bt`)
-		waitFor("finish backtrace", "(lldb)")
+		waitFor("finish backtrace", "(lldb)", 0)
 		w.printBuf()
 		if p := cmd.Process; p != nil {
 			p.Kill()
@@ -261,8 +269,9 @@
 	buf    []byte
 	suffix []byte // remove from each Write
 
-	findTxt []byte   // search buffer on each Write
-	findCh  chan int // report find position
+	findTxt   []byte   // search buffer on each Write
+	findCh    chan int // report find position
+	findAfter *time.Timer
 }
 
 func (w *bufWriter) Write(in []byte) (n int, err error) {
@@ -280,6 +289,10 @@
 			close(w.findCh)
 			w.findTxt = nil
 			w.findCh = nil
+			if w.findAfter != nil {
+				w.findAfter.Stop()
+				w.findAfter = nil
+			}
 		}
 	}
 	return n, nil
@@ -307,7 +320,12 @@
 	w.buf = w.buf[i:]
 }
 
-func (w *bufWriter) find(str string) <-chan int {
+// find returns a channel that will have exactly one byte index sent
+// to it when the text str appears in the buffer. If the text does not
+// appear before timeout, -1 is sent.
+//
+// A timeout of zero means no timeout.
+func (w *bufWriter) find(str string, timeout time.Duration) <-chan int {
 	w.mu.Lock()
 	defer w.mu.Unlock()
 	if len(w.findTxt) > 0 {
@@ -321,6 +339,19 @@
 	} else {
 		w.findTxt = txt
 		w.findCh = ch
+		if timeout > 0 {
+			w.findAfter = time.AfterFunc(timeout, func() {
+				w.mu.Lock()
+				defer w.mu.Unlock()
+				if w.findCh == ch {
+					w.findTxt = nil
+					w.findCh = nil
+					w.findAfter = nil
+					ch <- -1
+					close(ch)
+				}
+			})
+		}
 	}
 	return ch
 }