aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2017-06-13 19:31:19 +0200
committerGitHub <noreply@github.com>2017-06-13 19:31:19 +0200
commit5b060131006494cbc077f08b9b2fbf172f3eb239 (patch)
tree04f8586899db96f7fd8e7bc6a010fc10f1e2bb3b /vendor/cloud.google.com/go/cmd/go-cloud-debug-agent
parentcd8e13f826ff24f5f8e0b8de1b9d3373aaf93d2f (diff)
parent612b82714b3e6660bf702f801ab96aacb3432e1f (diff)
Merge pull request #226 from google/dvyukov-vendor
vendor: vendor dependencies
Diffstat (limited to 'vendor/cloud.google.com/go/cmd/go-cloud-debug-agent')
-rw-r--r--vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/debuglet.go450
-rw-r--r--vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/breakpoints/breakpoints.go174
-rw-r--r--vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/breakpoints/breakpoints_test.go168
-rw-r--r--vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/controller/client.go291
-rw-r--r--vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/controller/client_test.go254
-rw-r--r--vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/valuecollector/valuecollector.go460
-rw-r--r--vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/valuecollector/valuecollector_test.go418
7 files changed, 2215 insertions, 0 deletions
diff --git a/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/debuglet.go b/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/debuglet.go
new file mode 100644
index 000000000..6a8702c77
--- /dev/null
+++ b/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/debuglet.go
@@ -0,0 +1,450 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build linux
+
+package main
+
+import (
+ "encoding/json"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "math/rand"
+ "os"
+ "sync"
+ "time"
+
+ "cloud.google.com/go/cmd/go-cloud-debug-agent/internal/breakpoints"
+ debuglet "cloud.google.com/go/cmd/go-cloud-debug-agent/internal/controller"
+ "cloud.google.com/go/cmd/go-cloud-debug-agent/internal/valuecollector"
+ "cloud.google.com/go/compute/metadata"
+ "golang.org/x/debug"
+ "golang.org/x/debug/local"
+ "golang.org/x/net/context"
+ "golang.org/x/oauth2"
+ "golang.org/x/oauth2/google"
+ cd "google.golang.org/api/clouddebugger/v2"
+)
+
+var (
+ appModule = flag.String("appmodule", "", "Optional application module name.")
+ appVersion = flag.String("appversion", "", "Optional application module version name.")
+ sourceContextFile = flag.String("sourcecontext", "", "File containing JSON-encoded source context.")
+ verbose = flag.Bool("v", false, "Output verbose log messages.")
+ projectNumber = flag.String("projectnumber", "", "Project number."+
+ " If this is not set, it is read from the GCP metadata server.")
+ projectID = flag.String("projectid", "", "Project ID."+
+ " If this is not set, it is read from the GCP metadata server.")
+ serviceAccountFile = flag.String("serviceaccountfile", "", "File containing JSON service account credentials.")
+)
+
+const (
+ maxCapturedStackFrames = 50
+ maxCapturedVariables = 1000
+)
+
+func main() {
+ flag.Usage = usage
+ flag.Parse()
+ args := flag.Args()
+ if len(args) == 0 {
+ // The user needs to supply the name of the executable to run.
+ flag.Usage()
+ return
+ }
+ if *projectNumber == "" {
+ var err error
+ *projectNumber, err = metadata.NumericProjectID()
+ if err != nil {
+ log.Print("Debuglet initialization: ", err)
+ }
+ }
+ if *projectID == "" {
+ var err error
+ *projectID, err = metadata.ProjectID()
+ if err != nil {
+ log.Print("Debuglet initialization: ", err)
+ }
+ }
+ sourceContexts, err := readSourceContextFile(*sourceContextFile)
+ if err != nil {
+ log.Print("Reading source context file: ", err)
+ }
+ var ts oauth2.TokenSource
+ ctx := context.Background()
+ if *serviceAccountFile != "" {
+ if ts, err = serviceAcctTokenSource(ctx, *serviceAccountFile, cd.CloudDebuggerScope); err != nil {
+ log.Fatalf("Error getting credentials from file %s: %v", *serviceAccountFile, err)
+ }
+ } else if ts, err = google.DefaultTokenSource(ctx, cd.CloudDebuggerScope); err != nil {
+ log.Print("Error getting application default credentials for Cloud Debugger:", err)
+ os.Exit(103)
+ }
+ c, err := debuglet.NewController(ctx, debuglet.Options{
+ ProjectNumber: *projectNumber,
+ ProjectID: *projectID,
+ AppModule: *appModule,
+ AppVersion: *appVersion,
+ SourceContexts: sourceContexts,
+ Verbose: *verbose,
+ TokenSource: ts,
+ })
+ if err != nil {
+ log.Fatal("Error connecting to Cloud Debugger: ", err)
+ }
+ prog, err := local.New(args[0])
+ if err != nil {
+ log.Fatal("Error loading program: ", err)
+ }
+ // Load the program, but don't actually start it running yet.
+ if _, err = prog.Run(args[1:]...); err != nil {
+ log.Fatal("Error loading program: ", err)
+ }
+ bs := breakpoints.NewBreakpointStore(prog)
+
+ // Seed the random number generator.
+ rand.Seed(time.Now().UnixNano())
+
+ // Now we want to do two things: run the user's program, and start sending
+ // List requests periodically to the Debuglet Controller to get breakpoints
+ // to set.
+ //
+ // We want to give the Debuglet Controller a chance to give us breakpoints
+ // before we start the program, otherwise we would miss any breakpoint
+ // triggers that occur during program startup -- for example, a breakpoint on
+ // the first line of main. But if the Debuglet Controller is not responding or
+ // is returning errors, we don't want to delay starting the program
+ // indefinitely.
+ //
+ // We pass a channel to breakpointListLoop, which will close it when the first
+ // List call finishes. Then we wait until either the channel is closed or a
+ // 5-second timer has finished before starting the program.
+ ch := make(chan bool)
+ // Start a goroutine that sends List requests to the Debuglet Controller, and
+ // sets any breakpoints it gets back.
+ go breakpointListLoop(ctx, c, bs, ch)
+ // Wait until 5 seconds have passed or breakpointListLoop has closed ch.
+ select {
+ case <-time.After(5 * time.Second):
+ case <-ch:
+ }
+ // Run the debuggee.
+ programLoop(ctx, c, bs, prog)
+}
+
+// usage prints a usage message to stderr and exits.
+func usage() {
+ me := "a.out"
+ if len(os.Args) >= 1 {
+ me = os.Args[0]
+ }
+ fmt.Fprintf(os.Stderr, "Usage of %s:\n", me)
+ fmt.Fprintf(os.Stderr, "\t%s [flags...] -- <program name> args...\n", me)
+ fmt.Fprintf(os.Stderr, "Flags:\n")
+ flag.PrintDefaults()
+ fmt.Fprintf(os.Stderr,
+ "See https://cloud.google.com/tools/cloud-debugger/setting-up-on-compute-engine for more information.\n")
+ os.Exit(2)
+}
+
+// readSourceContextFile reads a JSON-encoded source context from the given file.
+// It returns a non-empty slice on success.
+func readSourceContextFile(filename string) ([]*cd.SourceContext, error) {
+ if filename == "" {
+ return nil, nil
+ }
+ scJSON, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return nil, fmt.Errorf("reading file %q: %v", filename, err)
+ }
+ var sc cd.SourceContext
+ if err = json.Unmarshal(scJSON, &sc); err != nil {
+ return nil, fmt.Errorf("parsing file %q: %v", filename, err)
+ }
+ return []*cd.SourceContext{&sc}, nil
+}
+
+// breakpointListLoop repeatedly calls the Debuglet Controller's List RPC, and
+// passes the results to the BreakpointStore so it can set and unset breakpoints
+// in the program.
+//
+// After the first List call finishes, ch is closed.
+func breakpointListLoop(ctx context.Context, c *debuglet.Controller, bs *breakpoints.BreakpointStore, first chan bool) {
+ const (
+ avgTimeBetweenCalls = time.Second
+ errorDelay = 5 * time.Second
+ )
+
+ // randomDuration returns a random duration with expected value avg.
+ randomDuration := func(avg time.Duration) time.Duration {
+ return time.Duration(rand.Int63n(int64(2*avg + 1)))
+ }
+
+ var consecutiveFailures uint
+
+ for {
+ callStart := time.Now()
+ resp, err := c.List(ctx)
+ if err != nil && err != debuglet.ErrListUnchanged {
+ log.Printf("Debuglet controller server error: %v", err)
+ }
+ if err == nil {
+ bs.ProcessBreakpointList(resp.Breakpoints)
+ }
+
+ if first != nil {
+ // We've finished one call to List and set any breakpoints we received.
+ close(first)
+ first = nil
+ }
+
+ // Asynchronously send updates for any breakpoints that caused an error when
+ // the BreakpointStore tried to process them. We don't wait for the update
+ // to finish before the program can exit, as we do for normal updates.
+ errorBps := bs.ErrorBreakpoints()
+ for _, bp := range errorBps {
+ go func(bp *cd.Breakpoint) {
+ if err := c.Update(ctx, bp.Id, bp); err != nil {
+ log.Printf("Failed to send breakpoint update for %s: %s", bp.Id, err)
+ }
+ }(bp)
+ }
+
+ // Make the next call not too soon after the one we just did.
+ delay := randomDuration(avgTimeBetweenCalls)
+
+ // If the call returned an error other than ErrListUnchanged, wait longer.
+ if err != nil && err != debuglet.ErrListUnchanged {
+ // Wait twice as long after each consecutive failure, to a maximum of 16x.
+ delay += randomDuration(errorDelay * (1 << consecutiveFailures))
+ if consecutiveFailures < 4 {
+ consecutiveFailures++
+ }
+ } else {
+ consecutiveFailures = 0
+ }
+
+ // Sleep until we reach time callStart+delay. If we've already passed that
+ // time, time.Sleep will return immediately -- this should be the common
+ // case, since the server will delay responding to List for a while when
+ // there are no changes to report.
+ time.Sleep(callStart.Add(delay).Sub(time.Now()))
+ }
+}
+
+// programLoop runs the program being debugged to completion. When a breakpoint's
+// conditions are satisfied, it sends an Update RPC to the Debuglet Controller.
+// The function returns when the program exits and all Update RPCs have finished.
+func programLoop(ctx context.Context, c *debuglet.Controller, bs *breakpoints.BreakpointStore, prog debug.Program) {
+ var wg sync.WaitGroup
+ for {
+ // Run the program until it hits a breakpoint or exits.
+ status, err := prog.Resume()
+ if err != nil {
+ break
+ }
+
+ // Get the breakpoints at this address whose conditions were satisfied,
+ // and remove the ones that aren't logpoints.
+ bps := bs.BreakpointsAtPC(status.PC)
+ bps = bpsWithConditionSatisfied(bps, prog)
+ for _, bp := range bps {
+ if bp.Action != "LOG" {
+ bs.RemoveBreakpoint(bp)
+ }
+ }
+
+ if len(bps) == 0 {
+ continue
+ }
+
+ // Evaluate expressions and get the stack.
+ vc := valuecollector.NewCollector(prog, maxCapturedVariables)
+ needStackFrames := false
+ for _, bp := range bps {
+ // If evaluating bp's condition didn't return an error, evaluate bp's
+ // expressions, and later get the stack frames.
+ if bp.Status == nil {
+ bp.EvaluatedExpressions = expressionValues(bp.Expressions, prog, vc)
+ needStackFrames = true
+ }
+ }
+ var (
+ stack []*cd.StackFrame
+ stackFramesStatusMessage *cd.StatusMessage
+ )
+ if needStackFrames {
+ stack, stackFramesStatusMessage = stackFrames(prog, vc)
+ }
+
+ // Read variable values from the program.
+ variableTable := vc.ReadValues()
+
+ // Start a goroutine to send updates to the Debuglet Controller or write
+ // to logs, concurrently with resuming the program.
+ // TODO: retry Update on failure.
+ for _, bp := range bps {
+ wg.Add(1)
+ switch bp.Action {
+ case "LOG":
+ go func(format string, evaluatedExpressions []*cd.Variable) {
+ s := valuecollector.LogString(format, evaluatedExpressions, variableTable)
+ log.Print(s)
+ wg.Done()
+ }(bp.LogMessageFormat, bp.EvaluatedExpressions)
+ bp.Status = nil
+ bp.EvaluatedExpressions = nil
+ default:
+ go func(bp *cd.Breakpoint) {
+ defer wg.Done()
+ bp.IsFinalState = true
+ if bp.Status == nil {
+ // If evaluating bp's condition didn't return an error, include the
+ // stack frames, variable table, and any status message produced when
+ // getting the stack frames.
+ bp.StackFrames = stack
+ bp.VariableTable = variableTable
+ bp.Status = stackFramesStatusMessage
+ }
+ if err := c.Update(ctx, bp.Id, bp); err != nil {
+ log.Printf("Failed to send breakpoint update for %s: %s", bp.Id, err)
+ }
+ }(bp)
+ }
+ }
+ }
+
+ // Wait for all updates to finish before returning.
+ wg.Wait()
+}
+
+// bpsWithConditionSatisfied returns the breakpoints whose conditions are true
+// (or that do not have a condition.)
+func bpsWithConditionSatisfied(bpsIn []*cd.Breakpoint, prog debug.Program) []*cd.Breakpoint {
+ var bpsOut []*cd.Breakpoint
+ for _, bp := range bpsIn {
+ cond, err := condTruth(bp.Condition, prog)
+ if err != nil {
+ bp.Status = errorStatusMessage(err.Error(), refersToBreakpointCondition)
+ // Include bp in the list to be updated when there's an error, so that
+ // the user gets a response.
+ bpsOut = append(bpsOut, bp)
+ } else if cond {
+ bpsOut = append(bpsOut, bp)
+ }
+ }
+ return bpsOut
+}
+
+// condTruth evaluates a condition.
+func condTruth(condition string, prog debug.Program) (bool, error) {
+ if condition == "" {
+ // A condition wasn't set.
+ return true, nil
+ }
+ val, err := prog.Evaluate(condition)
+ if err != nil {
+ return false, err
+ }
+ if v, ok := val.(bool); !ok {
+ return false, fmt.Errorf("condition expression has type %T, should be bool", val)
+ } else {
+ return v, nil
+ }
+}
+
+// expressionValues evaluates a slice of expressions and returns a []*cd.Variable
+// containing the results.
+// If the result of an expression evaluation refers to values from the program's
+// memory (e.g., the expression evaluates to a slice) a corresponding variable is
+// added to the value collector, to be read later.
+func expressionValues(expressions []string, prog debug.Program, vc *valuecollector.Collector) []*cd.Variable {
+ evaluatedExpressions := make([]*cd.Variable, len(expressions))
+ for i, exp := range expressions {
+ ee := &cd.Variable{Name: exp}
+ evaluatedExpressions[i] = ee
+ if val, err := prog.Evaluate(exp); err != nil {
+ ee.Status = errorStatusMessage(err.Error(), refersToBreakpointExpression)
+ } else {
+ vc.FillValue(val, ee)
+ }
+ }
+ return evaluatedExpressions
+}
+
+// stackFrames returns a stack trace for the program. It passes references to
+// function parameters and local variables to the value collector, so it can read
+// their values later.
+func stackFrames(prog debug.Program, vc *valuecollector.Collector) ([]*cd.StackFrame, *cd.StatusMessage) {
+ frames, err := prog.Frames(maxCapturedStackFrames)
+ if err != nil {
+ return nil, errorStatusMessage("Error getting stack: "+err.Error(), refersToUnspecified)
+ }
+ stackFrames := make([]*cd.StackFrame, len(frames))
+ for i, f := range frames {
+ frame := &cd.StackFrame{}
+ frame.Function = f.Function
+ for _, v := range f.Params {
+ frame.Arguments = append(frame.Arguments, vc.AddVariable(debug.LocalVar(v)))
+ }
+ for _, v := range f.Vars {
+ frame.Locals = append(frame.Locals, vc.AddVariable(v))
+ }
+ frame.Location = &cd.SourceLocation{
+ Path: f.File,
+ Line: int64(f.Line),
+ }
+ stackFrames[i] = frame
+ }
+ return stackFrames, nil
+}
+
+// errorStatusMessage returns a *cd.StatusMessage indicating an error,
+// with the given message and refersTo field.
+func errorStatusMessage(msg string, refersTo int) *cd.StatusMessage {
+ return &cd.StatusMessage{
+ Description: &cd.FormatMessage{Format: "$0", Parameters: []string{msg}},
+ IsError: true,
+ RefersTo: refersToString[refersTo],
+ }
+}
+
+const (
+ // RefersTo values for cd.StatusMessage.
+ refersToUnspecified = iota
+ refersToBreakpointCondition
+ refersToBreakpointExpression
+)
+
+// refersToString contains the strings for each refersTo value.
+// See the definition of StatusMessage in the v2/clouddebugger package.
+var refersToString = map[int]string{
+ refersToUnspecified: "UNSPECIFIED",
+ refersToBreakpointCondition: "BREAKPOINT_CONDITION",
+ refersToBreakpointExpression: "BREAKPOINT_EXPRESSION",
+}
+
+func serviceAcctTokenSource(ctx context.Context, filename string, scope ...string) (oauth2.TokenSource, error) {
+ data, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return nil, fmt.Errorf("cannot read service account file: %v", err)
+ }
+ cfg, err := google.JWTConfigFromJSON(data, scope...)
+ if err != nil {
+ return nil, fmt.Errorf("google.JWTConfigFromJSON: %v", err)
+ }
+ return cfg.TokenSource(ctx), nil
+}
diff --git a/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/breakpoints/breakpoints.go b/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/breakpoints/breakpoints.go
new file mode 100644
index 000000000..afe07cbfb
--- /dev/null
+++ b/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/breakpoints/breakpoints.go
@@ -0,0 +1,174 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package breakpoints handles breakpoint requests we get from the user through
+// the Debuglet Controller, and manages corresponding breakpoints set in the code.
+package breakpoints
+
+import (
+ "log"
+ "sync"
+
+ "golang.org/x/debug"
+ cd "google.golang.org/api/clouddebugger/v2"
+)
+
+// BreakpointStore stores the set of breakpoints for a program.
+type BreakpointStore struct {
+ mu sync.Mutex
+ // prog is the program being debugged.
+ prog debug.Program
+ // idToBreakpoint is a map from breakpoint identifier to *cd.Breakpoint. The
+ // map value is nil if the breakpoint is inactive. A breakpoint is active if:
+ // - We received it from the Debuglet Controller, and it was active at the time;
+ // - We were able to set code breakpoints for it;
+ // - We have not reached any of those code breakpoints while satisfying the
+ // breakpoint's conditions, or the breakpoint has action LOG; and
+ // - The Debuglet Controller hasn't informed us the breakpoint has become inactive.
+ idToBreakpoint map[string]*cd.Breakpoint
+ // pcToBps and bpToPCs store the many-to-many relationship between breakpoints we
+ // received from the Debuglet Controller and the code breakpoints we set for them.
+ pcToBps map[uint64][]*cd.Breakpoint
+ bpToPCs map[*cd.Breakpoint][]uint64
+ // errors contains any breakpoints which couldn't be set because they caused an
+ // error. These are retrieved with ErrorBreakpoints, and the caller is
+ // expected to handle sending updates for them.
+ errors []*cd.Breakpoint
+}
+
+// NewBreakpointStore returns a BreakpointStore for the given program.
+func NewBreakpointStore(prog debug.Program) *BreakpointStore {
+ return &BreakpointStore{
+ idToBreakpoint: make(map[string]*cd.Breakpoint),
+ pcToBps: make(map[uint64][]*cd.Breakpoint),
+ bpToPCs: make(map[*cd.Breakpoint][]uint64),
+ prog: prog,
+ }
+}
+
+// ProcessBreakpointList applies updates received from the Debuglet Controller through a List call.
+func (bs *BreakpointStore) ProcessBreakpointList(bps []*cd.Breakpoint) {
+ bs.mu.Lock()
+ defer bs.mu.Unlock()
+ for _, bp := range bps {
+ if storedBp, ok := bs.idToBreakpoint[bp.Id]; ok {
+ if storedBp != nil && bp.IsFinalState {
+ // IsFinalState indicates that the breakpoint has been made inactive.
+ bs.removeBreakpointLocked(storedBp)
+ }
+ } else {
+ if bp.IsFinalState {
+ // The controller is notifying us that the breakpoint is no longer active,
+ // but we didn't know about it anyway.
+ continue
+ }
+ if bp.Action != "" && bp.Action != "CAPTURE" && bp.Action != "LOG" {
+ bp.IsFinalState = true
+ bp.Status = &cd.StatusMessage{
+ Description: &cd.FormatMessage{Format: "Action is not supported"},
+ IsError: true,
+ }
+ bs.errors = append(bs.errors, bp)
+ // Note in idToBreakpoint that we've already seen this breakpoint, so that we
+ // don't try to report it as an error multiple times.
+ bs.idToBreakpoint[bp.Id] = nil
+ continue
+ }
+ pcs, err := bs.prog.BreakpointAtLine(bp.Location.Path, uint64(bp.Location.Line))
+ if err != nil {
+ log.Printf("error setting breakpoint at %s:%d: %v", bp.Location.Path, bp.Location.Line, err)
+ }
+ if len(pcs) == 0 {
+ // We can't find a PC for this breakpoint's source line, so don't make it active.
+ // TODO: we could snap the line to a location where we can break, or report an error to the user.
+ bs.idToBreakpoint[bp.Id] = nil
+ } else {
+ bs.idToBreakpoint[bp.Id] = bp
+ for _, pc := range pcs {
+ bs.pcToBps[pc] = append(bs.pcToBps[pc], bp)
+ }
+ bs.bpToPCs[bp] = pcs
+ }
+ }
+ }
+}
+
+// ErrorBreakpoints returns a slice of Breakpoints that caused errors when the
+// BreakpointStore tried to process them, and resets the list of such
+// breakpoints.
+// The caller is expected to send updates to the server to indicate the errors.
+func (bs *BreakpointStore) ErrorBreakpoints() []*cd.Breakpoint {
+ bs.mu.Lock()
+ defer bs.mu.Unlock()
+ bps := bs.errors
+ bs.errors = nil
+ return bps
+}
+
+// BreakpointsAtPC returns all the breakpoints for which we set a code
+// breakpoint at the given address.
+func (bs *BreakpointStore) BreakpointsAtPC(pc uint64) []*cd.Breakpoint {
+ bs.mu.Lock()
+ defer bs.mu.Unlock()
+ return bs.pcToBps[pc]
+}
+
+// RemoveBreakpoint makes the given breakpoint inactive.
+// This is called when either the debugged program hits the breakpoint, or the Debuglet
+// Controller informs us that the breakpoint is now inactive.
+func (bs *BreakpointStore) RemoveBreakpoint(bp *cd.Breakpoint) {
+ bs.mu.Lock()
+ bs.removeBreakpointLocked(bp)
+ bs.mu.Unlock()
+}
+
+func (bs *BreakpointStore) removeBreakpointLocked(bp *cd.Breakpoint) {
+ // Set the ID's corresponding breakpoint to nil, so that we won't activate it
+ // if we see it again.
+ // TODO: we could delete it after a few seconds.
+ bs.idToBreakpoint[bp.Id] = nil
+
+ // Delete bp from the list of cd breakpoints at each of its corresponding
+ // code breakpoint locations, and delete any code breakpoints which no longer
+ // have a corresponding cd breakpoint.
+ var codeBreakpointsToDelete []uint64
+ for _, pc := range bs.bpToPCs[bp] {
+ bps := remove(bs.pcToBps[pc], bp)
+ if len(bps) == 0 {
+ // bp was the last breakpoint set at this PC, so delete the code breakpoint.
+ codeBreakpointsToDelete = append(codeBreakpointsToDelete, pc)
+ delete(bs.pcToBps, pc)
+ } else {
+ bs.pcToBps[pc] = bps
+ }
+ }
+ if len(codeBreakpointsToDelete) > 0 {
+ bs.prog.DeleteBreakpoints(codeBreakpointsToDelete)
+ }
+ delete(bs.bpToPCs, bp)
+}
+
+// remove updates rs by removing r, then returns rs.
+// The mutex in the BreakpointStore which contains rs should be held.
+func remove(rs []*cd.Breakpoint, r *cd.Breakpoint) []*cd.Breakpoint {
+ for i := range rs {
+ if rs[i] == r {
+ rs[i] = rs[len(rs)-1]
+ rs = rs[0 : len(rs)-1]
+ return rs
+ }
+ }
+ // We shouldn't reach here.
+ return rs
+}
diff --git a/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/breakpoints/breakpoints_test.go b/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/breakpoints/breakpoints_test.go
new file mode 100644
index 000000000..089a3ba61
--- /dev/null
+++ b/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/breakpoints/breakpoints_test.go
@@ -0,0 +1,168 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package breakpoints
+
+import (
+ "reflect"
+ "testing"
+
+ "golang.org/x/debug"
+ cd "google.golang.org/api/clouddebugger/v2"
+)
+
+var (
+ testPC1 uint64 = 0x1234
+ testPC2 uint64 = 0x5678
+ testPC3 uint64 = 0x3333
+ testFile = "foo.go"
+ testLine uint64 = 42
+ testLine2 uint64 = 99
+ testLogPC uint64 = 0x9abc
+ testLogLine uint64 = 43
+ testBadPC uint64 = 0xdef0
+ testBadLine uint64 = 44
+ testBP = &cd.Breakpoint{
+ Action: "CAPTURE",
+ Id: "TestBreakpoint",
+ IsFinalState: false,
+ Location: &cd.SourceLocation{Path: testFile, Line: int64(testLine)},
+ }
+ testBP2 = &cd.Breakpoint{
+ Action: "CAPTURE",
+ Id: "TestBreakpoint2",
+ IsFinalState: false,
+ Location: &cd.SourceLocation{Path: testFile, Line: int64(testLine2)},
+ }
+ testLogBP = &cd.Breakpoint{
+ Action: "LOG",
+ Id: "TestLogBreakpoint",
+ IsFinalState: false,
+ Location: &cd.SourceLocation{Path: testFile, Line: int64(testLogLine)},
+ }
+ testBadBP = &cd.Breakpoint{
+ Action: "BEEP",
+ Id: "TestBadBreakpoint",
+ IsFinalState: false,
+ Location: &cd.SourceLocation{Path: testFile, Line: int64(testBadLine)},
+ }
+)
+
+func TestBreakpointStore(t *testing.T) {
+ p := &Program{breakpointPCs: make(map[uint64]bool)}
+ bs := NewBreakpointStore(p)
+ checkPCs := func(expected map[uint64]bool) {
+ if !reflect.DeepEqual(p.breakpointPCs, expected) {
+ t.Errorf("got breakpoint map %v want %v", p.breakpointPCs, expected)
+ }
+ }
+ bs.ProcessBreakpointList([]*cd.Breakpoint{testBP, testBP2, testLogBP, testBadBP})
+ checkPCs(map[uint64]bool{
+ testPC1: true,
+ testPC2: true,
+ testPC3: true,
+ testLogPC: true,
+ })
+ for _, test := range []struct {
+ pc uint64
+ expected []*cd.Breakpoint
+ }{
+ {testPC1, []*cd.Breakpoint{testBP}},
+ {testPC2, []*cd.Breakpoint{testBP}},
+ {testPC3, []*cd.Breakpoint{testBP2}},
+ {testLogPC, []*cd.Breakpoint{testLogBP}},
+ } {
+ if bps := bs.BreakpointsAtPC(test.pc); !reflect.DeepEqual(bps, test.expected) {
+ t.Errorf("BreakpointsAtPC(%x): got %v want %v", test.pc, bps, test.expected)
+ }
+ }
+ testBP2.IsFinalState = true
+ bs.ProcessBreakpointList([]*cd.Breakpoint{testBP, testBP2, testLogBP, testBadBP})
+ checkPCs(map[uint64]bool{
+ testPC1: true,
+ testPC2: true,
+ testPC3: false,
+ testLogPC: true,
+ })
+ bs.RemoveBreakpoint(testBP)
+ checkPCs(map[uint64]bool{
+ testPC1: false,
+ testPC2: false,
+ testPC3: false,
+ testLogPC: true,
+ })
+ for _, pc := range []uint64{testPC1, testPC2, testPC3} {
+ if bps := bs.BreakpointsAtPC(pc); len(bps) != 0 {
+ t.Errorf("BreakpointsAtPC(%x): got %v want []", pc, bps)
+ }
+ }
+ // bs.ErrorBreakpoints should return testBadBP.
+ errorBps := bs.ErrorBreakpoints()
+ if len(errorBps) != 1 {
+ t.Errorf("ErrorBreakpoints: got %d want 1", len(errorBps))
+ } else {
+ bp := errorBps[0]
+ if bp.Id != testBadBP.Id {
+ t.Errorf("ErrorBreakpoints: got id %q want 1", bp.Id)
+ }
+ if bp.Status == nil || !bp.Status.IsError {
+ t.Errorf("ErrorBreakpoints: got %v, want error", bp.Status)
+ }
+ }
+ // The error should have been removed by the last call to bs.ErrorBreakpoints.
+ errorBps = bs.ErrorBreakpoints()
+ if len(errorBps) != 0 {
+ t.Errorf("ErrorBreakpoints: got %d want 0", len(errorBps))
+ }
+ // Even if testBadBP is sent in a new list, it should not be returned again.
+ bs.ProcessBreakpointList([]*cd.Breakpoint{testBadBP})
+ errorBps = bs.ErrorBreakpoints()
+ if len(errorBps) != 0 {
+ t.Errorf("ErrorBreakpoints: got %d want 0", len(errorBps))
+ }
+}
+
+// Program implements the similarly-named interface in x/debug.
+// ValueCollector should only call its BreakpointAtLine and DeleteBreakpoints methods.
+type Program struct {
+ debug.Program
+ // breakpointPCs contains the state of code breakpoints -- true if the
+ // breakpoint is currently set, false if it has been deleted.
+ breakpointPCs map[uint64]bool
+}
+
+func (p *Program) BreakpointAtLine(file string, line uint64) ([]uint64, error) {
+ var pcs []uint64
+ switch {
+ case file == testFile && line == testLine:
+ pcs = []uint64{testPC1, testPC2}
+ case file == testFile && line == testLine2:
+ pcs = []uint64{testPC3}
+ case file == testFile && line == testLogLine:
+ pcs = []uint64{testLogPC}
+ default:
+ pcs = []uint64{0xbad}
+ }
+ for _, pc := range pcs {
+ p.breakpointPCs[pc] = true
+ }
+ return pcs, nil
+}
+
+func (p *Program) DeleteBreakpoints(pcs []uint64) error {
+ for _, pc := range pcs {
+ p.breakpointPCs[pc] = false
+ }
+ return nil
+}
diff --git a/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/controller/client.go b/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/controller/client.go
new file mode 100644
index 000000000..87d283546
--- /dev/null
+++ b/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/controller/client.go
@@ -0,0 +1,291 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package controller is a library for interacting with the Google Cloud Debugger's Debuglet Controller service.
+package controller
+
+import (
+ "crypto/sha256"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "log"
+ "sync"
+
+ "golang.org/x/net/context"
+ "golang.org/x/oauth2"
+ cd "google.golang.org/api/clouddebugger/v2"
+ "google.golang.org/api/googleapi"
+ "google.golang.org/api/option"
+ "google.golang.org/api/transport"
+)
+
+const (
+ // agentVersionString identifies the agent to the service.
+ agentVersionString = "google.com/go-gcp/v0.2"
+ // initWaitToken is the wait token sent in the first Update request to a server.
+ initWaitToken = "init"
+)
+
+var (
+ // ErrListUnchanged is returned by List if the server time limit is reached
+ // before the list of breakpoints changes.
+ ErrListUnchanged = errors.New("breakpoint list unchanged")
+ // ErrDebuggeeDisabled is returned by List or Update if the server has disabled
+ // this Debuggee. The caller can retry later.
+ ErrDebuggeeDisabled = errors.New("debuglet disabled by server")
+)
+
+// Controller manages a connection to the Debuglet Controller service.
+type Controller struct {
+ s serviceInterface
+ // waitToken is sent with List requests so the server knows which set of
+ // breakpoints this client has already seen. Each successful List request
+ // returns a new waitToken to send in the next request.
+ waitToken string
+ // verbose determines whether to do some logging
+ verbose bool
+ // options, uniquifier and description are used in register.
+ options Options
+ uniquifier string
+ description string
+ // labels are included when registering the debuggee. They should contain
+ // the module name, version and minorversion, and are used by the debug UI
+ // to label the correct version active for debugging.
+ labels map[string]string
+ // mu protects debuggeeID
+ mu sync.Mutex
+ // debuggeeID is returned from the server on registration, and is passed back
+ // to the server in List and Update requests.
+ debuggeeID string
+}
+
+// Options controls how the Debuglet Controller client identifies itself to the server.
+// See https://cloud.google.com/storage/docs/projects and
+// https://cloud.google.com/tools/cloud-debugger/setting-up-on-compute-engine
+// for further documentation of these parameters.
+type Options struct {
+ ProjectNumber string // GCP Project Number.
+ ProjectID string // GCP Project ID.
+ AppModule string // Module name for the debugged program.
+ AppVersion string // Version number for this module.
+ SourceContexts []*cd.SourceContext // Description of source.
+ Verbose bool
+ TokenSource oauth2.TokenSource // Source of Credentials used for Stackdriver Debugger.
+}
+
+type serviceInterface interface {
+ Register(ctx context.Context, req *cd.RegisterDebuggeeRequest) (*cd.RegisterDebuggeeResponse, error)
+ Update(ctx context.Context, debuggeeID, breakpointID string, req *cd.UpdateActiveBreakpointRequest) (*cd.UpdateActiveBreakpointResponse, error)
+ List(ctx context.Context, debuggeeID, waitToken string) (*cd.ListActiveBreakpointsResponse, error)
+}
+
+var newService = func(ctx context.Context, tokenSource oauth2.TokenSource) (serviceInterface, error) {
+ httpClient, endpoint, err := transport.NewHTTPClient(ctx, option.WithTokenSource(tokenSource))
+ if err != nil {
+ return nil, err
+ }
+ s, err := cd.New(httpClient)
+ if err != nil {
+ return nil, err
+ }
+ if endpoint != "" {
+ s.BasePath = endpoint
+ }
+ return &service{s: s}, nil
+}
+
+type service struct {
+ s *cd.Service
+}
+
+func (s service) Register(ctx context.Context, req *cd.RegisterDebuggeeRequest) (*cd.RegisterDebuggeeResponse, error) {
+ call := cd.NewControllerDebuggeesService(s.s).Register(req)
+ return call.Context(ctx).Do()
+}
+
+func (s service) Update(ctx context.Context, debuggeeID, breakpointID string, req *cd.UpdateActiveBreakpointRequest) (*cd.UpdateActiveBreakpointResponse, error) {
+ call := cd.NewControllerDebuggeesBreakpointsService(s.s).Update(debuggeeID, breakpointID, req)
+ return call.Context(ctx).Do()
+}
+
+func (s service) List(ctx context.Context, debuggeeID, waitToken string) (*cd.ListActiveBreakpointsResponse, error) {
+ call := cd.NewControllerDebuggeesBreakpointsService(s.s).List(debuggeeID)
+ call.WaitToken(waitToken)
+ return call.Context(ctx).Do()
+}
+
+// NewController connects to the Debuglet Controller server using the given options,
+// and returns a Controller for that connection.
+// Google Application Default Credentials are used to connect to the Debuglet Controller;
+// see https://developers.google.com/identity/protocols/application-default-credentials
+func NewController(ctx context.Context, o Options) (*Controller, error) {
+ // We build a JSON encoding of o.SourceContexts so we can hash it.
+ scJSON, err := json.Marshal(o.SourceContexts)
+ if err != nil {
+ scJSON = nil
+ o.SourceContexts = nil
+ }
+ const minorversion = "107157" // any arbitrary numeric string
+
+ // Compute a uniquifier string by hashing the project number, app module name,
+ // app module version, debuglet version, and source context.
+ // The choice of hash function is arbitrary.
+ h := sha256.Sum256([]byte(fmt.Sprintf("%d %s %d %s %d %s %d %s %d %s %d %s",
+ len(o.ProjectNumber), o.ProjectNumber,
+ len(o.AppModule), o.AppModule,
+ len(o.AppVersion), o.AppVersion,
+ len(agentVersionString), agentVersionString,
+ len(scJSON), scJSON,
+ len(minorversion), minorversion)))
+ uniquifier := fmt.Sprintf("%X", h[0:16]) // 32 hex characters
+
+ description := o.ProjectID
+ if o.AppModule != "" {
+ description += "-" + o.AppModule
+ }
+ if o.AppVersion != "" {
+ description += "-" + o.AppVersion
+ }
+
+ s, err := newService(ctx, o.TokenSource)
+ if err != nil {
+ return nil, err
+ }
+
+ // Construct client.
+ c := &Controller{
+ s: s,
+ waitToken: initWaitToken,
+ verbose: o.Verbose,
+ options: o,
+ uniquifier: uniquifier,
+ description: description,
+ labels: map[string]string{
+ "module": o.AppModule,
+ "version": o.AppVersion,
+ "minorversion": minorversion,
+ },
+ }
+
+ return c, nil
+}
+
+func (c *Controller) getDebuggeeID(ctx context.Context) (string, error) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ if c.debuggeeID != "" {
+ return c.debuggeeID, nil
+ }
+ // The debuglet hasn't been registered yet, or it is disabled and we should try registering again.
+ if err := c.register(ctx); err != nil {
+ return "", err
+ }
+ return c.debuggeeID, nil
+}
+
+// List retrieves the current list of breakpoints from the server.
+// If the set of breakpoints on the server is the same as the one returned in
+// the previous call to List, the server can delay responding until it changes,
+// and return an error instead if no change occurs before a time limit the
+// server sets. List can't be called concurrently with itself.
+func (c *Controller) List(ctx context.Context) (*cd.ListActiveBreakpointsResponse, error) {
+ id, err := c.getDebuggeeID(ctx)
+ if err != nil {
+ return nil, err
+ }
+ resp, err := c.s.List(ctx, id, c.waitToken)
+ if err != nil {
+ if isAbortedError(err) {
+ return nil, ErrListUnchanged
+ }
+ // For other errors, the protocol requires that we attempt to re-register.
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ if regError := c.register(ctx); regError != nil {
+ return nil, regError
+ }
+ return nil, err
+ }
+ if resp == nil {
+ return nil, errors.New("no response")
+ }
+ if c.verbose {
+ log.Printf("List response: %v", resp)
+ }
+ c.waitToken = resp.NextWaitToken
+ return resp, nil
+}
+
+// isAbortedError tests if err is a *googleapi.Error, that it contains one error
+// in Errors, and that that error's Reason is "aborted".
+func isAbortedError(err error) bool {
+ e, _ := err.(*googleapi.Error)
+ if e == nil {
+ return false
+ }
+ if len(e.Errors) != 1 {
+ return false
+ }
+ return e.Errors[0].Reason == "aborted"
+}
+
+// Update reports information to the server about a breakpoint that was hit.
+// Update can be called concurrently with List and Update.
+func (c *Controller) Update(ctx context.Context, breakpointID string, bp *cd.Breakpoint) error {
+ req := &cd.UpdateActiveBreakpointRequest{Breakpoint: bp}
+ if c.verbose {
+ log.Printf("sending update for %s: %v", breakpointID, req)
+ }
+ id, err := c.getDebuggeeID(ctx)
+ if err != nil {
+ return err
+ }
+ _, err = c.s.Update(ctx, id, breakpointID, req)
+ return err
+}
+
+// register calls the Debuglet Controller Register method, and sets c.debuggeeID.
+// c.mu should be locked while calling this function. List and Update can't
+// make progress until it returns.
+func (c *Controller) register(ctx context.Context) error {
+ req := cd.RegisterDebuggeeRequest{
+ Debuggee: &cd.Debuggee{
+ AgentVersion: agentVersionString,
+ Description: c.description,
+ Project: c.options.ProjectNumber,
+ SourceContexts: c.options.SourceContexts,
+ Uniquifier: c.uniquifier,
+ Labels: c.labels,
+ },
+ }
+ resp, err := c.s.Register(ctx, &req)
+ if err != nil {
+ return err
+ }
+ if resp == nil {
+ return errors.New("register: no response")
+ }
+ if resp.Debuggee.IsDisabled {
+ // Setting c.debuggeeID to empty makes sure future List and Update calls
+ // will call register first.
+ c.debuggeeID = ""
+ } else {
+ c.debuggeeID = resp.Debuggee.Id
+ }
+ if c.debuggeeID == "" {
+ return ErrDebuggeeDisabled
+ }
+ return nil
+}
diff --git a/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/controller/client_test.go b/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/controller/client_test.go
new file mode 100644
index 000000000..fa0634706
--- /dev/null
+++ b/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/controller/client_test.go
@@ -0,0 +1,254 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package controller
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "strconv"
+ "testing"
+
+ "golang.org/x/net/context"
+ "golang.org/x/oauth2"
+
+ cd "google.golang.org/api/clouddebugger/v2"
+ "google.golang.org/api/googleapi"
+)
+
+const (
+ testDebuggeeID = "d12345"
+ testBreakpointID = "bp12345"
+)
+
+var (
+ // The sequence of wait tokens in List requests and responses.
+ expectedWaitToken = []string{"init", "token1", "token2", "token1", "token1"}
+ // The set of breakpoints returned from each List call.
+ expectedBreakpoints = [][]*cd.Breakpoint{
+ nil,
+ {
+ &cd.Breakpoint{
+ Id: testBreakpointID,
+ IsFinalState: false,
+ Location: &cd.SourceLocation{Line: 42, Path: "foo.go"},
+ },
+ },
+ nil,
+ }
+ abortedError error = &googleapi.Error{
+ Code: 409,
+ Message: "Conflict",
+ Body: `{
+ "error": {
+ "errors": [
+ {
+ "domain": "global",
+ "reason": "aborted",
+ "message": "Conflict"
+ }
+ ],
+ "code": 409,
+ "message": "Conflict"
+ }
+ }`,
+ Errors: []googleapi.ErrorItem{
+ {Reason: "aborted", Message: "Conflict"},
+ },
+ }
+ backendError error = &googleapi.Error{
+ Code: 503,
+ Message: "Backend Error",
+ Body: `{
+ "error": {
+ "errors": [
+ {
+ "domain": "global",
+ "reason": "backendError",
+ "message": "Backend Error"
+ }
+ ],
+ "code": 503,
+ "message": "Backend Error"
+ }
+ }`,
+ Errors: []googleapi.ErrorItem{
+ {Reason: "backendError", Message: "Backend Error"},
+ },
+ }
+)
+
+type mockService struct {
+ t *testing.T
+ listCallsSeen int
+ registerCallsSeen int
+}
+
+func (s *mockService) Register(ctx context.Context, req *cd.RegisterDebuggeeRequest) (*cd.RegisterDebuggeeResponse, error) {
+ s.registerCallsSeen++
+ if req.Debuggee == nil {
+ s.t.Errorf("missing debuggee")
+ return nil, nil
+ }
+ if req.Debuggee.AgentVersion == "" {
+ s.t.Errorf("missing agent version")
+ }
+ if req.Debuggee.Description == "" {
+ s.t.Errorf("missing debuglet description")
+ }
+ if req.Debuggee.Project == "" {
+ s.t.Errorf("missing project id")
+ }
+ if req.Debuggee.Uniquifier == "" {
+ s.t.Errorf("missing uniquifier")
+ }
+ return &cd.RegisterDebuggeeResponse{
+ Debuggee: &cd.Debuggee{Id: testDebuggeeID},
+ }, nil
+}
+
+func (s *mockService) Update(ctx context.Context, id, breakpointID string, req *cd.UpdateActiveBreakpointRequest) (*cd.UpdateActiveBreakpointResponse, error) {
+ if id != testDebuggeeID {
+ s.t.Errorf("got debuggee ID %s want %s", id, testDebuggeeID)
+ }
+ if breakpointID != testBreakpointID {
+ s.t.Errorf("got breakpoint ID %s want %s", breakpointID, testBreakpointID)
+ }
+ if !req.Breakpoint.IsFinalState {
+ s.t.Errorf("got IsFinalState = false, want true")
+ }
+ return nil, nil
+}
+
+func (s *mockService) List(ctx context.Context, id, waitToken string) (*cd.ListActiveBreakpointsResponse, error) {
+ if id != testDebuggeeID {
+ s.t.Errorf("got debuggee ID %s want %s", id, testDebuggeeID)
+ }
+ if waitToken != expectedWaitToken[s.listCallsSeen] {
+ s.t.Errorf("got wait token %s want %s", waitToken, expectedWaitToken[s.listCallsSeen])
+ }
+ s.listCallsSeen++
+ if s.listCallsSeen == 4 {
+ return nil, backendError
+ }
+ if s.listCallsSeen == 5 {
+ return nil, abortedError
+ }
+ resp := &cd.ListActiveBreakpointsResponse{
+ Breakpoints: expectedBreakpoints[s.listCallsSeen-1],
+ NextWaitToken: expectedWaitToken[s.listCallsSeen],
+ }
+ return resp, nil
+}
+
+func TestDebugletControllerClientLibrary(t *testing.T) {
+ var (
+ m *mockService
+ c *Controller
+ list *cd.ListActiveBreakpointsResponse
+ err error
+ )
+ m = &mockService{t: t}
+ newService = func(context.Context, oauth2.TokenSource) (serviceInterface, error) { return m, nil }
+ opts := Options{
+ ProjectNumber: "5",
+ ProjectID: "p1",
+ AppModule: "mod1",
+ AppVersion: "v1",
+ }
+ ctx := context.Background()
+ if c, err = NewController(ctx, opts); err != nil {
+ t.Fatal("Initializing Controller client:", err)
+ }
+ if err := validateLabels(c, opts); err != nil {
+ t.Fatalf("Invalid labels:\n%v", err)
+ }
+ if list, err = c.List(ctx); err != nil {
+ t.Fatal("List:", err)
+ }
+ if m.registerCallsSeen != 1 {
+ t.Errorf("saw %d Register calls, want 1", m.registerCallsSeen)
+ }
+ if list, err = c.List(ctx); err != nil {
+ t.Fatal("List:", err)
+ }
+ if len(list.Breakpoints) != 1 {
+ t.Fatalf("got %d breakpoints, want 1", len(list.Breakpoints))
+ }
+ if err = c.Update(ctx, list.Breakpoints[0].Id, &cd.Breakpoint{Id: testBreakpointID, IsFinalState: true}); err != nil {
+ t.Fatal("Update:", err)
+ }
+ if list, err = c.List(ctx); err != nil {
+ t.Fatal("List:", err)
+ }
+ if m.registerCallsSeen != 1 {
+ t.Errorf("saw %d Register calls, want 1", m.registerCallsSeen)
+ }
+ // The next List call produces an error that should cause a Register call.
+ if list, err = c.List(ctx); err == nil {
+ t.Fatal("List should have returned an error")
+ }
+ if m.registerCallsSeen != 2 {
+ t.Errorf("saw %d Register calls, want 2", m.registerCallsSeen)
+ }
+ // The next List call produces an error that should not cause a Register call.
+ if list, err = c.List(ctx); err == nil {
+ t.Fatal("List should have returned an error")
+ }
+ if m.registerCallsSeen != 2 {
+ t.Errorf("saw %d Register calls, want 2", m.registerCallsSeen)
+ }
+ if m.listCallsSeen != 5 {
+ t.Errorf("saw %d list calls, want 5", m.listCallsSeen)
+ }
+}
+
+func validateLabels(c *Controller, o Options) error {
+ errMsg := new(bytes.Buffer)
+ if m, ok := c.labels["module"]; ok {
+ if m != o.AppModule {
+ errMsg.WriteString(fmt.Sprintf("label module: want %s, got %s\n", o.AppModule, m))
+ }
+ } else {
+ errMsg.WriteString("Missing \"module\" label\n")
+ }
+ if v, ok := c.labels["version"]; ok {
+ if v != o.AppVersion {
+ errMsg.WriteString(fmt.Sprintf("label version: want %s, got %s\n", o.AppVersion, v))
+ }
+ } else {
+ errMsg.WriteString("Missing \"version\" label\n")
+ }
+ if mv, ok := c.labels["minorversion"]; ok {
+ if _, err := strconv.Atoi(mv); err != nil {
+ errMsg.WriteString(fmt.Sprintln("label minorversion: not a numeric string:", mv))
+ }
+ } else {
+ errMsg.WriteString("Missing \"minorversion\" label\n")
+ }
+ if errMsg.Len() != 0 {
+ return errors.New(errMsg.String())
+ }
+ return nil
+}
+
+func TestIsAbortedError(t *testing.T) {
+ if !isAbortedError(abortedError) {
+ t.Errorf("isAborted(%+v): got false, want true", abortedError)
+ }
+ if isAbortedError(backendError) {
+ t.Errorf("isAborted(%+v): got true, want false", backendError)
+ }
+}
diff --git a/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/valuecollector/valuecollector.go b/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/valuecollector/valuecollector.go
new file mode 100644
index 000000000..8dadc2f63
--- /dev/null
+++ b/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/valuecollector/valuecollector.go
@@ -0,0 +1,460 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package valuecollector is used to collect the values of variables in a program.
+package valuecollector
+
+import (
+ "bytes"
+ "fmt"
+ "strconv"
+ "strings"
+
+ "golang.org/x/debug"
+ cd "google.golang.org/api/clouddebugger/v2"
+)
+
+const (
+ maxArrayLength = 50
+ maxMapLength = 20
+)
+
+// Collector is given references to variables from a program being debugged
+// using AddVariable. Then when ReadValues is called, the Collector will fetch
+// the values of those variables. Any variables referred to by those values
+// will also be fetched; e.g. the targets of pointers, members of structs,
+// elements of slices, etc. This continues iteratively, building a graph of
+// values, until all the reachable values are fetched, or a size limit is
+// reached.
+//
+// Variables are passed to the Collector as debug.Var, which is used by x/debug
+// to represent references to variables. Values are returned as cd.Variable,
+// which is used by the Debuglet Controller to represent the graph of values.
+//
+// For example, if the program has a struct variable:
+//
+// foo := SomeStruct{a:42, b:"xyz"}
+//
+// and we call AddVariable with a reference to foo, we will get back a result
+// like:
+//
+// cd.Variable{Name:"foo", VarTableIndex:10}
+//
+// which denotes a variable named "foo" which will have its value stored in
+// element 10 of the table that will later be returned by ReadValues. That
+// element might be:
+//
+// out[10] = &cd.Variable{Members:{{Name:"a", VarTableIndex:11},{Name:"b", VarTableIndex:12}}}
+//
+// which denotes a struct with two members a and b, whose values are in elements
+// 11 and 12 of the output table:
+//
+// out[11] = &cd.Variable{Value:"42"}
+// out[12] = &cd.Variable{Value:"xyz"}
+type Collector struct {
+ // prog is the program being debugged.
+ prog debug.Program
+ // limit is the maximum size of the output slice of values.
+ limit int
+ // index is a map from references (variables and map elements) to their
+ // locations in the table.
+ index map[reference]int
+ // table contains the references, including those given to the
+ // Collector directly and those the Collector itself found.
+ // If VarTableIndex is set to 0 in a cd.Variable, it is ignored, so the first entry
+ // of table can't be used. On initialization we put a dummy value there.
+ table []reference
+}
+
+// reference represents a value which is in the queue to be read by the
+// collector. It is either a debug.Var, or a mapElement.
+type reference interface{}
+
+// mapElement represents an element of a map in the debugged program's memory.
+type mapElement struct {
+ debug.Map
+ index uint64
+}
+
+// NewCollector returns a Collector for the given program and size limit.
+// The limit is the maximum size of the slice of values returned by ReadValues.
+func NewCollector(prog debug.Program, limit int) *Collector {
+ return &Collector{
+ prog: prog,
+ limit: limit,
+ index: make(map[reference]int),
+ table: []reference{debug.Var{}},
+ }
+}
+
+// AddVariable adds another variable to be collected.
+// The Collector doesn't get the value immediately; it returns a cd.Variable
+// that contains an index into the table which will later be returned by
+// ReadValues.
+func (c *Collector) AddVariable(lv debug.LocalVar) *cd.Variable {
+ ret := &cd.Variable{Name: lv.Name}
+ if index, ok := c.add(lv.Var); !ok {
+ // If the add call failed, it's because we reached the size limit.
+ // The Debuglet Controller's convention is to pass it a "Not Captured" error
+ // in this case.
+ ret.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
+ } else {
+ ret.VarTableIndex = int64(index)
+ }
+ return ret
+}
+
+// add adds a reference to the set of values to be read from the
+// program. It returns the index in the output table that will contain the
+// corresponding value. It fails if the table has reached the size limit.
+// It deduplicates references, so the index may be the same as one that was
+// returned from an earlier add call.
+func (c *Collector) add(r reference) (outputIndex int, ok bool) {
+ if i, ok := c.index[r]; ok {
+ return i, true
+ }
+ i := len(c.table)
+ if i >= c.limit {
+ return 0, false
+ }
+ c.index[r] = i
+ c.table = append(c.table, r)
+ return i, true
+}
+
+func addMember(v *cd.Variable, name string) *cd.Variable {
+ v2 := &cd.Variable{Name: name}
+ v.Members = append(v.Members, v2)
+ return v2
+}
+
+// ReadValues fetches values of the variables that were passed to the Collector
+// with AddVariable. The values of any new variables found are also fetched,
+// e.g. the targets of pointers or the members of structs, until we reach the
+// size limit or we run out of values to fetch.
+// The results are output as a []*cd.Variable, which is the type we need to send
+// to the Debuglet Controller after we trigger a breakpoint.
+func (c *Collector) ReadValues() (out []*cd.Variable) {
+ for i := 0; i < len(c.table); i++ {
+ // Create a new cd.Variable for this value, and append it to the output.
+ dcv := new(cd.Variable)
+ out = append(out, dcv)
+ if i == 0 {
+ // The first element is unused.
+ continue
+ }
+ switch x := c.table[i].(type) {
+ case mapElement:
+ key, value, err := c.prog.MapElement(x.Map, x.index)
+ if err != nil {
+ dcv.Status = statusMessage(err.Error(), true, refersToVariableValue)
+ continue
+ }
+ // Add a member for the key.
+ member := addMember(dcv, "key")
+ if index, ok := c.add(key); !ok {
+ // The table is full.
+ member.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
+ continue
+ } else {
+ member.VarTableIndex = int64(index)
+ }
+ // Add a member for the value.
+ member = addMember(dcv, "value")
+ if index, ok := c.add(value); !ok {
+ // The table is full.
+ member.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
+ } else {
+ member.VarTableIndex = int64(index)
+ }
+ case debug.Var:
+ if v, err := c.prog.Value(x); err != nil {
+ dcv.Status = statusMessage(err.Error(), true, refersToVariableValue)
+ } else {
+ c.FillValue(v, dcv)
+ }
+ }
+ }
+ return out
+}
+
+// indexable is an interface for arrays, slices and channels.
+type indexable interface {
+ Len() uint64
+ Element(uint64) debug.Var
+}
+
+// channel implements indexable.
+type channel struct {
+ debug.Channel
+}
+
+func (c channel) Len() uint64 {
+ return c.Length
+}
+
+var (
+ _ indexable = debug.Array{}
+ _ indexable = debug.Slice{}
+ _ indexable = channel{}
+)
+
+// FillValue copies a value into a cd.Variable. Any variables referred to by
+// that value, e.g. struct members and pointer targets, are added to the
+// collector's queue, to be fetched later by ReadValues.
+func (c *Collector) FillValue(v debug.Value, dcv *cd.Variable) {
+ if c, ok := v.(debug.Channel); ok {
+ // Convert to channel, which implements indexable.
+ v = channel{c}
+ }
+ // Fill in dcv in a manner depending on the type of the value we got.
+ switch val := v.(type) {
+ case int8, int16, int32, int64, bool, uint8, uint16, uint32, uint64, float32, float64, complex64, complex128:
+ // For simple types, we just print the value to dcv.Value.
+ dcv.Value = fmt.Sprint(val)
+ case string:
+ // Put double quotes around strings.
+ dcv.Value = strconv.Quote(val)
+ case debug.String:
+ if uint64(len(val.String)) < val.Length {
+ // This string value was truncated.
+ dcv.Value = strconv.Quote(val.String + "...")
+ } else {
+ dcv.Value = strconv.Quote(val.String)
+ }
+ case debug.Struct:
+ // For structs, we add an entry to dcv.Members for each field in the
+ // struct.
+ // Each member will contain the name of the field, and the index in the
+ // output table which will contain the value of that field.
+ for _, f := range val.Fields {
+ member := addMember(dcv, f.Name)
+ if index, ok := c.add(f.Var); !ok {
+ // The table is full.
+ member.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
+ } else {
+ member.VarTableIndex = int64(index)
+ }
+ }
+ case debug.Map:
+ dcv.Value = fmt.Sprintf("len = %d", val.Length)
+ for i := uint64(0); i < val.Length; i++ {
+ field := addMember(dcv, `⚫`)
+ if i == maxMapLength {
+ field.Name = "..."
+ field.Status = statusMessage(messageTruncated, true, refersToVariableName)
+ break
+ }
+ if index, ok := c.add(mapElement{val, i}); !ok {
+ // The value table is full; add a member to contain the error message.
+ field.Name = "..."
+ field.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
+ break
+ } else {
+ field.VarTableIndex = int64(index)
+ }
+ }
+ case debug.Pointer:
+ if val.Address == 0 {
+ dcv.Value = "<nil>"
+ } else if val.TypeID == 0 {
+ // We don't know the type of the pointer, so just output the address as
+ // the value.
+ dcv.Value = fmt.Sprintf("0x%X", val.Address)
+ dcv.Status = statusMessage(messageUnknownPointerType, false, refersToVariableName)
+ } else {
+ // Adds the pointed-to variable to the table, and links this value to
+ // that table entry through VarTableIndex.
+ dcv.Value = fmt.Sprintf("0x%X", val.Address)
+ target := addMember(dcv, "")
+ if index, ok := c.add(debug.Var(val)); !ok {
+ target.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
+ } else {
+ target.VarTableIndex = int64(index)
+ }
+ }
+ case indexable:
+ // Arrays, slices and channels.
+ dcv.Value = "len = " + fmt.Sprint(val.Len())
+ for j := uint64(0); j < val.Len(); j++ {
+ field := addMember(dcv, fmt.Sprint(`[`, j, `]`))
+ if j == maxArrayLength {
+ field.Name = "..."
+ field.Status = statusMessage(messageTruncated, true, refersToVariableName)
+ break
+ }
+ vr := val.Element(j)
+ if index, ok := c.add(vr); !ok {
+ // The value table is full; add a member to contain the error message.
+ field.Name = "..."
+ field.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
+ break
+ } else {
+ // Add a member with the index as the name.
+ field.VarTableIndex = int64(index)
+ }
+ }
+ default:
+ dcv.Status = statusMessage(messageUnknownType, false, refersToVariableName)
+ }
+}
+
+// statusMessage returns a *cd.StatusMessage with the given message, IsError
+// field and refersTo field.
+func statusMessage(msg string, isError bool, refersTo int) *cd.StatusMessage {
+ return &cd.StatusMessage{
+ Description: &cd.FormatMessage{Format: "$0", Parameters: []string{msg}},
+ IsError: isError,
+ RefersTo: refersToString[refersTo],
+ }
+}
+
+// LogString produces a string for a logpoint, substituting in variable values
+// using evaluatedExpressions and varTable.
+func LogString(s string, evaluatedExpressions []*cd.Variable, varTable []*cd.Variable) string {
+ var buf bytes.Buffer
+ fmt.Fprintf(&buf, "LOGPOINT: ")
+ seen := make(map[*cd.Variable]bool)
+ for i := 0; i < len(s); {
+ if s[i] == '$' {
+ i++
+ if num, n, ok := parseToken(s[i:], len(evaluatedExpressions)-1); ok {
+ // This token is one of $0, $1, etc. Write the corresponding expression.
+ writeExpression(&buf, evaluatedExpressions[num], false, varTable, seen)
+ i += n
+ } else {
+ // Something else, like $$.
+ buf.WriteByte(s[i])
+ i++
+ }
+ } else {
+ buf.WriteByte(s[i])
+ i++
+ }
+ }
+ return buf.String()
+}
+
+func parseToken(s string, max int) (num int, bytesRead int, ok bool) {
+ var i int
+ for i < len(s) && s[i] >= '0' && s[i] <= '9' {
+ i++
+ }
+ num, err := strconv.Atoi(s[:i])
+ return num, i, err == nil && num <= max
+}
+
+// writeExpression recursively writes variables to buf, in a format suitable
+// for logging. If printName is true, writes the name of the variable.
+func writeExpression(buf *bytes.Buffer, v *cd.Variable, printName bool, varTable []*cd.Variable, seen map[*cd.Variable]bool) {
+ if v == nil {
+ // Shouldn't happen.
+ return
+ }
+ name, value, status, members := v.Name, v.Value, v.Status, v.Members
+
+ // If v.VarTableIndex is not zero, it refers to an element of varTable.
+ // We merge its fields with the fields we got from v.
+ var other *cd.Variable
+ if idx := int(v.VarTableIndex); idx > 0 && idx < len(varTable) {
+ other = varTable[idx]
+ }
+ if other != nil {
+ if name == "" {
+ name = other.Name
+ }
+ if value == "" {
+ value = other.Value
+ }
+ if status == nil {
+ status = other.Status
+ }
+ if len(members) == 0 {
+ members = other.Members
+ }
+ }
+ if printName && name != "" {
+ buf.WriteString(name)
+ buf.WriteByte(':')
+ }
+
+ // If we have seen this value before, write "..." rather than repeating it.
+ if seen[v] {
+ buf.WriteString("...")
+ return
+ }
+ seen[v] = true
+ if other != nil {
+ if seen[other] {
+ buf.WriteString("...")
+ return
+ }
+ seen[other] = true
+ }
+
+ if value != "" && !strings.HasPrefix(value, "len = ") {
+ // A plain value.
+ buf.WriteString(value)
+ } else if status != nil && status.Description != nil {
+ // An error.
+ for _, p := range status.Description.Parameters {
+ buf.WriteByte('(')
+ buf.WriteString(p)
+ buf.WriteByte(')')
+ }
+ } else if name == `⚫` {
+ // A map element.
+ first := true
+ for _, member := range members {
+ if first {
+ first = false
+ } else {
+ buf.WriteByte(':')
+ }
+ writeExpression(buf, member, false, varTable, seen)
+ }
+ } else {
+ // A map, array, slice, channel, or struct.
+ isStruct := value == ""
+ first := true
+ buf.WriteByte('{')
+ for _, member := range members {
+ if first {
+ first = false
+ } else {
+ buf.WriteString(", ")
+ }
+ writeExpression(buf, member, isStruct, varTable, seen)
+ }
+ buf.WriteByte('}')
+ }
+}
+
+const (
+ // Error messages for cd.StatusMessage
+ messageNotCaptured = "Not captured"
+ messageTruncated = "Truncated"
+ messageUnknownPointerType = "Unknown pointer type"
+ messageUnknownType = "Unknown type"
+ // RefersTo values for cd.StatusMessage.
+ refersToVariableName = iota
+ refersToVariableValue
+)
+
+// refersToString contains the strings for each refersTo value.
+// See the definition of StatusMessage in the v2/clouddebugger package.
+var refersToString = map[int]string{
+ refersToVariableName: "VARIABLE_NAME",
+ refersToVariableValue: "VARIABLE_VALUE",
+}
diff --git a/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/valuecollector/valuecollector_test.go b/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/valuecollector/valuecollector_test.go
new file mode 100644
index 000000000..2bc97dcfa
--- /dev/null
+++ b/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/valuecollector/valuecollector_test.go
@@ -0,0 +1,418 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package valuecollector
+
+import (
+ "fmt"
+ "reflect"
+ "testing"
+
+ "golang.org/x/debug"
+ cd "google.golang.org/api/clouddebugger/v2"
+)
+
+const (
+ // Some arbitrary type IDs for the test, for use in debug.Var's TypeID field.
+ // A TypeID of 0 means the type is unknown, so we start at 1.
+ int16Type = iota + 1
+ stringType
+ structType
+ pointerType
+ arrayType
+ int32Type
+ debugStringType
+ mapType
+ channelType
+ sliceType
+)
+
+func TestValueCollector(t *testing.T) {
+ // Construct the collector.
+ c := NewCollector(&Program{}, 26)
+ // Add some variables of various types, whose values we want the collector to read.
+ variablesToAdd := []debug.LocalVar{
+ {Name: "a", Var: debug.Var{int16Type, 0x1}},
+ {Name: "b", Var: debug.Var{stringType, 0x2}},
+ {Name: "c", Var: debug.Var{structType, 0x3}},
+ {Name: "d", Var: debug.Var{pointerType, 0x4}},
+ {Name: "e", Var: debug.Var{arrayType, 0x5}},
+ {Name: "f", Var: debug.Var{debugStringType, 0x6}},
+ {Name: "g", Var: debug.Var{mapType, 0x7}},
+ {Name: "h", Var: debug.Var{channelType, 0x8}},
+ {Name: "i", Var: debug.Var{sliceType, 0x9}},
+ }
+ expectedResults := []*cd.Variable{
+ &cd.Variable{Name: "a", VarTableIndex: 1},
+ &cd.Variable{Name: "b", VarTableIndex: 2},
+ &cd.Variable{Name: "c", VarTableIndex: 3},
+ &cd.Variable{Name: "d", VarTableIndex: 4},
+ &cd.Variable{Name: "e", VarTableIndex: 5},
+ &cd.Variable{Name: "f", VarTableIndex: 6},
+ &cd.Variable{Name: "g", VarTableIndex: 7},
+ &cd.Variable{Name: "h", VarTableIndex: 8},
+ &cd.Variable{Name: "i", VarTableIndex: 9},
+ }
+ for i, v := range variablesToAdd {
+ added := c.AddVariable(v)
+ if !reflect.DeepEqual(added, expectedResults[i]) {
+ t.Errorf("AddVariable: got %+v want %+v", *added, *expectedResults[i])
+ }
+ }
+ // Read the values, compare the output to what we expect.
+ v := c.ReadValues()
+ expectedValues := []*cd.Variable{
+ &cd.Variable{},
+ &cd.Variable{Value: "1"},
+ &cd.Variable{Value: `"hello"`},
+ &cd.Variable{
+ Members: []*cd.Variable{
+ &cd.Variable{Name: "x", VarTableIndex: 1},
+ &cd.Variable{Name: "y", VarTableIndex: 2},
+ },
+ },
+ &cd.Variable{
+ Members: []*cd.Variable{
+ &cd.Variable{VarTableIndex: 1},
+ },
+ Value: "0x1",
+ },
+ &cd.Variable{
+ Members: []*cd.Variable{
+ &cd.Variable{Name: "[0]", VarTableIndex: 10},
+ &cd.Variable{Name: "[1]", VarTableIndex: 11},
+ &cd.Variable{Name: "[2]", VarTableIndex: 12},
+ &cd.Variable{Name: "[3]", VarTableIndex: 13},
+ },
+ Value: "len = 4",
+ },
+ &cd.Variable{Value: `"world"`},
+ &cd.Variable{
+ Members: []*cd.Variable{
+ &cd.Variable{Name: "⚫", VarTableIndex: 14},
+ &cd.Variable{Name: "⚫", VarTableIndex: 15},
+ &cd.Variable{Name: "⚫", VarTableIndex: 16},
+ },
+ Value: "len = 3",
+ },
+ &cd.Variable{
+ Members: []*cd.Variable{
+ &cd.Variable{Name: "[0]", VarTableIndex: 17},
+ &cd.Variable{Name: "[1]", VarTableIndex: 18},
+ },
+ Value: "len = 2",
+ },
+ &cd.Variable{
+ Members: []*cd.Variable{
+ &cd.Variable{Name: "[0]", VarTableIndex: 19},
+ &cd.Variable{Name: "[1]", VarTableIndex: 20},
+ },
+ Value: "len = 2",
+ },
+ &cd.Variable{Value: "100"},
+ &cd.Variable{Value: "104"},
+ &cd.Variable{Value: "108"},
+ &cd.Variable{Value: "112"},
+ &cd.Variable{
+ Members: []*cd.Variable{
+ &cd.Variable{Name: "key", VarTableIndex: 21},
+ &cd.Variable{Name: "value", VarTableIndex: 22},
+ },
+ },
+ &cd.Variable{
+ Members: []*cd.Variable{
+ &cd.Variable{Name: "key", VarTableIndex: 23},
+ &cd.Variable{Name: "value", VarTableIndex: 24},
+ },
+ },
+ &cd.Variable{
+ Members: []*cd.Variable{
+ &cd.Variable{Name: "key", VarTableIndex: 25},
+ &cd.Variable{
+ Name: "value",
+ Status: &cd.StatusMessage{
+ Description: &cd.FormatMessage{
+ Format: "$0",
+ Parameters: []string{"Not captured"},
+ },
+ IsError: true,
+ RefersTo: "VARIABLE_NAME",
+ },
+ },
+ },
+ },
+ &cd.Variable{Value: "246"},
+ &cd.Variable{Value: "210"},
+ &cd.Variable{Value: "300"},
+ &cd.Variable{Value: "304"},
+ &cd.Variable{Value: "400"},
+ &cd.Variable{Value: "404"},
+ &cd.Variable{Value: "1400"},
+ &cd.Variable{Value: "1404"},
+ &cd.Variable{Value: "2400"},
+ }
+ if !reflect.DeepEqual(v, expectedValues) {
+ t.Errorf("ReadValues: got %v want %v", v, expectedValues)
+ // Do element-by-element comparisons, for more useful error messages.
+ for i := range v {
+ if i < len(expectedValues) && !reflect.DeepEqual(v[i], expectedValues[i]) {
+ t.Errorf("element %d: got %+v want %+v", i, *v[i], *expectedValues[i])
+ }
+ }
+ }
+}
+
+// Program implements the similarly-named interface in x/debug.
+// ValueCollector should only call its Value and MapElement methods.
+type Program struct {
+ debug.Program
+}
+
+func (p *Program) Value(v debug.Var) (debug.Value, error) {
+ // We determine what to return using v.TypeID.
+ switch v.TypeID {
+ case int16Type:
+ // We use the address as the value, so that we're testing whether the right
+ // address was calculated.
+ return int16(v.Address), nil
+ case stringType:
+ // A string.
+ return "hello", nil
+ case structType:
+ // A struct with two elements.
+ return debug.Struct{
+ Fields: []debug.StructField{
+ {
+ Name: "x",
+ Var: debug.Var{int16Type, 0x1},
+ },
+ {
+ Name: "y",
+ Var: debug.Var{stringType, 0x2},
+ },
+ },
+ }, nil
+ case pointerType:
+ // A pointer to the first variable above.
+ return debug.Pointer{int16Type, 0x1}, nil
+ case arrayType:
+ // An array of 4 32-bit-wide elements.
+ return debug.Array{
+ ElementTypeID: int32Type,
+ Address: 0x64,
+ Length: 4,
+ StrideBits: 32,
+ }, nil
+ case debugStringType:
+ return debug.String{
+ Length: 5,
+ String: "world",
+ }, nil
+ case mapType:
+ return debug.Map{
+ TypeID: 99,
+ Address: 0x100,
+ Length: 3,
+ }, nil
+ case channelType:
+ return debug.Channel{
+ ElementTypeID: int32Type,
+ Address: 200,
+ Buffer: 210,
+ Length: 2,
+ Capacity: 10,
+ Stride: 4,
+ BufferStart: 9,
+ }, nil
+ case sliceType:
+ // A slice of 2 32-bit-wide elements.
+ return debug.Slice{
+ Array: debug.Array{
+ ElementTypeID: int32Type,
+ Address: 300,
+ Length: 2,
+ StrideBits: 32,
+ },
+ Capacity: 50,
+ }, nil
+ case int32Type:
+ // We use the address as the value, so that we're testing whether the right
+ // address was calculated.
+ return int32(v.Address), nil
+ }
+ return nil, fmt.Errorf("unexpected Value request")
+}
+
+func (p *Program) MapElement(m debug.Map, index uint64) (debug.Var, debug.Var, error) {
+ return debug.Var{TypeID: int16Type, Address: 1000*index + 400},
+ debug.Var{TypeID: int32Type, Address: 1000*index + 404},
+ nil
+}
+
+func TestLogString(t *testing.T) {
+ bp := cd.Breakpoint{
+ Action: "LOG",
+ LogMessageFormat: "$0 hello, $$7world! $1 $2 $3 $4 $5$6 $7 $8",
+ EvaluatedExpressions: []*cd.Variable{
+ &cd.Variable{Name: "a", VarTableIndex: 1},
+ &cd.Variable{Name: "b", VarTableIndex: 2},
+ &cd.Variable{Name: "c", VarTableIndex: 3},
+ &cd.Variable{Name: "d", VarTableIndex: 4},
+ &cd.Variable{Name: "e", VarTableIndex: 5},
+ &cd.Variable{Name: "f", VarTableIndex: 6},
+ &cd.Variable{Name: "g", VarTableIndex: 7},
+ &cd.Variable{Name: "h", VarTableIndex: 8},
+ &cd.Variable{Name: "i", VarTableIndex: 9},
+ },
+ }
+ varTable := []*cd.Variable{
+ &cd.Variable{},
+ &cd.Variable{Value: "1"},
+ &cd.Variable{Value: `"hello"`},
+ &cd.Variable{
+ Members: []*cd.Variable{
+ &cd.Variable{Name: "x", Value: "1"},
+ &cd.Variable{Name: "y", Value: `"hello"`},
+ &cd.Variable{Name: "z", VarTableIndex: 3},
+ },
+ },
+ &cd.Variable{
+ Members: []*cd.Variable{
+ &cd.Variable{VarTableIndex: 1},
+ },
+ Value: "0x1",
+ },
+ &cd.Variable{
+ Members: []*cd.Variable{
+ &cd.Variable{Name: "[0]", VarTableIndex: 10},
+ &cd.Variable{Name: "[1]", VarTableIndex: 11},
+ &cd.Variable{Name: "[2]", VarTableIndex: 12},
+ &cd.Variable{Name: "[3]", VarTableIndex: 13},
+ },
+ Value: "len = 4",
+ },
+ &cd.Variable{Value: `"world"`},
+ &cd.Variable{
+ Members: []*cd.Variable{
+ &cd.Variable{Name: "⚫", VarTableIndex: 14},
+ &cd.Variable{Name: "⚫", VarTableIndex: 15},
+ &cd.Variable{Name: "⚫", VarTableIndex: 16},
+ },
+ Value: "len = 3",
+ },
+ &cd.Variable{
+ Members: []*cd.Variable{
+ &cd.Variable{Name: "[0]", VarTableIndex: 17},
+ &cd.Variable{Name: "[1]", VarTableIndex: 18},
+ },
+ Value: "len = 2",
+ },
+ &cd.Variable{
+ Members: []*cd.Variable{
+ &cd.Variable{Name: "[0]", VarTableIndex: 19},
+ &cd.Variable{Name: "[1]", VarTableIndex: 20},
+ },
+ Value: "len = 2",
+ },
+ &cd.Variable{Value: "100"},
+ &cd.Variable{Value: "104"},
+ &cd.Variable{Value: "108"},
+ &cd.Variable{Value: "112"},
+ &cd.Variable{
+ Members: []*cd.Variable{
+ &cd.Variable{Name: "key", VarTableIndex: 21},
+ &cd.Variable{Name: "value", VarTableIndex: 22},
+ },
+ },
+ &cd.Variable{
+ Members: []*cd.Variable{
+ &cd.Variable{Name: "key", VarTableIndex: 23},
+ &cd.Variable{Name: "value", VarTableIndex: 24},
+ },
+ },
+ &cd.Variable{
+ Members: []*cd.Variable{
+ &cd.Variable{Name: "key", VarTableIndex: 25},
+ &cd.Variable{
+ Name: "value",
+ Status: &cd.StatusMessage{
+ Description: &cd.FormatMessage{
+ Format: "$0",
+ Parameters: []string{"Not captured"},
+ },
+ IsError: true,
+ RefersTo: "VARIABLE_NAME",
+ },
+ },
+ },
+ },
+ &cd.Variable{Value: "246"},
+ &cd.Variable{Value: "210"},
+ &cd.Variable{Value: "300"},
+ &cd.Variable{Value: "304"},
+ &cd.Variable{Value: "400"},
+ &cd.Variable{Value: "404"},
+ &cd.Variable{Value: "1400"},
+ &cd.Variable{Value: "1404"},
+ &cd.Variable{Value: "2400"},
+ }
+ s := LogString(bp.LogMessageFormat, bp.EvaluatedExpressions, varTable)
+ expected := `LOGPOINT: 1 hello, $7world! "hello" {x:1, y:"hello", z:...} ` +
+ `0x1 {100, 104, 108, 112} "world"{400:404, 1400:1404, 2400:(Not captured)} ` +
+ `{246, 210} {300, 304}`
+ if s != expected {
+ t.Errorf("LogString: got %q want %q", s, expected)
+ }
+}
+
+func TestParseToken(t *testing.T) {
+ for _, c := range []struct {
+ s string
+ max int
+ num int
+ n int
+ ok bool
+ }{
+ {"", 0, 0, 0, false},
+ {".", 0, 0, 0, false},
+ {"0", 0, 0, 1, true},
+ {"0", 1, 0, 1, true},
+ {"00", 0, 0, 2, true},
+ {"1.", 1, 1, 1, true},
+ {"1.", 0, 0, 0, false},
+ {"10", 10, 10, 2, true},
+ {"10..", 10, 10, 2, true},
+ {"10", 11, 10, 2, true},
+ {"10..", 11, 10, 2, true},
+ {"10", 9, 0, 0, false},
+ {"10..", 9, 0, 0, false},
+ {" 10", 10, 0, 0, false},
+ {"010", 10, 10, 3, true},
+ {"123456789", 123456789, 123456789, 9, true},
+ {"123456789", 123456788, 0, 0, false},
+ {"123456789123456789123456789", 999999999, 0, 0, false},
+ } {
+ num, n, ok := parseToken(c.s, c.max)
+ if ok != c.ok {
+ t.Errorf("parseToken(%q, %d): got ok=%t want ok=%t", c.s, c.max, ok, c.ok)
+ continue
+ }
+ if !ok {
+ continue
+ }
+ if num != c.num || n != c.n {
+ t.Errorf("parseToken(%q, %d): got %d,%d,%t want %d,%d,%t", c.s, c.max, num, n, ok, c.num, c.n, c.ok)
+ }
+ }
+}