Home | History | Annotate | Download | only in driver
      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