github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/pruner/worker_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package pruner_test
     5  
     6  import (
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/juju/clock/testclock"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	jc "github.com/juju/testing/checkers"
    14  	"github.com/juju/worker/v3"
    15  	gc "gopkg.in/check.v1"
    16  
    17  	"github.com/juju/juju/core/watcher"
    18  	"github.com/juju/juju/environs/config"
    19  	coretesting "github.com/juju/juju/testing"
    20  	"github.com/juju/juju/worker/pruner"
    21  	"github.com/juju/juju/worker/statushistorypruner"
    22  )
    23  
    24  type PrunerSuite struct {
    25  	coretesting.BaseSuite
    26  }
    27  
    28  var _ = gc.Suite(&PrunerSuite{})
    29  
    30  type newPrunerFunc func(pruner.Config) (worker.Worker, error)
    31  
    32  func (s *PrunerSuite) setupPruner(c *gc.C, newPruner newPrunerFunc) (*fakeFacade, *testclock.Clock) {
    33  	facade := newFakeFacade()
    34  	attrs := coretesting.FakeConfig()
    35  	attrs["max-status-history-age"] = "1s"
    36  	attrs["max-status-history-size"] = "3M"
    37  	cfg, err := config.New(config.UseDefaults, attrs)
    38  	c.Assert(err, jc.ErrorIsNil)
    39  	facade.modelConfig = cfg
    40  
    41  	testClock := testclock.NewClock(time.Time{})
    42  	conf := pruner.Config{
    43  		Facade:        facade,
    44  		PruneInterval: coretesting.ShortWait,
    45  		Clock:         testClock,
    46  		Logger:        loggo.GetLogger("test"),
    47  	}
    48  
    49  	pruner, err := newPruner(conf)
    50  	c.Check(err, jc.ErrorIsNil)
    51  	s.AddCleanup(func(*gc.C) {
    52  		c.Assert(worker.Stop(pruner), jc.ErrorIsNil)
    53  	})
    54  
    55  	facade.modelChangesWatcher.changes <- struct{}{}
    56  
    57  	return facade, testClock
    58  }
    59  
    60  func (s *PrunerSuite) assertWorkerCallsPrune(c *gc.C, facade *fakeFacade, testClock *testclock.Clock, collectionSize int) {
    61  	// NewTimer/Reset will have been called with the PruneInterval.
    62  	testClock.WaitAdvance(coretesting.ShortWait-time.Nanosecond, coretesting.LongWait, 1)
    63  	select {
    64  	case <-facade.pruned:
    65  		c.Fatal("unexpected call to Prune")
    66  	case <-time.After(coretesting.ShortWait):
    67  	}
    68  	testClock.Advance(time.Nanosecond)
    69  	select {
    70  	case args := <-facade.pruned:
    71  		c.Assert(args.maxHistoryMB, gc.Equals, collectionSize)
    72  	case <-time.After(coretesting.LongWait):
    73  		c.Fatal("timed out waiting for call to Prune")
    74  	}
    75  }
    76  
    77  func (s *PrunerSuite) TestWorkerCallsPrune(c *gc.C) {
    78  	facade, clock := s.setupPruner(c, statushistorypruner.New)
    79  	s.assertWorkerCallsPrune(c, facade, clock, 3)
    80  }
    81  
    82  func (s *PrunerSuite) TestWorkerWontCallPruneBeforeFiringTimer(c *gc.C) {
    83  	facade, _ := s.setupPruner(c, statushistorypruner.New)
    84  
    85  	select {
    86  	case <-facade.pruned:
    87  		c.Fatal("called before firing timer.")
    88  	case <-time.After(coretesting.ShortWait):
    89  	}
    90  }
    91  
    92  func (s *PrunerSuite) TestModelConfigChange(c *gc.C) {
    93  	facade, clock := s.setupPruner(c, statushistorypruner.New)
    94  	s.assertWorkerCallsPrune(c, facade, clock, 3)
    95  
    96  	var err error
    97  	facade.modelConfig, err = facade.modelConfig.Apply(map[string]interface{}{"max-status-history-size": "4M"})
    98  	c.Assert(err, jc.ErrorIsNil)
    99  	facade.modelChangesWatcher.changes <- struct{}{}
   100  
   101  	s.assertWorkerCallsPrune(c, facade, clock, 4)
   102  }
   103  
   104  type fakeFacade struct {
   105  	pruned              chan pruneParams
   106  	modelChangesWatcher *mockNotifyWatcher
   107  	modelConfig         *config.Config
   108  }
   109  
   110  type pruneParams struct {
   111  	maxAge       time.Duration
   112  	maxHistoryMB int
   113  }
   114  
   115  func newFakeFacade() *fakeFacade {
   116  	return &fakeFacade{
   117  		pruned:              make(chan pruneParams, 1),
   118  		modelChangesWatcher: newMockNotifyWatcher(),
   119  	}
   120  }
   121  
   122  // Prune implements Facade
   123  func (f *fakeFacade) Prune(maxAge time.Duration, maxHistoryMB int) error {
   124  	select {
   125  	case f.pruned <- pruneParams{maxAge, maxHistoryMB}:
   126  	case <-time.After(coretesting.LongWait):
   127  		return errors.New("timed out waiting for facade call Prune to run")
   128  	}
   129  	return nil
   130  }
   131  
   132  // WatchForModelConfigChanges implements Facade
   133  func (f *fakeFacade) WatchForModelConfigChanges() (watcher.NotifyWatcher, error) {
   134  	return f.modelChangesWatcher, nil
   135  }
   136  
   137  // ModelConfig implements Facade
   138  func (f *fakeFacade) ModelConfig() (*config.Config, error) {
   139  	return f.modelConfig, nil
   140  }
   141  
   142  func newMockWatcher() *mockWatcher {
   143  	return &mockWatcher{
   144  		stopped: make(chan struct{}),
   145  	}
   146  }
   147  
   148  type mockWatcher struct {
   149  	mu      sync.Mutex
   150  	stopped chan struct{}
   151  }
   152  
   153  func (w *mockWatcher) Kill() {
   154  	w.mu.Lock()
   155  	defer w.mu.Unlock()
   156  	if !w.Stopped() {
   157  		close(w.stopped)
   158  	}
   159  }
   160  
   161  func (w *mockWatcher) Wait() error {
   162  	<-w.stopped
   163  	return nil
   164  }
   165  
   166  func (w *mockWatcher) Stopped() bool {
   167  	select {
   168  	case <-w.stopped:
   169  		return true
   170  	default:
   171  		return false
   172  	}
   173  }
   174  
   175  func newMockNotifyWatcher() *mockNotifyWatcher {
   176  	return &mockNotifyWatcher{
   177  		mockWatcher: newMockWatcher(),
   178  		changes:     make(chan struct{}, 1),
   179  	}
   180  }
   181  
   182  type mockNotifyWatcher struct {
   183  	*mockWatcher
   184  	changes chan struct{}
   185  }
   186  
   187  func (w *mockNotifyWatcher) Changes() watcher.NotifyChannel {
   188  	return w.changes
   189  }