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