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 }