| // Copyright 2016 Google LLC |
| // |
| // 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 ( |
| "testing" |
| |
| "cloud.google.com/go/cmd/go-cloud-debug-agent/internal/debug" |
| "cloud.google.com/go/internal/testutil" |
| 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 !testutil.Equal(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); !testutil.Equal(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 |
| } |