-
Notifications
You must be signed in to change notification settings - Fork 640
Provide more helpful error message and require input for every non-bool option. #519
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
a9ea908
2a191ba
a2e56b4
f3c38ed
8563fb1
99ff77d
b6459a5
7483e19
a0929fd
2434176
68e3bcf
fee78be
71ef91f
c882260
54872a3
a7a638d
cebe0e4
cbe760c
091f3d9
05e747c
cfc1286
fc94335
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -157,7 +157,7 @@ var pprofVariables = variables{ | |
|
|
||
| // Filtering options | ||
| "nodecount": &variable{intKind, "-1", "", helpText( | ||
| "Max number of nodes to show", | ||
| "<n> is the max number of nodes to show", | ||
| "Uses heuristics to limit the number of locations to be displayed.", | ||
| "On graphs, dotted edges represent paths through nodes that have been removed.")}, | ||
| "nodefraction": &variable{floatKind, "0.005", "", "Hide nodes below <f>*total"}, | ||
|
|
@@ -208,14 +208,14 @@ var pprofVariables = variables{ | |
| "Discard tags that match this regexp")}, | ||
| // Heap profile options | ||
| "divide_by": &variable{floatKind, "1", "", helpText( | ||
| "Ratio to divide all samples before visualization", | ||
| "<f> is the ratio to divide all samples before visualization", | ||
| "Divide all samples values by a constant, eg the number of processors or jobs.")}, | ||
| "mean": &variable{boolKind, "f", "", helpText( | ||
| "Average sample value over first value (count)", | ||
| "For memory profiles, report average memory per allocation.", | ||
| "For time-based profiles, report average time per event.")}, | ||
| "sample_index": &variable{stringKind, "", "", helpText( | ||
| "Sample value to report (0-based index or name)", | ||
| "Sample value to report (<x> is 0-based index or name)", | ||
| "Profiles contain multiple values per sample.", | ||
| "Use sample_index=i to select the ith value (starting at 0).")}, | ||
| "normalize": &variable{boolKind, "f", "", helpText( | ||
|
|
@@ -246,6 +246,22 @@ func helpText(s ...string) string { | |
| return strings.Join(s, "\n") + "\n" | ||
| } | ||
|
|
||
| func addVariablesPostFix(name string, vr *variable) string { | ||
nolanmar511 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| var postfix string | ||
| switch vr.kind { | ||
| case intKind: | ||
| postfix = "=<n>" | ||
| case floatKind: | ||
| postfix = "=<f>" | ||
| case stringKind: | ||
| postfix = "=<s>" | ||
| } | ||
| if name == "sample_index" { | ||
| postfix = "=<x>" | ||
| } | ||
| return postfix | ||
| } | ||
|
|
||
| // usage returns a string describing the pprof commands and variables. | ||
| // if commandLine is set, the output reflect cli usage. | ||
| func usage(commandLine bool) string { | ||
|
|
@@ -254,7 +270,7 @@ func usage(commandLine bool) string { | |
| prefix = "-" | ||
| } | ||
| fmtHelp := func(c, d string) string { | ||
| return fmt.Sprintf(" %-16s %s", c, strings.SplitN(d, "\n", 2)[0]) | ||
| return fmt.Sprintf(" %-22s %s", c, strings.SplitN(d, "\n", 2)[0]) | ||
|
||
| } | ||
|
|
||
| var commands []string | ||
|
|
@@ -273,7 +289,11 @@ func usage(commandLine bool) string { | |
| } | ||
|
|
||
| help = help + strings.Join(commands, "\n") + "\n\n" + | ||
| " Options:\n" | ||
| " Options:\n" + | ||
| " General format is <option>=<value>\n" + | ||
| " <f> is a float, <n> is an integer, and <s> is a string\n" + | ||
| " If the option is a boolean value, then you can simply type <option>\n" + | ||
| " Type \"help <option>\" for detailed usage\n\n" | ||
wyk9787 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Print help for variables after sorting them. | ||
| // Collect radio variables by their group name to print them together. | ||
|
|
@@ -284,7 +304,9 @@ func usage(commandLine bool) string { | |
| radioOptions[vr.group] = append(radioOptions[vr.group], name) | ||
| continue | ||
| } | ||
| variables = append(variables, fmtHelp(prefix+name, vr.help)) | ||
| option := prefix + name + addVariablesPostFix(name, vr) | ||
|
|
||
| variables = append(variables, fmtHelp(option, vr.help)) | ||
| } | ||
| sort.Strings(variables) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -70,6 +70,12 @@ func interactive(p *profile.Profile, o *plugin.Options) error { | |
| value = strings.TrimSpace(value) | ||
| } | ||
| if v := pprofVariables[name]; v != nil { | ||
| // All non-bool options require inputs | ||
| if v.kind != boolKind && value == "" { | ||
| example := name + addVariablesPostFix(name, v) | ||
| o.UI.PrintErr(fmt.Errorf("please input a value, e.g. %v", example)) | ||
| continue | ||
| } | ||
| if name == "sample_index" { | ||
| // Error check sample_index=xxx to ensure xxx is a valid sample type. | ||
| index, err := p.SampleIndexByName(value) | ||
|
|
@@ -267,6 +273,9 @@ func parseCommandLine(input []string) ([]string, variables, error) { | |
| } | ||
| } | ||
| if c == nil { | ||
| if v := pprofVariables[name]; v != nil { | ||
| return nil, nil, fmt.Errorf("do you mean: \"%v=%v\"?", name, args[0]) | ||
|
||
| } | ||
| return nil, nil, fmt.Errorf("unrecognized command: %q", name) | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,62 +40,53 @@ func TestShell(t *testing.T) { | |
| savedVariables := pprofVariables | ||
| defer func() { pprofVariables = savedVariables }() | ||
|
|
||
| // Random interleave of independent scripts | ||
| pprofVariables = testVariables(savedVariables) | ||
| shortcuts1, scScript1 := makeShortcuts(interleave(script, 2), 1) | ||
| shortcuts2, scScript2 := makeShortcuts(interleave(script, 1), 2) | ||
|
|
||
| // 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) | ||
| var testcases = []struct { | ||
| name string | ||
| input []string | ||
| shortcuts shortcuts | ||
| allowRx string | ||
| numAllowRxMatches int | ||
| propagateError bool | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This flag seems to be false in all test cases, is this expected?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now fixed. |
||
| }{ | ||
| {"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 vale", []string{"cumulative=this"}, pprofShortcuts, `unrecognized value for cumulative: "this". Use one of cum, flat`, 1, false}, | ||
|
||
| {"No special value provided for the option", []string{"sample_index"}, pprofShortcuts, `please input a value, e.g. sample_index=<x>`, 1, false}, | ||
| {"No string value provided for the option", []string{"focus="}, pprofShortcuts, `please input a value, e.g. focus=<s>`, 1, false}, | ||
| {"No float value provided for the option", []string{"divide_by"}, pprofShortcuts, `please input a value, e.g. divide_by=<f>`, 1, false}, | ||
| {"Helpful input format reminder", []string{"sample_index 0"}, pprofShortcuts, `do you mean: "sample_index=0`, 1, false}, | ||
| {"Verify propagation of IO errors", []string{"**error**"}, pprofShortcuts, "", 0, false}, | ||
| } | ||
|
|
||
nolanmar511 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // 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) | ||
| } | ||
| o := setDefaults(&plugin.Options{HTTPTransport: transport.New(nil)}) | ||
| for _, tc := range testcases { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
| pprofVariables = testVariables(savedVariables) | ||
| pprofShortcuts = tc.shortcuts | ||
| ui := &proftest.TestUI{ | ||
| T: t, | ||
| Input: tc.input, | ||
| AllowRx: tc.allowRx, | ||
| } | ||
| o.UI = ui | ||
|
|
||
| // 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) | ||
| } | ||
| err := interactive(p, o) | ||
| if (tc.propagateError && err == nil) || (tc.propagateError && err != nil) { | ||
| t.Errorf("%v: %v", tc.name, 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") | ||
| // Confirm error message written out once. | ||
| if tc.numAllowRxMatches != ui.NumAllowRxMatches { | ||
| t.Errorf("want error message to be printed %v time(s), got %v", | ||
|
||
| tc.numAllowRxMatches, ui.NumAllowRxMatches) | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| } | ||
|
|
||
| var testCommands = commands{ | ||
|
|
@@ -132,7 +123,7 @@ var script = []string{ | |
| "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", | ||
| "ss=tree;check ss=tree;ss=forest;check ss=forest", | ||
nolanmar511 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "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", | ||
| } | ||
|
|
||
|
|
@@ -162,13 +153,6 @@ func makeShortcuts(input []string, seed int) (shortcuts, []string) { | |
| 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) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.