Browse Source

Clarify error messages in interactive for groups (#294)

Margaret Nolan 7 years ago
parent
commit
532a375ba6
3 changed files with 54 additions and 40 deletions
  1. 23
    0
      internal/driver/interactive.go
  2. 19
    39
      internal/driver/interactive_test.go
  3. 12
    1
      internal/proftest/proftest.go

+ 23
- 0
internal/driver/interactive.go View File

42
 	interactiveMode = true
42
 	interactiveMode = true
43
 	shortcuts := profileShortcuts(p)
43
 	shortcuts := profileShortcuts(p)
44
 
44
 
45
+	// Get all groups in pprofVariables to allow for clearer error messages.
46
+	groups := groupOptions(pprofVariables)
47
+
45
 	greetings(p, o.UI)
48
 	greetings(p, o.UI)
46
 	for {
49
 	for {
47
 		input, err := o.UI.ReadLine("(pprof) ")
50
 		input, err := o.UI.ReadLine("(pprof) ")
87
 						o.UI.PrintErr(err)
90
 						o.UI.PrintErr(err)
88
 					}
91
 					}
89
 					continue
92
 					continue
93
+				} else if okValues := groups[name]; okValues != nil {
94
+					o.UI.PrintErr(fmt.Errorf("Unrecognized value for %s: %q. Use one of %s", name, value, strings.Join(okValues, ", ")))
95
+					continue
90
 				}
96
 				}
91
 			}
97
 			}
92
 
98
 
118
 	}
124
 	}
119
 }
125
 }
120
 
126
 
127
+// groupOptions returns a map containing all non-empty groups
128
+// mapped to an array of the option names in that group in
129
+// sorted order.
130
+func groupOptions(vars variables) map[string][]string {
131
+	groups := make(map[string][]string)
132
+	for name, option := range vars {
133
+		group := option.group
134
+		if group != "" {
135
+			groups[group] = append(groups[group], name)
136
+		}
137
+	}
138
+	for _, names := range groups {
139
+		sort.Strings(names)
140
+	}
141
+	return groups
142
+}
143
+
121
 var generateReportWrapper = generateReport // For testing purposes.
144
 var generateReportWrapper = generateReport // For testing purposes.
122
 
145
 
123
 // greetings prints a brief welcome and some overall profile
146
 // greetings prints a brief welcome and some overall profile

+ 19
- 39
internal/driver/interactive_test.go View File

16
 
16
 
17
 import (
17
 import (
18
 	"fmt"
18
 	"fmt"
19
-	"io"
20
 	"math/rand"
19
 	"math/rand"
21
 	"strings"
20
 	"strings"
22
 	"testing"
21
 	"testing"
23
 
22
 
24
 	"github.com/google/pprof/internal/plugin"
23
 	"github.com/google/pprof/internal/plugin"
24
+	"github.com/google/pprof/internal/proftest"
25
 	"github.com/google/pprof/internal/report"
25
 	"github.com/google/pprof/internal/report"
26
 	"github.com/google/pprof/profile"
26
 	"github.com/google/pprof/profile"
27
 )
27
 )
70
 		t.Error("second shortcut attempt:", err)
70
 		t.Error("second shortcut attempt:", err)
71
 	}
71
 	}
72
 
72
 
73
+	// Group with invalid value
74
+	pprofVariables = testVariables(savedVariables)
75
+	ui := &proftest.TestUI{
76
+		T:       t,
77
+		Input:   []string{"cumulative=this"},
78
+		AllowRx: `Unrecognized value for cumulative: "this". Use one of cum, flat`,
79
+	}
80
+	o.UI = ui
81
+	if err := interactive(p, o); err != nil {
82
+		t.Error("invalid group value:", err)
83
+	}
84
+	// Confirm error message written out once.
85
+	if ui.NumAllowRxMatches != 1 {
86
+		t.Errorf("want error message to be printed 1 time, got %v", ui.NumAllowRxMatches)
87
+	}
73
 	// Verify propagation of IO errors
88
 	// Verify propagation of IO errors
74
 	pprofVariables = testVariables(savedVariables)
89
 	pprofVariables = testVariables(savedVariables)
75
 	o.UI = newUI(t, []string{"**error**"})
90
 	o.UI = newUI(t, []string{"**error**"})
144
 }
159
 }
145
 
160
 
146
 func newUI(t *testing.T, input []string) plugin.UI {
161
 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
+	return &proftest.TestUI{
163
+		T:     t,
164
+		Input: input,
162
 	}
165
 	}
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
 }
166
 }
187
 
167
 
188
 func checkValue(p *profile.Profile, cmd []string, vars variables, o *plugin.Options) error {
168
 func checkValue(p *profile.Profile, cmd []string, vars variables, o *plugin.Options) error {

+ 12
- 1
internal/proftest/proftest.go View File

19
 import (
19
 import (
20
 	"encoding/json"
20
 	"encoding/json"
21
 	"fmt"
21
 	"fmt"
22
+	"io"
22
 	"io/ioutil"
23
 	"io/ioutil"
23
 	"os"
24
 	"os"
24
 	"os/exec"
25
 	"os/exec"
80
 	Ignore            int
81
 	Ignore            int
81
 	AllowRx           string
82
 	AllowRx           string
82
 	NumAllowRxMatches int
83
 	NumAllowRxMatches int
84
+	Input             []string
85
+	index             int
83
 }
86
 }
84
 
87
 
85
 // ReadLine returns no input, as no input is expected during testing.
88
 // ReadLine returns no input, as no input is expected during testing.
86
 func (ui *TestUI) ReadLine(_ string) (string, error) {
89
 func (ui *TestUI) ReadLine(_ string) (string, error) {
87
-	return "", fmt.Errorf("no input")
90
+	if ui.index >= len(ui.Input) {
91
+		return "", io.EOF
92
+	}
93
+	input := ui.Input[ui.index]
94
+	ui.index++
95
+	if input == "**error**" {
96
+		return "", fmt.Errorf("Error: %s", input)
97
+	}
98
+	return input, nil
88
 }
99
 }
89
 
100
 
90
 // Print messages are discarded by the test UI.
101
 // Print messages are discarded by the test UI.