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  }