components/golang/patches/0060-release-branch.go1.5-misc-ios-keep-whole-buffer-in-g.patch
changeset 5331 9c955076ffe3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/golang/patches/0060-release-branch.go1.5-misc-ios-keep-whole-buffer-in-g.patch	Thu Jan 21 09:20:59 2016 -0800
@@ -0,0 +1,474 @@
+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
+