components/golang/patches/0060-release-branch.go1.5-misc-ios-keep-whole-buffer-in-g.patch
author Shawn Walker-Salas <shawn.walker@oracle.com>
Thu, 21 Jan 2016 09:20:59 -0800
changeset 5331 9c955076ffe3
permissions -rw-r--r--
PSARC/2015/203 Google Go version 1.5 21480408 sample-manifest COMPONENT_VERSION transforms cause erroneous results 22297561 we should add Go to userland

From 3facf7ecebdf10fbd48a87aabd688082c567b78c Mon Sep 17 00:00:00 2001
From: David Crawshaw <[email protected]>
Date: Wed, 4 Nov 2015 11:21:55 -0500
Subject: [PATCH 60/63] [release-branch.go1.5] misc/ios: keep whole buffer in
 go_darwin_arm_exec

The existing go_darwin_arm_exec.go script does not work with Xcode 7,
not due to any significant changes, but just ordering and timing of
statements from lldb. Unfortunately the current design of
go_darwin_arm_exec.go makes it not obvious what gets stuck where, so
this moves from a moving buffer window to a complete buffer of the
lldb output.

The result is easier code to follow, and it works with Xcode 7.

Updates #12660.

Change-Id: I3b8b890b0bf4474119482e95d84e821a86d1eaed
Reviewed-on: https://go-review.googlesource.com/16634
Reviewed-by: Michael Matloob <[email protected]>
Reviewed-on: https://go-review.googlesource.com/17146
---
 misc/ios/go_darwin_arm_exec.go | 366 +++++++++++++++++------------------------
 1 file changed, 149 insertions(+), 217 deletions(-)

diff --git a/misc/ios/go_darwin_arm_exec.go b/misc/ios/go_darwin_arm_exec.go
index debd2cd..3131b15 100644
--- a/misc/ios/go_darwin_arm_exec.go
+++ b/misc/ios/go_darwin_arm_exec.go
@@ -160,9 +160,6 @@ func run(bin string, args []string) (err error) {
 	}
 	defer os.Chdir(oldwd)
 
-	type waitPanic struct {
-		err error
-	}
 	defer func() {
 		if r := recover(); r != nil {
 			if w, ok := r.(waitPanic); ok {
@@ -174,14 +171,96 @@ func run(bin string, args []string) (err error) {
 	}()
 
 	defer exec.Command("killall", "ios-deploy").Run() // cleanup
-
 	exec.Command("killall", "ios-deploy").Run()
 
 	var opts options
 	opts, args = parseArgs(args)
 
 	// ios-deploy invokes lldb to give us a shell session with the app.
-	cmd = exec.Command(
+	s, err := newSession(appdir, args, opts)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		b := s.out.Bytes()
+		if err == nil && !debug {
+			i := bytes.Index(b, []byte("(lldb) process continue"))
+			if i > 0 {
+				b = b[i:]
+			}
+		}
+		os.Stdout.Write(b)
+	}()
+
+	// Script LLDB. Oh dear.
+	s.do(`process handle SIGHUP  --stop false --pass true --notify false`)
+	s.do(`process handle SIGPIPE --stop false --pass true --notify false`)
+	s.do(`process handle SIGUSR1 --stop false --pass true --notify false`)
+	s.do(`process handle SIGSEGV --stop false --pass true --notify false`) // does not work
+	s.do(`process handle SIGBUS  --stop false --pass true --notify false`) // does not work
+
+	if opts.lldb {
+		_, err := io.Copy(s.in, os.Stdin)
+		if err != io.EOF {
+			return err
+		}
+		return nil
+	}
+
+	s.do(`breakpoint set -n getwd`) // in runtime/cgo/gcc_darwin_arm.go
+
+	s.doCmd("run", "stop reason = breakpoint", 20*time.Second)
+
+	// Move the current working directory into the faux gopath.
+	if pkgpath != "src" {
+		s.do(`breakpoint delete 1`)
+		s.do(`expr char* $mem = (char*)malloc(512)`)
+		s.do(`expr $mem = (char*)getwd($mem, 512)`)
+		s.do(`expr $mem = (char*)strcat($mem, "/` + pkgpath + `")`)
+		s.do(`call (void)chdir($mem)`)
+	}
+
+	startTestsLen := s.out.Len()
+	fmt.Fprintln(s.in, `process continue`)
+
+	passed := func(out *buf) bool {
+		// Just to make things fun, lldb sometimes translates \n into \r\n.
+		return s.out.LastIndex([]byte("\nPASS\n")) > startTestsLen ||
+			s.out.LastIndex([]byte("\nPASS\r")) > startTestsLen ||
+			s.out.LastIndex([]byte("\n(lldb) PASS\n")) > startTestsLen ||
+			s.out.LastIndex([]byte("\n(lldb) PASS\r")) > startTestsLen
+	}
+	err = s.wait("test completion", passed, opts.timeout)
+	if passed(s.out) {
+		// The returned lldb error code is usually non-zero.
+		// We check for test success by scanning for the final
+		// PASS returned by the test harness, assuming the worst
+		// in its absence.
+		return nil
+	}
+	return err
+}
+
+type lldbSession struct {
+	cmd      *exec.Cmd
+	in       *os.File
+	out      *buf
+	timedout chan struct{}
+	exited   chan error
+}
+
+func newSession(appdir string, args []string, opts options) (*lldbSession, error) {
+	lldbr, in, err := os.Pipe()
+	if err != nil {
+		return nil, err
+	}
+	s := &lldbSession{
+		in:     in,
+		out:    new(buf),
+		exited: make(chan error),
+	}
+
+	s.cmd = exec.Command(
 		// lldb tries to be clever with terminals.
 		// So we wrap it in script(1) and be clever
 		// right back at it.
@@ -198,267 +277,120 @@ func run(bin string, args []string) (err error) {
 		"--bundle", appdir,
 	)
 	if debug {
-		log.Println(strings.Join(cmd.Args, " "))
+		log.Println(strings.Join(s.cmd.Args, " "))
 	}
 
-	lldbr, lldb, err := os.Pipe()
-	if err != nil {
-		return err
-	}
-	w := new(bufWriter)
+	var out io.Writer = s.out
 	if opts.lldb {
-		mw := io.MultiWriter(w, os.Stderr)
-		cmd.Stdout = mw
-		cmd.Stderr = mw
-	} else {
-		cmd.Stdout = w
-		cmd.Stderr = w // everything of interest is on stderr
+		out = io.MultiWriter(out, os.Stderr)
 	}
-	cmd.Stdin = lldbr
+	s.cmd.Stdout = out
+	s.cmd.Stderr = out // everything of interest is on stderr
+	s.cmd.Stdin = lldbr
 
-	if err := cmd.Start(); err != nil {
-		return fmt.Errorf("ios-deploy failed to start: %v", err)
+	if err := s.cmd.Start(); err != nil {
+		return nil, fmt.Errorf("ios-deploy failed to start: %v", err)
 	}
 
 	// 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.
-	var timedout chan struct{}
 	if opts.timeout > 1*time.Second {
-		timedout = make(chan struct{})
+		s.timedout = make(chan struct{})
 		time.AfterFunc(opts.timeout-1*time.Second, func() {
-			close(timedout)
+			close(s.timedout)
 		})
 	}
 
-	exited := make(chan error)
 	go func() {
-		exited <- cmd.Wait()
+		s.exited <- s.cmd.Wait()
 	}()
 
-	waitFor := func(stage, str string, timeout time.Duration) error {
-		select {
-		case <-timedout:
-			w.printBuf()
-			if p := cmd.Process; p != nil {
-				p.Kill()
-			}
-			return fmt.Errorf("timeout (stage %s)", stage)
-		case err := <-exited:
-			w.printBuf()
-			return fmt.Errorf("failed (stage %s): %v", stage, err)
-		case i := <-w.find(str, timeout):
-			if i < 0 {
-				log.Printf("timed out on stage %q, retrying", stage)
-				return errRetry
-			}
-			w.clearTo(i + len(str))
-			return nil
-		}
+	cond := func(out *buf) bool {
+		i0 := s.out.LastIndex([]byte("(lldb)"))
+		i1 := s.out.LastIndex([]byte("fruitstrap"))
+		i2 := s.out.LastIndex([]byte(" connect"))
+		return i0 > 0 && i1 > 0 && i2 > 0
 	}
-	do := func(cmd string) {
-		fmt.Fprintln(lldb, cmd)
-		if err := waitFor(fmt.Sprintf("prompt after %q", cmd), "(lldb)", 0); err != nil {
-			panic(waitPanic{err})
-		}
+	if err := s.wait("lldb start", cond, 5*time.Second); err != nil {
+		fmt.Printf("lldb start error: %v\n", err)
+		return nil, errRetry
 	}
+	return s, nil
+}
 
-	// Wait for installation and connection.
-	if err := waitFor("ios-deploy before run", "(lldb)", 0); err != nil {
-		// Retry if we see a rare and longstanding ios-deploy bug.
-		// https://github.com/phonegap/ios-deploy/issues/11
-		//	Assertion failed: (AMDeviceStartService(device, CFSTR("com.apple.debugserver"), &gdbfd, NULL) == 0)
-		log.Printf("%v, retrying", err)
-		return errRetry
-	}
+func (s *lldbSession) do(cmd string) { s.doCmd(cmd, "(lldb)", 0) }
 
-	// Script LLDB. Oh dear.
-	do(`process handle SIGHUP  --stop false --pass true --notify false`)
-	do(`process handle SIGPIPE --stop false --pass true --notify false`)
-	do(`process handle SIGUSR1 --stop false --pass true --notify false`)
-	do(`process handle SIGSEGV --stop false --pass true --notify false`) // does not work
-	do(`process handle SIGBUS  --stop false --pass true --notify false`) // does not work
-
-	if opts.lldb {
-		_, err := io.Copy(lldb, os.Stdin)
-		if err != io.EOF {
-			return err
-		}
-		return nil
+func (s *lldbSession) doCmd(cmd string, waitFor string, extraTimeout time.Duration) {
+	startLen := s.out.Len()
+	fmt.Fprintln(s.in, cmd)
+	cond := func(out *buf) bool {
+		i := s.out.LastIndex([]byte(waitFor))
+		return i > startLen
 	}
-
-	do(`breakpoint set -n getwd`) // in runtime/cgo/gcc_darwin_arm.go
-
-	fmt.Fprintln(lldb, `run`)
-	if err := waitFor("br getwd", "stop reason = breakpoint", 20*time.Second); err != nil {
-		// At this point we see several flaky errors from the iOS
-		// build infrastructure. The most common is never reaching
-		// the breakpoint, which we catch with a timeout. Very
-		// occasionally lldb can produce errors like:
-		//
-		//	Breakpoint 1: no locations (pending).
-		//	WARNING:  Unable to resolve breakpoint to any actual locations.
-		//
-		// As no actual test code has been executed by this point,
-		// we treat all errors as recoverable.
-		if err != errRetry {
-			log.Printf("%v, retrying", err)
-			err = errRetry
-		}
-		return err
-	}
-	if err := waitFor("br getwd prompt", "(lldb)", 0); err != nil {
-		return err
+	if err := s.wait(fmt.Sprintf("running cmd %q", cmd), cond, extraTimeout); err != nil {
+		panic(waitPanic{err})
 	}
+}
 
-	// Move the current working directory into the faux gopath.
-	if pkgpath != "src" {
-		do(`breakpoint delete 1`)
-		do(`expr char* $mem = (char*)malloc(512)`)
-		do(`expr $mem = (char*)getwd($mem, 512)`)
-		do(`expr $mem = (char*)strcat($mem, "/` + pkgpath + `")`)
-		do(`call (void)chdir($mem)`)
-	}
-
-	// Run the tests.
-	w.trimSuffix("(lldb) ")
-	fmt.Fprintln(lldb, `process continue`)
-
-	// Wait for the test to complete.
-	select {
-	case <-timedout:
-		w.printBuf()
-		if p := cmd.Process; p != nil {
-			p.Kill()
-		}
-		return errors.New("timeout running tests")
-	case <-w.find("\nPASS", 0):
-		passed := w.isPass()
-		w.printBuf()
-		if passed {
-			return nil
-		}
-		return errors.New("test failure")
-	case err := <-exited:
-		// The returned lldb error code is usually non-zero.
-		// We check for test success by scanning for the final
-		// PASS returned by the test harness, assuming the worst
-		// in its absence.
-		if w.isPass() {
-			err = nil
-		} else if err == nil {
-			err = errors.New("test failure")
+func (s *lldbSession) wait(reason string, cond func(out *buf) bool, extraTimeout time.Duration) error {
+	doTimeout := 1*time.Second + extraTimeout
+	doTimedout := time.After(doTimeout)
+	for {
+		select {
+		case <-s.timedout:
+			if p := s.cmd.Process; p != nil {
+				p.Kill()
+			}
+			return fmt.Errorf("test timeout (%s)", reason)
+		case <-doTimedout:
+			return fmt.Errorf("command timeout (%s for %v)", reason, doTimeout)
+		case err := <-s.exited:
+			return fmt.Errorf("exited (%s: %v)", reason, err)
+		default:
+			if cond(s.out) {
+				return nil
+			}
+			time.Sleep(20 * time.Millisecond)
 		}
-		w.printBuf()
-		return err
 	}
 }
 
-type bufWriter struct {
-	mu     sync.Mutex
-	buf    []byte
-	suffix []byte // remove from each Write
-
-	findTxt   []byte   // search buffer on each Write
-	findCh    chan int // report find position
-	findAfter *time.Timer
+type buf struct {
+	mu  sync.Mutex
+	buf []byte
 }
 
-func (w *bufWriter) Write(in []byte) (n int, err error) {
+func (w *buf) Write(in []byte) (n int, err error) {
 	w.mu.Lock()
 	defer w.mu.Unlock()
-
-	n = len(in)
-	in = bytes.TrimSuffix(in, w.suffix)
-
-	if debug {
-		inTxt := strings.Replace(string(in), "\n", "\\n", -1)
-		findTxt := strings.Replace(string(w.findTxt), "\n", "\\n", -1)
-		fmt.Printf("debug --> %s <-- debug (findTxt='%s')\n", inTxt, findTxt)
-	}
-
 	w.buf = append(w.buf, in...)
-
-	if len(w.findTxt) > 0 {
-		if i := bytes.Index(w.buf, w.findTxt); i >= 0 {
-			w.findCh <- i
-			close(w.findCh)
-			w.findTxt = nil
-			w.findCh = nil
-			if w.findAfter != nil {
-				w.findAfter.Stop()
-				w.findAfter = nil
-			}
-		}
-	}
-	return n, nil
+	return len(in), nil
 }
 
-func (w *bufWriter) trimSuffix(p string) {
+func (w *buf) LastIndex(sep []byte) int {
 	w.mu.Lock()
 	defer w.mu.Unlock()
-	w.suffix = []byte(p)
+	return bytes.LastIndex(w.buf, sep)
 }
 
-func (w *bufWriter) printBuf() {
+func (w *buf) Bytes() []byte {
 	w.mu.Lock()
 	defer w.mu.Unlock()
-	fmt.Fprintf(os.Stderr, "%s", w.buf)
-	w.buf = nil
-}
 
-func (w *bufWriter) clearTo(i int) {
-	w.mu.Lock()
-	defer w.mu.Unlock()
-	w.buf = w.buf[i:]
+	b := make([]byte, len(w.buf))
+	copy(b, w.buf)
+	return b
 }
 
-// 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 {
+func (w *buf) Len() int {
 	w.mu.Lock()
 	defer w.mu.Unlock()
-	if len(w.findTxt) > 0 {
-		panic(fmt.Sprintf("find(%s): already trying to find %s", str, w.findTxt))
-	}
-	txt := []byte(str)
-	ch := make(chan int, 1)
-	if i := bytes.Index(w.buf, txt); i >= 0 {
-		ch <- i
-		close(ch)
-	} 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
+	return len(w.buf)
 }
 
-func (w *bufWriter) isPass() bool {
-	w.mu.Lock()
-	defer w.mu.Unlock()
-
-	// The final stdio of lldb is non-deterministic, so we
-	// scan the whole buffer.
-	//
-	// Just to make things fun, lldb sometimes translates \n
-	// into \r\n.
-	return bytes.Contains(w.buf, []byte("\nPASS\n")) || bytes.Contains(w.buf, []byte("\nPASS\r"))
+type waitPanic struct {
+	err error
 }
 
 type options struct {
-- 
2.6.1