github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/overlord/hookstate/hookstate_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package hookstate_test 21 22 import ( 23 "context" 24 "encoding/json" 25 "fmt" 26 "path/filepath" 27 "regexp" 28 "sync/atomic" 29 "testing" 30 "time" 31 32 . "gopkg.in/check.v1" 33 "gopkg.in/tomb.v2" 34 35 "github.com/snapcore/snapd/dirs" 36 "github.com/snapcore/snapd/overlord" 37 "github.com/snapcore/snapd/overlord/configstate/config" 38 "github.com/snapcore/snapd/overlord/hookstate" 39 "github.com/snapcore/snapd/overlord/hookstate/hooktest" 40 "github.com/snapcore/snapd/overlord/snapstate" 41 "github.com/snapcore/snapd/overlord/state" 42 "github.com/snapcore/snapd/snap" 43 "github.com/snapcore/snapd/snap/snaptest" 44 "github.com/snapcore/snapd/testutil" 45 ) 46 47 func TestHookManager(t *testing.T) { TestingT(t) } 48 49 type baseHookManagerSuite struct { 50 testutil.BaseTest 51 52 o *overlord.Overlord 53 state *state.State 54 se *overlord.StateEngine 55 manager *hookstate.HookManager 56 context *hookstate.Context 57 mockHandler *hooktest.MockHandler 58 task *state.Task 59 change *state.Change 60 command *testutil.MockCmd 61 } 62 63 func (s *baseHookManagerSuite) commonSetUpTest(c *C) { 64 s.BaseTest.SetUpTest(c) 65 66 hooktype1 := snap.NewHookType(regexp.MustCompile("^do-something$")) 67 hooktype2 := snap.NewHookType(regexp.MustCompile("^undo-something$")) 68 s.AddCleanup(snap.MockAppendSupportedHookTypes([]*snap.HookType{hooktype1, hooktype2})) 69 70 dirs.SetRootDir(c.MkDir()) 71 s.o = overlord.Mock() 72 s.state = s.o.State() 73 manager, err := hookstate.Manager(s.state, s.o.TaskRunner()) 74 c.Assert(err, IsNil) 75 s.manager = manager 76 s.se = s.o.StateEngine() 77 s.o.AddManager(s.manager) 78 s.o.AddManager(s.o.TaskRunner()) 79 c.Assert(s.o.StartUp(), IsNil) 80 81 s.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})) 82 83 s.command = testutil.MockCommand(c, "snap", "") 84 s.AddCleanup(s.command.Restore) 85 86 s.context = nil 87 s.mockHandler = hooktest.NewMockHandler() 88 s.manager.Register(regexp.MustCompile("configure"), func(context *hookstate.Context) hookstate.Handler { 89 s.context = context 90 return s.mockHandler 91 }) 92 s.AddCleanup(hookstate.MockErrtrackerReport(func(string, string, string, map[string]string) (string, error) { 93 return "", nil 94 })) 95 } 96 97 func (s *baseHookManagerSuite) commonTearDownTest(c *C) { 98 s.BaseTest.TearDownTest(c) 99 100 s.manager.StopHooks() 101 s.se.Stop() 102 dirs.SetRootDir("") 103 } 104 105 func (s *baseHookManagerSuite) setUpSnap(c *C, instanceName string, yaml string) { 106 hooksup := &hookstate.HookSetup{ 107 Snap: instanceName, 108 Hook: "configure", 109 Revision: snap.R(1), 110 } 111 112 initialContext := map[string]interface{}{ 113 "test-key": "test-value", 114 } 115 116 s.state.Lock() 117 defer s.state.Unlock() 118 s.task = hookstate.HookTask(s.state, "test summary", hooksup, initialContext) 119 c.Assert(s.task, NotNil, Commentf("Expected HookTask to return a task")) 120 121 s.change = s.state.NewChange("kind", "summary") 122 s.change.AddTask(s.task) 123 124 snapName, instanceKey := snap.SplitInstanceName(instanceName) 125 126 sideInfo := &snap.SideInfo{RealName: snapName, SnapID: "some-snap-id", Revision: snap.R(1)} 127 snaptest.MockSnapInstance(c, instanceName, yaml, sideInfo) 128 snapstate.Set(s.state, instanceName, &snapstate.SnapState{ 129 Active: true, 130 Sequence: []*snap.SideInfo{sideInfo}, 131 Current: snap.R(1), 132 InstanceKey: instanceKey, 133 }) 134 } 135 136 type hookManagerSuite struct { 137 baseHookManagerSuite 138 } 139 140 var _ = Suite(&hookManagerSuite{}) 141 142 var snapYaml = ` 143 name: test-snap 144 version: 1.0 145 hooks: 146 configure: 147 prepare-device: 148 do-something: 149 undo-something: 150 ` 151 152 var snapYaml1 = ` 153 name: test-snap-1 154 version: 1.0 155 hooks: 156 prepare-device: 157 ` 158 159 var snapYaml2 = ` 160 name: test-snap-2 161 version: 1.0 162 hooks: 163 prepare-device: 164 ` 165 166 func (s *hookManagerSuite) SetUpTest(c *C) { 167 s.commonSetUpTest(c) 168 169 s.setUpSnap(c, "test-snap", snapYaml) 170 } 171 172 func (s *hookManagerSuite) TearDownTest(c *C) { 173 s.commonTearDownTest(c) 174 } 175 176 func (s *hookManagerSuite) settle(c *C) { 177 err := s.o.Settle(5 * time.Second) 178 c.Assert(err, IsNil) 179 } 180 181 func (s *hookManagerSuite) TestSmoke(c *C) { 182 s.se.Ensure() 183 s.se.Wait() 184 } 185 186 func (s *hookManagerSuite) TestHookSetupJsonMarshal(c *C) { 187 hookSetup := &hookstate.HookSetup{Snap: "snap-name", Revision: snap.R(1), Hook: "hook-name"} 188 out, err := json.Marshal(hookSetup) 189 c.Assert(err, IsNil) 190 c.Check(string(out), Equals, "{\"snap\":\"snap-name\",\"revision\":\"1\",\"hook\":\"hook-name\"}") 191 } 192 193 func (s *hookManagerSuite) TestHookSetupJsonUnmarshal(c *C) { 194 out, err := json.Marshal(hookstate.HookSetup{Snap: "snap-name", Revision: snap.R(1), Hook: "hook-name"}) 195 c.Assert(err, IsNil) 196 197 var setup hookstate.HookSetup 198 err = json.Unmarshal(out, &setup) 199 c.Assert(err, IsNil) 200 c.Check(setup.Snap, Equals, "snap-name") 201 c.Check(setup.Revision, Equals, snap.R(1)) 202 c.Check(setup.Hook, Equals, "hook-name") 203 } 204 205 func (s *hookManagerSuite) TestHookTask(c *C) { 206 s.state.Lock() 207 defer s.state.Unlock() 208 209 hooksup := &hookstate.HookSetup{ 210 Snap: "test-snap", 211 Hook: "configure", 212 Revision: snap.R(1), 213 } 214 215 task := hookstate.HookTask(s.state, "test summary", hooksup, nil) 216 c.Check(task.Kind(), Equals, "run-hook") 217 218 var setup hookstate.HookSetup 219 err := task.Get("hook-setup", &setup) 220 c.Check(err, IsNil) 221 c.Check(setup.Snap, Equals, "test-snap") 222 c.Check(setup.Revision, Equals, snap.R(1)) 223 c.Check(setup.Hook, Equals, "configure") 224 } 225 226 func (s *hookManagerSuite) TestHookTaskEnsure(c *C) { 227 didRun := make(chan bool) 228 s.mockHandler.BeforeCallback = func() { 229 c.Check(s.manager.NumRunningHooks(), Equals, 1) 230 go func() { 231 didRun <- s.manager.GracefullyWaitRunningHooks() 232 }() 233 } 234 s.se.Ensure() 235 select { 236 case ok := <-didRun: 237 c.Check(ok, Equals, true) 238 case <-time.After(5 * time.Second): 239 c.Fatal("hook run should have been done by now") 240 } 241 s.se.Wait() 242 243 s.state.Lock() 244 defer s.state.Unlock() 245 246 c.Assert(s.context, NotNil, Commentf("Expected handler generator to be called with a valid context")) 247 c.Check(s.context.InstanceName(), Equals, "test-snap") 248 c.Check(s.context.SnapRevision(), Equals, snap.R(1)) 249 c.Check(s.context.HookName(), Equals, "configure") 250 251 c.Check(s.command.Calls(), DeepEquals, [][]string{{ 252 "snap", "run", "--hook", "configure", "-r", "1", "test-snap", 253 }}) 254 255 c.Check(s.mockHandler.BeforeCalled, Equals, true) 256 c.Check(s.mockHandler.DoneCalled, Equals, true) 257 c.Check(s.mockHandler.ErrorCalled, Equals, false) 258 259 c.Check(s.task.Kind(), Equals, "run-hook") 260 c.Check(s.task.Status(), Equals, state.DoneStatus) 261 c.Check(s.change.Status(), Equals, state.DoneStatus) 262 263 c.Check(s.manager.NumRunningHooks(), Equals, 0) 264 } 265 266 func (s *hookManagerSuite) TestHookTaskEnsureRestarting(c *C) { 267 // we do no start new hooks runs if we are restarting 268 s.state.RequestRestart(state.RestartDaemon) 269 270 s.se.Ensure() 271 s.se.Wait() 272 273 s.state.Lock() 274 defer s.state.Unlock() 275 276 c.Assert(s.context, IsNil) 277 278 c.Check(s.command.Calls(), HasLen, 0) 279 280 c.Check(s.mockHandler.BeforeCalled, Equals, false) 281 c.Check(s.mockHandler.DoneCalled, Equals, false) 282 c.Check(s.mockHandler.ErrorCalled, Equals, false) 283 284 c.Check(s.task.Status(), Equals, state.DoingStatus) 285 c.Check(s.change.Status(), Equals, state.DoingStatus) 286 287 c.Check(s.manager.NumRunningHooks(), Equals, 0) 288 } 289 290 func (s *hookManagerSuite) TestHookSnapMissing(c *C) { 291 s.state.Lock() 292 snapstate.Set(s.state, "test-snap", nil) 293 s.state.Unlock() 294 295 s.se.Ensure() 296 s.se.Wait() 297 298 s.state.Lock() 299 defer s.state.Unlock() 300 301 c.Check(s.change.Err(), ErrorMatches, `(?s).*cannot find "test-snap" snap.*`) 302 } 303 304 func (s *hookManagerSuite) TestHookHijackingHappy(c *C) { 305 // this works even if test-snap is not present 306 s.state.Lock() 307 snapstate.Set(s.state, "test-snap", nil) 308 s.state.Unlock() 309 310 var hijackedContext *hookstate.Context 311 s.manager.RegisterHijack("configure", "test-snap", func(ctx *hookstate.Context) error { 312 hijackedContext = ctx 313 return nil 314 }) 315 316 s.se.Ensure() 317 s.se.Wait() 318 319 s.state.Lock() 320 defer s.state.Unlock() 321 322 c.Check(hijackedContext, DeepEquals, s.context) 323 c.Check(s.command.Calls(), HasLen, 0) 324 325 c.Assert(s.context, NotNil) 326 c.Check(s.context.InstanceName(), Equals, "test-snap") 327 c.Check(s.context.SnapRevision(), Equals, snap.R(1)) 328 c.Check(s.context.HookName(), Equals, "configure") 329 330 c.Check(s.mockHandler.BeforeCalled, Equals, true) 331 c.Check(s.mockHandler.DoneCalled, Equals, true) 332 c.Check(s.mockHandler.ErrorCalled, Equals, false) 333 334 c.Check(s.task.Kind(), Equals, "run-hook") 335 c.Check(s.task.Status(), Equals, state.DoneStatus) 336 c.Check(s.change.Status(), Equals, state.DoneStatus) 337 } 338 339 func (s *hookManagerSuite) TestHookHijackingUnHappy(c *C) { 340 s.manager.RegisterHijack("configure", "test-snap", func(ctx *hookstate.Context) error { 341 return fmt.Errorf("not-happy-at-all") 342 }) 343 344 s.se.Ensure() 345 s.se.Wait() 346 347 s.state.Lock() 348 defer s.state.Unlock() 349 350 c.Check(s.command.Calls(), HasLen, 0) 351 352 c.Assert(s.context, NotNil) 353 c.Check(s.context.InstanceName(), Equals, "test-snap") 354 c.Check(s.context.SnapRevision(), Equals, snap.R(1)) 355 c.Check(s.context.HookName(), Equals, "configure") 356 357 c.Check(s.mockHandler.BeforeCalled, Equals, true) 358 c.Check(s.mockHandler.DoneCalled, Equals, false) 359 c.Check(s.mockHandler.ErrorCalled, Equals, true) 360 361 c.Check(s.task.Kind(), Equals, "run-hook") 362 c.Check(s.task.Status(), Equals, state.ErrorStatus) 363 c.Check(s.change.Status(), Equals, state.ErrorStatus) 364 } 365 366 func (s *hookManagerSuite) TestHookHijackingVeryUnHappy(c *C) { 367 f := func(ctx *hookstate.Context) error { 368 return nil 369 } 370 s.manager.RegisterHijack("configure", "test-snap", f) 371 c.Check(func() { s.manager.RegisterHijack("configure", "test-snap", f) }, PanicMatches, "hook configure for snap test-snap already hijacked") 372 } 373 374 func (s *hookManagerSuite) TestHookTaskInitializesContext(c *C) { 375 s.se.Ensure() 376 s.se.Wait() 377 378 var value string 379 c.Assert(s.context, NotNil, Commentf("Expected handler generator to be called with a valid context")) 380 s.context.Lock() 381 defer s.context.Unlock() 382 c.Check(s.context.Get("test-key", &value), IsNil, Commentf("Expected context to be initialized")) 383 c.Check(value, Equals, "test-value") 384 } 385 386 func (s *hookManagerSuite) TestHookTaskHandlesHookError(c *C) { 387 // Force the snap command to exit 1, and print something to stderr 388 cmd := testutil.MockCommand( 389 c, "snap", ">&2 echo 'hook failed at user request'; exit 1") 390 defer cmd.Restore() 391 392 s.se.Ensure() 393 s.se.Wait() 394 395 s.state.Lock() 396 defer s.state.Unlock() 397 398 c.Check(s.mockHandler.BeforeCalled, Equals, true) 399 c.Check(s.mockHandler.DoneCalled, Equals, false) 400 c.Check(s.mockHandler.ErrorCalled, Equals, true) 401 402 c.Check(s.task.Kind(), Equals, "run-hook") 403 c.Check(s.task.Status(), Equals, state.ErrorStatus) 404 c.Check(s.change.Status(), Equals, state.ErrorStatus) 405 checkTaskLogContains(c, s.task, ".*failed at user request.*") 406 407 c.Check(s.manager.NumRunningHooks(), Equals, 0) 408 } 409 410 func (s *hookManagerSuite) TestHookTaskHandleIgnoreErrorWorks(c *C) { 411 s.state.Lock() 412 var hooksup hookstate.HookSetup 413 s.task.Get("hook-setup", &hooksup) 414 hooksup.IgnoreError = true 415 s.task.Set("hook-setup", &hooksup) 416 s.state.Unlock() 417 418 // Force the snap command to exit 1, and print something to stderr 419 cmd := testutil.MockCommand( 420 c, "snap", ">&2 echo 'hook failed at user request'; exit 1") 421 defer cmd.Restore() 422 423 s.se.Ensure() 424 s.se.Wait() 425 426 s.state.Lock() 427 defer s.state.Unlock() 428 429 c.Check(s.mockHandler.BeforeCalled, Equals, true) 430 c.Check(s.mockHandler.DoneCalled, Equals, true) 431 c.Check(s.mockHandler.ErrorCalled, Equals, false) 432 433 c.Check(s.task.Kind(), Equals, "run-hook") 434 c.Check(s.task.Status(), Equals, state.DoneStatus) 435 c.Check(s.change.Status(), Equals, state.DoneStatus) 436 checkTaskLogContains(c, s.task, ".*ignoring failure in hook.*") 437 } 438 439 func (s *hookManagerSuite) TestHookTaskEnforcesTimeout(c *C) { 440 var hooksup hookstate.HookSetup 441 442 s.state.Lock() 443 s.task.Get("hook-setup", &hooksup) 444 hooksup.Timeout = time.Duration(200 * time.Millisecond) 445 s.task.Set("hook-setup", &hooksup) 446 s.state.Unlock() 447 448 // Force the snap command to hang 449 cmd := testutil.MockCommand(c, "snap", "while true; do sleep 1; done") 450 defer cmd.Restore() 451 452 s.se.Ensure() 453 s.se.Wait() 454 455 s.state.Lock() 456 defer s.state.Unlock() 457 458 c.Check(s.mockHandler.BeforeCalled, Equals, true) 459 c.Check(s.mockHandler.DoneCalled, Equals, false) 460 c.Check(s.mockHandler.ErrorCalled, Equals, true) 461 c.Check(s.mockHandler.Err, ErrorMatches, `.*exceeded maximum runtime of 200ms.*`) 462 463 c.Check(s.task.Kind(), Equals, "run-hook") 464 c.Check(s.task.Status(), Equals, state.ErrorStatus) 465 c.Check(s.change.Status(), Equals, state.ErrorStatus) 466 checkTaskLogContains(c, s.task, `.*exceeded maximum runtime of 200ms`) 467 } 468 469 func (s *hookManagerSuite) TestHookTaskEnforcesDefaultTimeout(c *C) { 470 restore := hookstate.MockDefaultHookTimeout(150 * time.Millisecond) 471 defer restore() 472 473 // Force the snap command to hang 474 cmd := testutil.MockCommand(c, "snap", "while true; do sleep 1; done") 475 defer cmd.Restore() 476 477 s.se.Ensure() 478 s.se.Wait() 479 480 s.state.Lock() 481 defer s.state.Unlock() 482 483 c.Check(s.mockHandler.BeforeCalled, Equals, true) 484 c.Check(s.mockHandler.DoneCalled, Equals, false) 485 c.Check(s.mockHandler.ErrorCalled, Equals, true) 486 c.Check(s.mockHandler.Err, ErrorMatches, `.*exceeded maximum runtime of 150ms.*`) 487 488 c.Check(s.task.Kind(), Equals, "run-hook") 489 c.Check(s.task.Status(), Equals, state.ErrorStatus) 490 c.Check(s.change.Status(), Equals, state.ErrorStatus) 491 checkTaskLogContains(c, s.task, `.*exceeded maximum runtime of 150ms`) 492 } 493 494 func (s *hookManagerSuite) TestHookTaskEnforcedTimeoutWithIgnoreError(c *C) { 495 var hooksup hookstate.HookSetup 496 497 s.state.Lock() 498 s.task.Get("hook-setup", &hooksup) 499 hooksup.Timeout = time.Duration(200 * time.Millisecond) 500 hooksup.IgnoreError = true 501 s.task.Set("hook-setup", &hooksup) 502 s.state.Unlock() 503 504 // Force the snap command to hang 505 cmd := testutil.MockCommand(c, "snap", "while true; do sleep 1; done") 506 defer cmd.Restore() 507 508 s.se.Ensure() 509 s.se.Wait() 510 511 s.state.Lock() 512 defer s.state.Unlock() 513 514 c.Check(s.mockHandler.BeforeCalled, Equals, true) 515 c.Check(s.mockHandler.DoneCalled, Equals, true) 516 c.Check(s.mockHandler.ErrorCalled, Equals, false) 517 c.Check(s.mockHandler.Err, IsNil) 518 519 c.Check(s.task.Kind(), Equals, "run-hook") 520 c.Check(s.task.Status(), Equals, state.DoneStatus) 521 c.Check(s.change.Status(), Equals, state.DoneStatus) 522 checkTaskLogContains(c, s.task, `.*ignoring failure in hook.*exceeded maximum runtime of 200ms`) 523 } 524 525 func (s *hookManagerSuite) TestHookTaskCanKillHook(c *C) { 526 // Force the snap command to hang 527 cmd := testutil.MockCommand(c, "snap", "while true; do sleep 1; done") 528 defer cmd.Restore() 529 530 s.se.Ensure() 531 532 // Abort the change, which should kill the hanging hook, and 533 // wait for the task to complete. 534 s.state.Lock() 535 s.change.Abort() 536 s.state.Unlock() 537 s.se.Ensure() 538 s.se.Wait() 539 540 s.state.Lock() 541 defer s.state.Unlock() 542 543 c.Check(s.mockHandler.BeforeCalled, Equals, true) 544 c.Check(s.mockHandler.DoneCalled, Equals, false) 545 c.Check(s.mockHandler.ErrorCalled, Equals, true) 546 c.Check(s.mockHandler.Err, ErrorMatches, "<aborted>") 547 548 c.Check(s.task.Kind(), Equals, "run-hook") 549 c.Check(s.task.Status(), Equals, state.ErrorStatus) 550 c.Check(s.change.Status(), Equals, state.ErrorStatus) 551 checkTaskLogContains(c, s.task, `run hook "[^"]*": <aborted>`) 552 553 c.Check(s.manager.NumRunningHooks(), Equals, 0) 554 } 555 556 func (s *hookManagerSuite) TestHookTaskCorrectlyIncludesContext(c *C) { 557 // Force the snap command to exit with a failure and print to stderr so we 558 // can catch and verify it. 559 cmd := testutil.MockCommand( 560 c, "snap", ">&2 echo \"SNAP_COOKIE=$SNAP_COOKIE\"; exit 1") 561 defer cmd.Restore() 562 563 s.se.Ensure() 564 s.se.Wait() 565 566 s.state.Lock() 567 defer s.state.Unlock() 568 569 c.Check(s.mockHandler.BeforeCalled, Equals, true) 570 c.Check(s.mockHandler.DoneCalled, Equals, false) 571 c.Check(s.mockHandler.ErrorCalled, Equals, true) 572 573 c.Check(s.task.Kind(), Equals, "run-hook") 574 c.Check(s.task.Status(), Equals, state.ErrorStatus) 575 c.Check(s.change.Status(), Equals, state.ErrorStatus) 576 checkTaskLogContains(c, s.task, `.*SNAP_COOKIE=\S+`) 577 } 578 579 func (s *hookManagerSuite) TestHookTaskHandlerBeforeError(c *C) { 580 s.mockHandler.BeforeError = true 581 582 s.se.Ensure() 583 s.se.Wait() 584 585 s.state.Lock() 586 defer s.state.Unlock() 587 588 c.Check(s.mockHandler.BeforeCalled, Equals, true) 589 c.Check(s.mockHandler.DoneCalled, Equals, false) 590 c.Check(s.mockHandler.ErrorCalled, Equals, false) 591 592 c.Check(s.task.Kind(), Equals, "run-hook") 593 c.Check(s.task.Status(), Equals, state.ErrorStatus) 594 c.Check(s.change.Status(), Equals, state.ErrorStatus) 595 checkTaskLogContains(c, s.task, `.*Before failed at user request.*`) 596 } 597 598 func (s *hookManagerSuite) TestHookTaskHandlerDoneError(c *C) { 599 s.mockHandler.DoneError = true 600 601 s.se.Ensure() 602 s.se.Wait() 603 604 s.state.Lock() 605 defer s.state.Unlock() 606 607 c.Check(s.mockHandler.BeforeCalled, Equals, true) 608 c.Check(s.mockHandler.DoneCalled, Equals, true) 609 c.Check(s.mockHandler.ErrorCalled, Equals, false) 610 611 c.Check(s.task.Kind(), Equals, "run-hook") 612 c.Check(s.task.Status(), Equals, state.ErrorStatus) 613 c.Check(s.change.Status(), Equals, state.ErrorStatus) 614 checkTaskLogContains(c, s.task, `.*Done failed at user request.*`) 615 } 616 617 func (s *hookManagerSuite) TestHookTaskHandlerErrorError(c *C) { 618 s.mockHandler.ErrorError = true 619 620 // Force the snap command to simply exit 1, so the handler Error() runs 621 cmd := testutil.MockCommand(c, "snap", "exit 1") 622 defer cmd.Restore() 623 624 s.se.Ensure() 625 s.se.Wait() 626 627 s.state.Lock() 628 defer s.state.Unlock() 629 630 c.Check(s.mockHandler.BeforeCalled, Equals, true) 631 c.Check(s.mockHandler.DoneCalled, Equals, false) 632 c.Check(s.mockHandler.ErrorCalled, Equals, true) 633 634 c.Check(s.task.Kind(), Equals, "run-hook") 635 c.Check(s.task.Status(), Equals, state.ErrorStatus) 636 c.Check(s.change.Status(), Equals, state.ErrorStatus) 637 checkTaskLogContains(c, s.task, `.*Error failed at user request.*`) 638 } 639 640 func (s *hookManagerSuite) TestHookUndoRunsOnError(c *C) { 641 handler := hooktest.NewMockHandler() 642 undoHandler := hooktest.NewMockHandler() 643 644 s.manager.Register(regexp.MustCompile("^do-something$"), func(context *hookstate.Context) hookstate.Handler { 645 return handler 646 }) 647 s.manager.Register(regexp.MustCompile("^undo-something$"), func(context *hookstate.Context) hookstate.Handler { 648 return undoHandler 649 }) 650 651 hooksup := &hookstate.HookSetup{ 652 Snap: "test-snap", 653 Hook: "do-something", 654 Revision: snap.R(1), 655 } 656 undohooksup := &hookstate.HookSetup{ 657 Snap: "test-snap", 658 Hook: "undo-something", 659 Revision: snap.R(1), 660 } 661 662 // use unknown hook to fail the change 663 failinghooksup := &hookstate.HookSetup{ 664 Snap: "test-snap", 665 Hook: "unknown-hook", 666 Revision: snap.R(1), 667 } 668 669 initialContext := map[string]interface{}{} 670 671 s.state.Lock() 672 task := hookstate.HookTaskWithUndo(s.state, "test summary", hooksup, undohooksup, initialContext) 673 c.Assert(task, NotNil) 674 failtask := hookstate.HookTask(s.state, "test summary", failinghooksup, initialContext) 675 failtask.WaitFor(task) 676 677 change := s.state.NewChange("kind", "summary") 678 change.AddTask(task) 679 change.AddTask(failtask) 680 s.state.Unlock() 681 682 s.settle(c) 683 684 s.state.Lock() 685 defer s.state.Unlock() 686 687 c.Check(handler.BeforeCalled, Equals, true) 688 c.Check(handler.DoneCalled, Equals, true) 689 c.Check(handler.ErrorCalled, Equals, false) 690 691 c.Check(undoHandler.BeforeCalled, Equals, true) 692 c.Check(undoHandler.DoneCalled, Equals, true) 693 c.Check(undoHandler.ErrorCalled, Equals, false) 694 695 c.Check(task.Status(), Equals, state.UndoneStatus) 696 c.Check(change.Status(), Equals, state.ErrorStatus) 697 698 c.Check(s.manager.NumRunningHooks(), Equals, 0) 699 } 700 701 func (s *hookManagerSuite) TestHookWithoutHandlerIsError(c *C) { 702 hooksup := &hookstate.HookSetup{ 703 Snap: "test-snap", 704 Hook: "prepare-device", 705 Revision: snap.R(1), 706 } 707 s.state.Lock() 708 s.task.Set("hook-setup", hooksup) 709 s.state.Unlock() 710 711 s.se.Ensure() 712 s.se.Wait() 713 714 s.state.Lock() 715 defer s.state.Unlock() 716 717 c.Check(s.task.Kind(), Equals, "run-hook") 718 c.Check(s.task.Status(), Equals, state.ErrorStatus) 719 c.Check(s.change.Status(), Equals, state.ErrorStatus) 720 checkTaskLogContains(c, s.task, `.*no registered handlers for hook "prepare-device".*`) 721 } 722 723 func (s *hookManagerSuite) TestHookWithMultipleHandlersIsError(c *C) { 724 // Register multiple times for this hook 725 s.manager.Register(regexp.MustCompile("configure"), func(context *hookstate.Context) hookstate.Handler { 726 return hooktest.NewMockHandler() 727 }) 728 729 s.se.Ensure() 730 s.se.Wait() 731 732 s.state.Lock() 733 defer s.state.Unlock() 734 735 c.Check(s.task.Kind(), Equals, "run-hook") 736 c.Check(s.task.Status(), Equals, state.ErrorStatus) 737 c.Check(s.change.Status(), Equals, state.ErrorStatus) 738 739 checkTaskLogContains(c, s.task, `.*2 handlers registered for hook "configure".*`) 740 } 741 742 func (s *hookManagerSuite) TestHookWithoutHookIsError(c *C) { 743 hooksup := &hookstate.HookSetup{ 744 Snap: "test-snap", 745 Hook: "missing-hook", 746 } 747 s.state.Lock() 748 s.task.Set("hook-setup", hooksup) 749 s.state.Unlock() 750 751 s.se.Ensure() 752 s.se.Wait() 753 754 s.state.Lock() 755 defer s.state.Unlock() 756 757 c.Check(s.task.Kind(), Equals, "run-hook") 758 c.Check(s.task.Status(), Equals, state.ErrorStatus) 759 c.Check(s.change.Status(), Equals, state.ErrorStatus) 760 checkTaskLogContains(c, s.task, `.*snap "test-snap" has no "missing-hook" hook`) 761 } 762 763 func (s *hookManagerSuite) TestHookWithoutHookOptional(c *C) { 764 s.manager.Register(regexp.MustCompile("missing-hook"), func(context *hookstate.Context) hookstate.Handler { 765 return s.mockHandler 766 }) 767 768 hooksup := &hookstate.HookSetup{ 769 Snap: "test-snap", 770 Hook: "missing-hook", 771 Optional: true, 772 } 773 s.state.Lock() 774 s.task.Set("hook-setup", hooksup) 775 s.state.Unlock() 776 777 s.se.Ensure() 778 s.se.Wait() 779 780 c.Check(s.mockHandler.BeforeCalled, Equals, false) 781 c.Check(s.mockHandler.DoneCalled, Equals, false) 782 c.Check(s.mockHandler.ErrorCalled, Equals, false) 783 784 c.Check(s.command.Calls(), IsNil) 785 786 s.state.Lock() 787 defer s.state.Unlock() 788 789 c.Check(s.task.Kind(), Equals, "run-hook") 790 c.Check(s.task.Status(), Equals, state.DoneStatus) 791 c.Check(s.change.Status(), Equals, state.DoneStatus) 792 793 c.Logf("Task log:\n%s\n", s.task.Log()) 794 } 795 796 func (s *hookManagerSuite) TestHookWithoutHookAlways(c *C) { 797 s.manager.Register(regexp.MustCompile("missing-hook"), func(context *hookstate.Context) hookstate.Handler { 798 return s.mockHandler 799 }) 800 801 hooksup := &hookstate.HookSetup{ 802 Snap: "test-snap", 803 Hook: "missing-hook", 804 Optional: true, 805 Always: true, 806 } 807 s.state.Lock() 808 s.task.Set("hook-setup", hooksup) 809 s.state.Unlock() 810 811 s.se.Ensure() 812 s.se.Wait() 813 814 c.Check(s.mockHandler.BeforeCalled, Equals, true) 815 c.Check(s.mockHandler.DoneCalled, Equals, true) 816 c.Check(s.mockHandler.ErrorCalled, Equals, false) 817 818 c.Check(s.command.Calls(), IsNil) 819 820 s.state.Lock() 821 defer s.state.Unlock() 822 823 c.Check(s.task.Kind(), Equals, "run-hook") 824 c.Check(s.task.Status(), Equals, state.DoneStatus) 825 c.Check(s.change.Status(), Equals, state.DoneStatus) 826 827 c.Logf("Task log:\n%s\n", s.task.Log()) 828 } 829 830 func (s *hookManagerSuite) TestOptionalHookWithMissingHandler(c *C) { 831 hooksup := &hookstate.HookSetup{ 832 Snap: "test-snap", 833 Hook: "missing-hook-and-no-handler", 834 Optional: true, 835 } 836 s.state.Lock() 837 s.task.Set("hook-setup", hooksup) 838 s.state.Unlock() 839 840 s.se.Ensure() 841 s.se.Wait() 842 843 c.Check(s.command.Calls(), IsNil) 844 845 s.state.Lock() 846 defer s.state.Unlock() 847 848 c.Check(s.task.Kind(), Equals, "run-hook") 849 c.Check(s.task.Status(), Equals, state.DoneStatus) 850 c.Check(s.change.Status(), Equals, state.DoneStatus) 851 852 c.Logf("Task log:\n%s\n", s.task.Log()) 853 } 854 855 func checkTaskLogContains(c *C, task *state.Task, pattern string) { 856 exp := regexp.MustCompile(pattern) 857 found := false 858 for _, message := range task.Log() { 859 if exp.MatchString(message) { 860 found = true 861 } 862 } 863 864 c.Check(found, Equals, true, Commentf("Expected to find regex %q in task log: %v", pattern, task.Log())) 865 } 866 867 func (s *hookManagerSuite) TestHookTaskRunsRightSnapCmd(c *C) { 868 coreSnapCmdPath := filepath.Join(dirs.SnapMountDir, "core/12/usr/bin/snap") 869 cmd := testutil.MockCommand(c, coreSnapCmdPath, "") 870 defer cmd.Restore() 871 872 r := hookstate.MockReadlink(func(p string) (string, error) { 873 c.Assert(p, Equals, "/proc/self/exe") 874 return filepath.Join(dirs.SnapMountDir, "core/12/usr/lib/snapd/snapd"), nil 875 }) 876 defer r() 877 878 s.se.Ensure() 879 s.se.Wait() 880 881 s.state.Lock() 882 defer s.state.Unlock() 883 884 c.Assert(s.context, NotNil, Commentf("Expected handler generator to be called with a valid context")) 885 c.Check(cmd.Calls(), DeepEquals, [][]string{{ 886 "snap", "run", "--hook", "configure", "-r", "1", "test-snap", 887 }}) 888 889 } 890 891 func (s *hookManagerSuite) TestHookTaskHandlerReportsErrorIfRequested(c *C) { 892 s.state.Lock() 893 var hooksup hookstate.HookSetup 894 s.task.Get("hook-setup", &hooksup) 895 hooksup.TrackError = true 896 s.task.Set("hook-setup", &hooksup) 897 s.state.Unlock() 898 899 errtrackerCalled := false 900 hookstate.MockErrtrackerReport(func(snap, errmsg, dupSig string, extra map[string]string) (string, error) { 901 c.Check(snap, Equals, "test-snap") 902 c.Check(errmsg, Equals, "hook configure in snap \"test-snap\" failed: hook failed at user request") 903 c.Check(dupSig, Equals, "hook:test-snap:configure:exit status 1\nhook failed at user request\n") 904 905 errtrackerCalled = true 906 return "some-oopsid", nil 907 }) 908 909 // Force the snap command to exit 1, and print something to stderr 910 cmd := testutil.MockCommand( 911 c, "snap", ">&2 echo 'hook failed at user request'; exit 1") 912 defer cmd.Restore() 913 914 s.se.Ensure() 915 s.se.Wait() 916 917 s.state.Lock() 918 defer s.state.Unlock() 919 920 c.Check(errtrackerCalled, Equals, true) 921 } 922 923 func (s *hookManagerSuite) TestHookTaskHandlerReportsErrorDisabled(c *C) { 924 s.state.Lock() 925 var hooksup hookstate.HookSetup 926 s.task.Get("hook-setup", &hooksup) 927 hooksup.TrackError = true 928 s.task.Set("hook-setup", &hooksup) 929 930 tr := config.NewTransaction(s.state) 931 tr.Set("core", "problem-reports.disabled", true) 932 tr.Commit() 933 s.state.Unlock() 934 935 hookstate.MockErrtrackerReport(func(snap, errmsg, dupSig string, extra map[string]string) (string, error) { 936 c.Fatalf("no error reports should be generated") 937 return "", nil 938 }) 939 940 // Force the snap command to exit 1, and print something to stderr 941 cmd := testutil.MockCommand( 942 c, "snap", ">&2 echo 'hook failed at user request'; exit 1") 943 defer cmd.Restore() 944 945 s.se.Ensure() 946 s.se.Wait() 947 948 s.state.Lock() 949 defer s.state.Unlock() 950 } 951 952 func (s *hookManagerSuite) TestHookTasksForSameSnapAreSerialized(c *C) { 953 var Executing int32 954 var TotalExecutions int32 955 956 s.mockHandler.BeforeCallback = func() { 957 executing := atomic.AddInt32(&Executing, 1) 958 if executing != 1 { 959 panic(fmt.Sprintf("More than one handler executed: %d", executing)) 960 } 961 } 962 963 s.mockHandler.DoneCallback = func() { 964 executing := atomic.AddInt32(&Executing, -1) 965 if executing != 0 { 966 panic(fmt.Sprintf("More than one handler executed: %d", executing)) 967 } 968 atomic.AddInt32(&TotalExecutions, 1) 969 } 970 971 hooksup := &hookstate.HookSetup{ 972 Snap: "test-snap", 973 Hook: "configure", 974 Revision: snap.R(1), 975 } 976 977 s.state.Lock() 978 979 var tasks []*state.Task 980 for i := 0; i < 20; i++ { 981 task := hookstate.HookTask(s.state, "test summary", hooksup, nil) 982 c.Assert(s.task, NotNil) 983 change := s.state.NewChange("kind", "summary") 984 change.AddTask(task) 985 tasks = append(tasks, task) 986 } 987 s.state.Unlock() 988 989 s.settle(c) 990 991 s.state.Lock() 992 defer s.state.Unlock() 993 994 c.Check(s.task.Kind(), Equals, "run-hook") 995 c.Check(s.task.Status(), Equals, state.DoneStatus) 996 c.Check(s.change.Status(), Equals, state.DoneStatus) 997 998 for i := 0; i < len(tasks); i++ { 999 c.Check(tasks[i].Kind(), Equals, "run-hook") 1000 c.Check(tasks[i].Status(), Equals, state.DoneStatus) 1001 } 1002 c.Assert(atomic.LoadInt32(&TotalExecutions), Equals, int32(1+len(tasks))) 1003 c.Assert(atomic.LoadInt32(&Executing), Equals, int32(0)) 1004 } 1005 1006 type MockConcurrentHandler struct { 1007 onDone func() 1008 } 1009 1010 func (h *MockConcurrentHandler) Before() error { 1011 return nil 1012 } 1013 1014 func (h *MockConcurrentHandler) Done() error { 1015 h.onDone() 1016 return nil 1017 } 1018 1019 func (h *MockConcurrentHandler) Error(err error) error { 1020 return nil 1021 } 1022 1023 func NewMockConcurrentHandler(onDone func()) *MockConcurrentHandler { 1024 return &MockConcurrentHandler{onDone: onDone} 1025 } 1026 1027 func (s *hookManagerSuite) TestHookTasksForDifferentSnapsRunConcurrently(c *C) { 1028 hooksup1 := &hookstate.HookSetup{ 1029 Snap: "test-snap-1", 1030 Hook: "prepare-device", 1031 Revision: snap.R(1), 1032 } 1033 hooksup2 := &hookstate.HookSetup{ 1034 Snap: "test-snap-2", 1035 Hook: "prepare-device", 1036 Revision: snap.R(1), 1037 } 1038 1039 s.state.Lock() 1040 1041 sideInfo := &snap.SideInfo{RealName: "test-snap-1", SnapID: "some-snap-id1", Revision: snap.R(1)} 1042 info := snaptest.MockSnap(c, snapYaml1, sideInfo) 1043 c.Assert(info.Hooks, HasLen, 1) 1044 snapstate.Set(s.state, "test-snap-1", &snapstate.SnapState{ 1045 Active: true, 1046 Sequence: []*snap.SideInfo{sideInfo}, 1047 Current: snap.R(1), 1048 }) 1049 1050 sideInfo = &snap.SideInfo{RealName: "test-snap-2", SnapID: "some-snap-id2", Revision: snap.R(1)} 1051 snaptest.MockSnap(c, snapYaml2, sideInfo) 1052 snapstate.Set(s.state, "test-snap-2", &snapstate.SnapState{ 1053 Active: true, 1054 Sequence: []*snap.SideInfo{sideInfo}, 1055 Current: snap.R(1), 1056 }) 1057 1058 var testSnap1HookCalls, testSnap2HookCalls int 1059 ch := make(chan struct{}) 1060 mockHandler1 := NewMockConcurrentHandler(func() { 1061 ch <- struct{}{} 1062 testSnap1HookCalls++ 1063 }) 1064 mockHandler2 := NewMockConcurrentHandler(func() { 1065 <-ch 1066 testSnap2HookCalls++ 1067 }) 1068 s.manager.Register(regexp.MustCompile("prepare-device"), func(context *hookstate.Context) hookstate.Handler { 1069 if context.InstanceName() == "test-snap-1" { 1070 return mockHandler1 1071 } 1072 if context.InstanceName() == "test-snap-2" { 1073 return mockHandler2 1074 } 1075 c.Fatalf("unknown snap: %s", context.InstanceName()) 1076 return nil 1077 }) 1078 1079 task1 := hookstate.HookTask(s.state, "test summary", hooksup1, nil) 1080 c.Assert(task1, NotNil) 1081 change1 := s.state.NewChange("kind", "summary") 1082 change1.AddTask(task1) 1083 1084 task2 := hookstate.HookTask(s.state, "test summary", hooksup2, nil) 1085 c.Assert(task2, NotNil) 1086 change2 := s.state.NewChange("kind", "summary") 1087 change2.AddTask(task2) 1088 1089 s.state.Unlock() 1090 1091 s.settle(c) 1092 1093 s.state.Lock() 1094 defer s.state.Unlock() 1095 1096 c.Check(task1.Status(), Equals, state.DoneStatus) 1097 c.Check(change1.Status(), Equals, state.DoneStatus) 1098 c.Check(task2.Status(), Equals, state.DoneStatus) 1099 c.Check(change2.Status(), Equals, state.DoneStatus) 1100 c.Assert(testSnap1HookCalls, Equals, 1) 1101 c.Assert(testSnap2HookCalls, Equals, 1) 1102 } 1103 1104 func (s *hookManagerSuite) TestCompatForConfigureSnapd(c *C) { 1105 st := s.state 1106 1107 st.Lock() 1108 defer st.Unlock() 1109 1110 task := st.NewTask("configure-snapd", "Snapd between 2.29 and 2.30 in edge insertd those tasks") 1111 chg := st.NewChange("configure", "configure snapd") 1112 chg.AddTask(task) 1113 1114 st.Unlock() 1115 s.se.Ensure() 1116 s.se.Wait() 1117 st.Lock() 1118 1119 c.Check(chg.Status(), Equals, state.DoneStatus) 1120 c.Check(task.Status(), Equals, state.DoneStatus) 1121 } 1122 1123 func (s *hookManagerSuite) TestGracefullyWaitRunningHooksTimeout(c *C) { 1124 restore := hookstate.MockDefaultHookTimeout(100 * time.Millisecond) 1125 defer restore() 1126 1127 // this works even if test-snap is not present 1128 s.state.Lock() 1129 snapstate.Set(s.state, "test-snap", nil) 1130 s.state.Unlock() 1131 1132 quit := make(chan struct{}) 1133 defer func() { 1134 quit <- struct{}{} 1135 }() 1136 didRun := make(chan bool) 1137 s.mockHandler.BeforeCallback = func() { 1138 c.Check(s.manager.NumRunningHooks(), Equals, 1) 1139 go func() { 1140 didRun <- s.manager.GracefullyWaitRunningHooks() 1141 }() 1142 } 1143 1144 s.manager.RegisterHijack("configure", "test-snap", func(ctx *hookstate.Context) error { 1145 <-quit 1146 return nil 1147 }) 1148 1149 s.se.Ensure() 1150 select { 1151 case noPending := <-didRun: 1152 c.Check(noPending, Equals, false) 1153 case <-time.After(2 * time.Second): 1154 c.Fatal("timeout should have expired") 1155 } 1156 } 1157 1158 func (s *hookManagerSuite) TestSnapstateOpConflict(c *C) { 1159 s.state.Lock() 1160 defer s.state.Unlock() 1161 _, err := snapstate.Disable(s.state, "test-snap") 1162 c.Assert(err, ErrorMatches, `snap "test-snap" has "kind" change in progress`) 1163 } 1164 1165 func (s *hookManagerSuite) TestHookHijackingNoConflict(c *C) { 1166 s.state.Lock() 1167 defer s.state.Unlock() 1168 1169 s.manager.RegisterHijack("configure", "test-snap", func(ctx *hookstate.Context) error { 1170 return nil 1171 }) 1172 1173 // no conflict on hijacked hooks 1174 _, err := snapstate.Disable(s.state, "test-snap") 1175 c.Assert(err, IsNil) 1176 } 1177 1178 func (s *hookManagerSuite) TestEphemeralRunHook(c *C) { 1179 contextData := map[string]interface{}{ 1180 "key": "value", 1181 "key2": "value2", 1182 } 1183 s.testEphemeralRunHook(c, contextData) 1184 } 1185 1186 func (s *hookManagerSuite) TestEphemeralRunHookNoContextData(c *C) { 1187 var contextData map[string]interface{} = nil 1188 s.testEphemeralRunHook(c, contextData) 1189 } 1190 1191 func (s *hookManagerSuite) testEphemeralRunHook(c *C, contextData map[string]interface{}) { 1192 var hookInvokeCalled []string 1193 hookInvoke := func(ctx *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) { 1194 c.Check(ctx.HookName(), Equals, "configure") 1195 hookInvokeCalled = append(hookInvokeCalled, ctx.HookName()) 1196 1197 // check that context data was set correctly 1198 var s string 1199 ctx.Lock() 1200 defer ctx.Unlock() 1201 for k, v := range contextData { 1202 ctx.Get(k, &s) 1203 c.Check(s, Equals, v) 1204 } 1205 ctx.Set("key-set-from-hook", "value-set-from-hook") 1206 1207 return []byte("some output"), nil 1208 } 1209 restore := hookstate.MockRunHook(hookInvoke) 1210 defer restore() 1211 1212 hooksup := &hookstate.HookSetup{ 1213 Snap: "test-snap", 1214 Revision: snap.R(1), 1215 Hook: "configure", 1216 } 1217 context, err := s.manager.EphemeralRunHook(context.Background(), hooksup, contextData) 1218 c.Assert(err, IsNil) 1219 c.Check(hookInvokeCalled, DeepEquals, []string{"configure"}) 1220 1221 var value string 1222 context.Lock() 1223 context.Get("key-set-from-hook", &value) 1224 context.Unlock() 1225 c.Check(value, Equals, "value-set-from-hook") 1226 } 1227 1228 func (s *hookManagerSuite) TestEphemeralRunHookNoSnap(c *C) { 1229 hookInvoke := func(ctx *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) { 1230 c.Fatalf("hook should not be invoked in this test") 1231 return nil, nil 1232 } 1233 restore := hookstate.MockRunHook(hookInvoke) 1234 defer restore() 1235 1236 hooksup := &hookstate.HookSetup{ 1237 Snap: "not-installed-snap", 1238 Revision: snap.R(1), 1239 Hook: "configure", 1240 } 1241 contextData := map[string]interface{}{ 1242 "key": "value", 1243 } 1244 _, err := s.manager.EphemeralRunHook(context.Background(), hooksup, contextData) 1245 c.Assert(err, ErrorMatches, `cannot run ephemeral hook "configure" for snap "not-installed-snap": no state entry for key`) 1246 } 1247 1248 func (s *hookManagerSuite) TestEphemeralRunHookContextCanCancel(c *C) { 1249 tombDying := 0 1250 hookRunning := make(chan struct{}) 1251 1252 hookInvoke := func(_ *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) { 1253 close(hookRunning) 1254 1255 select { 1256 case <-tomb.Dying(): 1257 tombDying++ 1258 case <-time.After(10 * time.Second): 1259 c.Fatalf("hook not canceled after 10s") 1260 } 1261 return nil, nil 1262 } 1263 restore := hookstate.MockRunHook(hookInvoke) 1264 defer restore() 1265 1266 hooksup := &hookstate.HookSetup{ 1267 Snap: "test-snap", 1268 Revision: snap.R(1), 1269 Hook: "configure", 1270 } 1271 1272 ctx, cancelFunc := context.WithCancel(context.Background()) 1273 go func() { 1274 <-hookRunning 1275 cancelFunc() 1276 }() 1277 _, err := s.manager.EphemeralRunHook(ctx, hooksup, nil) 1278 c.Assert(err, IsNil) 1279 c.Check(tombDying, Equals, 1) 1280 } 1281 1282 type parallelInstancesHookManagerSuite struct { 1283 baseHookManagerSuite 1284 } 1285 1286 var _ = Suite(¶llelInstancesHookManagerSuite{}) 1287 1288 func (s *parallelInstancesHookManagerSuite) SetUpTest(c *C) { 1289 s.commonSetUpTest(c) 1290 s.setUpSnap(c, "test-snap_instance", snapYaml) 1291 } 1292 1293 func (s *parallelInstancesHookManagerSuite) TearDownTest(c *C) { 1294 s.commonTearDownTest(c) 1295 } 1296 1297 func (s *parallelInstancesHookManagerSuite) TestHookTaskEnsureHookRan(c *C) { 1298 didRun := make(chan bool) 1299 s.mockHandler.BeforeCallback = func() { 1300 c.Check(s.manager.NumRunningHooks(), Equals, 1) 1301 go func() { 1302 didRun <- s.manager.GracefullyWaitRunningHooks() 1303 }() 1304 } 1305 s.se.Ensure() 1306 select { 1307 case ok := <-didRun: 1308 c.Check(ok, Equals, true) 1309 case <-time.After(5 * time.Second): 1310 c.Fatal("hook run should have been done by now") 1311 } 1312 s.se.Wait() 1313 1314 s.state.Lock() 1315 defer s.state.Unlock() 1316 1317 c.Check(s.context.InstanceName(), Equals, "test-snap_instance") 1318 c.Check(s.context.SnapRevision(), Equals, snap.R(1)) 1319 c.Check(s.context.HookName(), Equals, "configure") 1320 1321 c.Check(s.command.Calls(), DeepEquals, [][]string{{ 1322 "snap", "run", "--hook", "configure", "-r", "1", "test-snap_instance", 1323 }}) 1324 1325 c.Check(s.mockHandler.BeforeCalled, Equals, true) 1326 c.Check(s.mockHandler.DoneCalled, Equals, true) 1327 c.Check(s.mockHandler.ErrorCalled, Equals, false) 1328 1329 c.Check(s.task.Kind(), Equals, "run-hook") 1330 c.Check(s.task.Status(), Equals, state.DoneStatus) 1331 c.Check(s.change.Status(), Equals, state.DoneStatus) 1332 1333 c.Check(s.manager.NumRunningHooks(), Equals, 0) 1334 }