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 15 package driver 16 17 import ( 18 "fmt" 19 "io" 20 "math/rand" 21 "strings" 22 "testing" 23 24 "github.com/google/pprof/internal/plugin" 25 "github.com/google/pprof/internal/report" 26 "github.com/google/pprof/profile" 27 ) 28 29 func TestShell(t *testing.T) { 30 p := &profile.Profile{} 31 generateReportWrapper = checkValue 32 defer func() { generateReportWrapper = generateReport }() 33 34 // Use test commands and variables to exercise interactive processing 35 var savedCommands commands 36 savedCommands, pprofCommands = pprofCommands, testCommands 37 defer func() { pprofCommands = savedCommands }() 38 39 savedVariables := pprofVariables 40 defer func() { pprofVariables = savedVariables }() 41 42 // Random interleave of independent scripts 43 pprofVariables = testVariables(savedVariables) 44 o := setDefaults(nil) 45 o.UI = newUI(t, interleave(script, 0)) 46 if err := interactive(p, o); err != nil { 47 t.Error("first attempt:", err) 48 } 49 // Random interleave of independent scripts 50 pprofVariables = testVariables(savedVariables) 51 o.UI = newUI(t, interleave(script, 1)) 52 if err := interactive(p, o); err != nil { 53 t.Error("second attempt:", err) 54 } 55 56 // Random interleave of independent scripts with shortcuts 57 pprofVariables = testVariables(savedVariables) 58 var scScript []string 59 pprofShortcuts, scScript = makeShortcuts(interleave(script, 2), 1) 60 o.UI = newUI(t, scScript) 61 if err := interactive(p, o); err != nil { 62 t.Error("first shortcut attempt:", err) 63 } 64 65 // Random interleave of independent scripts with shortcuts 66 pprofVariables = testVariables(savedVariables) 67 pprofShortcuts, scScript = makeShortcuts(interleave(script, 1), 2) 68 o.UI = newUI(t, scScript) 69 if err := interactive(p, o); err != nil { 70 t.Error("second shortcut attempt:", err) 71 } 72 73 // Verify propagation of IO errors 74 pprofVariables = testVariables(savedVariables) 75 o.UI = newUI(t, []string{"**error**"}) 76 if err := interactive(p, o); err == nil { 77 t.Error("expected IO error, got nil") 78 } 79 80 } 81 82 var testCommands = commands{ 83 "check": &command{report.Raw, nil, nil, true, "", ""}, 84 } 85 86 func testVariables(base variables) variables { 87 v := base.makeCopy() 88 89 v["b"] = &variable{boolKind, "f", "", ""} 90 v["bb"] = &variable{boolKind, "f", "", ""} 91 v["i"] = &variable{intKind, "0", "", ""} 92 v["ii"] = &variable{intKind, "0", "", ""} 93 v["f"] = &variable{floatKind, "0", "", ""} 94 v["ff"] = &variable{floatKind, "0", "", ""} 95 v["s"] = &variable{stringKind, "", "", ""} 96 v["ss"] = &variable{stringKind, "", "", ""} 97 98 v["ta"] = &variable{boolKind, "f", "radio", ""} 99 v["tb"] = &variable{boolKind, "f", "radio", ""} 100 v["tc"] = &variable{boolKind, "t", "radio", ""} 101 102 return v 103 } 104 105 // script contains sequences of commands to be executed for testing. Commands 106 // are split by semicolon and interleaved randomly, so they must be 107 // independent from each other. 108 var script = []string{ 109 "bb=true;bb=false;check bb=false;bb=yes;check bb=true", 110 "b=1;check b=true;b=n;check b=false", 111 "i=-1;i=-2;check i=-2;i=999999;check i=999999", 112 "check ii=0;ii=-1;check ii=-1;ii=100;check ii=100", 113 "f=-1;f=-2.5;check f=-2.5;f=0.0001;check f=0.0001", 114 "check ff=0;ff=-1.01;check ff=-1.01;ff=100;check ff=100", 115 "s=one;s=two;check s=two", 116 "ss=tree;check ss=tree;ss=;check ss;ss=forest;check ss=forest", 117 "ta=true;check ta=true;check tb=false;check tc=false;tb=1;check tb=true;check ta=false;check tc=false;tc=yes;check tb=false;check ta=false;check tc=true", 118 } 119 120 func makeShortcuts(input []string, seed int) (shortcuts, []string) { 121 rand.Seed(int64(seed)) 122 123 s := shortcuts{} 124 var output, chunk []string 125 for _, l := range input { 126 chunk = append(chunk, l) 127 switch rand.Intn(3) { 128 case 0: 129 // Create a macro for commands in 'chunk'. 130 macro := fmt.Sprintf("alias%d", len(s)) 131 s[macro] = chunk 132 output = append(output, macro) 133 chunk = nil 134 case 1: 135 // Append commands in 'chunk' by themselves. 136 output = append(output, chunk...) 137 chunk = nil 138 case 2: 139 // Accumulate commands into 'chunk' 140 } 141 } 142 output = append(output, chunk...) 143 return s, output 144 } 145 146 func newUI(t *testing.T, input []string) plugin.UI { 147 return &testUI{ 148 t: t, 149 input: input, 150 } 151 } 152 153 type testUI struct { 154 t *testing.T 155 input []string 156 index int 157 } 158 159 func (ui *testUI) ReadLine(_ string) (string, error) { 160 if ui.index >= len(ui.input) { 161 return "", io.EOF 162 } 163 input := ui.input[ui.index] 164 if input == "**error**" { 165 return "", fmt.Errorf("Error: %s", input) 166 } 167 ui.index++ 168 return input, nil 169 } 170 171 func (ui *testUI) Print(args ...interface{}) { 172 } 173 174 func (ui *testUI) PrintErr(args ...interface{}) { 175 output := fmt.Sprint(args) 176 if output != "" { 177 ui.t.Error(output) 178 } 179 } 180 181 func (ui *testUI) IsTerminal() bool { 182 return false 183 } 184 185 func (ui *testUI) SetAutoComplete(func(string) string) { 186 } 187 188 func checkValue(p *profile.Profile, cmd []string, vars variables, o *plugin.Options) error { 189 if len(cmd) != 2 { 190 return fmt.Errorf("expected len(cmd)==2, got %v", cmd) 191 } 192 193 input := cmd[1] 194 args := strings.SplitN(input, "=", 2) 195 if len(args) == 0 { 196 return fmt.Errorf("unexpected empty input") 197 } 198 name, value := args[0], "" 199 if len(args) == 2 { 200 value = args[1] 201 } 202 203 gotv := vars[name] 204 if gotv == nil { 205 return fmt.Errorf("Could not find variable named %s", name) 206 } 207 208 if got := gotv.stringValue(); got != value { 209 return fmt.Errorf("Variable %s, want %s, got %s", name, value, got) 210 } 211 return nil 212 } 213 214 func interleave(input []string, seed int) []string { 215 var inputs [][]string 216 for _, s := range input { 217 inputs = append(inputs, strings.Split(s, ";")) 218 } 219 rand.Seed(int64(seed)) 220 var output []string 221 for len(inputs) > 0 { 222 next := rand.Intn(len(inputs)) 223 output = append(output, inputs[next][0]) 224 if tail := inputs[next][1:]; len(tail) > 0 { 225 inputs[next] = tail 226 } else { 227 inputs = append(inputs[:next], inputs[next+1:]...) 228 } 229 } 230 return output 231 } 232 233 func TestInteractiveCommands(t *testing.T) { 234 type interactiveTestcase struct { 235 input string 236 want map[string]string 237 } 238 239 testcases := []interactiveTestcase{ 240 { 241 "top 10 --cum focus1 -ignore focus2", 242 map[string]string{ 243 "functions": "true", 244 "nodecount": "10", 245 "cum": "true", 246 "focus": "focus1|focus2", 247 "ignore": "ignore", 248 }, 249 }, 250 { 251 "top10 --cum focus1 -ignore focus2", 252 map[string]string{ 253 "functions": "true", 254 "nodecount": "10", 255 "cum": "true", 256 "focus": "focus1|focus2", 257 "ignore": "ignore", 258 }, 259 }, 260 { 261 "dot", 262 map[string]string{ 263 "functions": "true", 264 "nodecount": "80", 265 "cum": "false", 266 }, 267 }, 268 { 269 "tags -ignore1 -ignore2 focus1 >out", 270 map[string]string{ 271 "functions": "true", 272 "nodecount": "80", 273 "cum": "false", 274 "output": "out", 275 "tagfocus": "focus1", 276 "tagignore": "ignore1|ignore2", 277 }, 278 }, 279 { 280 "weblist find -test", 281 map[string]string{ 282 "functions": "false", 283 "addressnoinlines": "true", 284 "nodecount": "0", 285 "cum": "false", 286 "flat": "true", 287 "ignore": "test", 288 }, 289 }, 290 { 291 "callgrind fun -ignore >out", 292 map[string]string{ 293 "functions": "false", 294 "addresses": "true", 295 "nodecount": "0", 296 "cum": "false", 297 "flat": "true", 298 "output": "out", 299 }, 300 }, 301 { 302 "999", 303 nil, // Error 304 }, 305 } 306 307 for _, tc := range testcases { 308 cmd, vars, err := parseCommandLine(strings.Fields(tc.input)) 309 if tc.want == nil && err != nil { 310 // Error expected 311 continue 312 } 313 if err != nil { 314 t.Errorf("failed on %q: %v", tc.input, err) 315 continue 316 } 317 vars = applyCommandOverrides(cmd, vars) 318 319 for n, want := range tc.want { 320 if got := vars[n].stringValue(); got != want { 321 t.Errorf("failed on %q, cmd=%q, %s got %s, want %s", tc.input, cmd, n, got, want) 322 } 323 } 324 } 325 } 326