github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/state/logdb/buf_test.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package logdb_test 5 6 import ( 7 "errors" 8 "time" 9 10 "github.com/juju/clock/testclock" 11 "github.com/juju/testing" 12 jc "github.com/juju/testing/checkers" 13 gc "gopkg.in/check.v1" 14 "gopkg.in/juju/names.v2" 15 16 "github.com/juju/juju/state" 17 "github.com/juju/juju/state/logdb" 18 coretesting "github.com/juju/juju/testing" 19 ) 20 21 var _ = gc.Suite(&BufferedLoggerSuite{}) 22 23 type BufferedLoggerSuite struct { 24 testing.IsolationSuite 25 26 mock mockLogger 27 clock *testclock.Clock 28 } 29 30 func (s *BufferedLoggerSuite) SetUpTest(c *gc.C) { 31 s.IsolationSuite.SetUpTest(c) 32 s.mock = mockLogger{} 33 s.clock = testclock.NewClock(time.Time{}) 34 } 35 36 func (s *BufferedLoggerSuite) waitFlush(c *gc.C) []state.LogRecord { 37 select { 38 case records := <-s.mock.called: 39 return records 40 case <-time.After(coretesting.LongWait): 41 } 42 c.Fatal("timed out waiting for logs to be flushed") 43 panic("unreachable") 44 } 45 46 func (s *BufferedLoggerSuite) assertNoFlush(c *gc.C) { 47 err := s.clock.WaitAdvance(0, 0, 0) // There should be no active timers 48 c.Assert(err, jc.ErrorIsNil) 49 select { 50 case records := <-s.mock.called: 51 c.Fatalf("unexpected log records: %v", records) 52 case <-time.After(coretesting.ShortWait): 53 } 54 } 55 56 func (s *BufferedLoggerSuite) TestLogFlushes(c *gc.C) { 57 const bufsz = 3 58 b := logdb.NewBufferedLogger(&s.mock, bufsz, time.Minute, s.clock) 59 in := []state.LogRecord{{ 60 Entity: names.NewMachineTag("0"), 61 Message: "foo", 62 }, { 63 Entity: names.NewMachineTag("0"), 64 Message: "bar", 65 }, { 66 Entity: names.NewMachineTag("0"), 67 Message: "baz", 68 }} 69 70 err := b.Log(in[:2]) 71 c.Assert(err, jc.ErrorIsNil) 72 s.mock.CheckNoCalls(c) 73 74 err = b.Log(in[2:]) 75 c.Assert(err, jc.ErrorIsNil) 76 s.mock.CheckCalls(c, []testing.StubCall{ 77 {"Log", []interface{}{in}}, 78 }) 79 80 err = s.clock.WaitAdvance(0, coretesting.LongWait, 0) 81 c.Assert(err, jc.ErrorIsNil) 82 s.assertNoFlush(c) 83 } 84 85 func (s *BufferedLoggerSuite) TestLogFlushesMultiple(c *gc.C) { 86 const bufsz = 1 87 b := logdb.NewBufferedLogger(&s.mock, bufsz, time.Minute, s.clock) 88 in := []state.LogRecord{{ 89 Entity: names.NewMachineTag("0"), 90 Message: "foo", 91 }, { 92 Entity: names.NewMachineTag("0"), 93 Message: "bar", 94 }, { 95 Entity: names.NewMachineTag("0"), 96 Message: "baz", 97 }} 98 99 err := b.Log(in) 100 c.Assert(err, jc.ErrorIsNil) 101 s.mock.CheckCalls(c, []testing.StubCall{ 102 {"Log", []interface{}{in[:1]}}, 103 {"Log", []interface{}{in[1:2]}}, 104 {"Log", []interface{}{in[2:]}}, 105 }) 106 } 107 108 func (s *BufferedLoggerSuite) TestTimerFlushes(c *gc.C) { 109 const bufsz = 10 110 const flushInterval = time.Minute 111 s.mock.called = make(chan []state.LogRecord) 112 113 b := logdb.NewBufferedLogger(&s.mock, bufsz, flushInterval, s.clock) 114 in := []state.LogRecord{{ 115 Entity: names.NewMachineTag("0"), 116 Message: "foo", 117 }, { 118 Entity: names.NewMachineTag("0"), 119 Message: "bar", 120 }} 121 122 err := b.Log(in[:1]) 123 c.Assert(err, jc.ErrorIsNil) 124 s.mock.CheckNoCalls(c) 125 126 // Advance, but not far enough to trigger the flush. 127 s.clock.WaitAdvance(30*time.Second, coretesting.LongWait, 1) 128 s.mock.CheckNoCalls(c) 129 130 // Log again; the timer should not have been reset. 131 err = b.Log(in[1:]) 132 s.mock.CheckNoCalls(c) 133 134 // Advance to to the flush interval. 135 s.clock.Advance(30 * time.Second) 136 c.Assert(s.waitFlush(c), jc.DeepEquals, in) 137 s.mock.CheckCalls(c, []testing.StubCall{ 138 {"Log", []interface{}{in}}, 139 }) 140 s.assertNoFlush(c) 141 s.mock.ResetCalls() 142 143 // Logging again, the timer resets to the time at which 144 // the new log records are inserted. 145 err = b.Log(in) 146 c.Assert(err, jc.ErrorIsNil) 147 s.clock.WaitAdvance(59*time.Second, coretesting.LongWait, 1) 148 s.mock.CheckNoCalls(c) 149 s.clock.Advance(1 * time.Second) 150 c.Assert(s.waitFlush(c), jc.DeepEquals, in) 151 s.mock.CheckCalls(c, []testing.StubCall{ 152 {"Log", []interface{}{in}}, 153 }) 154 s.assertNoFlush(c) 155 } 156 157 func (s *BufferedLoggerSuite) TestLogOverCapacity(c *gc.C) { 158 const bufsz = 2 159 const flushInterval = time.Minute 160 s.mock.called = make(chan []state.LogRecord, 1) 161 162 // The buffer has a capacity of 2, so writing 3 logs will 163 // cause 2 to be flushed, with 1 remaining in the buffer 164 // until the timer triggers. 165 b := logdb.NewBufferedLogger(&s.mock, bufsz, flushInterval, s.clock) 166 in := []state.LogRecord{{ 167 Entity: names.NewMachineTag("0"), 168 Message: "foo", 169 }, { 170 Entity: names.NewMachineTag("0"), 171 Message: "bar", 172 }, { 173 Entity: names.NewMachineTag("0"), 174 Message: "baz", 175 }} 176 177 err := b.Log(in) 178 c.Assert(err, jc.ErrorIsNil) 179 c.Assert(s.waitFlush(c), jc.DeepEquals, in[:bufsz]) 180 181 s.clock.WaitAdvance(time.Minute, coretesting.LongWait, 1) 182 c.Assert(s.waitFlush(c), jc.DeepEquals, in[bufsz:]) 183 184 s.mock.CheckCalls(c, []testing.StubCall{ 185 {"Log", []interface{}{in[:bufsz]}}, 186 {"Log", []interface{}{in[bufsz:]}}, 187 }) 188 } 189 190 func (s *BufferedLoggerSuite) TestFlushNothing(c *gc.C) { 191 b := logdb.NewBufferedLogger(&s.mock, 1, time.Minute, s.clock) 192 err := b.Flush() 193 c.Assert(err, jc.ErrorIsNil) 194 s.mock.CheckNoCalls(c) 195 } 196 197 func (s *BufferedLoggerSuite) TestFlushReportsError(c *gc.C) { 198 s.mock.SetErrors(errors.New("nope")) 199 b := logdb.NewBufferedLogger(&s.mock, 2, time.Minute, s.clock) 200 err := b.Log([]state.LogRecord{{ 201 Entity: names.NewMachineTag("0"), 202 Message: "foo", 203 }}) 204 c.Assert(err, jc.ErrorIsNil) 205 err = b.Flush() 206 c.Assert(err, gc.ErrorMatches, "nope") 207 } 208 209 func (s *BufferedLoggerSuite) TestLogReportsError(c *gc.C) { 210 s.mock.SetErrors(errors.New("nope")) 211 b := logdb.NewBufferedLogger(&s.mock, 1, time.Minute, s.clock) 212 err := b.Log([]state.LogRecord{{ 213 Entity: names.NewMachineTag("0"), 214 Message: "foo", 215 }}) 216 c.Assert(err, gc.ErrorMatches, "nope") 217 } 218 219 type mockLogger struct { 220 testing.Stub 221 called chan []state.LogRecord 222 } 223 224 func (m *mockLogger) Log(in []state.LogRecord) error { 225 incopy := make([]state.LogRecord, len(in)) 226 copy(incopy, in) 227 m.MethodCall(m, "Log", incopy) 228 if m.called != nil { 229 m.called <- incopy 230 } 231 return m.NextErr() 232 }