punchy_test.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. package nebula
  2. import (
  3. "context"
  4. "log/slog"
  5. "testing"
  6. "time"
  7. "github.com/slackhq/nebula/config"
  8. "github.com/slackhq/nebula/test"
  9. "github.com/stretchr/testify/assert"
  10. "github.com/stretchr/testify/require"
  11. )
  12. func TestNewPunchyFromConfig(t *testing.T) {
  13. l := test.NewLogger()
  14. c := config.NewC(l)
  15. // Test defaults
  16. p := NewPunchyFromConfig(test.NewLogger(), c)
  17. assert.False(t, p.GetPunch())
  18. assert.False(t, p.GetRespond())
  19. assert.Equal(t, time.Second, p.GetDelay())
  20. assert.Equal(t, 5*time.Second, p.GetRespondDelay())
  21. // punchy deprecation
  22. c.Settings["punchy"] = true
  23. p = NewPunchyFromConfig(test.NewLogger(), c)
  24. assert.True(t, p.GetPunch())
  25. // punchy.punch
  26. c.Settings["punchy"] = map[string]any{"punch": true}
  27. p = NewPunchyFromConfig(test.NewLogger(), c)
  28. assert.True(t, p.GetPunch())
  29. // punch_back deprecation
  30. c.Settings["punch_back"] = true
  31. p = NewPunchyFromConfig(test.NewLogger(), c)
  32. assert.True(t, p.GetRespond())
  33. // punchy.respond
  34. c.Settings["punchy"] = map[string]any{"respond": true}
  35. c.Settings["punch_back"] = false
  36. p = NewPunchyFromConfig(test.NewLogger(), c)
  37. assert.True(t, p.GetRespond())
  38. // punchy.delay
  39. c.Settings["punchy"] = map[string]any{"delay": "1m"}
  40. p = NewPunchyFromConfig(test.NewLogger(), c)
  41. assert.Equal(t, time.Minute, p.GetDelay())
  42. // punchy.respond_delay
  43. c.Settings["punchy"] = map[string]any{"respond_delay": "1m"}
  44. p = NewPunchyFromConfig(test.NewLogger(), c)
  45. assert.Equal(t, time.Minute, p.GetRespondDelay())
  46. }
  47. func TestPunchy_reload(t *testing.T) {
  48. l := test.NewLogger()
  49. c := config.NewC(l)
  50. delay, _ := time.ParseDuration("1m")
  51. require.NoError(t, c.LoadString(`
  52. punchy:
  53. delay: 1m
  54. respond: false
  55. `))
  56. p := NewPunchyFromConfig(test.NewLogger(), c)
  57. assert.Equal(t, delay, p.GetDelay())
  58. assert.False(t, p.GetRespond())
  59. newDelay, _ := time.ParseDuration("10m")
  60. require.NoError(t, c.ReloadConfigString(`
  61. punchy:
  62. delay: 10m
  63. respond: true
  64. `))
  65. p.reload(c, false)
  66. assert.Equal(t, newDelay, p.GetDelay())
  67. assert.True(t, p.GetRespond())
  68. }
  69. // The tests below pin the shape of each log line Punchy produces so changes
  70. // cannot silently break whatever operators are grepping for. The assertions
  71. // are on the structured message + attrs (e.g. "punchy.respond changed" with
  72. // a respond=true field) rather than a formatted string.
  73. //
  74. // Punchy.reload also emits a spurious "Changing punchy.punch with reload is
  75. // not supported" warning whenever any key under punchy changes, because of
  76. // the c.HasChanged("punchy") fallback kept for the deprecated top-level
  77. // punchy form. The tests filter by message rather than asserting total
  78. // entry counts so that warning is tolerated without being locked into
  79. // the format.
  80. type capturedEntry struct {
  81. Level slog.Level
  82. Msg string
  83. Attrs map[string]any
  84. }
  85. // capturingHandler is a slog.Handler that records each Record it receives so
  86. // tests can assert on the level, message, and attribute map of individual log
  87. // lines without coupling to any specific text format.
  88. type capturingHandler struct {
  89. entries []capturedEntry
  90. }
  91. func (h *capturingHandler) Enabled(_ context.Context, _ slog.Level) bool { return true }
  92. func (h *capturingHandler) Handle(_ context.Context, r slog.Record) error {
  93. e := capturedEntry{
  94. Level: r.Level,
  95. Msg: r.Message,
  96. Attrs: make(map[string]any),
  97. }
  98. r.Attrs(func(a slog.Attr) bool {
  99. e.Attrs[a.Key] = a.Value.Resolve().Any()
  100. return true
  101. })
  102. h.entries = append(h.entries, e)
  103. return nil
  104. }
  105. func (h *capturingHandler) WithAttrs(_ []slog.Attr) slog.Handler { return h }
  106. func (h *capturingHandler) WithGroup(_ string) slog.Handler { return h }
  107. func newCapturingPunchyLogger(t *testing.T) (*slog.Logger, *capturingHandler) {
  108. t.Helper()
  109. hook := &capturingHandler{}
  110. return slog.New(hook), hook
  111. }
  112. func findEntry(t *testing.T, entries []capturedEntry, msg string) capturedEntry {
  113. t.Helper()
  114. for _, e := range entries {
  115. if e.Msg == msg {
  116. return e
  117. }
  118. }
  119. t.Fatalf("no entry with message %q among %d entries", msg, len(entries))
  120. return capturedEntry{}
  121. }
  122. func TestPunchy_LogFormat_InitialEnabled(t *testing.T) {
  123. l, hook := newCapturingPunchyLogger(t)
  124. c := config.NewC(test.NewLogger())
  125. require.NoError(t, c.LoadString(`punchy: {punch: true}`))
  126. NewPunchyFromConfig(l, c)
  127. entry := findEntry(t, hook.entries, "punchy enabled")
  128. assert.Equal(t, slog.LevelInfo, entry.Level)
  129. assert.Empty(t, entry.Attrs)
  130. }
  131. func TestPunchy_LogFormat_InitialDisabled(t *testing.T) {
  132. l, hook := newCapturingPunchyLogger(t)
  133. c := config.NewC(test.NewLogger())
  134. require.NoError(t, c.LoadString(`punchy: {punch: false}`))
  135. NewPunchyFromConfig(l, c)
  136. entry := findEntry(t, hook.entries, "punchy disabled")
  137. assert.Equal(t, slog.LevelInfo, entry.Level)
  138. assert.Empty(t, entry.Attrs)
  139. }
  140. func TestPunchy_LogFormat_ReloadPunchUnsupported(t *testing.T) {
  141. l, hook := newCapturingPunchyLogger(t)
  142. c := config.NewC(test.NewLogger())
  143. require.NoError(t, c.LoadString(`punchy: {punch: false}`))
  144. NewPunchyFromConfig(l, c)
  145. hook.entries = nil
  146. require.NoError(t, c.ReloadConfigString(`punchy: {punch: true}`))
  147. entry := findEntry(t, hook.entries, "Changing punchy.punch with reload is not supported, ignoring.")
  148. assert.Equal(t, slog.LevelWarn, entry.Level)
  149. assert.Empty(t, entry.Attrs)
  150. }
  151. func TestPunchy_LogFormat_ReloadRespond(t *testing.T) {
  152. l, hook := newCapturingPunchyLogger(t)
  153. c := config.NewC(test.NewLogger())
  154. require.NoError(t, c.LoadString(`punchy: {respond: false}`))
  155. NewPunchyFromConfig(l, c)
  156. hook.entries = nil
  157. require.NoError(t, c.ReloadConfigString(`punchy: {respond: true}`))
  158. entry := findEntry(t, hook.entries, "punchy.respond changed")
  159. assert.Equal(t, slog.LevelInfo, entry.Level)
  160. assert.Equal(t, map[string]any{"respond": true}, entry.Attrs)
  161. }
  162. func TestPunchy_LogFormat_ReloadDelay(t *testing.T) {
  163. l, hook := newCapturingPunchyLogger(t)
  164. c := config.NewC(test.NewLogger())
  165. require.NoError(t, c.LoadString(`punchy: {delay: 1s}`))
  166. NewPunchyFromConfig(l, c)
  167. hook.entries = nil
  168. require.NoError(t, c.ReloadConfigString(`punchy: {delay: 10s}`))
  169. entry := findEntry(t, hook.entries, "punchy.delay changed")
  170. assert.Equal(t, slog.LevelInfo, entry.Level)
  171. assert.Equal(t, map[string]any{"delay": 10 * time.Second}, entry.Attrs)
  172. }
  173. func TestPunchy_LogFormat_ReloadTargetAllRemotes(t *testing.T) {
  174. l, hook := newCapturingPunchyLogger(t)
  175. c := config.NewC(test.NewLogger())
  176. require.NoError(t, c.LoadString(`punchy: {target_all_remotes: false}`))
  177. NewPunchyFromConfig(l, c)
  178. hook.entries = nil
  179. require.NoError(t, c.ReloadConfigString(`punchy: {target_all_remotes: true}`))
  180. entry := findEntry(t, hook.entries, "punchy.target_all_remotes changed")
  181. assert.Equal(t, slog.LevelInfo, entry.Level)
  182. assert.Equal(t, map[string]any{"target_all_remotes": true}, entry.Attrs)
  183. }
  184. func TestPunchy_LogFormat_ReloadRespondDelay(t *testing.T) {
  185. l, hook := newCapturingPunchyLogger(t)
  186. c := config.NewC(test.NewLogger())
  187. require.NoError(t, c.LoadString(`punchy: {respond_delay: 5s}`))
  188. NewPunchyFromConfig(l, c)
  189. hook.entries = nil
  190. require.NoError(t, c.ReloadConfigString(`punchy: {respond_delay: 15s}`))
  191. entry := findEntry(t, hook.entries, "punchy.respond_delay changed")
  192. assert.Equal(t, slog.LevelInfo, entry.Level)
  193. assert.Equal(t, map[string]any{"respond_delay": 15 * time.Second}, entry.Attrs)
  194. }