1// Copyright 2016 Google LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package breakpoints 16 17import ( 18 "testing" 19 20 "cloud.google.com/go/cmd/go-cloud-debug-agent/internal/debug" 21 "cloud.google.com/go/internal/testutil" 22 cd "google.golang.org/api/clouddebugger/v2" 23) 24 25var ( 26 testPC1 uint64 = 0x1234 27 testPC2 uint64 = 0x5678 28 testPC3 uint64 = 0x3333 29 testFile = "foo.go" 30 testLine uint64 = 42 31 testLine2 uint64 = 99 32 testLogPC uint64 = 0x9abc 33 testLogLine uint64 = 43 34 testBadPC uint64 = 0xdef0 35 testBadLine uint64 = 44 36 testBP = &cd.Breakpoint{ 37 Action: "CAPTURE", 38 Id: "TestBreakpoint", 39 IsFinalState: false, 40 Location: &cd.SourceLocation{Path: testFile, Line: int64(testLine)}, 41 } 42 testBP2 = &cd.Breakpoint{ 43 Action: "CAPTURE", 44 Id: "TestBreakpoint2", 45 IsFinalState: false, 46 Location: &cd.SourceLocation{Path: testFile, Line: int64(testLine2)}, 47 } 48 testLogBP = &cd.Breakpoint{ 49 Action: "LOG", 50 Id: "TestLogBreakpoint", 51 IsFinalState: false, 52 Location: &cd.SourceLocation{Path: testFile, Line: int64(testLogLine)}, 53 } 54 testBadBP = &cd.Breakpoint{ 55 Action: "BEEP", 56 Id: "TestBadBreakpoint", 57 IsFinalState: false, 58 Location: &cd.SourceLocation{Path: testFile, Line: int64(testBadLine)}, 59 } 60) 61 62func TestBreakpointStore(t *testing.T) { 63 p := &Program{breakpointPCs: make(map[uint64]bool)} 64 bs := NewBreakpointStore(p) 65 checkPCs := func(expected map[uint64]bool) { 66 if !testutil.Equal(p.breakpointPCs, expected) { 67 t.Errorf("got breakpoint map %v want %v", p.breakpointPCs, expected) 68 } 69 } 70 bs.ProcessBreakpointList([]*cd.Breakpoint{testBP, testBP2, testLogBP, testBadBP}) 71 checkPCs(map[uint64]bool{ 72 testPC1: true, 73 testPC2: true, 74 testPC3: true, 75 testLogPC: true, 76 }) 77 for _, test := range []struct { 78 pc uint64 79 expected []*cd.Breakpoint 80 }{ 81 {testPC1, []*cd.Breakpoint{testBP}}, 82 {testPC2, []*cd.Breakpoint{testBP}}, 83 {testPC3, []*cd.Breakpoint{testBP2}}, 84 {testLogPC, []*cd.Breakpoint{testLogBP}}, 85 } { 86 if bps := bs.BreakpointsAtPC(test.pc); !testutil.Equal(bps, test.expected) { 87 t.Errorf("BreakpointsAtPC(%x): got %v want %v", test.pc, bps, test.expected) 88 } 89 } 90 testBP2.IsFinalState = true 91 bs.ProcessBreakpointList([]*cd.Breakpoint{testBP, testBP2, testLogBP, testBadBP}) 92 checkPCs(map[uint64]bool{ 93 testPC1: true, 94 testPC2: true, 95 testPC3: false, 96 testLogPC: true, 97 }) 98 bs.RemoveBreakpoint(testBP) 99 checkPCs(map[uint64]bool{ 100 testPC1: false, 101 testPC2: false, 102 testPC3: false, 103 testLogPC: true, 104 }) 105 for _, pc := range []uint64{testPC1, testPC2, testPC3} { 106 if bps := bs.BreakpointsAtPC(pc); len(bps) != 0 { 107 t.Errorf("BreakpointsAtPC(%x): got %v want []", pc, bps) 108 } 109 } 110 // bs.ErrorBreakpoints should return testBadBP. 111 errorBps := bs.ErrorBreakpoints() 112 if len(errorBps) != 1 { 113 t.Errorf("ErrorBreakpoints: got %d want 1", len(errorBps)) 114 } else { 115 bp := errorBps[0] 116 if bp.Id != testBadBP.Id { 117 t.Errorf("ErrorBreakpoints: got id %q want 1", bp.Id) 118 } 119 if bp.Status == nil || !bp.Status.IsError { 120 t.Errorf("ErrorBreakpoints: got %v, want error", bp.Status) 121 } 122 } 123 // The error should have been removed by the last call to bs.ErrorBreakpoints. 124 errorBps = bs.ErrorBreakpoints() 125 if len(errorBps) != 0 { 126 t.Errorf("ErrorBreakpoints: got %d want 0", len(errorBps)) 127 } 128 // Even if testBadBP is sent in a new list, it should not be returned again. 129 bs.ProcessBreakpointList([]*cd.Breakpoint{testBadBP}) 130 errorBps = bs.ErrorBreakpoints() 131 if len(errorBps) != 0 { 132 t.Errorf("ErrorBreakpoints: got %d want 0", len(errorBps)) 133 } 134} 135 136// Program implements the similarly-named interface in x/debug. 137// ValueCollector should only call its BreakpointAtLine and DeleteBreakpoints methods. 138type Program struct { 139 debug.Program 140 // breakpointPCs contains the state of code breakpoints -- true if the 141 // breakpoint is currently set, false if it has been deleted. 142 breakpointPCs map[uint64]bool 143} 144 145func (p *Program) BreakpointAtLine(file string, line uint64) ([]uint64, error) { 146 var pcs []uint64 147 switch { 148 case file == testFile && line == testLine: 149 pcs = []uint64{testPC1, testPC2} 150 case file == testFile && line == testLine2: 151 pcs = []uint64{testPC3} 152 case file == testFile && line == testLogLine: 153 pcs = []uint64{testLogPC} 154 default: 155 pcs = []uint64{0xbad} 156 } 157 for _, pc := range pcs { 158 p.breakpointPCs[pc] = true 159 } 160 return pcs, nil 161} 162 163func (p *Program) DeleteBreakpoints(pcs []uint64) error { 164 for _, pc := range pcs { 165 p.breakpointPCs[pc] = false 166 } 167 return nil 168} 169