1// Copyright 2014 Google Inc. All Rights Reserved. 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 driver 16 17import ( 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 30func TestShell(t *testing.T) { 31 p := &profile.Profile{} 32 generateReportWrapper = checkValue 33 defer func() { generateReportWrapper = generateReport }() 34 35 // Use test commands and variables to exercise interactive processing 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 // Confirm error message written out once. 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 92var testCommands = commands{ 93 "check": &command{report.Raw, nil, nil, true, "", ""}, 94} 95 96// script contains sequences of commands to be executed for testing. Commands 97// are split by semicolon and interleaved randomly, so they must be 98// independent from each other. 99var 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 108func makeShortcuts(input []string, seed int) (shortcuts, []string) { 109 rand.Seed(int64(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 // Create a macro for commands in 'chunk'. 118 macro := fmt.Sprintf("alias%d", len(s)) 119 s[macro] = chunk 120 output = append(output, macro) 121 chunk = nil 122 case 1: 123 // Append commands in 'chunk' by themselves. 124 output = append(output, chunk...) 125 chunk = nil 126 case 2: 127 // Accumulate commands into 'chunk' 128 } 129 } 130 output = append(output, chunk...) 131 return s, output 132} 133 134func 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 160func interleave(input []string, seed int) []string { 161 var inputs [][]string 162 for _, s := range input { 163 inputs = append(inputs, strings.Split(s, ";")) 164 } 165 rand.Seed(int64(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 179func 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, // Error 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 // Error expected 254 continue 255 } 256 if err != nil { 257 t.Errorf("failed on %q: %v", tc.input, err) 258 continue 259 } 260 261 // Get report output format 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