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