github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/cleaner/cleaner_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package cleaner_test
     5  
     6  import (
     7  	"errors"
     8  	"time"
     9  
    10  	"github.com/juju/clock/testclock"
    11  	"github.com/juju/loggo"
    12  	jc "github.com/juju/testing/checkers"
    13  	"github.com/juju/worker/v3"
    14  	gc "gopkg.in/check.v1"
    15  	"gopkg.in/tomb.v2"
    16  
    17  	"github.com/juju/juju/core/watcher"
    18  	coretesting "github.com/juju/juju/testing"
    19  	"github.com/juju/juju/worker/cleaner"
    20  )
    21  
    22  type CleanerSuite struct {
    23  	coretesting.BaseSuite
    24  	mockState *cleanerMock
    25  	mockClock *testclock.Clock
    26  	logger    loggo.Logger
    27  }
    28  
    29  var _ = gc.Suite(&CleanerSuite{})
    30  
    31  func (s *CleanerSuite) SetUpTest(c *gc.C) {
    32  	s.BaseSuite.SetUpTest(c)
    33  	s.mockState = &cleanerMock{
    34  		calls: make(chan string, 1),
    35  	}
    36  	s.mockState.watcher = s.newMockNotifyWatcher(nil)
    37  	s.mockClock = testclock.NewClock(time.Time{})
    38  	s.logger = loggo.GetLogger("test")
    39  }
    40  
    41  func (s *CleanerSuite) AssertReceived(c *gc.C, expect string) {
    42  	select {
    43  	case call := <-s.mockState.calls:
    44  		c.Assert(call, gc.Matches, expect)
    45  	case <-time.After(coretesting.LongWait):
    46  		c.Fatalf("Timed out waiting for %s", expect)
    47  	}
    48  }
    49  
    50  func (s *CleanerSuite) AssertEmpty(c *gc.C) {
    51  	select {
    52  	case call, ok := <-s.mockState.calls:
    53  		c.Fatalf("Unexpected %s (ok: %v)", call, ok)
    54  	case <-time.After(coretesting.ShortWait):
    55  	}
    56  }
    57  
    58  func (s *CleanerSuite) TestCleaner(c *gc.C) {
    59  	cln, err := cleaner.NewCleaner(s.mockState, s.mockClock, s.logger)
    60  	c.Assert(err, jc.ErrorIsNil)
    61  	defer func() { c.Assert(worker.Stop(cln), jc.ErrorIsNil) }()
    62  
    63  	s.AssertReceived(c, "WatchCleanups")
    64  	s.AssertReceived(c, "Cleanup")
    65  	s.AssertEmpty(c)
    66  
    67  	s.mockState.watcher.Change()
    68  	s.AssertReceived(c, "Cleanup")
    69  	s.AssertEmpty(c)
    70  }
    71  
    72  func (s *CleanerSuite) TestCleanerPeriodic(c *gc.C) {
    73  	cln, err := cleaner.NewCleaner(s.mockState, s.mockClock, s.logger)
    74  	c.Assert(err, jc.ErrorIsNil)
    75  	defer func() { c.Assert(worker.Stop(cln), jc.ErrorIsNil) }()
    76  
    77  	s.AssertReceived(c, "WatchCleanups")
    78  	s.AssertReceived(c, "Cleanup")
    79  	s.AssertEmpty(c)
    80  
    81  	// The cleaner will start a timer that waits for 30 seconds after
    82  	// each call to Cleanup, regardless of whether the previous call
    83  	// to Cleanup was triggered by the watcher or a timer.
    84  	for i := 0; i < 2; i++ {
    85  		s.mockClock.WaitAdvance(29*time.Second, coretesting.LongWait, 1)
    86  		s.AssertEmpty(c)
    87  		s.mockClock.WaitAdvance(1*time.Second, coretesting.LongWait, 1)
    88  		s.AssertReceived(c, "Cleanup")
    89  		s.AssertEmpty(c)
    90  	}
    91  }
    92  
    93  func (s *CleanerSuite) TestWatchCleanupsError(c *gc.C) {
    94  	s.mockState.err = []error{errors.New("hello")}
    95  	_, err := cleaner.NewCleaner(s.mockState, s.mockClock, s.logger)
    96  	c.Assert(err, gc.ErrorMatches, "hello")
    97  
    98  	s.AssertReceived(c, "WatchCleanups")
    99  	s.AssertEmpty(c)
   100  }
   101  
   102  func (s *CleanerSuite) TestCleanupError(c *gc.C) {
   103  	s.mockState.err = []error{nil, errors.New("hello")}
   104  	cln, err := cleaner.NewCleaner(s.mockState, s.mockClock, s.logger)
   105  	c.Assert(err, jc.ErrorIsNil)
   106  
   107  	s.AssertReceived(c, "WatchCleanups")
   108  	s.AssertReceived(c, "Cleanup")
   109  	err = worker.Stop(cln)
   110  	c.Assert(err, jc.ErrorIsNil)
   111  	log := c.GetTestLog()
   112  	c.Assert(log, jc.Contains, "ERROR test cannot cleanup state: hello")
   113  }
   114  
   115  func (s *CleanerSuite) newMockNotifyWatcher(err error) *mockNotifyWatcher {
   116  	m := &mockNotifyWatcher{
   117  		changes: make(chan struct{}, 1),
   118  		err:     err,
   119  	}
   120  	m.tomb.Go(func() error {
   121  		<-m.tomb.Dying()
   122  		return m.err
   123  	})
   124  	s.AddCleanup(func(c *gc.C) {
   125  		err := worker.Stop(m)
   126  		c.Check(err, jc.ErrorIsNil)
   127  	})
   128  	m.Change()
   129  	return m
   130  }
   131  
   132  type mockNotifyWatcher struct {
   133  	watcher.NotifyWatcher
   134  
   135  	tomb    tomb.Tomb
   136  	err     error
   137  	changes chan struct{}
   138  }
   139  
   140  func (m *mockNotifyWatcher) Kill() {
   141  	m.tomb.Kill(nil)
   142  }
   143  
   144  func (m *mockNotifyWatcher) Wait() error {
   145  	return m.tomb.Wait()
   146  }
   147  
   148  func (m *mockNotifyWatcher) Changes() watcher.NotifyChannel {
   149  	return m.changes
   150  }
   151  
   152  func (m *mockNotifyWatcher) Change() {
   153  	m.changes <- struct{}{}
   154  }
   155  
   156  // cleanerMock is used to check the
   157  // calls of Cleanup() and WatchCleanups()
   158  type cleanerMock struct {
   159  	cleaner.StateCleaner
   160  	watcher *mockNotifyWatcher
   161  	calls   chan string
   162  	err     []error
   163  }
   164  
   165  func (m *cleanerMock) getError() (e error) {
   166  	if len(m.err) > 0 {
   167  		e = m.err[0]
   168  		m.err = m.err[1:]
   169  	}
   170  	return
   171  }
   172  
   173  func (m *cleanerMock) Cleanup() error {
   174  	m.calls <- "Cleanup"
   175  	return m.getError()
   176  }
   177  
   178  func (m *cleanerMock) WatchCleanups() (watcher.NotifyWatcher, error) {
   179  	m.calls <- "WatchCleanups"
   180  	return m.watcher, m.getError()
   181  }