debug: add an implementation of program.Program for running and debugging programs locally, instead of through a proxy.

Change-Id: Ib9e1392409e09e62a647122870813c3526b0fc8c
Reviewed-on: https://go-review.googlesource.com/10565
Reviewed-by: Rob Pike <r@golang.org>
diff --git a/ogle/demo/ogler/ogler_test.go b/ogle/demo/ogler/ogler_test.go
index fef5aff..8bceaff 100644
--- a/ogle/demo/ogler/ogler_test.go
+++ b/ogle/demo/ogler/ogler_test.go
@@ -16,6 +16,7 @@
 
 	"golang.org/x/debug/ogle/program"
 	"golang.org/x/debug/ogle/program/client"
+	"golang.org/x/debug/ogle/program/local"
 )
 
 var expectedVarValues = map[string]interface{}{
@@ -119,14 +120,11 @@
 	return j == len(s)
 }
 
-func run(t *testing.T, name string, args ...string) {
+func run(name string, args ...string) error {
 	cmd := exec.Command(name, args...)
 	cmd.Stdout = os.Stdout
 	cmd.Stderr = os.Stderr
-	err := cmd.Run()
-	if err != nil {
-		t.Fatal(err)
-	}
+	return cmd.Run()
 }
 
 const (
@@ -136,24 +134,43 @@
 	traceeBinary = "./tracee"
 )
 
-func TestBreakAndEval(t *testing.T) {
-	run(t, "go", "build", "-o", proxyBinary, proxySrc)
-	defer os.Remove(proxyBinary)
+func TestMain(m *testing.M) {
+	os.Exit(buildAndRunTests(m))
+}
 
-	run(t, "go", "build", "-o", traceeBinary, traceeSrc)
-	defer os.Remove(traceeBinary)
-
-	client.OgleproxyCmd = proxyBinary
-	var (
-		prog program.Program
-		err  error
-	)
-	prog, err = client.New("localhost", traceeBinary)
-	if err != nil {
-		log.Fatalf("New: %v", err)
+func buildAndRunTests(m *testing.M) int {
+	if err := run("go", "build", "-o", proxyBinary, proxySrc); err != nil {
+		fmt.Println(err)
+		return 1
 	}
+	client.OgleproxyCmd = proxyBinary
+	defer os.Remove(proxyBinary)
+	if err := run("go", "build", "-o", traceeBinary, traceeSrc); err != nil {
+		fmt.Println(err)
+		return 1
+	}
+	defer os.Remove(traceeBinary)
+	return m.Run()
+}
 
-	_, err = prog.Run("some", "arguments")
+func TestLocalProgram(t *testing.T) {
+	prog, err := local.New(traceeBinary)
+	if err != nil {
+		t.Fatal("local.New:", err)
+	}
+	testProgram(t, prog)
+}
+
+func TestRemoteProgram(t *testing.T) {
+	prog, err := client.New("localhost", traceeBinary)
+	if err != nil {
+		t.Fatal("client.New:", err)
+	}
+	testProgram(t, prog)
+}
+
+func testProgram(t *testing.T, prog program.Program) {
+	_, err := prog.Run("some", "arguments")
 	if err != nil {
 		log.Fatalf("Run: %v", err)
 	}
diff --git a/ogle/program/local/local.go b/ogle/program/local/local.go
new file mode 100644
index 0000000..45868c4
--- /dev/null
+++ b/ogle/program/local/local.go
@@ -0,0 +1,151 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package local provides access to a local program.
+package local // import "golang.org/x/debug/ogle/program/local"
+
+import (
+	"golang.org/x/debug/ogle/program"
+	"golang.org/x/debug/ogle/program/proxyrpc"
+	"golang.org/x/debug/ogle/program/server"
+)
+
+var _ program.Program = (*Local)(nil)
+var _ program.File = (*File)(nil)
+
+// Local implements the interface program.Program.
+// Through that interface it provides access to a program being debugged.
+type Local struct {
+	s *server.Server
+}
+
+// New creates a new program from the specified file.
+// The program can then be started by the Run method.
+func New(textFile string) (*Local, error) {
+	s, err := server.New(textFile)
+	return &Local{s: s}, err
+}
+
+func (l *Local) Open(name string, mode string) (program.File, error) {
+	req := proxyrpc.OpenRequest{
+		Name: name,
+		Mode: mode,
+	}
+	var resp proxyrpc.OpenResponse
+	err := l.s.Open(&req, &resp)
+	if err != nil {
+		return nil, err
+	}
+	f := &File{
+		prog: l,
+		fd:   resp.FD,
+	}
+	return f, nil
+}
+
+func (l *Local) Run(args ...string) (program.Status, error) {
+	req := proxyrpc.RunRequest{args}
+	var resp proxyrpc.RunResponse
+	err := l.s.Run(&req, &resp)
+	if err != nil {
+		return program.Status{}, err
+	}
+	return resp.Status, nil
+}
+
+func (l *Local) Stop() (program.Status, error) {
+	panic("unimplemented")
+}
+
+func (l *Local) Resume() (program.Status, error) {
+	req := proxyrpc.ResumeRequest{}
+	var resp proxyrpc.ResumeResponse
+	err := l.s.Resume(&req, &resp)
+	if err != nil {
+		return program.Status{}, err
+	}
+	return resp.Status, nil
+}
+
+func (l *Local) Kill() (program.Status, error) {
+	panic("unimplemented")
+}
+
+func (l *Local) Breakpoint(address string) ([]uint64, error) {
+	req := proxyrpc.BreakpointRequest{
+		Address: address,
+	}
+	var resp proxyrpc.BreakpointResponse
+	err := l.s.Breakpoint(&req, &resp)
+	return resp.PCs, err
+}
+
+func (l *Local) DeleteBreakpoints(pcs []uint64) error {
+	req := proxyrpc.DeleteBreakpointsRequest{PCs: pcs}
+	var resp proxyrpc.DeleteBreakpointsResponse
+	return l.s.DeleteBreakpoints(&req, &resp)
+}
+
+func (l *Local) Eval(expr string) ([]string, error) {
+	req := proxyrpc.EvalRequest{
+		Expr: expr,
+	}
+	var resp proxyrpc.EvalResponse
+	err := l.s.Eval(&req, &resp)
+	return resp.Result, err
+}
+
+func (l *Local) Frames(count int) ([]program.Frame, error) {
+	req := proxyrpc.FramesRequest{
+		Count: count,
+	}
+	var resp proxyrpc.FramesResponse
+	err := l.s.Frames(&req, &resp)
+	return resp.Frames, err
+}
+
+func (l *Local) VarByName(name string) (program.Var, error) {
+	req := proxyrpc.VarByNameRequest{Name: name}
+	var resp proxyrpc.VarByNameResponse
+	err := l.s.VarByName(&req, &resp)
+	return resp.Var, err
+}
+
+func (l *Local) Value(v program.Var) (program.Value, error) {
+	req := proxyrpc.ValueRequest{Var: v}
+	var resp proxyrpc.ValueResponse
+	err := l.s.Value(&req, &resp)
+	return resp.Value, err
+}
+
+// File implements the program.File interface, providing access
+// to file-like resources associated with the target program.
+type File struct {
+	prog *Local // The Program associated with the file.
+	fd   int    // File descriptor.
+}
+
+func (f *File) ReadAt(p []byte, offset int64) (int, error) {
+	req := proxyrpc.ReadAtRequest{
+		FD:     f.fd,
+		Len:    len(p),
+		Offset: offset,
+	}
+	var resp proxyrpc.ReadAtResponse
+	err := f.prog.s.ReadAt(&req, &resp)
+	return copy(p, resp.Data), err
+}
+
+func (f *File) WriteAt(p []byte, offset int64) (int, error) {
+	panic("unimplemented")
+}
+
+func (f *File) Close() error {
+	req := proxyrpc.CloseRequest{
+		FD: f.fd,
+	}
+	var resp proxyrpc.CloseResponse
+	err := f.prog.s.Close(&req, &resp)
+	return err
+}