debug_test.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package tsweb
  4. import (
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "net/http/httptest"
  9. "runtime"
  10. "strings"
  11. "testing"
  12. )
  13. func TestDebugger(t *testing.T) {
  14. mux := http.NewServeMux()
  15. dbg1 := Debugger(mux)
  16. if dbg1 == nil {
  17. t.Fatal("didn't get a debugger from mux")
  18. }
  19. dbg2 := Debugger(mux)
  20. if dbg2 != dbg1 {
  21. t.Fatal("Debugger returned different debuggers for the same mux")
  22. }
  23. t.Run("cpu_pprof", func(t *testing.T) {
  24. if testing.Short() {
  25. t.Skip("skipping second long test")
  26. }
  27. switch runtime.GOOS {
  28. case "linux", "darwin":
  29. default:
  30. t.Skipf("skipping test on %v", runtime.GOOS)
  31. }
  32. req := httptest.NewRequest("GET", "/debug/pprof/profile?seconds=1", nil)
  33. req.RemoteAddr = "100.101.102.103:1234"
  34. rec := httptest.NewRecorder()
  35. mux.ServeHTTP(rec, req)
  36. res := rec.Result()
  37. if res.StatusCode != 200 {
  38. t.Errorf("unexpected %v", res.Status)
  39. }
  40. })
  41. }
  42. func get(m http.Handler, path, srcIP string) (int, string) {
  43. req := httptest.NewRequest("GET", path, nil)
  44. req.RemoteAddr = srcIP + ":1234"
  45. rec := httptest.NewRecorder()
  46. m.ServeHTTP(rec, req)
  47. return rec.Result().StatusCode, rec.Body.String()
  48. }
  49. const (
  50. tsIP = "100.100.100.100"
  51. pubIP = "8.8.8.8"
  52. )
  53. func TestDebuggerKV(t *testing.T) {
  54. mux := http.NewServeMux()
  55. dbg := Debugger(mux)
  56. dbg.KV("Donuts", 42)
  57. dbg.KV("Secret code", "hunter2")
  58. val := "red"
  59. dbg.KVFunc("Condition", func() any { return val })
  60. code, _ := get(mux, "/debug/", pubIP)
  61. if code != 403 {
  62. t.Fatalf("debug access wasn't denied, got %v", code)
  63. }
  64. code, body := get(mux, "/debug/", tsIP)
  65. if code != 200 {
  66. t.Fatalf("debug access failed, got %v", code)
  67. }
  68. for _, want := range []string{"Donuts", "42", "Secret code", "hunter2", "Condition", "red"} {
  69. if !strings.Contains(body, want) {
  70. t.Errorf("want %q in output, not found", want)
  71. }
  72. }
  73. val = "green"
  74. code, body = get(mux, "/debug/", tsIP)
  75. if code != 200 {
  76. t.Fatalf("debug access failed, got %v", code)
  77. }
  78. for _, want := range []string{"Condition", "green"} {
  79. if !strings.Contains(body, want) {
  80. t.Errorf("want %q in output, not found", want)
  81. }
  82. }
  83. }
  84. func TestDebuggerURL(t *testing.T) {
  85. mux := http.NewServeMux()
  86. dbg := Debugger(mux)
  87. dbg.URL("https://www.tailscale.com", "Homepage")
  88. code, body := get(mux, "/debug/", tsIP)
  89. if code != 200 {
  90. t.Fatalf("debug access failed, got %v", code)
  91. }
  92. for _, want := range []string{"https://www.tailscale.com", "Homepage"} {
  93. if !strings.Contains(body, want) {
  94. t.Errorf("want %q in output, not found", want)
  95. }
  96. }
  97. }
  98. func TestDebuggerSection(t *testing.T) {
  99. mux := http.NewServeMux()
  100. dbg := Debugger(mux)
  101. dbg.Section(func(w io.Writer, r *http.Request) {
  102. fmt.Fprintf(w, "Test output %v", r.RemoteAddr)
  103. })
  104. code, body := get(mux, "/debug/", tsIP)
  105. if code != 200 {
  106. t.Fatalf("debug access failed, got %v", code)
  107. }
  108. want := `Test output 100.100.100.100:1234`
  109. if !strings.Contains(body, want) {
  110. t.Errorf("want %q in output, not found", want)
  111. }
  112. }
  113. func TestDebuggerHandle(t *testing.T) {
  114. mux := http.NewServeMux()
  115. dbg := Debugger(mux)
  116. dbg.Handle("check", "Consistency check", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  117. fmt.Fprintf(w, "Test output %v", r.RemoteAddr)
  118. }))
  119. code, body := get(mux, "/debug/", tsIP)
  120. if code != 200 {
  121. t.Fatalf("debug access failed, got %v", code)
  122. }
  123. for _, want := range []string{"/debug/check", "Consistency check"} {
  124. if !strings.Contains(body, want) {
  125. t.Errorf("want %q in output, not found", want)
  126. }
  127. }
  128. code, _ = get(mux, "/debug/check", pubIP)
  129. if code != 403 {
  130. t.Fatal("/debug/check should be protected, but isn't")
  131. }
  132. code, body = get(mux, "/debug/check", tsIP)
  133. if code != 200 {
  134. t.Fatal("/debug/check denied debug access")
  135. }
  136. want := "Test output " + tsIP
  137. if !strings.Contains(body, want) {
  138. t.Errorf("want %q in output, not found", want)
  139. }
  140. }
  141. func ExampleDebugHandler_Handle() {
  142. mux := http.NewServeMux()
  143. dbg := Debugger(mux)
  144. // Registers /debug/flushcache with the given handler, and adds a
  145. // link to /debug/ with the description "Flush caches".
  146. dbg.Handle("flushcache", "Flush caches", http.HandlerFunc(http.NotFound))
  147. }
  148. func ExampleDebugHandler_KV() {
  149. mux := http.NewServeMux()
  150. dbg := Debugger(mux)
  151. // Adds two list items to /debug/, showing that the condition is
  152. // red and there are 42 donuts.
  153. dbg.KV("Condition", "red")
  154. dbg.KV("Donuts", 42)
  155. }
  156. func ExampleDebugHandler_KVFunc() {
  157. mux := http.NewServeMux()
  158. dbg := Debugger(mux)
  159. // Adds an count of page renders to /debug/. Note this example
  160. // isn't concurrency-safe.
  161. views := 0
  162. dbg.KVFunc("Debug pageviews", func() any {
  163. views = views + 1
  164. return views
  165. })
  166. dbg.KV("Donuts", 42)
  167. }
  168. func ExampleDebugHandler_URL() {
  169. mux := http.NewServeMux()
  170. dbg := Debugger(mux)
  171. // Links to the Tailscale website from /debug/.
  172. dbg.URL("https://www.tailscale.com", "Homepage")
  173. }
  174. func ExampleDebugHandler_Section() {
  175. mux := http.NewServeMux()
  176. dbg := Debugger(mux)
  177. // Adds a section to /debug/ that dumps the HTTP request of the
  178. // visitor.
  179. dbg.Section(func(w io.Writer, r *http.Request) {
  180. io.WriteString(w, "<h3>Dump of your HTTP request</h3>")
  181. fmt.Fprintf(w, "<code>%#v</code>", r)
  182. })
  183. }