| // Copyright 2014 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package driver |
| |
| import ( |
| "fmt" |
| "math/rand" |
| "strings" |
| "testing" |
| |
| "github.com/google/pprof/internal/plugin" |
| "github.com/google/pprof/internal/proftest" |
| "github.com/google/pprof/internal/report" |
| "github.com/google/pprof/internal/transport" |
| "github.com/google/pprof/profile" |
| ) |
| |
| func TestShell(t *testing.T) { |
| p := &profile.Profile{} |
| generateReportWrapper = checkValue |
| defer func() { generateReportWrapper = generateReport }() |
| |
| // Use test commands and variables to exercise interactive processing |
| var savedCommands commands |
| savedCommands, pprofCommands = pprofCommands, testCommands |
| defer func() { pprofCommands = savedCommands }() |
| |
| savedConfig := currentConfig() |
| defer setCurrentConfig(savedConfig) |
| |
| shortcuts1, scScript1 := makeShortcuts(interleave(script, 2), 1) |
| shortcuts2, scScript2 := makeShortcuts(interleave(script, 1), 2) |
| |
| var testcases = []struct { |
| name string |
| input []string |
| shortcuts shortcuts |
| allowRx string |
| numAllowRxMatches int |
| propagateError bool |
| }{ |
| {"Random interleave of independent scripts 1", interleave(script, 0), pprofShortcuts, "", 0, false}, |
| {"Random interleave of independent scripts 2", interleave(script, 1), pprofShortcuts, "", 0, false}, |
| {"Random interleave of independent scripts with shortcuts 1", scScript1, shortcuts1, "", 0, false}, |
| {"Random interleave of independent scripts with shortcuts 2", scScript2, shortcuts2, "", 0, false}, |
| {"Group with invalid value", []string{"sort=this"}, pprofShortcuts, `invalid "sort" value`, 1, false}, |
| {"No special value provided for the option", []string{"sample_index"}, pprofShortcuts, `please specify a value, e.g. sample_index=<val>`, 1, false}, |
| {"No string value provided for the option", []string{"focus"}, pprofShortcuts, `please specify a value, e.g. focus=<val>`, 1, false}, |
| {"No float value provided for the option", []string{"divide_by"}, pprofShortcuts, `please specify a value, e.g. divide_by=<val>`, 1, false}, |
| {"Helpful input format reminder", []string{"sample_index 0"}, pprofShortcuts, `did you mean: sample_index=0`, 1, false}, |
| {"Verify propagation of IO errors", []string{"**error**"}, pprofShortcuts, "", 0, true}, |
| } |
| |
| o := setDefaults(&plugin.Options{HTTPTransport: transport.New(nil)}) |
| for _, tc := range testcases { |
| t.Run(tc.name, func(t *testing.T) { |
| setCurrentConfig(savedConfig) |
| pprofShortcuts = tc.shortcuts |
| ui := &proftest.TestUI{ |
| T: t, |
| Input: tc.input, |
| AllowRx: tc.allowRx, |
| } |
| o.UI = ui |
| |
| err := interactive(p, o) |
| if (tc.propagateError && err == nil) || (!tc.propagateError && err != nil) { |
| t.Errorf("%s: %v", tc.name, err) |
| } |
| |
| // Confirm error message written out once. |
| if tc.numAllowRxMatches != ui.NumAllowRxMatches { |
| t.Errorf("want error message to be printed %d time(s), got %d", |
| tc.numAllowRxMatches, ui.NumAllowRxMatches) |
| } |
| }) |
| } |
| } |
| |
| var testCommands = commands{ |
| "check": &command{report.Raw, nil, nil, true, "", ""}, |
| } |
| |
| // script contains sequences of commands to be executed for testing. Commands |
| // are split by semicolon and interleaved randomly, so they must be |
| // independent from each other. |
| var script = []string{ |
| "call_tree=true;call_tree=false;check call_tree=false;call_tree=yes;check call_tree=true", |
| "mean=1;check mean=true;mean=n;check mean=false", |
| "nodecount=-1;nodecount=-2;check nodecount=-2;nodecount=999999;check nodecount=999999", |
| "nodefraction=-1;nodefraction=-2.5;check nodefraction=-2.5;nodefraction=0.0001;check nodefraction=0.0001", |
| "focus=one;focus=two;check focus=two", |
| "flat=true;check sort=flat;cum=1;check sort=cum", |
| } |
| |
| func makeShortcuts(input []string, seed int) (shortcuts, []string) { |
| rand.Seed(int64(seed)) |
| |
| s := shortcuts{} |
| var output, chunk []string |
| for _, l := range input { |
| chunk = append(chunk, l) |
| switch rand.Intn(3) { |
| case 0: |
| // Create a macro for commands in 'chunk'. |
| macro := fmt.Sprintf("alias%d", len(s)) |
| s[macro] = chunk |
| output = append(output, macro) |
| chunk = nil |
| case 1: |
| // Append commands in 'chunk' by themselves. |
| output = append(output, chunk...) |
| chunk = nil |
| case 2: |
| // Accumulate commands into 'chunk' |
| } |
| } |
| output = append(output, chunk...) |
| return s, output |
| } |
| |
| func checkValue(p *profile.Profile, cmd []string, cfg config, o *plugin.Options) error { |
| if len(cmd) != 2 { |
| return fmt.Errorf("expected len(cmd)==2, got %v", cmd) |
| } |
| |
| input := cmd[1] |
| args := strings.SplitN(input, "=", 2) |
| if len(args) == 0 { |
| return fmt.Errorf("unexpected empty input") |
| } |
| name, value := args[0], "" |
| if len(args) == 2 { |
| value = args[1] |
| } |
| |
| f, ok := configFieldMap[name] |
| if !ok { |
| return fmt.Errorf("Could not find variable named %s", name) |
| } |
| |
| if got := cfg.get(f); got != value { |
| return fmt.Errorf("Variable %s, want %s, got %s", name, value, got) |
| } |
| return nil |
| } |
| |
| func interleave(input []string, seed int) []string { |
| var inputs [][]string |
| for _, s := range input { |
| inputs = append(inputs, strings.Split(s, ";")) |
| } |
| rand.Seed(int64(seed)) |
| var output []string |
| for len(inputs) > 0 { |
| next := rand.Intn(len(inputs)) |
| output = append(output, inputs[next][0]) |
| if tail := inputs[next][1:]; len(tail) > 0 { |
| inputs[next] = tail |
| } else { |
| inputs = append(inputs[:next], inputs[next+1:]...) |
| } |
| } |
| return output |
| } |
| |
| func TestInteractiveCommands(t *testing.T) { |
| type interactiveTestcase struct { |
| input string |
| want map[string]string |
| } |
| |
| testcases := []interactiveTestcase{ |
| { |
| "top 10 --cum focus1 -ignore focus2", |
| map[string]string{ |
| "granularity": "functions", |
| "nodecount": "10", |
| "sort": "cum", |
| "focus": "focus1|focus2", |
| "ignore": "ignore", |
| }, |
| }, |
| { |
| "top10 --cum focus1 -ignore focus2", |
| map[string]string{ |
| "granularity": "functions", |
| "nodecount": "10", |
| "sort": "cum", |
| "focus": "focus1|focus2", |
| "ignore": "ignore", |
| }, |
| }, |
| { |
| "dot", |
| map[string]string{ |
| "granularity": "functions", |
| "nodecount": "80", |
| "sort": "flat", |
| }, |
| }, |
| { |
| "tags -ignore1 -ignore2 focus1 >out", |
| map[string]string{ |
| "granularity": "functions", |
| "nodecount": "80", |
| "sort": "flat", |
| "output": "out", |
| "tagfocus": "focus1", |
| "tagignore": "ignore1|ignore2", |
| }, |
| }, |
| { |
| "weblist find -test", |
| map[string]string{ |
| "granularity": "addresses", |
| "noinlines": "true", |
| "nodecount": "0", |
| "sort": "flat", |
| "ignore": "test", |
| }, |
| }, |
| { |
| "callgrind fun -ignore >out", |
| map[string]string{ |
| "granularity": "addresses", |
| "nodecount": "0", |
| "sort": "flat", |
| "output": "out", |
| }, |
| }, |
| { |
| "999", |
| nil, // Error |
| }, |
| } |
| |
| for _, tc := range testcases { |
| cmd, cfg, err := parseCommandLine(strings.Fields(tc.input)) |
| if tc.want == nil && err != nil { |
| // Error expected |
| continue |
| } |
| if err != nil { |
| t.Errorf("failed on %q: %v", tc.input, err) |
| continue |
| } |
| |
| // Get report output format |
| c := pprofCommands[cmd[0]] |
| if c == nil { |
| t.Fatalf("unexpected nil command") |
| } |
| cfg = applyCommandOverrides(cmd[0], c.format, cfg) |
| |
| for n, want := range tc.want { |
| if got := cfg.get(configFieldMap[n]); got != want { |
| t.Errorf("failed on %q, cmd=%q, %s got %s, want %s", tc.input, cmd, n, got, want) |
| } |
| } |
| } |
| } |