1
0

punchy_test.go 7.1 KB

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