Keine Beschreibung

interactive_test.go 7.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  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. package driver
  15. import (
  16. "fmt"
  17. "math/rand"
  18. "strings"
  19. "testing"
  20. "github.com/google/pprof/pkg/plugin"
  21. "github.com/google/pprof/pkg/proftest"
  22. "github.com/google/pprof/pkg/report"
  23. "github.com/google/pprof/pkg/transport"
  24. "github.com/google/pprof/profile"
  25. )
  26. func TestShell(t *testing.T) {
  27. p := &profile.Profile{}
  28. generateReportWrapper = checkValue
  29. defer func() { generateReportWrapper = generateReport }()
  30. // Use test commands and variables to exercise interactive processing
  31. var savedCommands commands
  32. savedCommands, pprofCommands = pprofCommands, testCommands
  33. defer func() { pprofCommands = savedCommands }()
  34. savedConfig := currentConfig()
  35. defer setCurrentConfig(savedConfig)
  36. shortcuts1, scScript1 := makeShortcuts(interleave(script, 2), 1)
  37. shortcuts2, scScript2 := makeShortcuts(interleave(script, 1), 2)
  38. var testcases = []struct {
  39. name string
  40. input []string
  41. shortcuts shortcuts
  42. allowRx string
  43. numAllowRxMatches int
  44. propagateError bool
  45. }{
  46. {"Random interleave of independent scripts 1", interleave(script, 0), pprofShortcuts, "", 0, false},
  47. {"Random interleave of independent scripts 2", interleave(script, 1), pprofShortcuts, "", 0, false},
  48. {"Random interleave of independent scripts with shortcuts 1", scScript1, shortcuts1, "", 0, false},
  49. {"Random interleave of independent scripts with shortcuts 2", scScript2, shortcuts2, "", 0, false},
  50. {"Group with invalid value", []string{"sort=this"}, pprofShortcuts, `invalid "sort" value`, 1, false},
  51. {"No special value provided for the option", []string{"sample_index"}, pprofShortcuts, `please specify a value, e.g. sample_index=<val>`, 1, false},
  52. {"No string value provided for the option", []string{"focus"}, pprofShortcuts, `please specify a value, e.g. focus=<val>`, 1, false},
  53. {"No float value provided for the option", []string{"divide_by"}, pprofShortcuts, `please specify a value, e.g. divide_by=<val>`, 1, false},
  54. {"Helpful input format reminder", []string{"sample_index 0"}, pprofShortcuts, `did you mean: sample_index=0`, 1, false},
  55. {"Verify propagation of IO errors", []string{"**error**"}, pprofShortcuts, "", 0, true},
  56. }
  57. o := setDefaults(&plugin.Options{HTTPTransport: transport.New(nil)})
  58. for _, tc := range testcases {
  59. t.Run(tc.name, func(t *testing.T) {
  60. setCurrentConfig(savedConfig)
  61. pprofShortcuts = tc.shortcuts
  62. ui := &proftest.TestUI{
  63. T: t,
  64. Input: tc.input,
  65. AllowRx: tc.allowRx,
  66. }
  67. o.UI = ui
  68. err := interactive(p, o)
  69. if (tc.propagateError && err == nil) || (!tc.propagateError && err != nil) {
  70. t.Errorf("%s: %v", tc.name, err)
  71. }
  72. // Confirm error message written out once.
  73. if tc.numAllowRxMatches != ui.NumAllowRxMatches {
  74. t.Errorf("want error message to be printed %d time(s), got %d",
  75. tc.numAllowRxMatches, ui.NumAllowRxMatches)
  76. }
  77. })
  78. }
  79. }
  80. var testCommands = commands{
  81. "check": &command{report.Raw, nil, nil, true, "", ""},
  82. }
  83. // script contains sequences of commands to be executed for testing. Commands
  84. // are split by semicolon and interleaved randomly, so they must be
  85. // independent from each other.
  86. var script = []string{
  87. "call_tree=true;call_tree=false;check call_tree=false;call_tree=yes;check call_tree=true",
  88. "mean=1;check mean=true;mean=n;check mean=false",
  89. "nodecount=-1;nodecount=-2;check nodecount=-2;nodecount=999999;check nodecount=999999",
  90. "nodefraction=-1;nodefraction=-2.5;check nodefraction=-2.5;nodefraction=0.0001;check nodefraction=0.0001",
  91. "focus=one;focus=two;check focus=two",
  92. "flat=true;check sort=flat;cum=1;check sort=cum",
  93. }
  94. func makeShortcuts(input []string, seed int) (shortcuts, []string) {
  95. rand.Seed(int64(seed))
  96. s := shortcuts{}
  97. var output, chunk []string
  98. for _, l := range input {
  99. chunk = append(chunk, l)
  100. switch rand.Intn(3) {
  101. case 0:
  102. // Create a macro for commands in 'chunk'.
  103. macro := fmt.Sprintf("alias%d", len(s))
  104. s[macro] = chunk
  105. output = append(output, macro)
  106. chunk = nil
  107. case 1:
  108. // Append commands in 'chunk' by themselves.
  109. output = append(output, chunk...)
  110. chunk = nil
  111. case 2:
  112. // Accumulate commands into 'chunk'
  113. }
  114. }
  115. output = append(output, chunk...)
  116. return s, output
  117. }
  118. func checkValue(p *profile.Profile, cmd []string, cfg config, o *plugin.Options) error {
  119. if len(cmd) != 2 {
  120. return fmt.Errorf("expected len(cmd)==2, got %v", cmd)
  121. }
  122. input := cmd[1]
  123. args := strings.SplitN(input, "=", 2)
  124. if len(args) == 0 {
  125. return fmt.Errorf("unexpected empty input")
  126. }
  127. name, value := args[0], ""
  128. if len(args) == 2 {
  129. value = args[1]
  130. }
  131. f, ok := configFieldMap[name]
  132. if !ok {
  133. return fmt.Errorf("Could not find variable named %s", name)
  134. }
  135. if got := cfg.get(f); got != value {
  136. return fmt.Errorf("Variable %s, want %s, got %s", name, value, got)
  137. }
  138. return nil
  139. }
  140. func interleave(input []string, seed int) []string {
  141. var inputs [][]string
  142. for _, s := range input {
  143. inputs = append(inputs, strings.Split(s, ";"))
  144. }
  145. rand.Seed(int64(seed))
  146. var output []string
  147. for len(inputs) > 0 {
  148. next := rand.Intn(len(inputs))
  149. output = append(output, inputs[next][0])
  150. if tail := inputs[next][1:]; len(tail) > 0 {
  151. inputs[next] = tail
  152. } else {
  153. inputs = append(inputs[:next], inputs[next+1:]...)
  154. }
  155. }
  156. return output
  157. }
  158. func TestInteractiveCommands(t *testing.T) {
  159. type interactiveTestcase struct {
  160. input string
  161. want map[string]string
  162. }
  163. testcases := []interactiveTestcase{
  164. {
  165. "top 10 --cum focus1 -ignore focus2",
  166. map[string]string{
  167. "granularity": "functions",
  168. "nodecount": "10",
  169. "sort": "cum",
  170. "focus": "focus1|focus2",
  171. "ignore": "ignore",
  172. },
  173. },
  174. {
  175. "top10 --cum focus1 -ignore focus2",
  176. map[string]string{
  177. "granularity": "functions",
  178. "nodecount": "10",
  179. "sort": "cum",
  180. "focus": "focus1|focus2",
  181. "ignore": "ignore",
  182. },
  183. },
  184. {
  185. "dot",
  186. map[string]string{
  187. "granularity": "functions",
  188. "nodecount": "80",
  189. "sort": "flat",
  190. },
  191. },
  192. {
  193. "tags -ignore1 -ignore2 focus1 >out",
  194. map[string]string{
  195. "granularity": "functions",
  196. "nodecount": "80",
  197. "sort": "flat",
  198. "output": "out",
  199. "tagfocus": "focus1",
  200. "tagignore": "ignore1|ignore2",
  201. },
  202. },
  203. {
  204. "weblist find -test",
  205. map[string]string{
  206. "granularity": "addresses",
  207. "noinlines": "true",
  208. "nodecount": "0",
  209. "sort": "flat",
  210. "ignore": "test",
  211. },
  212. },
  213. {
  214. "callgrind fun -ignore >out",
  215. map[string]string{
  216. "granularity": "addresses",
  217. "nodecount": "0",
  218. "sort": "flat",
  219. "output": "out",
  220. },
  221. },
  222. {
  223. "999",
  224. nil, // Error
  225. },
  226. }
  227. for _, tc := range testcases {
  228. cmd, cfg, err := parseCommandLine(strings.Fields(tc.input))
  229. if tc.want == nil && err != nil {
  230. // Error expected
  231. continue
  232. }
  233. if err != nil {
  234. t.Errorf("failed on %q: %v", tc.input, err)
  235. continue
  236. }
  237. // Get report output format
  238. c := pprofCommands[cmd[0]]
  239. if c == nil {
  240. t.Fatalf("unexpected nil command")
  241. }
  242. cfg = applyCommandOverrides(cmd[0], c.format, cfg)
  243. for n, want := range tc.want {
  244. if got := cfg.get(configFieldMap[n]); got != want {
  245. t.Errorf("failed on %q, cmd=%q, %s got %s, want %s", tc.input, cmd, n, got, want)
  246. }
  247. }
  248. }
  249. }