github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/testing/suite.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package testing 5 6 import ( 7 "sync" 8 "time" 9 10 "github.com/juju/clock/testclock" 11 "github.com/juju/loggo" 12 mgotesting "github.com/juju/mgo/v3/testing" 13 "github.com/juju/names/v5" 14 jujutesting "github.com/juju/testing" 15 jc "github.com/juju/testing/checkers" 16 gc "gopkg.in/check.v1" 17 18 "github.com/juju/juju/cloud" 19 "github.com/juju/juju/environs/config" 20 "github.com/juju/juju/state" 21 statewatcher "github.com/juju/juju/state/watcher" 22 "github.com/juju/juju/testing" 23 "github.com/juju/juju/testing/factory" 24 ) 25 26 var _ = gc.Suite(&StateSuite{}) 27 28 // StateSuite provides setup and teardown for tests that require a 29 // state.State. 30 type StateSuite struct { 31 mgotesting.MgoSuite 32 testing.BaseSuite 33 NewPolicy state.NewPolicyFunc 34 Controller *state.Controller 35 StatePool *state.StatePool 36 State *state.State 37 Model *state.Model 38 Owner names.UserTag 39 AdminPassword string 40 Factory *factory.Factory 41 InitialConfig *config.Config 42 ControllerConfig map[string]interface{} 43 ControllerInheritedConfig map[string]interface{} 44 ControllerModelType state.ModelType 45 RegionConfig cloud.RegionConfig 46 Clock testclock.AdvanceableClock 47 txnSyncNotify chan struct{} 48 modelWatcherIdle chan string 49 modelWatcherMutex *sync.Mutex 50 } 51 52 func (s *StateSuite) SetUpSuite(c *gc.C) { 53 s.MgoSuite.SetUpSuite(c) 54 s.BaseSuite.SetUpSuite(c) 55 } 56 57 func (s *StateSuite) TearDownSuite(c *gc.C) { 58 s.BaseSuite.TearDownSuite(c) 59 s.MgoSuite.TearDownSuite(c) 60 } 61 62 func (s *StateSuite) SetUpTest(c *gc.C) { 63 s.MgoSuite.SetUpTest(c) 64 s.BaseSuite.SetUpTest(c) 65 66 s.txnSyncNotify = make(chan struct{}) 67 s.modelWatcherIdle = nil 68 s.modelWatcherMutex = &sync.Mutex{} 69 s.PatchValue(&statewatcher.TxnPollNotifyFunc, s.txnNotifyFunc) 70 s.PatchValue(&statewatcher.HubWatcherIdleFunc, s.hubWatcherIdleFunc) 71 72 s.Owner = names.NewLocalUserTag("test-admin") 73 74 if s.Clock == nil { 75 s.Clock = testclock.NewDilatedWallClock(100 * time.Millisecond) 76 } 77 78 s.AdminPassword = "admin-secret" 79 s.Controller = InitializeWithArgs(c, InitializeArgs{ 80 Owner: s.Owner, 81 AdminPassword: s.AdminPassword, 82 InitialConfig: s.InitialConfig, 83 ControllerConfig: s.ControllerConfig, 84 ControllerInheritedConfig: s.ControllerInheritedConfig, 85 ControllerModelType: s.ControllerModelType, 86 RegionConfig: s.RegionConfig, 87 NewPolicy: s.NewPolicy, 88 Clock: s.Clock, 89 }) 90 s.AddCleanup(func(*gc.C) { 91 _ = s.Controller.Close() 92 close(s.txnSyncNotify) 93 }) 94 s.StatePool = s.Controller.StatePool() 95 var err error 96 s.State, err = s.StatePool.SystemState() 97 c.Assert(err, jc.ErrorIsNil) 98 model, err := s.State.Model() 99 c.Assert(err, jc.ErrorIsNil) 100 s.Model = model 101 102 s.Factory = factory.NewFactory(s.State, s.StatePool) 103 } 104 105 func (s *StateSuite) TearDownTest(c *gc.C) { 106 s.BaseSuite.TearDownTest(c) 107 s.MgoSuite.TearDownTest(c) 108 } 109 110 func (s *StateSuite) txnNotifyFunc() { 111 select { 112 case s.txnSyncNotify <- struct{}{}: 113 // Try to send something down the channel. 114 default: 115 // However don't get stressed if no one is listening. 116 } 117 } 118 119 func (s *StateSuite) hubWatcherIdleFunc(modelUUID string) { 120 s.modelWatcherMutex.Lock() 121 idleChan := s.modelWatcherIdle 122 s.modelWatcherMutex.Unlock() 123 if idleChan == nil { 124 return 125 } 126 // There is a very small race condition between when the 127 // idle channel is cleared and when the function exits. 128 // Under normal circumstances, there is a goroutine in a tight loop 129 // reading off the idle channel. If the channel isn't read 130 // within a short wait, we don't send the message. 131 select { 132 case idleChan <- modelUUID: 133 case <-time.After(testing.ShortWait): 134 } 135 } 136 137 // WaitForNextSync repeatedly advances the testing clock 138 // with short waits between until the txn poller doesn't find 139 // any more changes. 140 func (s *StateSuite) WaitForNextSync(c *gc.C) { 141 done := make(chan struct{}) 142 go func() { 143 <-s.txnSyncNotify 144 close(done) 145 }() 146 timeout := time.After(jujutesting.LongWait) 147 for { 148 select { 149 case <-done: 150 return 151 case <-timeout: 152 c.Fatal("no sync event sent, is the watcher dead?") 153 } 154 } 155 } 156 157 // WaitForModelWatchersIdle firstly waits for the txn poller to process 158 // all pending changes, then waits for the hub watcher on the state object 159 // to have finished processing all those events. 160 func (s *StateSuite) WaitForModelWatchersIdle(c *gc.C, modelUUID string) { 161 // Use a logger rather than c.Log so we get timestamps. 162 logger := loggo.GetLogger("test") 163 logger.Infof("waiting for model %s to be idle", modelUUID) 164 s.WaitForNextSync(c) 165 // Create idle channel after the sync so as to be sure that at least 166 // one sync is complete before signalling the idle timer. 167 s.modelWatcherMutex.Lock() 168 idleChan := make(chan string) 169 s.modelWatcherIdle = idleChan 170 s.modelWatcherMutex.Unlock() 171 172 defer func() { 173 s.modelWatcherMutex.Lock() 174 s.modelWatcherIdle = nil 175 s.modelWatcherMutex.Unlock() 176 // Clear out any pending events. 177 for { 178 select { 179 case <-idleChan: 180 default: 181 return 182 } 183 } 184 }() 185 186 timeout := time.After(jujutesting.LongWait) 187 for { 188 loop := time.After(10 * time.Millisecond) 189 select { 190 case <-loop: 191 case uuid := <-idleChan: 192 if uuid == modelUUID { 193 return 194 } else { 195 logger.Infof("model %s is idle", uuid) 196 } 197 case <-timeout: 198 c.Fatal("no sync event sent, is the watcher dead?") 199 } 200 } 201 }