1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package driver
16
17 import (
18 "fmt"
19 "math/rand"
20 "strings"
21 "testing"
22
23 "github.com/google/pprof/internal/plugin"
24 "github.com/google/pprof/internal/proftest"
25 "github.com/google/pprof/internal/report"
26 "github.com/google/pprof/internal/transport"
27 "github.com/google/pprof/profile"
28 )
29
30 func TestShell(t *testing.T) {
31 p := &profile.Profile{}
32 generateReportWrapper = checkValue
33 defer func() { generateReportWrapper = generateReport }()
34
35
36 var savedCommands commands
37 savedCommands, pprofCommands = pprofCommands, testCommands
38 defer func() { pprofCommands = savedCommands }()
39
40 savedConfig := currentConfig()
41 defer setCurrentConfig(savedConfig)
42
43 shortcuts1, scScript1 := makeShortcuts(interleave(script, 2), 1)
44 shortcuts2, scScript2 := makeShortcuts(interleave(script, 1), 2)
45
46 var testcases = []struct {
47 name string
48 input []string
49 shortcuts shortcuts
50 allowRx string
51 numAllowRxMatches int
52 propagateError bool
53 }{
54 {"Random interleave of independent scripts 1", interleave(script, 0), pprofShortcuts, "", 0, false},
55 {"Random interleave of independent scripts 2", interleave(script, 1), pprofShortcuts, "", 0, false},
56 {"Random interleave of independent scripts with shortcuts 1", scScript1, shortcuts1, "", 0, false},
57 {"Random interleave of independent scripts with shortcuts 2", scScript2, shortcuts2, "", 0, false},
58 {"Group with invalid value", []string{"sort=this"}, pprofShortcuts, `invalid "sort" value`, 1, false},
59 {"No special value provided for the option", []string{"sample_index"}, pprofShortcuts, `please specify a value, e.g. sample_index=<val>`, 1, false},
60 {"No string value provided for the option", []string{"focus"}, pprofShortcuts, `please specify a value, e.g. focus=<val>`, 1, false},
61 {"No float value provided for the option", []string{"divide_by"}, pprofShortcuts, `please specify a value, e.g. divide_by=<val>`, 1, false},
62 {"Helpful input format reminder", []string{"sample_index 0"}, pprofShortcuts, `did you mean: sample_index=0`, 1, false},
63 {"Verify propagation of IO errors", []string{"**error**"}, pprofShortcuts, "", 0, true},
64 }
65
66 o := setDefaults(&plugin.Options{HTTPTransport: transport.New(nil)})
67 for _, tc := range testcases {
68 t.Run(tc.name, func(t *testing.T) {
69 setCurrentConfig(savedConfig)
70 pprofShortcuts = tc.shortcuts
71 ui := &proftest.TestUI{
72 T: t,
73 Input: tc.input,
74 AllowRx: tc.allowRx,
75 }
76 o.UI = ui
77
78 err := interactive(p, o)
79 if (tc.propagateError && err == nil) || (!tc.propagateError && err != nil) {
80 t.Errorf("%s: %v", tc.name, err)
81 }
82
83
84 if tc.numAllowRxMatches != ui.NumAllowRxMatches {
85 t.Errorf("want error message to be printed %d time(s), got %d",
86 tc.numAllowRxMatches, ui.NumAllowRxMatches)
87 }
88 })
89 }
90 }
91
92 var testCommands = commands{
93 "check": &command{report.Raw, nil, nil, true, "", ""},
94 }
95
96
97
98
99 var script = []string{
100 "call_tree=true;call_tree=false;check call_tree=false;call_tree=yes;check call_tree=true",
101 "mean=1;check mean=true;mean=n;check mean=false",
102 "nodecount=-1;nodecount=-2;check nodecount=-2;nodecount=999999;check nodecount=999999",
103 "nodefraction=-1;nodefraction=-2.5;check nodefraction=-2.5;nodefraction=0.0001;check nodefraction=0.0001",
104 "focus=one;focus=two;check focus=two",
105 "flat=true;check sort=flat;cum=1;check sort=cum",
106 }
107
108 func makeShortcuts(input []string, seed int64) (shortcuts, []string) {
109 rand := rand.New(rand.NewSource(seed))
110
111 s := shortcuts{}
112 var output, chunk []string
113 for _, l := range input {
114 chunk = append(chunk, l)
115 switch rand.Intn(3) {
116 case 0:
117
118 macro := fmt.Sprintf("alias%d", len(s))
119 s[macro] = chunk
120 output = append(output, macro)
121 chunk = nil
122 case 1:
123
124 output = append(output, chunk...)
125 chunk = nil
126 case 2:
127
128 }
129 }
130 output = append(output, chunk...)
131 return s, output
132 }
133
134 func checkValue(p *profile.Profile, cmd []string, cfg config, o *plugin.Options) error {
135 if len(cmd) != 2 {
136 return fmt.Errorf("expected len(cmd)==2, got %v", cmd)
137 }
138
139 input := cmd[1]
140 args := strings.SplitN(input, "=", 2)
141 if len(args) == 0 {
142 return fmt.Errorf("unexpected empty input")
143 }
144 name, value := args[0], ""
145 if len(args) == 2 {
146 value = args[1]
147 }
148
149 f, ok := configFieldMap[name]
150 if !ok {
151 return fmt.Errorf("Could not find variable named %s", name)
152 }
153
154 if got := cfg.get(f); got != value {
155 return fmt.Errorf("Variable %s, want %s, got %s", name, value, got)
156 }
157 return nil
158 }
159
160 func interleave(input []string, seed int64) []string {
161 var inputs [][]string
162 for _, s := range input {
163 inputs = append(inputs, strings.Split(s, ";"))
164 }
165 rand := rand.New(rand.NewSource(seed))
166 var output []string
167 for len(inputs) > 0 {
168 next := rand.Intn(len(inputs))
169 output = append(output, inputs[next][0])
170 if tail := inputs[next][1:]; len(tail) > 0 {
171 inputs[next] = tail
172 } else {
173 inputs = append(inputs[:next], inputs[next+1:]...)
174 }
175 }
176 return output
177 }
178
179 func TestInteractiveCommands(t *testing.T) {
180 type interactiveTestcase struct {
181 input string
182 want map[string]string
183 }
184
185 testcases := []interactiveTestcase{
186 {
187 "top 10 --cum focus1 -ignore focus2",
188 map[string]string{
189 "granularity": "functions",
190 "nodecount": "10",
191 "sort": "cum",
192 "focus": "focus1|focus2",
193 "ignore": "ignore",
194 },
195 },
196 {
197 "top10 --cum focus1 -ignore focus2",
198 map[string]string{
199 "granularity": "functions",
200 "nodecount": "10",
201 "sort": "cum",
202 "focus": "focus1|focus2",
203 "ignore": "ignore",
204 },
205 },
206 {
207 "dot",
208 map[string]string{
209 "granularity": "functions",
210 "nodecount": "80",
211 "sort": "flat",
212 },
213 },
214 {
215 "tags -ignore1 -ignore2 focus1 >out",
216 map[string]string{
217 "granularity": "functions",
218 "nodecount": "80",
219 "sort": "flat",
220 "output": "out",
221 "tagfocus": "focus1",
222 "tagignore": "ignore1|ignore2",
223 },
224 },
225 {
226 "weblist find -test",
227 map[string]string{
228 "granularity": "addresses",
229 "noinlines": "false",
230 "nodecount": "0",
231 "sort": "flat",
232 "ignore": "test",
233 },
234 },
235 {
236 "callgrind fun -ignore >out",
237 map[string]string{
238 "granularity": "addresses",
239 "nodecount": "0",
240 "sort": "flat",
241 "output": "out",
242 },
243 },
244 {
245 "999",
246 nil,
247 },
248 }
249
250 for _, tc := range testcases {
251 cmd, cfg, err := parseCommandLine(strings.Fields(tc.input))
252 if tc.want == nil && err != nil {
253
254 continue
255 }
256 if err != nil {
257 t.Errorf("failed on %q: %v", tc.input, err)
258 continue
259 }
260
261
262 c := pprofCommands[cmd[0]]
263 if c == nil {
264 t.Fatalf("unexpected nil command")
265 }
266 cfg = applyCommandOverrides(cmd[0], c.format, cfg)
267
268 for n, want := range tc.want {
269 if got := cfg.get(configFieldMap[n]); got != want {
270 t.Errorf("failed on %q, cmd=%q, %s got %s, want %s", tc.input, cmd, n, got, want)
271 }
272 }
273 }
274 }
275
View as plain text