| // 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 }() |
| |
| savedVariables := pprofVariables |
| defer func() { pprofVariables = savedVariables }() |
| |
| // Random interleave of independent scripts |
| pprofVariables = testVariables(savedVariables) |
| |
| // pass in HTTPTransport when setting defaults, because otherwise default |
| // transport will try to add flags to the default flag set. |
| o := setDefaults(&plugin.Options{HTTPTransport: transport.New(nil)}) |
| o.UI = newUI(t, interleave(script, 0)) |
| if err := interactive(p, o); err != nil { |
| t.Error("first attempt:", err) |
| } |
| // Random interleave of independent scripts |
| pprofVariables = testVariables(savedVariables) |
| o.UI = newUI(t, interleave(script, 1)) |
| if err := interactive(p, o); err != nil { |
| t.Error("second attempt:", err) |
| } |
| |
| // Random interleave of independent scripts with shortcuts |
| pprofVariables = testVariables(savedVariables) |
| var scScript []string |
| pprofShortcuts, scScript = makeShortcuts(interleave(script, 2), 1) |
| o.UI = newUI(t, scScript) |
| if err := interactive(p, o); err != nil { |
| t.Error("first shortcut attempt:", err) |
| } |
| |
| // Random interleave of independent scripts with shortcuts |
| pprofVariables = testVariables(savedVariables) |
| pprofShortcuts, scScript = makeShortcuts(interleave(script, 1), 2) |
| o.UI = newUI(t, scScript) |
| if err := interactive(p, o); err != nil { |
| t.Error("second shortcut attempt:", err) |
| } |
| |
| // Group with invalid value |
| pprofVariables = testVariables(savedVariables) |
| ui := &proftest.TestUI{ |
| T: t, |
| Input: []string{"cumulative=this"}, |
| AllowRx: `Unrecognized value for cumulative: "this". Use one of cum, flat`, |
| } |
| o.UI = ui |
| if err := interactive(p, o); err != nil { |
| t.Error("invalid group value:", err) |
| } |
| // Confirm error message written out once. |
| if ui.NumAllowRxMatches != 1 { |
| t.Errorf("want error message to be printed 1 time, got %v", ui.NumAllowRxMatches) |
| } |
| // Verify propagation of IO errors |
| pprofVariables = testVariables(savedVariables) |
| o.UI = newUI(t, []string{"**error**"}) |
| if err := interactive(p, o); err == nil { |
| t.Error("expected IO error, got nil") |
| } |
| |
| } |
| |
| var testCommands = commands{ |
| "check": &command{report.Raw, nil, nil, true, "", ""}, |
| } |
| |
| func testVariables(base variables) variables { |
| v := base.makeCopy() |
| |
| v["b"] = &variable{boolKind, "f", "", ""} |
| v["bb"] = &variable{boolKind, "f", "", ""} |
| v["i"] = &variable{intKind, "0", "", ""} |
| v["ii"] = &variable{intKind, "0", "", ""} |
| v["f"] = &variable{floatKind, "0", "", ""} |
| v["ff"] = &variable{floatKind, "0", "", ""} |
| v["s"] = &variable{stringKind, "", "", ""} |
| v["ss"] = &variable{stringKind, "", "", ""} |
| |
| v["ta"] = &variable{boolKind, "f", "radio", ""} |
| v["tb"] = &variable{boolKind, "f", "radio", ""} |
| v["tc"] = &variable{boolKind, "t", "radio", ""} |
| |
| return v |
| } |
| |
| // 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{ |
| "bb=true;bb=false;check bb=false;bb=yes;check bb=true", |
| "b=1;check b=true;b=n;check b=false", |
| "i=-1;i=-2;check i=-2;i=999999;check i=999999", |
| "check ii=0;ii=-1;check ii=-1;ii=100;check ii=100", |
| "f=-1;f=-2.5;check f=-2.5;f=0.0001;check f=0.0001", |
| "check ff=0;ff=-1.01;check ff=-1.01;ff=100;check ff=100", |
| "s=one;s=two;check s=two", |
| "ss=tree;check ss=tree;ss=;check ss;ss=forest;check ss=forest", |
| "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", |
| } |
| |
| 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 newUI(t *testing.T, input []string) plugin.UI { |
| return &proftest.TestUI{ |
| T: t, |
| Input: input, |
| } |
| } |
| |
| func checkValue(p *profile.Profile, cmd []string, vars variables, 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] |
| } |
| |
| gotv := vars[name] |
| if gotv == nil { |
| return fmt.Errorf("Could not find variable named %s", name) |
| } |
| |
| if got := gotv.stringValue(); 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{ |
| "functions": "true", |
| "nodecount": "10", |
| "cum": "true", |
| "focus": "focus1|focus2", |
| "ignore": "ignore", |
| }, |
| }, |
| { |
| "top10 --cum focus1 -ignore focus2", |
| map[string]string{ |
| "functions": "true", |
| "nodecount": "10", |
| "cum": "true", |
| "focus": "focus1|focus2", |
| "ignore": "ignore", |
| }, |
| }, |
| { |
| "dot", |
| map[string]string{ |
| "functions": "true", |
| "nodecount": "80", |
| "cum": "false", |
| }, |
| }, |
| { |
| "tags -ignore1 -ignore2 focus1 >out", |
| map[string]string{ |
| "functions": "true", |
| "nodecount": "80", |
| "cum": "false", |
| "output": "out", |
| "tagfocus": "focus1", |
| "tagignore": "ignore1|ignore2", |
| }, |
| }, |
| { |
| "weblist find -test", |
| map[string]string{ |
| "functions": "false", |
| "addresses": "true", |
| "noinlines": "true", |
| "nodecount": "0", |
| "cum": "false", |
| "flat": "true", |
| "ignore": "test", |
| }, |
| }, |
| { |
| "callgrind fun -ignore >out", |
| map[string]string{ |
| "functions": "false", |
| "addresses": "true", |
| "nodecount": "0", |
| "cum": "false", |
| "flat": "true", |
| "output": "out", |
| }, |
| }, |
| { |
| "999", |
| nil, // Error |
| }, |
| } |
| |
| for _, tc := range testcases { |
| cmd, vars, 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.Errorf("unexpected nil command") |
| } |
| vars = applyCommandOverrides(cmd[0], c.format, vars) |
| |
| for n, want := range tc.want { |
| if got := vars[n].stringValue(); got != want { |
| t.Errorf("failed on %q, cmd=%q, %s got %s, want %s", tc.input, cmd, n, got, want) |
| } |
| } |
| } |
| } |