You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

222 lines
5.7 KiB

  1. package eventlog_test
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "math/rand"
  7. "strconv"
  8. "sync"
  9. "testing"
  10. "time"
  11. "github.com/fortytw2/leaktest"
  12. "github.com/google/go-cmp/cmp"
  13. "github.com/tendermint/tendermint/internal/eventlog"
  14. "github.com/tendermint/tendermint/internal/eventlog/cursor"
  15. "github.com/tendermint/tendermint/types"
  16. )
  17. // fakeTime is a fake clock to use to control cursor assignment.
  18. // The timeIndex method reports the current "time" and advance manually updates
  19. // the apparent time.
  20. type fakeTime struct{ now int64 }
  21. func newFakeTime(init int64) *fakeTime { return &fakeTime{now: init} }
  22. func (f *fakeTime) timeIndex() int64 { return f.now }
  23. func (f *fakeTime) advance(d time.Duration) { f.now += int64(d) }
  24. // eventData is a placeholder event data implementation for testing.
  25. type eventData string
  26. func (eventData) TypeTag() string { return "eventData" }
  27. func TestNewError(t *testing.T) {
  28. lg, err := eventlog.New(eventlog.LogSettings{})
  29. if err == nil {
  30. t.Fatalf("New: got %+v, wanted error", lg)
  31. } else {
  32. t.Logf("New: got expected error: %v", err)
  33. }
  34. }
  35. func TestPruneTime(t *testing.T) {
  36. clk := newFakeTime(0)
  37. // Construct a log with a 60-second time window.
  38. lg, err := eventlog.New(eventlog.LogSettings{
  39. WindowSize: 60 * time.Second,
  40. Source: cursor.Source{
  41. TimeIndex: clk.timeIndex,
  42. },
  43. })
  44. if err != nil {
  45. t.Fatalf("New unexpectedly failed: %v", err)
  46. }
  47. // Add events up to the time window, at seconds 0, 15, 30, 45, 60.
  48. // None of these should be pruned (yet).
  49. var want []string // cursor strings
  50. for i := 1; i <= 5; i++ {
  51. want = append(want, fmt.Sprintf("%016x-%04x", clk.timeIndex(), i))
  52. mustAdd(t, lg, "test-event", eventData("whatever"))
  53. clk.advance(15 * time.Second)
  54. }
  55. // time now: 75 sec.
  56. // Verify that all the events we added are present.
  57. got := cursors(t, lg)
  58. if diff := cmp.Diff(want, got); diff != "" {
  59. t.Errorf("Cursors before pruning: (-want, +got)\n%s", diff)
  60. }
  61. // Add an event past the end of the window at second 90, and verify that
  62. // this triggered an age-based prune of the oldest events (0, 15) that are
  63. // outside the 60-second window.
  64. clk.advance(15 * time.Second) // time now: 90 sec.
  65. want = append(want[2:], fmt.Sprintf("%016x-%04x", clk.timeIndex(), 6))
  66. mustAdd(t, lg, "test-event", eventData("extra"))
  67. got = cursors(t, lg)
  68. if diff := cmp.Diff(want, got); diff != "" {
  69. t.Errorf("Cursors after pruning: (-want, +got)\n%s", diff)
  70. }
  71. }
  72. // Run a publisher and concurrent subscribers to tickle the race detector with
  73. // concurrent add and scan operations.
  74. func TestConcurrent(t *testing.T) {
  75. defer leaktest.Check(t)
  76. if testing.Short() {
  77. t.Skip("Skipping concurrency exercise because -short is set")
  78. }
  79. lg, err := eventlog.New(eventlog.LogSettings{
  80. WindowSize: 30 * time.Second,
  81. })
  82. if err != nil {
  83. t.Fatalf("New unexpectedly failed: %v", err)
  84. }
  85. ctx, cancel := context.WithCancel(context.Background())
  86. defer cancel()
  87. var wg sync.WaitGroup
  88. // Publisher: Add events and handle expirations.
  89. wg.Add(1)
  90. go func() {
  91. defer wg.Done()
  92. tick := time.NewTimer(0)
  93. defer tick.Stop()
  94. for {
  95. select {
  96. case <-ctx.Done():
  97. return
  98. case t := <-tick.C:
  99. _ = lg.Add("test-event", eventData(t.Format(time.RFC3339Nano)))
  100. tick.Reset(time.Duration(rand.Intn(50)) * time.Millisecond)
  101. }
  102. }
  103. }()
  104. // Subscribers: Wait for new events at the head of the queue. This
  105. // simulates the typical operation of a subscriber by waiting for the head
  106. // cursor to change and then scanning down toward the unconsumed item.
  107. const numSubs = 16
  108. for i := 0; i < numSubs; i++ {
  109. task := i
  110. wg.Add(1)
  111. go func() {
  112. defer wg.Done()
  113. tick := time.NewTimer(0)
  114. var cur cursor.Cursor
  115. for {
  116. // Simulate the subscriber being busy with other things.
  117. select {
  118. case <-ctx.Done():
  119. return
  120. case <-tick.C:
  121. tick.Reset(time.Duration(rand.Intn(150)) * time.Millisecond)
  122. }
  123. // Wait for new data to arrive.
  124. info, err := lg.WaitScan(ctx, cur, func(itm *eventlog.Item) error {
  125. if itm.Cursor == cur {
  126. return eventlog.ErrStopScan
  127. }
  128. return nil
  129. })
  130. if err != nil {
  131. if !errors.Is(err, context.Canceled) {
  132. t.Errorf("Wait scan for task %d failed: %v", task, err)
  133. }
  134. return
  135. }
  136. cur = info.Newest
  137. }
  138. }()
  139. }
  140. time.AfterFunc(2*time.Second, cancel)
  141. wg.Wait()
  142. }
  143. func TestPruneSize(t *testing.T) {
  144. const maxItems = 25
  145. lg, err := eventlog.New(eventlog.LogSettings{
  146. WindowSize: 60 * time.Second,
  147. MaxItems: maxItems,
  148. })
  149. if err != nil {
  150. t.Fatalf("New unexpectedly failed: %v", err)
  151. }
  152. // Add a lot of items to the log and verify that we never exceed the
  153. // specified cap.
  154. for i := 0; i < 60; i++ {
  155. mustAdd(t, lg, "test-event", eventData(strconv.Itoa(i+1)))
  156. if got := lg.Info().Size; got > maxItems {
  157. t.Errorf("After add %d: log size is %d, want ≤ %d", i+1, got, maxItems)
  158. }
  159. }
  160. }
  161. // mustAdd adds a single event to lg. If Add reports an error other than for
  162. // pruning, the test fails; otherwise the error is returned.
  163. func mustAdd(t *testing.T, lg *eventlog.Log, etype string, data types.EventData) {
  164. t.Helper()
  165. err := lg.Add(etype, data)
  166. if err != nil && !errors.Is(err, eventlog.ErrLogPruned) {
  167. t.Fatalf("Add %q failed: %v", etype, err)
  168. }
  169. }
  170. // cursors extracts the cursors from lg in ascending order of time.
  171. func cursors(t *testing.T, lg *eventlog.Log) []string {
  172. t.Helper()
  173. var cursors []string
  174. if _, err := lg.Scan(func(itm *eventlog.Item) error {
  175. cursors = append(cursors, itm.Cursor.String())
  176. return nil
  177. }); err != nil {
  178. t.Fatalf("Scan failed: %v", err)
  179. }
  180. reverse(cursors) // put in forward-time order for comparison
  181. return cursors
  182. }
  183. func reverse(ss []string) {
  184. for i, j := 0, len(ss)-1; i < j; {
  185. ss[i], ss[j] = ss[j], ss[i]
  186. i++
  187. j--
  188. }
  189. }