gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/state/change_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 state_test 21 22 import ( 23 "fmt" 24 "sort" 25 "strconv" 26 "strings" 27 "time" 28 29 . "gopkg.in/check.v1" 30 31 "github.com/snapcore/snapd/overlord/state" 32 ) 33 34 type changeSuite struct{} 35 36 var _ = Suite(&changeSuite{}) 37 38 func (cs *changeSuite) TestNewChange(c *C) { 39 st := state.New(nil) 40 st.Lock() 41 defer st.Unlock() 42 43 chg := st.NewChange("install", "summary...") 44 c.Check(chg.Kind(), Equals, "install") 45 c.Check(chg.Summary(), Equals, "summary...") 46 } 47 48 func (cs *changeSuite) TestReadyTime(c *C) { 49 st := state.New(nil) 50 st.Lock() 51 defer st.Unlock() 52 53 chg := st.NewChange("install", "summary...") 54 55 now := time.Now() 56 57 t := chg.SpawnTime() 58 c.Check(t.After(now.Add(-5*time.Second)), Equals, true) 59 c.Check(t.Before(now.Add(5*time.Second)), Equals, true) 60 61 c.Check(chg.ReadyTime().IsZero(), Equals, true) 62 63 chg.SetStatus(state.DoneStatus) 64 65 t = chg.ReadyTime() 66 c.Check(t.After(now.Add(-5*time.Second)), Equals, true) 67 c.Check(t.Before(now.Add(5*time.Second)), Equals, true) 68 } 69 70 func (cs *changeSuite) TestStatusString(c *C) { 71 for s := state.Status(0); s < state.ErrorStatus+1; s++ { 72 c.Assert(s.String(), Matches, ".+") 73 } 74 } 75 76 func (cs *changeSuite) TestGetSet(c *C) { 77 st := state.New(nil) 78 st.Lock() 79 defer st.Unlock() 80 81 chg := st.NewChange("install", "...") 82 83 chg.Set("a", 1) 84 85 var v int 86 err := chg.Get("a", &v) 87 c.Assert(err, IsNil) 88 c.Check(v, Equals, 1) 89 } 90 91 // TODO Better testing of full change roundtripping via JSON. 92 93 func (cs *changeSuite) TestNewTaskAddTaskAndTasks(c *C) { 94 st := state.New(nil) 95 st.Lock() 96 defer st.Unlock() 97 98 chg := st.NewChange("install", "...") 99 100 t1 := st.NewTask("download", "1...") 101 chg.AddTask(t1) 102 t2 := st.NewTask("verify", "2...") 103 chg.AddTask(t2) 104 105 tasks := chg.Tasks() 106 // Tasks must return tasks in the order they were added (first)! 107 c.Check(tasks, DeepEquals, []*state.Task{t1, t2}) 108 c.Check(t1.Change(), Equals, chg) 109 c.Check(t2.Change(), Equals, chg) 110 111 chg2 := st.NewChange("install", "...") 112 c.Check(func() { chg2.AddTask(t1) }, PanicMatches, `internal error: cannot add one "download" task to multiple changes`) 113 } 114 115 func (cs *changeSuite) TestAddAll(c *C) { 116 st := state.New(nil) 117 st.Lock() 118 defer st.Unlock() 119 120 chg := st.NewChange("install", "...") 121 122 t1 := st.NewTask("download", "1...") 123 t2 := st.NewTask("verify", "2...") 124 chg.AddAll(state.NewTaskSet(t1, t2)) 125 126 tasks := chg.Tasks() 127 c.Check(tasks, DeepEquals, []*state.Task{t1, t2}) 128 c.Check(t1.Change(), Equals, chg) 129 c.Check(t2.Change(), Equals, chg) 130 } 131 132 func (cs *changeSuite) TestStatusExplicitlyDefined(c *C) { 133 st := state.New(nil) 134 st.Lock() 135 defer st.Unlock() 136 137 chg := st.NewChange("install", "...") 138 c.Assert(chg.Status(), Equals, state.HoldStatus) 139 140 t := st.NewTask("download", "...") 141 chg.AddTask(t) 142 143 t.SetStatus(state.DoingStatus) 144 c.Assert(chg.Status(), Equals, state.DoingStatus) 145 chg.SetStatus(state.ErrorStatus) 146 c.Assert(chg.Status(), Equals, state.ErrorStatus) 147 } 148 149 func (cs *changeSuite) TestLaneTasks(c *C) { 150 st := state.New(nil) 151 st.Lock() 152 defer st.Unlock() 153 154 chg := st.NewChange("change", "...") 155 156 lane1 := st.NewLane() 157 lane2 := st.NewLane() 158 159 t1 := st.NewTask("task1", "...") 160 t2 := st.NewTask("task2", "...") 161 t3 := st.NewTask("task3", "...") 162 t4 := st.NewTask("task4", "...") 163 t5 := st.NewTask("task5", "...") 164 t6 := st.NewTask("task6", "...") 165 166 // lane1: task1, task2, task4 167 // lane2: task3, task4 168 t1.JoinLane(lane1) 169 t2.JoinLane(lane1) 170 t3.JoinLane(lane2) 171 t4.JoinLane(lane1) 172 t4.JoinLane(lane2) 173 174 chg.AddTask(t1) 175 chg.AddTask(t2) 176 chg.AddTask(t3) 177 chg.AddTask(t4) 178 chg.AddTask(t5) 179 chg.AddTask(t6) 180 181 checkTasks := func(obtained, expected []*state.Task) { 182 c.Assert(obtained, HasLen, len(expected)) 183 184 tasks1 := make([]string, len(obtained)) 185 tasks2 := make([]string, len(expected)) 186 187 for i, t := range obtained { 188 tasks1[i] = t.ID() 189 } 190 for i, t := range expected { 191 tasks2[i] = t.ID() 192 } 193 194 sort.Strings(tasks1) 195 sort.Strings(tasks2) 196 197 c.Assert(tasks1, DeepEquals, tasks2) 198 } 199 200 c.Assert(chg.LaneTasks(), HasLen, 0) 201 202 tasks := chg.LaneTasks(0) 203 checkTasks(tasks, []*state.Task{t5, t6}) 204 205 tasks = chg.LaneTasks(0, lane2) 206 checkTasks(tasks, []*state.Task{t3, t4, t5, t6}) 207 208 tasks = chg.LaneTasks(lane1) 209 checkTasks(tasks, []*state.Task{t1, t2, t4}) 210 211 tasks = chg.LaneTasks(lane2) 212 checkTasks(tasks, []*state.Task{t3, t4}) 213 214 tasks = chg.LaneTasks(lane1, lane2) 215 checkTasks(tasks, []*state.Task{t1, t2, t3, t4}) 216 } 217 218 func (cs *changeSuite) TestStatusDerivedFromTasks(c *C) { 219 st := state.New(nil) 220 st.Lock() 221 defer st.Unlock() 222 223 chg := st.NewChange("install", "...") 224 225 // Nothing to do with it if there are no tasks. 226 c.Assert(chg.Status(), Equals, state.HoldStatus) 227 228 tasks := make(map[state.Status]*state.Task) 229 230 for s := state.DefaultStatus + 1; s < state.ErrorStatus+1; s++ { 231 t := st.NewTask("download", s.String()) 232 t.SetStatus(s) 233 chg.AddTask(t) 234 tasks[s] = t 235 } 236 237 order := []state.Status{ 238 state.AbortStatus, 239 state.UndoingStatus, 240 state.UndoStatus, 241 state.DoingStatus, 242 state.DoStatus, 243 state.ErrorStatus, 244 state.UndoneStatus, 245 state.DoneStatus, 246 state.HoldStatus, 247 } 248 249 for _, s := range order { 250 // Set all tasks with previous statuses to s as well. 251 for _, s2 := range order { 252 if s == s2 { 253 break 254 } 255 tasks[s2].SetStatus(s) 256 } 257 c.Assert(chg.Status(), Equals, s) 258 } 259 } 260 261 func (cs *changeSuite) TestCloseReadyOnExplicitStatus(c *C) { 262 st := state.New(nil) 263 st.Lock() 264 defer st.Unlock() 265 266 chg := st.NewChange("install", "...") 267 268 select { 269 case <-chg.Ready(): 270 c.Fatalf("Change should not be ready") 271 default: 272 } 273 c.Assert(chg.IsReady(), Equals, false) 274 275 chg.SetStatus(state.ErrorStatus) 276 277 select { 278 case <-chg.Ready(): 279 default: 280 c.Fatalf("Change should be ready") 281 } 282 c.Assert(chg.IsReady(), Equals, true) 283 } 284 285 func (cs *changeSuite) TestCloseReadyWhenTasksReady(c *C) { 286 st := state.New(nil) 287 st.Lock() 288 defer st.Unlock() 289 290 chg := st.NewChange("install", "...") 291 t1 := st.NewTask("download", "...") 292 t2 := st.NewTask("download", "...") 293 chg.AddTask(t1) 294 chg.AddTask(t2) 295 296 select { 297 case <-chg.Ready(): 298 c.Fatalf("Change should not be ready") 299 default: 300 } 301 c.Assert(chg.IsReady(), Equals, false) 302 303 t1.SetStatus(state.DoneStatus) 304 305 select { 306 case <-chg.Ready(): 307 c.Fatalf("Change should not be ready") 308 default: 309 } 310 c.Assert(chg.IsReady(), Equals, false) 311 312 t2.SetStatus(state.DoneStatus) 313 314 select { 315 case <-chg.Ready(): 316 default: 317 c.Fatalf("Change should be ready") 318 } 319 c.Assert(chg.IsReady(), Equals, true) 320 } 321 322 func (cs *changeSuite) TestIsClean(c *C) { 323 st := state.New(nil) 324 st.Lock() 325 defer st.Unlock() 326 327 chg := st.NewChange("install", "...") 328 329 t1 := st.NewTask("download", "1...") 330 t2 := st.NewTask("verify", "2...") 331 chg.AddAll(state.NewTaskSet(t1, t2)) 332 333 t1.SetStatus(state.DoneStatus) 334 c.Assert(t1.SetClean, PanicMatches, ".*while change not ready") 335 t2.SetStatus(state.DoneStatus) 336 337 t1.SetClean() 338 c.Assert(chg.IsClean(), Equals, false) 339 t2.SetClean() 340 c.Assert(chg.IsClean(), Equals, true) 341 } 342 343 func (cs *changeSuite) TestState(c *C) { 344 st := state.New(nil) 345 st.Lock() 346 chg := st.NewChange("install", "...") 347 st.Unlock() 348 349 c.Assert(chg.State(), Equals, st) 350 } 351 352 func (cs *changeSuite) TestErr(c *C) { 353 st := state.New(nil) 354 st.Lock() 355 defer st.Unlock() 356 357 chg := st.NewChange("install", "...") 358 359 t1 := st.NewTask("download", "Download") 360 t2 := st.NewTask("activate", "Activate") 361 362 chg.AddTask(t1) 363 chg.AddTask(t2) 364 365 c.Assert(chg.Err(), IsNil) 366 367 // t2 still running so change not yet in ErrorStatus 368 t1.SetStatus(state.ErrorStatus) 369 c.Assert(chg.Err(), IsNil) 370 371 t2.SetStatus(state.ErrorStatus) 372 c.Assert(chg.Err(), ErrorMatches, `internal inconsistency: change "install" in ErrorStatus with no task errors logged`) 373 374 t1.Errorf("Download error") 375 c.Assert(chg.Err(), ErrorMatches, ""+ 376 "cannot perform the following tasks:\n"+ 377 "- Download \\(Download error\\)") 378 379 t2.Errorf("Activate error") 380 c.Assert(chg.Err(), ErrorMatches, ""+ 381 "cannot perform the following tasks:\n"+ 382 "- Download \\(Download error\\)\n"+ 383 "- Activate \\(Activate error\\)") 384 } 385 386 func (cs *changeSuite) TestMethodEntrance(c *C) { 387 st := state.New(&fakeStateBackend{}) 388 st.Lock() 389 chg := st.NewChange("install", "...") 390 st.Unlock() 391 392 writes := []func(){ 393 func() { chg.Set("a", 1) }, 394 func() { chg.SetStatus(state.DoStatus) }, 395 func() { chg.AddTask(nil) }, 396 func() { chg.AddAll(nil) }, 397 func() { chg.UnmarshalJSON(nil) }, 398 } 399 400 reads := []func(){ 401 func() { chg.Get("a", nil) }, 402 func() { chg.Status() }, 403 func() { chg.IsClean() }, 404 func() { chg.Tasks() }, 405 func() { chg.Err() }, 406 func() { chg.MarshalJSON() }, 407 func() { chg.SpawnTime() }, 408 func() { chg.ReadyTime() }, 409 } 410 411 for i, f := range reads { 412 c.Logf("Testing read function #%d", i) 413 c.Assert(f, PanicMatches, "internal error: accessing state without lock") 414 c.Assert(st.Modified(), Equals, false) 415 } 416 417 for i, f := range writes { 418 st.Lock() 419 st.Unlock() 420 c.Assert(st.Modified(), Equals, false) 421 422 c.Logf("Testing write function #%d", i) 423 c.Assert(f, PanicMatches, "internal error: accessing state without lock") 424 c.Assert(st.Modified(), Equals, true) 425 } 426 } 427 428 func (cs *changeSuite) TestAbort(c *C) { 429 st := state.New(nil) 430 st.Lock() 431 defer st.Unlock() 432 433 chg := st.NewChange("install", "...") 434 435 for s := state.DefaultStatus + 1; s < state.ErrorStatus+1; s++ { 436 t := st.NewTask("download", s.String()) 437 t.SetStatus(s) 438 t.Set("old-status", s) 439 chg.AddTask(t) 440 } 441 442 chg.Abort() 443 444 tasks := chg.Tasks() 445 for _, t := range tasks { 446 var s state.Status 447 err := t.Get("old-status", &s) 448 c.Assert(err, IsNil) 449 450 c.Logf("Checking %s task after abort", t.Summary()) 451 switch s { 452 case state.DoStatus: 453 c.Assert(t.Status(), Equals, state.HoldStatus) 454 case state.DoneStatus: 455 c.Assert(t.Status(), Equals, state.UndoStatus) 456 case state.DoingStatus: 457 c.Assert(t.Status(), Equals, state.AbortStatus) 458 default: 459 c.Assert(t.Status(), Equals, s) 460 } 461 } 462 } 463 464 func (cs *changeSuite) TestAbortCircular(c *C) { 465 st := state.New(nil) 466 st.Lock() 467 defer st.Unlock() 468 469 chg := st.NewChange("circular", "...") 470 471 t1 := st.NewTask("one", "one") 472 t2 := st.NewTask("two", "two") 473 t1.WaitFor(t2) 474 t2.WaitFor(t1) 475 chg.AddTask(t1) 476 chg.AddTask(t2) 477 478 chg.Abort() 479 480 tasks := chg.Tasks() 481 for _, t := range tasks { 482 c.Assert(t.Status(), Equals, state.HoldStatus) 483 } 484 } 485 486 func (cs *changeSuite) TestAbortKⁿ(c *C) { 487 st := state.New(nil) 488 st.Lock() 489 defer st.Unlock() 490 491 chg := st.NewChange("Kⁿ", "...") 492 493 var prev *state.TaskSet 494 N := 22 // ∛10,000 495 for i := 0; i < N; i++ { 496 ts := make([]*state.Task, N) 497 for j := range ts { 498 name := fmt.Sprintf("task-%d", j) 499 ts[j] = st.NewTask(name, name) 500 } 501 t := state.NewTaskSet(ts...) 502 if prev != nil { 503 t.WaitAll(prev) 504 } 505 prev = t 506 chg.AddAll(t) 507 508 for j := 0; j < N; j++ { 509 lid := st.NewLane() 510 for k := range ts { 511 name := fmt.Sprintf("task-%d-%d", lid, k) 512 ts[k] = st.NewTask(name, name) 513 } 514 t := state.NewTaskSet(ts...) 515 t.WaitAll(prev) 516 chg.AddAll(t) 517 } 518 } 519 chg.Abort() 520 521 tasks := chg.Tasks() 522 for _, t := range tasks { 523 c.Assert(t.Status(), Equals, state.HoldStatus) 524 } 525 } 526 527 // Task wait order: 528 // 529 // => t21 => t22 530 // / \ 531 // t11 => t12 => t41 => t42 532 // \ / 533 // => t31 => t32 534 // 535 // setup and result lines are <task>:<status>[:<lane>,...] 536 // 537 // "*" as task name means "all remaining". 538 // 539 var abortLanesTests = []struct { 540 setup string 541 abort []int 542 result string 543 }{ 544 545 // Some basics. 546 { 547 setup: "*:do", 548 abort: []int{}, 549 result: "*:do", 550 }, { 551 setup: "*:do", 552 abort: []int{1}, 553 result: "*:do", 554 }, { 555 setup: "*:do", 556 abort: []int{0}, 557 result: "*:hold", 558 }, { 559 setup: "t11:done t12:doing t22:do", 560 abort: []int{0}, 561 result: "t11:undo t12:abort t22:hold", 562 }, 563 564 // => t21 (2) => t22 (2) 565 // / \ 566 // t11 (1) => t12 (1) => t41 (4) => t42 (4) 567 // \ / 568 // => t31 (3) => t32 (3) 569 { 570 setup: "t11:do:1 t12:do:1 t21:do:2 t22:do:2 t31:do:3 t32:do:3 t41:do:4 t42:do:4", 571 abort: []int{0}, 572 result: "*:do", 573 }, { 574 setup: "t11:do:1 t12:do:1 t21:do:2 t22:do:2 t31:do:3 t32:do:3 t41:do:4 t42:do:4", 575 abort: []int{1}, 576 result: "*:hold", 577 }, { 578 setup: "t11:do:1 t12:do:1 t21:do:2 t22:do:2 t31:do:3 t32:do:3 t41:do:4 t42:do:4", 579 abort: []int{2}, 580 result: "t21:hold t22:hold t41:hold t42:hold *:do", 581 }, { 582 setup: "t11:do:1 t12:do:1 t21:do:2 t22:do:2 t31:do:3 t32:do:3 t41:do:4 t42:do:4", 583 abort: []int{3}, 584 result: "t31:hold t32:hold t41:hold t42:hold *:do", 585 }, { 586 setup: "t11:do:1 t12:do:1 t21:do:2 t22:do:2 t31:do:3 t32:do:3 t41:do:4 t42:do:4", 587 abort: []int{2, 3}, 588 result: "t21:hold t22:hold t31:hold t32:hold t41:hold t42:hold *:do", 589 }, { 590 setup: "t11:do:1 t12:do:1 t21:do:2 t22:do:2 t31:do:3 t32:do:3 t41:do:4 t42:do:4", 591 abort: []int{4}, 592 result: "t41:hold t42:hold *:do", 593 }, { 594 setup: "t11:do:1 t12:do:1 t21:do:2 t22:do:2 t31:do:3 t32:do:3 t41:do:4 t42:do:4", 595 abort: []int{5}, 596 result: "*:do", 597 }, 598 599 // => t21 (2) => t22 (2) 600 // / \ 601 // t11 (2,3) => t12 (2,3) => t41 (4) => t42 (4) 602 // \ / 603 // => t31 (3) => t32 (3) 604 { 605 setup: "t11:do:2,3 t12:do:2,3 t21:do:2 t22:do:2 t31:do:3 t32:do:3 t41:do:4 t42:do:4", 606 abort: []int{2}, 607 result: "t21:hold t22:hold t41:hold t42:hold *:do", 608 }, { 609 setup: "t11:do:2,3 t12:do:2,3 t21:do:2 t22:do:2 t31:do:3 t32:do:3 t41:do:4 t42:do:4", 610 abort: []int{3}, 611 result: "t31:hold t32:hold t41:hold t42:hold *:do", 612 }, { 613 setup: "t11:do:2,3 t12:do:2,3 t21:do:2 t22:do:2 t31:do:3 t32:do:3 t41:do:4 t42:do:4", 614 abort: []int{2, 3}, 615 result: "*:hold", 616 }, 617 618 // => t21 (1) => t22 (1) 619 // / \ 620 // t11 (1) => t12 (1) => t41 (4) => t42 (4) 621 // \ / 622 // => t31 (1) => t32 (1) 623 { 624 setup: "t41:error:4 t42:do:4 *:do:1", 625 abort: []int{1}, 626 result: "t41:error *:hold", 627 }, 628 } 629 630 func (ts *taskRunnerSuite) TestAbortLanes(c *C) { 631 632 names := strings.Fields("t11 t12 t21 t22 t31 t32 t41 t42") 633 634 for _, test := range abortLanesTests { 635 sb := &stateBackend{} 636 st := state.New(sb) 637 r := state.NewTaskRunner(st) 638 defer r.Stop() 639 640 st.Lock() 641 defer st.Unlock() 642 643 c.Assert(len(st.Tasks()), Equals, 0) 644 645 chg := st.NewChange("install", "...") 646 tasks := make(map[string]*state.Task) 647 for _, name := range names { 648 tasks[name] = st.NewTask("do", name) 649 chg.AddTask(tasks[name]) 650 } 651 tasks["t12"].WaitFor(tasks["t11"]) 652 tasks["t21"].WaitFor(tasks["t12"]) 653 tasks["t22"].WaitFor(tasks["t21"]) 654 tasks["t31"].WaitFor(tasks["t12"]) 655 tasks["t32"].WaitFor(tasks["t31"]) 656 tasks["t41"].WaitFor(tasks["t22"]) 657 tasks["t41"].WaitFor(tasks["t32"]) 658 tasks["t42"].WaitFor(tasks["t41"]) 659 660 c.Logf("-----") 661 c.Logf("Testing setup: %s", test.setup) 662 663 statuses := make(map[string]state.Status) 664 for s := state.DefaultStatus; s <= state.ErrorStatus; s++ { 665 statuses[strings.ToLower(s.String())] = s 666 } 667 668 items := strings.Fields(test.setup) 669 seen := make(map[string]bool) 670 for i := 0; i < len(items); i++ { 671 item := items[i] 672 parts := strings.Split(item, ":") 673 if parts[0] == "*" { 674 for _, name := range names { 675 if !seen[name] { 676 parts[0] = name 677 items = append(items, strings.Join(parts, ":")) 678 } 679 } 680 continue 681 } 682 seen[parts[0]] = true 683 task := tasks[parts[0]] 684 task.SetStatus(statuses[parts[1]]) 685 if len(parts) > 2 { 686 lanes := strings.Split(parts[2], ",") 687 for _, lane := range lanes { 688 n, err := strconv.Atoi(lane) 689 c.Assert(err, IsNil) 690 task.JoinLane(n) 691 } 692 } 693 } 694 695 c.Logf("Aborting with: %v", test.abort) 696 697 chg.AbortLanes(test.abort) 698 699 c.Logf("Expected result: %s", test.result) 700 701 seen = make(map[string]bool) 702 var expected = strings.Fields(test.result) 703 var obtained []string 704 for i := 0; i < len(expected); i++ { 705 item := expected[i] 706 parts := strings.Split(item, ":") 707 if parts[0] == "*" { 708 var expanded []string 709 for _, name := range names { 710 if !seen[name] { 711 parts[0] = name 712 expanded = append(expanded, strings.Join(parts, ":")) 713 } 714 } 715 expected = append(expected[:i], append(expanded, expected[i+1:]...)...) 716 i-- 717 continue 718 } 719 name := parts[0] 720 seen[parts[0]] = true 721 obtained = append(obtained, name+":"+strings.ToLower(tasks[name].Status().String())) 722 } 723 724 c.Assert(strings.Join(obtained, " "), Equals, strings.Join(expected, " "), Commentf("setup: %s", test.setup)) 725 } 726 }