gitee.com/mysnapcore/mysnapd@v0.1.0/bootloader/bootloadertest/bootloadertest.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2020 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 bootloadertest 21 22 import ( 23 "fmt" 24 "strings" 25 26 "gitee.com/mysnapcore/mysnapd/bootloader" 27 "gitee.com/mysnapcore/mysnapd/snap" 28 ) 29 30 // MockBootloader mocks the bootloader interface and records all 31 // set/get calls. 32 type MockBootloader struct { 33 MockedPresent bool 34 PresentErr error 35 36 BootVars map[string]string 37 SetBootVarsCalls int 38 SetErr error 39 SetErrFunc func() error 40 GetErr error 41 42 name string 43 bootdir string 44 45 ExtractKernelAssetsCalls []snap.PlaceInfo 46 RemoveKernelAssetsCalls []snap.PlaceInfo 47 48 InstallBootConfigCalled []string 49 InstallBootConfigErr error 50 51 enabledKernel snap.PlaceInfo 52 enabledTryKernel snap.PlaceInfo 53 54 panicMethods map[string]bool 55 } 56 57 // ensure MockBootloader(s) implement the Bootloader interface 58 var _ bootloader.Bootloader = (*MockBootloader)(nil) 59 var _ bootloader.RecoveryAwareBootloader = (*MockRecoveryAwareBootloader)(nil) 60 var _ bootloader.TrustedAssetsBootloader = (*MockTrustedAssetsBootloader)(nil) 61 var _ bootloader.ExtractedRunKernelImageBootloader = (*MockExtractedRunKernelImageBootloader)(nil) 62 var _ bootloader.ExtractedRecoveryKernelImageBootloader = (*MockExtractedRecoveryKernelImageBootloader)(nil) 63 var _ bootloader.RecoveryAwareBootloader = (*MockRecoveryAwareTrustedAssetsBootloader)(nil) 64 var _ bootloader.TrustedAssetsBootloader = (*MockRecoveryAwareTrustedAssetsBootloader)(nil) 65 var _ bootloader.NotScriptableBootloader = (*MockNotScriptableBootloader)(nil) 66 var _ bootloader.NotScriptableBootloader = (*MockExtractedRecoveryKernelNotScriptableBootloader)(nil) 67 var _ bootloader.ExtractedRecoveryKernelImageBootloader = (*MockExtractedRecoveryKernelNotScriptableBootloader)(nil) 68 var _ bootloader.RebootBootloader = (*MockRebootBootloader)(nil) 69 70 func Mock(name, bootdir string) *MockBootloader { 71 return &MockBootloader{ 72 name: name, 73 bootdir: bootdir, 74 75 BootVars: make(map[string]string), 76 77 panicMethods: make(map[string]bool), 78 } 79 } 80 81 func (b *MockBootloader) maybePanic(which string) { 82 if b.panicMethods[which] { 83 panic(fmt.Sprintf("mocked reboot panic in %s", which)) 84 } 85 } 86 87 func (b *MockBootloader) SetBootVars(values map[string]string) error { 88 b.maybePanic("SetBootVars") 89 b.SetBootVarsCalls++ 90 for k, v := range values { 91 b.BootVars[k] = v 92 } 93 if b.SetErrFunc != nil { 94 return b.SetErrFunc() 95 } 96 return b.SetErr 97 } 98 99 func (b *MockBootloader) GetBootVars(keys ...string) (map[string]string, error) { 100 b.maybePanic("GetBootVars") 101 102 out := map[string]string{} 103 for _, k := range keys { 104 out[k] = b.BootVars[k] 105 } 106 107 return out, b.GetErr 108 } 109 110 func (b *MockBootloader) Name() string { 111 return b.name 112 } 113 114 func (b *MockBootloader) Present() (bool, error) { 115 return b.MockedPresent, b.PresentErr 116 } 117 118 func (b *MockBootloader) ExtractKernelAssets(s snap.PlaceInfo, snapf snap.Container) error { 119 b.ExtractKernelAssetsCalls = append(b.ExtractKernelAssetsCalls, s) 120 return nil 121 } 122 123 func (b *MockBootloader) RemoveKernelAssets(s snap.PlaceInfo) error { 124 b.RemoveKernelAssetsCalls = append(b.RemoveKernelAssetsCalls, s) 125 return nil 126 } 127 128 func (b *MockBootloader) SetEnabledKernel(s snap.PlaceInfo) (restore func()) { 129 oldSn := b.enabledTryKernel 130 oldVar := b.BootVars["snap_kernel"] 131 b.enabledKernel = s 132 b.BootVars["snap_kernel"] = s.Filename() 133 return func() { 134 b.BootVars["snap_kernel"] = oldVar 135 b.enabledKernel = oldSn 136 } 137 } 138 139 func (b *MockBootloader) SetEnabledTryKernel(s snap.PlaceInfo) (restore func()) { 140 oldSn := b.enabledTryKernel 141 oldVar := b.BootVars["snap_try_kernel"] 142 b.enabledTryKernel = s 143 b.BootVars["snap_try_kernel"] = s.Filename() 144 return func() { 145 b.BootVars["snap_try_kernel"] = oldVar 146 b.enabledTryKernel = oldSn 147 } 148 } 149 150 // InstallBootConfig installs the boot config in the gadget directory to the 151 // mock bootloader's root directory. 152 func (b *MockBootloader) InstallBootConfig(gadgetDir string, opts *bootloader.Options) error { 153 b.InstallBootConfigCalled = append(b.InstallBootConfigCalled, gadgetDir) 154 return b.InstallBootConfigErr 155 } 156 157 // SetMockToPanic allows setting any method in the Bootloader interface or derived 158 // interface to panic instead of returning. This allows one to test what would 159 // happen if the system was rebooted during execution of a particular function. 160 // Specifically, the panic will be done immediately entering the function so 161 // setting SetBootVars to panic will emulate a reboot before any boot vars are 162 // set persistently 163 func (b *MockBootloader) SetMockToPanic(f string) (restore func()) { 164 switch f { 165 // XXX: update this list as more calls in this interface or derived ones 166 // are added 167 case "SetBootVars", "GetBootVars", 168 "EnableKernel", "EnableTryKernel", "Kernel", "TryKernel", "DisableTryKernel": 169 170 old := b.panicMethods[f] 171 b.panicMethods[f] = true 172 return func() { 173 b.panicMethods[f] = old 174 } 175 default: 176 panic(fmt.Sprintf("unknown bootloader method %q to mock reboot via panic for", f)) 177 } 178 } 179 180 // MockRecoveryAwareMixin implements the RecoveryAware interface. 181 type MockRecoveryAwareMixin struct { 182 RecoverySystemDir string 183 RecoverySystemBootVars map[string]string 184 } 185 186 // MockRecoveryAwareBootloader mocks a bootloader implementing the 187 // RecoveryAware interface. 188 type MockRecoveryAwareBootloader struct { 189 *MockBootloader 190 MockRecoveryAwareMixin 191 } 192 193 // RecoveryAware derives a MockRecoveryAwareBootloader from a base 194 // MockBootloader. 195 func (b *MockBootloader) RecoveryAware() *MockRecoveryAwareBootloader { 196 return &MockRecoveryAwareBootloader{MockBootloader: b} 197 } 198 199 // SetRecoverySystemEnv sets the recovery system environment bootloader 200 // variables; part of RecoveryAwareBootloader. 201 func (b *MockRecoveryAwareMixin) SetRecoverySystemEnv(recoverySystemDir string, blVars map[string]string) error { 202 if recoverySystemDir == "" { 203 panic("MockBootloader.SetRecoverySystemEnv called without recoverySystemDir") 204 } 205 b.RecoverySystemDir = recoverySystemDir 206 b.RecoverySystemBootVars = blVars 207 return nil 208 } 209 210 // GetRecoverySystemEnv gets the recovery system environment bootloader 211 // variables; part of RecoveryAwareBootloader. 212 func (b *MockRecoveryAwareMixin) GetRecoverySystemEnv(recoverySystemDir, key string) (string, error) { 213 if recoverySystemDir == "" { 214 panic("MockBootloader.GetRecoverySystemEnv called without recoverySystemDir") 215 } 216 b.RecoverySystemDir = recoverySystemDir 217 return b.RecoverySystemBootVars[key], nil 218 } 219 220 type ExtractedRecoveryKernelCall struct { 221 RecoverySystemDir string 222 S snap.PlaceInfo 223 } 224 225 // MockExtractedRecoveryKernelImageBootloader mocks a bootloader implementing 226 // the ExtractedRecoveryKernelImage interface. 227 type MockExtractedRecoveryKernelImageBootloader struct { 228 *MockBootloader 229 230 ExtractRecoveryKernelAssetsCalls []ExtractedRecoveryKernelCall 231 } 232 233 // ExtractedRecoveryKernelImage derives a MockRecoveryAwareBootloader from a base 234 // MockBootloader. 235 func (b *MockBootloader) ExtractedRecoveryKernelImage() *MockExtractedRecoveryKernelImageBootloader { 236 return &MockExtractedRecoveryKernelImageBootloader{MockBootloader: b} 237 } 238 239 // ExtractRecoveryKernelAssets extracts the kernel assets for the provided 240 // kernel snap into the specified recovery system dir; part of 241 // RecoveryAwareBootloader. 242 func (b *MockExtractedRecoveryKernelImageBootloader) ExtractRecoveryKernelAssets(recoverySystemDir string, s snap.PlaceInfo, snapf snap.Container) error { 243 if recoverySystemDir == "" { 244 panic("MockBootloader.ExtractRecoveryKernelAssets called without recoverySystemDir") 245 } 246 247 b.ExtractRecoveryKernelAssetsCalls = append( 248 b.ExtractRecoveryKernelAssetsCalls, 249 ExtractedRecoveryKernelCall{ 250 S: s, 251 RecoverySystemDir: recoverySystemDir}, 252 ) 253 return nil 254 } 255 256 // MockExtractedRunKernelImageMixin implements the 257 // ExtractedRunKernelImageBootloader interface. 258 type MockExtractedRunKernelImageMixin struct { 259 runKernelImageEnableKernelCalls []snap.PlaceInfo 260 runKernelImageEnableTryKernelCalls []snap.PlaceInfo 261 runKernelImageEnabledKernel snap.PlaceInfo 262 runKernelImageEnabledTryKernel snap.PlaceInfo 263 264 runKernelImageMockedErrs map[string]error 265 runKernelImageMockedNumCalls map[string]int 266 267 maybePanic func(name string) 268 } 269 270 // MockExtractedRunKernelImageBootloader mocks a bootloader 271 // implementing the ExtractedRunKernelImageBootloader interface. 272 type MockExtractedRunKernelImageBootloader struct { 273 *MockBootloader 274 275 MockExtractedRunKernelImageMixin 276 } 277 278 func (b *MockExtractedRunKernelImageBootloader) SetEnabledKernel(kernel snap.PlaceInfo) (restore func()) { 279 // pick the right implementation 280 return b.MockExtractedRunKernelImageMixin.SetEnabledKernel(kernel) 281 } 282 283 func (b *MockExtractedRunKernelImageBootloader) SetEnabledTryKernel(kernel snap.PlaceInfo) (restore func()) { 284 // pick the right implementation 285 return b.MockExtractedRunKernelImageMixin.SetEnabledTryKernel(kernel) 286 } 287 288 // WithExtractedRunKernelImage derives a MockExtractedRunKernelImageBootloader 289 // from a base MockBootloader. 290 func (b *MockBootloader) WithExtractedRunKernelImage() *MockExtractedRunKernelImageBootloader { 291 return &MockExtractedRunKernelImageBootloader{ 292 MockBootloader: b, 293 294 MockExtractedRunKernelImageMixin: MockExtractedRunKernelImageMixin{ 295 runKernelImageMockedErrs: make(map[string]error), 296 runKernelImageMockedNumCalls: make(map[string]int), 297 maybePanic: b.maybePanic, 298 }, 299 } 300 } 301 302 // SetEnabledKernel sets the current kernel "symlink" as returned 303 // by Kernel(); returns' a restore function to set it back to what it was 304 // before. 305 func (b *MockExtractedRunKernelImageMixin) SetEnabledKernel(kernel snap.PlaceInfo) (restore func()) { 306 old := b.runKernelImageEnabledKernel 307 b.runKernelImageEnabledKernel = kernel 308 return func() { 309 b.runKernelImageEnabledKernel = old 310 } 311 } 312 313 // SetEnabledTryKernel sets the current try-kernel "symlink" as 314 // returned by TryKernel(). If set to nil, TryKernel()'s second return value 315 // will be false; returns' a restore function to set it back to what it was 316 // before. 317 func (b *MockExtractedRunKernelImageMixin) SetEnabledTryKernel(kernel snap.PlaceInfo) (restore func()) { 318 old := b.runKernelImageEnabledTryKernel 319 b.runKernelImageEnabledTryKernel = kernel 320 return func() { 321 b.runKernelImageEnabledTryKernel = old 322 } 323 } 324 325 // SetRunKernelImageFunctionError allows setting an error to be returned for the 326 // specified function; it returns a restore function to set it back to what it 327 // was before. 328 func (b *MockExtractedRunKernelImageMixin) SetRunKernelImageFunctionError(f string, err error) (restore func()) { 329 // check the function 330 switch f { 331 case "EnableKernel", "EnableTryKernel", "Kernel", "TryKernel", "DisableTryKernel": 332 old := b.runKernelImageMockedErrs[f] 333 b.runKernelImageMockedErrs[f] = err 334 return func() { 335 b.runKernelImageMockedErrs[f] = old 336 } 337 default: 338 panic(fmt.Sprintf("unknown ExtractedRunKernelImageBootloader method %q to mock error for", f)) 339 } 340 } 341 342 // GetRunKernelImageFunctionSnapCalls returns which snaps were specified during 343 // execution, in order of calls, as well as the number of calls for methods that 344 // don't take a snap to set. 345 func (b *MockExtractedRunKernelImageMixin) GetRunKernelImageFunctionSnapCalls(f string) ([]snap.PlaceInfo, int) { 346 switch f { 347 case "EnableKernel": 348 l := b.runKernelImageEnableKernelCalls 349 return l, len(l) 350 case "EnableTryKernel": 351 l := b.runKernelImageEnableTryKernelCalls 352 return l, len(l) 353 case "Kernel", "TryKernel", "DisableTryKernel": 354 return nil, b.runKernelImageMockedNumCalls[f] 355 default: 356 panic(fmt.Sprintf("unknown ExtractedRunKernelImageBootloader method %q to return snap args for", f)) 357 } 358 } 359 360 // EnableKernel enables the kernel; part of ExtractedRunKernelImageBootloader. 361 func (b *MockExtractedRunKernelImageMixin) EnableKernel(s snap.PlaceInfo) error { 362 b.maybePanic("EnableKernel") 363 b.runKernelImageEnableKernelCalls = append(b.runKernelImageEnableKernelCalls, s) 364 b.runKernelImageEnabledKernel = s 365 return b.runKernelImageMockedErrs["EnableKernel"] 366 } 367 368 // EnableTryKernel enables a try-kernel; part of 369 // ExtractedRunKernelImageBootloader. 370 func (b *MockExtractedRunKernelImageMixin) EnableTryKernel(s snap.PlaceInfo) error { 371 b.maybePanic("EnableTryKernel") 372 b.runKernelImageEnableTryKernelCalls = append(b.runKernelImageEnableTryKernelCalls, s) 373 b.runKernelImageEnabledTryKernel = s 374 return b.runKernelImageMockedErrs["EnableTryKernel"] 375 } 376 377 // Kernel returns the current kernel set in the bootloader; part of 378 // ExtractedRunKernelImageBootloader. 379 func (b *MockExtractedRunKernelImageMixin) Kernel() (snap.PlaceInfo, error) { 380 b.maybePanic("Kernel") 381 b.runKernelImageMockedNumCalls["Kernel"]++ 382 err := b.runKernelImageMockedErrs["Kernel"] 383 if err != nil { 384 return nil, err 385 } 386 return b.runKernelImageEnabledKernel, nil 387 } 388 389 // TryKernel returns the current kernel set in the bootloader; part of 390 // ExtractedRunKernelImageBootloader. 391 func (b *MockExtractedRunKernelImageMixin) TryKernel() (snap.PlaceInfo, error) { 392 b.maybePanic("TryKernel") 393 b.runKernelImageMockedNumCalls["TryKernel"]++ 394 err := b.runKernelImageMockedErrs["TryKernel"] 395 if err != nil { 396 return nil, err 397 } 398 if b.runKernelImageEnabledTryKernel == nil { 399 return nil, bootloader.ErrNoTryKernelRef 400 } 401 return b.runKernelImageEnabledTryKernel, nil 402 } 403 404 // DisableTryKernel removes the current try-kernel "symlink" set in the 405 // bootloader; part of ExtractedRunKernelImageBootloader. 406 func (b *MockExtractedRunKernelImageMixin) DisableTryKernel() error { 407 b.maybePanic("DisableTryKernel") 408 b.runKernelImageMockedNumCalls["DisableTryKernel"]++ 409 b.runKernelImageEnabledTryKernel = nil 410 return b.runKernelImageMockedErrs["DisableTryKernel"] 411 } 412 413 // MockTrustedAssetsMixin implements the bootloader.TrustedAssetsBootloader 414 // interface. 415 type MockTrustedAssetsMixin struct { 416 TrustedAssetsList []string 417 TrustedAssetsErr error 418 TrustedAssetsCalls int 419 420 RecoveryBootChainList []bootloader.BootFile 421 RecoveryBootChainErr error 422 BootChainList []bootloader.BootFile 423 BootChainErr error 424 425 RecoveryBootChainCalls []string 426 BootChainRunBl []bootloader.Bootloader 427 BootChainKernelPath []string 428 429 UpdateErr error 430 UpdateCalls int 431 Updated bool 432 ManagedAssetsList []string 433 StaticCommandLine string 434 CandidateStaticCommandLine string 435 CommandLineErr error 436 } 437 438 // MockTrustedAssetsBootloader mocks a bootloader implementing the 439 // bootloader.TrustedAssetsBootloader interface. 440 type MockTrustedAssetsBootloader struct { 441 *MockBootloader 442 443 MockTrustedAssetsMixin 444 } 445 446 func (b *MockBootloader) WithTrustedAssets() *MockTrustedAssetsBootloader { 447 return &MockTrustedAssetsBootloader{ 448 MockBootloader: b, 449 } 450 } 451 452 func (b *MockTrustedAssetsMixin) ManagedAssets() []string { 453 return b.ManagedAssetsList 454 } 455 456 func (b *MockTrustedAssetsMixin) UpdateBootConfig() (bool, error) { 457 b.UpdateCalls++ 458 return b.Updated, b.UpdateErr 459 } 460 461 func glueCommandLine(pieces bootloader.CommandLineComponents, staticArgs string) (string, error) { 462 if err := pieces.Validate(); err != nil { 463 return "", err 464 } 465 466 args := []string(nil) 467 extraOrFull := []string{staticArgs, pieces.ExtraArgs} 468 if pieces.FullArgs != "" { 469 extraOrFull = []string{pieces.FullArgs} 470 } 471 for _, argSet := range append([]string{pieces.ModeArg, pieces.SystemArg}, extraOrFull...) { 472 if argSet != "" { 473 args = append(args, argSet) 474 } 475 } 476 line := strings.Join(args, " ") 477 return strings.TrimSpace(line), nil 478 } 479 480 func (b *MockTrustedAssetsMixin) CommandLine(pieces bootloader.CommandLineComponents) (string, error) { 481 if b.CommandLineErr != nil { 482 return "", b.CommandLineErr 483 } 484 return glueCommandLine(pieces, b.StaticCommandLine) 485 } 486 487 func (b *MockTrustedAssetsMixin) CandidateCommandLine(pieces bootloader.CommandLineComponents) (string, error) { 488 if b.CommandLineErr != nil { 489 return "", b.CommandLineErr 490 } 491 return glueCommandLine(pieces, b.CandidateStaticCommandLine) 492 } 493 494 func (b *MockTrustedAssetsMixin) TrustedAssets() ([]string, error) { 495 b.TrustedAssetsCalls++ 496 return b.TrustedAssetsList, b.TrustedAssetsErr 497 } 498 499 func (b *MockTrustedAssetsMixin) RecoveryBootChain(kernelPath string) ([]bootloader.BootFile, error) { 500 b.RecoveryBootChainCalls = append(b.RecoveryBootChainCalls, kernelPath) 501 return b.RecoveryBootChainList, b.RecoveryBootChainErr 502 } 503 504 func (b *MockTrustedAssetsMixin) BootChain(runBl bootloader.Bootloader, kernelPath string) ([]bootloader.BootFile, error) { 505 b.BootChainRunBl = append(b.BootChainRunBl, runBl) 506 b.BootChainKernelPath = append(b.BootChainKernelPath, kernelPath) 507 return b.BootChainList, b.BootChainErr 508 } 509 510 // MockRecoveryAwareTrustedAssetsBootloader implements the 511 // bootloader.RecoveryAwareBootloader and bootloader.TrustedAssetsBootloader 512 // interfaces. 513 type MockRecoveryAwareTrustedAssetsBootloader struct { 514 *MockBootloader 515 516 MockRecoveryAwareMixin 517 MockTrustedAssetsMixin 518 } 519 520 func (b *MockBootloader) WithRecoveryAwareTrustedAssets() *MockRecoveryAwareTrustedAssetsBootloader { 521 return &MockRecoveryAwareTrustedAssetsBootloader{ 522 MockBootloader: b, 523 } 524 } 525 526 // MockNotScriptableBootloader implements the 527 // bootloader.NotScriptableBootloader interface. 528 type MockNotScriptableBootloader struct { 529 *MockBootloader 530 } 531 532 func (b *MockBootloader) WithNotScriptable() *MockNotScriptableBootloader { 533 return &MockNotScriptableBootloader{ 534 MockBootloader: b, 535 } 536 } 537 538 func (b *MockNotScriptableBootloader) SetBootVarsFromInitramfs(values map[string]string) error { 539 for k, v := range values { 540 b.BootVars[k] = v 541 } 542 return nil 543 } 544 545 // MockExtractedRecoveryKernelNotScriptableBootloader implements the 546 // bootloader.ExtractedRecoveryKernelImageBootloader interface and 547 // includes MockNotScriptableBootloader 548 type MockExtractedRecoveryKernelNotScriptableBootloader struct { 549 *MockNotScriptableBootloader 550 551 ExtractRecoveryKernelAssetsCalls []ExtractedRecoveryKernelCall 552 } 553 554 func (b *MockNotScriptableBootloader) WithExtractedRecoveryKernel() *MockExtractedRecoveryKernelNotScriptableBootloader { 555 return &MockExtractedRecoveryKernelNotScriptableBootloader{ 556 MockNotScriptableBootloader: b, 557 } 558 } 559 560 // ExtractRecoveryKernelAssets extracts the kernel assets for the provided 561 // kernel snap into the specified recovery system dir; part of 562 // RecoveryAwareBootloader. 563 func (b *MockExtractedRecoveryKernelNotScriptableBootloader) ExtractRecoveryKernelAssets(recoverySystemDir string, s snap.PlaceInfo, snapf snap.Container) error { 564 if recoverySystemDir == "" { 565 panic("MockBootloader.ExtractRecoveryKernelAssets called without recoverySystemDir") 566 } 567 568 b.ExtractRecoveryKernelAssetsCalls = append( 569 b.ExtractRecoveryKernelAssetsCalls, 570 ExtractedRecoveryKernelCall{ 571 S: s, 572 RecoverySystemDir: recoverySystemDir}, 573 ) 574 return nil 575 } 576 577 // MockRebootBootloaderMixin implements the bootloader.RebootBootloader 578 // interface. 579 type MockRebootBootloaderMixin struct { 580 RebootArgs string 581 } 582 583 // MockRebootBootloader mocks a bootloader implementing the 584 // bootloader.RebootBootloader interface. 585 type MockRebootBootloader struct { 586 *MockBootloader 587 588 MockRebootBootloaderMixin 589 } 590 591 func (b *MockRebootBootloaderMixin) GetRebootArguments() (string, error) { 592 return b.RebootArgs, nil 593 } 594 595 func (b *MockBootloader) WithRebootBootloader() *MockRebootBootloader { 596 return &MockRebootBootloader{ 597 MockBootloader: b, 598 } 599 }