Bez popisu

interactive_test.go 8.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  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/internal/plugin"
  21. "github.com/google/pprof/internal/proftest"
  22. "github.com/google/pprof/internal/report"
  23. "github.com/google/pprof/internal/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. savedVariables := pprofVariables
  35. defer func() { pprofVariables = savedVariables }()
  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{"cumulative=this"}, pprofShortcuts, `unrecognized value for cumulative: "this". Use one of cum, flat`, 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. pprofVariables = testVariables(savedVariables)
  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. func testVariables(base variables) variables {
  84. v := base.makeCopy()
  85. v["b"] = &variable{boolKind, "f", "", ""}
  86. v["bb"] = &variable{boolKind, "f", "", ""}
  87. v["i"] = &variable{intKind, "0", "", ""}
  88. v["ii"] = &variable{intKind, "0", "", ""}
  89. v["f"] = &variable{floatKind, "0", "", ""}
  90. v["ff"] = &variable{floatKind, "0", "", ""}
  91. v["s"] = &variable{stringKind, "", "", ""}
  92. v["ss"] = &variable{stringKind, "", "", ""}
  93. v["ta"] = &variable{boolKind, "f", "radio", ""}
  94. v["tb"] = &variable{boolKind, "f", "radio", ""}
  95. v["tc"] = &variable{boolKind, "t", "radio", ""}
  96. return v
  97. }
  98. // script contains sequences of commands to be executed for testing. Commands
  99. // are split by semicolon and interleaved randomly, so they must be
  100. // independent from each other.
  101. var script = []string{
  102. "bb=true;bb=false;check bb=false;bb=yes;check bb=true",
  103. "b=1;check b=true;b=n;check b=false",
  104. "i=-1;i=-2;check i=-2;i=999999;check i=999999",
  105. "check ii=0;ii=-1;check ii=-1;ii=100;check ii=100",
  106. "f=-1;f=-2.5;check f=-2.5;f=0.0001;check f=0.0001",
  107. "check ff=0;ff=-1.01;check ff=-1.01;ff=100;check ff=100",
  108. "s=one;s=two;check s=two",
  109. "ss=tree;check ss=tree;ss=forest;check ss=forest",
  110. "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",
  111. }
  112. func makeShortcuts(input []string, seed int) (shortcuts, []string) {
  113. rand.Seed(int64(seed))
  114. s := shortcuts{}
  115. var output, chunk []string
  116. for _, l := range input {
  117. chunk = append(chunk, l)
  118. switch rand.Intn(3) {
  119. case 0:
  120. // Create a macro for commands in 'chunk'.
  121. macro := fmt.Sprintf("alias%d", len(s))
  122. s[macro] = chunk
  123. output = append(output, macro)
  124. chunk = nil
  125. case 1:
  126. // Append commands in 'chunk' by themselves.
  127. output = append(output, chunk...)
  128. chunk = nil
  129. case 2:
  130. // Accumulate commands into 'chunk'
  131. }
  132. }
  133. output = append(output, chunk...)
  134. return s, output
  135. }
  136. func checkValue(p *profile.Profile, cmd []string, vars variables, o *plugin.Options) error {
  137. if len(cmd) != 2 {
  138. return fmt.Errorf("expected len(cmd)==2, got %v", cmd)
  139. }
  140. input := cmd[1]
  141. args := strings.SplitN(input, "=", 2)
  142. if len(args) == 0 {
  143. return fmt.Errorf("unexpected empty input")
  144. }
  145. name, value := args[0], ""
  146. if len(args) == 2 {
  147. value = args[1]
  148. }
  149. gotv := vars[name]
  150. if gotv == nil {
  151. return fmt.Errorf("Could not find variable named %s", name)
  152. }
  153. if got := gotv.stringValue(); got != value {
  154. return fmt.Errorf("Variable %s, want %s, got %s", name, value, got)
  155. }
  156. return nil
  157. }
  158. func interleave(input []string, seed int) []string {
  159. var inputs [][]string
  160. for _, s := range input {
  161. inputs = append(inputs, strings.Split(s, ";"))
  162. }
  163. rand.Seed(int64(seed))
  164. var output []string
  165. for len(inputs) > 0 {
  166. next := rand.Intn(len(inputs))
  167. output = append(output, inputs[next][0])
  168. if tail := inputs[next][1:]; len(tail) > 0 {
  169. inputs[next] = tail
  170. } else {
  171. inputs = append(inputs[:next], inputs[next+1:]...)
  172. }
  173. }
  174. return output
  175. }
  176. func TestInteractiveCommands(t *testing.T) {
  177. type interactiveTestcase struct {
  178. input string
  179. want map[string]string
  180. }
  181. testcases := []interactiveTestcase{
  182. {
  183. "top 10 --cum focus1 -ignore focus2",
  184. map[string]string{
  185. "functions": "true",
  186. "nodecount": "10",
  187. "cum": "true",
  188. "focus": "focus1|focus2",
  189. "ignore": "ignore",
  190. },
  191. },
  192. {
  193. "top10 --cum focus1 -ignore focus2",
  194. map[string]string{
  195. "functions": "true",
  196. "nodecount": "10",
  197. "cum": "true",
  198. "focus": "focus1|focus2",
  199. "ignore": "ignore",
  200. },
  201. },
  202. {
  203. "dot",
  204. map[string]string{
  205. "functions": "true",
  206. "nodecount": "80",
  207. "cum": "false",
  208. },
  209. },
  210. {
  211. "tags -ignore1 -ignore2 focus1 >out",
  212. map[string]string{
  213. "functions": "true",
  214. "nodecount": "80",
  215. "cum": "false",
  216. "output": "out",
  217. "tagfocus": "focus1",
  218. "tagignore": "ignore1|ignore2",
  219. },
  220. },
  221. {
  222. "weblist find -test",
  223. map[string]string{
  224. "functions": "false",
  225. "addresses": "true",
  226. "noinlines": "true",
  227. "nodecount": "0",
  228. "cum": "false",
  229. "flat": "true",
  230. "ignore": "test",
  231. },
  232. },
  233. {
  234. "callgrind fun -ignore >out",
  235. map[string]string{
  236. "functions": "false",
  237. "addresses": "true",
  238. "nodecount": "0",
  239. "cum": "false",
  240. "flat": "true",
  241. "output": "out",
  242. },
  243. },
  244. {
  245. "999",
  246. nil, // Error
  247. },
  248. }
  249. for _, tc := range testcases {
  250. cmd, vars, err := parseCommandLine(strings.Fields(tc.input))
  251. if tc.want == nil && err != nil {
  252. // Error expected
  253. continue
  254. }
  255. if err != nil {
  256. t.Errorf("failed on %q: %v", tc.input, err)
  257. continue
  258. }
  259. // Get report output format
  260. c := pprofCommands[cmd[0]]
  261. if c == nil {
  262. t.Fatalf("unexpected nil command")
  263. }
  264. vars = applyCommandOverrides(cmd[0], c.format, vars)
  265. for n, want := range tc.want {
  266. if got := vars[n].stringValue(); got != want {
  267. t.Errorf("failed on %q, cmd=%q, %s got %s, want %s", tc.input, cmd, n, got, want)
  268. }
  269. }
  270. }
  271. }