mirror of
https://github.com/golang/go.git
synced 2025-05-05 23:53:05 +00:00
[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 <matloob@golang.org> Reviewed-on: https://go-review.googlesource.com/17146
This commit is contained in:
parent
bebbc62402
commit
3facf7eceb
@ -160,9 +160,6 @@ func run(bin string, args []string) (err error) {
|
|||||||
}
|
}
|
||||||
defer os.Chdir(oldwd)
|
defer os.Chdir(oldwd)
|
||||||
|
|
||||||
type waitPanic struct {
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
if w, ok := r.(waitPanic); ok {
|
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
|
defer exec.Command("killall", "ios-deploy").Run() // cleanup
|
||||||
|
|
||||||
exec.Command("killall", "ios-deploy").Run()
|
exec.Command("killall", "ios-deploy").Run()
|
||||||
|
|
||||||
var opts options
|
var opts options
|
||||||
opts, args = parseArgs(args)
|
opts, args = parseArgs(args)
|
||||||
|
|
||||||
// ios-deploy invokes lldb to give us a shell session with the app.
|
// 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.
|
// lldb tries to be clever with terminals.
|
||||||
// So we wrap it in script(1) and be clever
|
// So we wrap it in script(1) and be clever
|
||||||
// right back at it.
|
// right back at it.
|
||||||
@ -198,267 +277,120 @@ func run(bin string, args []string) (err error) {
|
|||||||
"--bundle", appdir,
|
"--bundle", appdir,
|
||||||
)
|
)
|
||||||
if debug {
|
if debug {
|
||||||
log.Println(strings.Join(cmd.Args, " "))
|
log.Println(strings.Join(s.cmd.Args, " "))
|
||||||
}
|
}
|
||||||
|
|
||||||
lldbr, lldb, err := os.Pipe()
|
var out io.Writer = s.out
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w := new(bufWriter)
|
|
||||||
if opts.lldb {
|
if opts.lldb {
|
||||||
mw := io.MultiWriter(w, os.Stderr)
|
out = io.MultiWriter(out, os.Stderr)
|
||||||
cmd.Stdout = mw
|
|
||||||
cmd.Stderr = mw
|
|
||||||
} else {
|
|
||||||
cmd.Stdout = w
|
|
||||||
cmd.Stderr = w // everything of interest is on 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 {
|
if err := s.cmd.Start(); err != nil {
|
||||||
return fmt.Errorf("ios-deploy failed to start: %v", err)
|
return nil, fmt.Errorf("ios-deploy failed to start: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manage the -test.timeout here, outside of the test. There is a lot
|
// 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
|
// of moving parts in an iOS test harness (notably lldb) that can
|
||||||
// swallow useful stdio or cause its own ruckus.
|
// swallow useful stdio or cause its own ruckus.
|
||||||
var timedout chan struct{}
|
|
||||||
if opts.timeout > 1*time.Second {
|
if opts.timeout > 1*time.Second {
|
||||||
timedout = make(chan struct{})
|
s.timedout = make(chan struct{})
|
||||||
time.AfterFunc(opts.timeout-1*time.Second, func() {
|
time.AfterFunc(opts.timeout-1*time.Second, func() {
|
||||||
close(timedout)
|
close(s.timedout)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
exited := make(chan error)
|
|
||||||
go func() {
|
go func() {
|
||||||
exited <- cmd.Wait()
|
s.exited <- s.cmd.Wait()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
waitFor := func(stage, str string, timeout time.Duration) error {
|
cond := func(out *buf) bool {
|
||||||
select {
|
i0 := s.out.LastIndex([]byte("(lldb)"))
|
||||||
case <-timedout:
|
i1 := s.out.LastIndex([]byte("fruitstrap"))
|
||||||
w.printBuf()
|
i2 := s.out.LastIndex([]byte(" connect"))
|
||||||
if p := cmd.Process; p != nil {
|
return i0 > 0 && i1 > 0 && i2 > 0
|
||||||
p.Kill()
|
|
||||||
}
|
}
|
||||||
return fmt.Errorf("timeout (stage %s)", stage)
|
if err := s.wait("lldb start", cond, 5*time.Second); err != nil {
|
||||||
case err := <-exited:
|
fmt.Printf("lldb start error: %v\n", err)
|
||||||
w.printBuf()
|
return nil, errRetry
|
||||||
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 s, nil
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *lldbSession) do(cmd string) { s.doCmd(cmd, "(lldb)", 0) }
|
||||||
|
|
||||||
|
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 := func(cmd string) {
|
if err := s.wait(fmt.Sprintf("running cmd %q", cmd), cond, extraTimeout); err != nil {
|
||||||
fmt.Fprintln(lldb, cmd)
|
|
||||||
if err := waitFor(fmt.Sprintf("prompt after %q", cmd), "(lldb)", 0); err != nil {
|
|
||||||
panic(waitPanic{err})
|
panic(waitPanic{err})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for installation and connection.
|
func (s *lldbSession) wait(reason string, cond func(out *buf) bool, extraTimeout time.Duration) error {
|
||||||
if err := waitFor("ios-deploy before run", "(lldb)", 0); err != nil {
|
doTimeout := 1*time.Second + extraTimeout
|
||||||
// Retry if we see a rare and longstanding ios-deploy bug.
|
doTimedout := time.After(doTimeout)
|
||||||
// https://github.com/phonegap/ios-deploy/issues/11
|
for {
|
||||||
// Assertion failed: (AMDeviceStartService(device, CFSTR("com.apple.debugserver"), &gdbfd, NULL) == 0)
|
|
||||||
log.Printf("%v, retrying", err)
|
|
||||||
return errRetry
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
select {
|
||||||
case <-timedout:
|
case <-s.timedout:
|
||||||
w.printBuf()
|
if p := s.cmd.Process; p != nil {
|
||||||
if p := cmd.Process; p != nil {
|
|
||||||
p.Kill()
|
p.Kill()
|
||||||
}
|
}
|
||||||
return errors.New("timeout running tests")
|
return fmt.Errorf("test timeout (%s)", reason)
|
||||||
case <-w.find("\nPASS", 0):
|
case <-doTimedout:
|
||||||
passed := w.isPass()
|
return fmt.Errorf("command timeout (%s for %v)", reason, doTimeout)
|
||||||
w.printBuf()
|
case err := <-s.exited:
|
||||||
if passed {
|
return fmt.Errorf("exited (%s: %v)", reason, err)
|
||||||
|
default:
|
||||||
|
if cond(s.out) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return errors.New("test failure")
|
time.Sleep(20 * time.Millisecond)
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
w.printBuf()
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type bufWriter struct {
|
type buf struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
buf []byte
|
buf []byte
|
||||||
suffix []byte // remove from each Write
|
|
||||||
|
|
||||||
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) {
|
func (w *buf) Write(in []byte) (n int, err error) {
|
||||||
w.mu.Lock()
|
w.mu.Lock()
|
||||||
defer w.mu.Unlock()
|
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...)
|
w.buf = append(w.buf, in...)
|
||||||
|
return len(in), nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *bufWriter) trimSuffix(p string) {
|
func (w *buf) LastIndex(sep []byte) int {
|
||||||
w.mu.Lock()
|
w.mu.Lock()
|
||||||
defer w.mu.Unlock()
|
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:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *bufWriter) isPass() bool {
|
|
||||||
w.mu.Lock()
|
w.mu.Lock()
|
||||||
defer w.mu.Unlock()
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
// The final stdio of lldb is non-deterministic, so we
|
b := make([]byte, len(w.buf))
|
||||||
// scan the whole buffer.
|
copy(b, w.buf)
|
||||||
//
|
return b
|
||||||
// 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"))
|
func (w *buf) Len() int {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
return len(w.buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
type waitPanic struct {
|
||||||
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
type options struct {
|
type options struct {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user