diff options
Diffstat (limited to 'vendor/cloud.google.com/go/cmd/go-cloud-debug-agent')
7 files changed, 0 insertions, 2215 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 deleted file mode 100644 index 6a8702c77..000000000 --- a/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/debuglet.go +++ /dev/null @@ -1,450 +0,0 @@ -// 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 deleted file mode 100644 index afe07cbfb..000000000 --- a/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/breakpoints/breakpoints.go +++ /dev/null @@ -1,174 +0,0 @@ -// 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 deleted file mode 100644 index 089a3ba61..000000000 --- a/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/breakpoints/breakpoints_test.go +++ /dev/null @@ -1,168 +0,0 @@ -// 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 deleted file mode 100644 index 87d283546..000000000 --- a/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/controller/client.go +++ /dev/null @@ -1,291 +0,0 @@ -// 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 deleted file mode 100644 index fa0634706..000000000 --- a/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/controller/client_test.go +++ /dev/null @@ -1,254 +0,0 @@ -// 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 deleted file mode 100644 index 8dadc2f63..000000000 --- a/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/valuecollector/valuecollector.go +++ /dev/null @@ -1,460 +0,0 @@ -// 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 deleted file mode 100644 index 2bc97dcfa..000000000 --- a/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/valuecollector/valuecollector_test.go +++ /dev/null @@ -1,418 +0,0 @@ -// 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) - } - } -} |
