github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/worker/uniter/resolver/loop_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package resolver_test 5 6 import ( 7 "errors" 8 "time" 9 10 jc "github.com/juju/testing/checkers" 11 gc "gopkg.in/check.v1" 12 "gopkg.in/juju/charm.v6-unstable" 13 14 "github.com/juju/juju/testing" 15 coretesting "github.com/juju/juju/testing" 16 "github.com/juju/juju/worker/uniter/operation" 17 "github.com/juju/juju/worker/uniter/remotestate" 18 "github.com/juju/juju/worker/uniter/resolver" 19 ) 20 21 type LoopSuite struct { 22 testing.BaseSuite 23 24 resolver resolver.Resolver 25 watcher *mockRemoteStateWatcher 26 opFactory *mockOpFactory 27 executor *mockOpExecutor 28 charmURL *charm.URL 29 abort chan struct{} 30 onIdle func() error 31 } 32 33 var _ = gc.Suite(&LoopSuite{}) 34 35 func (s *LoopSuite) SetUpTest(c *gc.C) { 36 s.BaseSuite.SetUpTest(c) 37 s.resolver = resolver.ResolverFunc(func(resolver.LocalState, remotestate.Snapshot, operation.Factory) (operation.Operation, error) { 38 return nil, resolver.ErrNoOperation 39 }) 40 s.watcher = &mockRemoteStateWatcher{ 41 changes: make(chan struct{}, 1), 42 } 43 s.opFactory = &mockOpFactory{} 44 s.executor = &mockOpExecutor{} 45 s.charmURL = charm.MustParseURL("cs:trusty/mysql") 46 s.abort = make(chan struct{}) 47 } 48 49 func (s *LoopSuite) loop() (resolver.LocalState, error) { 50 localState := resolver.LocalState{ 51 CharmURL: s.charmURL, 52 } 53 err := resolver.Loop(resolver.LoopConfig{ 54 Resolver: s.resolver, 55 Factory: s.opFactory, 56 Watcher: s.watcher, 57 Executor: s.executor, 58 Abort: s.abort, 59 OnIdle: s.onIdle, 60 CharmDirGuard: &mockCharmDirGuard{}, 61 }, &localState) 62 return localState, err 63 } 64 65 func (s *LoopSuite) TestAbort(c *gc.C) { 66 close(s.abort) 67 _, err := s.loop() 68 c.Assert(err, gc.Equals, resolver.ErrLoopAborted) 69 } 70 71 func (s *LoopSuite) TestOnIdle(c *gc.C) { 72 onIdleCh := make(chan interface{}, 1) 73 s.onIdle = func() error { 74 onIdleCh <- nil 75 return nil 76 } 77 78 done := make(chan interface{}, 1) 79 go func() { 80 _, err := s.loop() 81 done <- err 82 }() 83 84 waitChannel(c, onIdleCh, "waiting for onIdle") 85 s.watcher.changes <- struct{}{} 86 waitChannel(c, onIdleCh, "waiting for onIdle") 87 close(s.abort) 88 89 err := waitChannel(c, done, "waiting for loop to exit") 90 c.Assert(err, gc.Equals, resolver.ErrLoopAborted) 91 92 select { 93 case <-onIdleCh: 94 c.Fatal("unexpected onIdle call") 95 default: 96 } 97 } 98 99 func (s *LoopSuite) TestOnIdleError(c *gc.C) { 100 s.onIdle = func() error { 101 return errors.New("onIdle failed") 102 } 103 close(s.abort) 104 _, err := s.loop() 105 c.Assert(err, gc.ErrorMatches, "onIdle failed") 106 } 107 108 func (s *LoopSuite) TestErrWaitingNoOnIdle(c *gc.C) { 109 var onIdleCalled bool 110 s.onIdle = func() error { 111 onIdleCalled = true 112 return nil 113 } 114 s.resolver = resolver.ResolverFunc(func( 115 _ resolver.LocalState, 116 _ remotestate.Snapshot, 117 _ operation.Factory, 118 ) (operation.Operation, error) { 119 return nil, resolver.ErrWaiting 120 }) 121 close(s.abort) 122 _, err := s.loop() 123 c.Assert(err, gc.Equals, resolver.ErrLoopAborted) 124 c.Assert(onIdleCalled, jc.IsFalse) 125 } 126 127 func (s *LoopSuite) TestInitialFinalLocalState(c *gc.C) { 128 var local resolver.LocalState 129 s.resolver = resolver.ResolverFunc(func( 130 l resolver.LocalState, 131 _ remotestate.Snapshot, 132 _ operation.Factory, 133 ) (operation.Operation, error) { 134 local = l 135 return nil, resolver.ErrNoOperation 136 }) 137 138 close(s.abort) 139 lastLocal, err := s.loop() 140 c.Assert(err, gc.Equals, resolver.ErrLoopAborted) 141 c.Assert(local, jc.DeepEquals, resolver.LocalState{ 142 CharmURL: s.charmURL, 143 }) 144 c.Assert(lastLocal, jc.DeepEquals, local) 145 } 146 147 func (s *LoopSuite) TestLoop(c *gc.C) { 148 var resolverCalls int 149 theOp := &mockOp{} 150 s.resolver = resolver.ResolverFunc(func( 151 _ resolver.LocalState, 152 _ remotestate.Snapshot, 153 _ operation.Factory, 154 ) (operation.Operation, error) { 155 resolverCalls++ 156 switch resolverCalls { 157 // On the first call, return an operation. 158 case 1: 159 return theOp, nil 160 // On the second call, simulate having 161 // no operations to perform, at which 162 // point we'll wait for a remote state 163 // change. 164 case 2: 165 s.watcher.changes <- struct{}{} 166 break 167 // On the third call, kill the loop. 168 case 3: 169 close(s.abort) 170 break 171 } 172 return nil, resolver.ErrNoOperation 173 }) 174 175 _, err := s.loop() 176 c.Assert(err, gc.Equals, resolver.ErrLoopAborted) 177 c.Assert(resolverCalls, gc.Equals, 3) 178 s.executor.CheckCallNames(c, "State", "State", "Run", "State", "State") 179 c.Assert(s.executor.Calls()[2].Args, jc.SameContents, []interface{}{theOp}) 180 } 181 182 func (s *LoopSuite) TestRunFails(c *gc.C) { 183 s.executor.SetErrors(errors.New("Run fails")) 184 s.resolver = resolver.ResolverFunc(func( 185 _ resolver.LocalState, 186 _ remotestate.Snapshot, 187 _ operation.Factory, 188 ) (operation.Operation, error) { 189 return mockOp{}, nil 190 }) 191 _, err := s.loop() 192 c.Assert(err, gc.ErrorMatches, "Run fails") 193 } 194 195 func (s *LoopSuite) TestNextOpFails(c *gc.C) { 196 s.resolver = resolver.ResolverFunc(func( 197 _ resolver.LocalState, 198 _ remotestate.Snapshot, 199 _ operation.Factory, 200 ) (operation.Operation, error) { 201 return nil, errors.New("NextOp fails") 202 }) 203 _, err := s.loop() 204 c.Assert(err, gc.ErrorMatches, "NextOp fails") 205 } 206 207 func waitChannel(c *gc.C, ch <-chan interface{}, activity string) interface{} { 208 select { 209 case v := <-ch: 210 return v 211 case <-time.After(coretesting.LongWait): 212 c.Fatalf("timed out " + activity) 213 panic("unreachable") 214 } 215 }