1# Lint as: python2, python3 2# Copyright 2016 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Unit tests for the `repair` module.""" 7 8# pylint: disable=missing-docstring 9 10from __future__ import absolute_import 11from __future__ import division 12from __future__ import print_function 13 14import functools 15import logging 16import unittest 17 18import common 19from autotest_lib.client.common_lib import hosts 20from autotest_lib.client.common_lib.hosts import repair 21from autotest_lib.server import constants 22from autotest_lib.server.hosts import host_info 23from six.moves import range 24 25 26class _GoodVerifier(hosts.Verifier): 27 """Verifier is always good""" 28 def verify(self, host): 29 pass 30 31 32class _BadVerifier(hosts.Verifier): 33 """Verifier is always fail""" 34 def verify(self, host): 35 raise Exception('Just not your day') 36 37 38class _SkipVerifier(hosts.Verifier): 39 """Verifier is always not applicable""" 40 def verify(self, host): 41 pass 42 43 def _is_applicable(self, host): 44 return False 45 46 47class _StubHost(object): 48 """ 49 Stub class to fill in the relevant methods of `Host`. 50 51 This class provides mocking and stub behaviors for `Host` for use by 52 tests within this module. The class implements only those methods 53 that `Verifier` and `RepairAction` actually use. 54 """ 55 56 def __init__(self): 57 self._record_sequence = [] 58 fake_board_name = constants.Labels.BOARD_PREFIX + 'fubar' 59 info = host_info.HostInfo(labels=[fake_board_name]) 60 self.host_info_store = host_info.InMemoryHostInfoStore(info) 61 self.hostname = 'unittest_host' 62 63 64 def record(self, status_code, subdir, operation, status=''): 65 """ 66 Mock method to capture records written to `status.log`. 67 68 Each record is remembered in order to be checked for correctness 69 by individual tests later. 70 71 @param status_code As for `Host.record()`. 72 @param subdir As for `Host.record()`. 73 @param operation As for `Host.record()`. 74 @param status As for `Host.record()`. 75 """ 76 full_record = (status_code, subdir, operation, status) 77 self._record_sequence.append(full_record) 78 79 80 def get_log_records(self): 81 """ 82 Return the records logged for this fake host. 83 84 The returned list of records excludes records where the 85 `operation` parameter is not in `tagset`. 86 87 @param tagset Only include log records with these tags. 88 """ 89 return self._record_sequence 90 91 92 def reset_log_records(self): 93 """Clear our history of log records to allow re-testing.""" 94 self._record_sequence = [] 95 96 97class _StubVerifier(hosts.Verifier): 98 """ 99 Stub implementation of `Verifier` for testing purposes. 100 101 This is a full implementation of a concrete `Verifier` subclass 102 designed to allow calling unit tests control over whether verify 103 passes or fails. 104 105 A `_StubVerifier()` will pass whenever the value of `_fail_count` 106 is non-zero. Calls to `try_repair()` (typically made by a 107 `_StubRepairAction()`) will reduce this count, eventually 108 "repairing" the verifier. 109 110 @property verify_count The number of calls made to the instance's 111 `verify()` method. 112 @property message If verification fails, the exception raised, 113 when converted to a string, will have this 114 value. 115 @property _fail_count The number of repair attempts required 116 before this verifier will succeed. A 117 non-zero value means verification will fail. 118 @property _description The value of the `description` property. 119 """ 120 121 def __init__(self, tag, deps, fail_count): 122 super(_StubVerifier, self).__init__(tag, deps) 123 self.verify_count = 0 124 self.message = 'Failing "%s" by request' % tag 125 self._fail_count = fail_count 126 self._description = 'Testing verify() for "%s"' % tag 127 self._log_record_map = { 128 r[0]: r for r in [ 129 ('GOOD', None, self._record_tag, ''), 130 ('FAIL', None, self._record_tag, self.message), 131 ] 132 } 133 134 135 def __repr__(self): 136 return '_StubVerifier(%r, %r, %r)' % ( 137 self.tag, self._dependency_list, self._fail_count) 138 139 140 def verify(self, host): 141 self.verify_count += 1 142 if self._fail_count: 143 raise hosts.AutoservVerifyError(self.message) 144 145 146 def try_repair(self): 147 """Bring ourselves one step closer to working.""" 148 if self._fail_count: 149 self._fail_count -= 1 150 151 152 def unrepair(self): 153 """Make ourselves more broken.""" 154 self._fail_count += 1 155 156 157 def get_log_record(self, status): 158 """ 159 Return a host log record for this verifier. 160 161 Calculates the arguments expected to be passed to 162 `Host.record()` by `Verifier._verify_host()` when this verifier 163 runs. The passed in `status` corresponds to the argument of the 164 same name to be passed to `Host.record()`. 165 166 @param status Status value of the log record. 167 """ 168 return self._log_record_map[status] 169 170 171 @property 172 def description(self): 173 return self._description 174 175 176class _StubRepairFailure(Exception): 177 """Exception to be raised by `_StubRepairAction.repair()`.""" 178 pass 179 180 181class _StubRepairAction(hosts.RepairAction): 182 """Stub implementation of `RepairAction` for testing purposes. 183 184 This is a full implementation of a concrete `RepairAction` subclass 185 designed to allow calling unit tests control over whether repair 186 passes or fails. 187 188 The behavior of `repair()` depends on the `_success` property of a 189 `_StubRepairAction`. When the property is true, repair will call 190 `try_repair()` for all triggers, and then report success. When the 191 property is false, repair reports failure. 192 193 @property repair_count The number of calls made to the instance's 194 `repair()` method. 195 @property message If repair fails, the exception raised, when 196 converted to a string, will have this value. 197 @property _success Whether repair will follow its "success" or 198 "failure" paths. 199 @property _description The value of the `description` property. 200 """ 201 202 def __init__(self, tag, deps, triggers, host_class, success): 203 super(_StubRepairAction, self).__init__(tag, deps, triggers, 204 host_class) 205 self.repair_count = 0 206 self.message = 'Failed repair for "%s"' % tag 207 self._success = success 208 self._description = 'Testing repair for "%s"' % tag 209 self._log_record_map = { 210 r[0]: r for r in [ 211 ('START', None, self._record_tag, ''), 212 ('FAIL', None, self._record_tag, self.message), 213 ('END FAIL', None, self._record_tag, ''), 214 ('END GOOD', None, self._record_tag, ''), 215 ] 216 } 217 218 219 def __repr__(self): 220 return '_StubRepairAction(%r, %r, %r, %r)' % ( 221 self.tag, self._dependency_list, 222 self._trigger_list, self._success) 223 224 225 def repair(self, host): 226 self.repair_count += 1 227 if not self._success: 228 raise _StubRepairFailure(self.message) 229 for v in self._trigger_list: 230 v.try_repair() 231 232 233 def get_log_record(self, status): 234 """ 235 Return a host log record for this repair action. 236 237 Calculates the arguments expected to be passed to 238 `Host.record()` by `RepairAction._repair_host()` when repair 239 runs. The passed in `status` corresponds to the argument of the 240 same name to be passed to `Host.record()`. 241 242 @param status Status value of the log record. 243 """ 244 return self._log_record_map[status] 245 246 247 @property 248 def description(self): 249 return self._description 250 251 252class _DependencyNodeTestCase(unittest.TestCase): 253 """ 254 Abstract base class for `RepairAction` and `Verifier` test cases. 255 256 This class provides `_make_verifier()` and `_make_repair_action()` 257 methods to create `_StubVerifier` and `_StubRepairAction` instances, 258 respectively, for testing. Constructed verifiers and repair actions 259 are remembered in `self.nodes`, a dictionary indexed by the tag 260 used to construct the object. 261 """ 262 263 def setUp(self): 264 logging.disable(logging.CRITICAL) 265 self._fake_host = _StubHost() 266 self.nodes = {} 267 268 269 def tearDown(self): 270 logging.disable(logging.NOTSET) 271 272 273 def _make_verifier(self, count, tag, deps): 274 """ 275 Make a `_StubVerifier` and remember it in `self.nodes`. 276 277 @param count As for the `_StubVerifer` constructor. 278 @param tag As for the `_StubVerifer` constructor. 279 @param deps As for the `_StubVerifer` constructor. 280 """ 281 verifier = _StubVerifier(tag, deps, count) 282 self.nodes[tag] = verifier 283 return verifier 284 285 286 def _make_repair_action(self, success, tag, deps, triggers, 287 host_class='unittest'): 288 """ 289 Make a `_StubRepairAction` and remember it in `self.nodes`. 290 291 @param success As for the `_StubRepairAction` constructor. 292 @param tag As for the `_StubRepairAction` constructor. 293 @param deps As for the `_StubRepairAction` constructor. 294 @param triggers As for the `_StubRepairAction` constructor. 295 @param host_class As for the `_StubRepairAction` constructor. 296 """ 297 repair_action = _StubRepairAction(tag, deps, triggers, host_class, 298 success) 299 self.nodes[tag] = repair_action 300 return repair_action 301 302 303 def _make_expected_failures(self, *verifiers): 304 """ 305 Make a set of `_DependencyFailure` objects from `verifiers`. 306 307 Return the set of `_DependencyFailure` objects that we would 308 expect to see in the `failures` attribute of an 309 `AutoservVerifyDependencyError` if all of the given verifiers 310 report failure. 311 312 @param verifiers A list of `_StubVerifier` objects that are 313 expected to fail. 314 315 @return A set of `_DependencyFailure` objects. 316 """ 317 failures = [repair._DependencyFailure(v.description, v.message, v.tag) 318 for v in verifiers] 319 return set(failures) 320 321 322 def _generate_silent(self): 323 """ 324 Iterator to test different settings of the `silent` parameter. 325 326 This iterator exists to standardize testing assertions that 327 This iterator exists to standardize testing common 328 assertions about the `silent` parameter: 329 * When the parameter is true, no calls are made to the 330 `record()` method on the target host. 331 * When the parameter is false, certain expected calls are made 332 to the `record()` method on the target host. 333 334 The iterator is meant to be used like this: 335 336 for silent in self._generate_silent(): 337 # run test case that uses the silent parameter 338 self._check_log_records(silent, ... expected records ... ) 339 340 The code above will run its test case twice, once with 341 `silent=True` and once with `silent=False`. In between the 342 calls, log records are cleared. 343 344 @yields A boolean setting for `silent`. 345 """ 346 for silent in [False, True]: 347 yield silent 348 self._fake_host.reset_log_records() 349 350 351 def _check_log_records(self, silent, *record_data): 352 """ 353 Assert that log records occurred as expected. 354 355 Elements of `record_data` should be tuples of the form 356 `(tag, status)`, describing one expected log record. 357 The verifier or repair action for `tag` provides the expected 358 log record based on the status value. 359 360 The `silent` parameter is the value that was passed to the 361 verifier or repair action that did the logging. When true, 362 it indicates that no records should have been logged. 363 364 @param record_data List describing the expected record events. 365 @param silent When true, ignore `record_data` and assert 366 that nothing was logged. 367 """ 368 expected_records = [] 369 if not silent: 370 for tag, status in record_data: 371 expected_records.append( 372 self.nodes[tag].get_log_record(status)) 373 actual_records = self._fake_host.get_log_records() 374 self.assertEqual(expected_records, actual_records) 375 376 377class VerifyTests(_DependencyNodeTestCase): 378 """ 379 Unit tests for `Verifier`. 380 381 The tests in this class test the fundamental behaviors of the 382 `Verifier` class: 383 * Results from the `verify()` method are cached; the method is 384 only called the first time that `_verify_host()` is called. 385 * The `_verify_host()` method makes the expected calls to 386 `Host.record()` for every call to the `verify()` method. 387 * When a dependency fails, the dependent verifier isn't called. 388 * Verifier calls are made in the order required by the DAG. 389 390 The test cases don't use `RepairStrategy` to build DAG structures, 391 but instead rely on custom-built DAGs. 392 """ 393 394 def _generate_verify_count(self, verifier): 395 """ 396 Iterator to force a standard sequence with calls to `_reverify()`. 397 398 This iterator exists to standardize testing two common 399 assertions: 400 * The side effects from calling `_verify_host()` only 401 happen on the first call to the method, except... 402 * Calling `_reverify()` resets a verifier so that the 403 next call to `_verify_host()` will repeat the side 404 effects. 405 406 The iterator is meant to be used like this: 407 408 for count in self._generate_verify_cases(verifier): 409 # run a verifier._verify_host() test case 410 self.assertEqual(verifier.verify_count, count) 411 self._check_log_records(silent, ... expected records ... ) 412 413 The code above will run the `_verify_host()` test case twice, 414 then call `_reverify()` to clear cached results, then re-run 415 the test case two more times. 416 417 @param verifier The verifier to be tested and reverified. 418 @yields Each iteration yields the number of times `_reverify()` 419 has been called. 420 """ 421 for i in range(1, 3): 422 for _ in range(0, 2): 423 yield i 424 verifier._reverify() 425 self._fake_host.reset_log_records() 426 427 428 def test_success(self): 429 """ 430 Test proper handling of a successful verification. 431 432 Construct and call a simple, single-node verification that will 433 pass. Assert the following: 434 * The `verify()` method is called once. 435 * The expected 'GOOD' record is logged via `Host.record()`. 436 * If `_verify_host()` is called more than once, there are no 437 visible side-effects after the first call. 438 * Calling `_reverify()` clears all cached results. 439 """ 440 for silent in self._generate_silent(): 441 verifier = self._make_verifier(0, 'pass', []) 442 for count in self._generate_verify_count(verifier): 443 verifier._verify_host(self._fake_host, silent) 444 self.assertEqual(verifier.verify_count, count) 445 self._check_log_records(silent, ('pass', 'GOOD')) 446 447 448 def test_fail(self): 449 """ 450 Test proper handling of verification failure. 451 452 Construct and call a simple, single-node verification that will 453 fail. Assert the following: 454 * The failure is reported with the actual exception raised 455 by the verifier. 456 * The `verify()` method is called once. 457 * The expected 'FAIL' record is logged via `Host.record()`. 458 * If `_verify_host()` is called more than once, there are no 459 visible side-effects after the first call. 460 * Calling `_reverify()` clears all cached results. 461 """ 462 for silent in self._generate_silent(): 463 verifier = self._make_verifier(1, 'fail', []) 464 for count in self._generate_verify_count(verifier): 465 with self.assertRaises(hosts.AutoservVerifyError) as e: 466 verifier._verify_host(self._fake_host, silent) 467 self.assertEqual(verifier.verify_count, count) 468 self.assertEqual(verifier.message, str(e.exception)) 469 self._check_log_records(silent, ('fail', 'FAIL')) 470 471 472 def test_dependency_success(self): 473 """ 474 Test proper handling of dependencies that succeed. 475 476 Construct and call a two-node verification with one node 477 dependent on the other, where both nodes will pass. Assert the 478 following: 479 * The `verify()` method for both nodes is called once. 480 * The expected 'GOOD' record is logged via `Host.record()` 481 for both nodes. 482 * If `_verify_host()` is called more than once, there are no 483 visible side-effects after the first call. 484 * Calling `_reverify()` clears all cached results. 485 """ 486 for silent in self._generate_silent(): 487 child = self._make_verifier(0, 'pass', []) 488 parent = self._make_verifier(0, 'parent', [child]) 489 for count in self._generate_verify_count(parent): 490 parent._verify_host(self._fake_host, silent) 491 self.assertEqual(parent.verify_count, count) 492 self.assertEqual(child.verify_count, count) 493 self._check_log_records(silent, 494 ('pass', 'GOOD'), 495 ('parent', 'GOOD')) 496 497 498 def test_dependency_fail(self): 499 """ 500 Test proper handling of dependencies that fail. 501 502 Construct and call a two-node verification with one node 503 dependent on the other, where the dependency will fail. Assert 504 the following: 505 * The verification exception is `AutoservVerifyDependencyError`, 506 and the exception argument is the description of the failed 507 node. 508 * The `verify()` method for the failing node is called once, 509 and for the other node, not at all. 510 * The expected 'FAIL' record is logged via `Host.record()` 511 for the single failed node. 512 * If `_verify_host()` is called more than once, there are no 513 visible side-effects after the first call. 514 * Calling `_reverify()` clears all cached results. 515 """ 516 for silent in self._generate_silent(): 517 child = self._make_verifier(1, 'fail', []) 518 parent = self._make_verifier(0, 'parent', [child]) 519 failures = self._make_expected_failures(child) 520 for count in self._generate_verify_count(parent): 521 expected_exception = hosts.AutoservVerifyDependencyError 522 with self.assertRaises(expected_exception) as e: 523 parent._verify_host(self._fake_host, silent) 524 self.assertEqual(e.exception.failures, failures) 525 self.assertEqual(child.verify_count, count) 526 self.assertEqual(parent.verify_count, 0) 527 self._check_log_records(silent, ('fail', 'FAIL')) 528 529 530 def test_two_dependencies_pass(self): 531 """ 532 Test proper handling with two passing dependencies. 533 534 Construct and call a three-node verification with one node 535 dependent on the other two, where all nodes will pass. Assert 536 the following: 537 * The `verify()` method for all nodes is called once. 538 * The expected 'GOOD' records are logged via `Host.record()` 539 for all three nodes. 540 * If `_verify_host()` is called more than once, there are no 541 visible side-effects after the first call. 542 * Calling `_reverify()` clears all cached results. 543 """ 544 for silent in self._generate_silent(): 545 left = self._make_verifier(0, 'left', []) 546 right = self._make_verifier(0, 'right', []) 547 top = self._make_verifier(0, 'top', [left, right]) 548 for count in self._generate_verify_count(top): 549 top._verify_host(self._fake_host, silent) 550 self.assertEqual(top.verify_count, count) 551 self.assertEqual(left.verify_count, count) 552 self.assertEqual(right.verify_count, count) 553 self._check_log_records(silent, 554 ('left', 'GOOD'), 555 ('right', 'GOOD'), 556 ('top', 'GOOD')) 557 558 559 def test_two_dependencies_fail(self): 560 """ 561 Test proper handling with two failing dependencies. 562 563 Construct and call a three-node verification with one node 564 dependent on the other two, where both dependencies will fail. 565 Assert the following: 566 * The verification exception is `AutoservVerifyDependencyError`, 567 and the exception argument has the descriptions of both the 568 failed nodes. 569 * The `verify()` method for each failing node is called once, 570 and for the parent node not at all. 571 * The expected 'FAIL' records are logged via `Host.record()` 572 for the failing nodes. 573 * If `_verify_host()` is called more than once, there are no 574 visible side-effects after the first call. 575 * Calling `_reverify()` clears all cached results. 576 """ 577 for silent in self._generate_silent(): 578 left = self._make_verifier(1, 'left', []) 579 right = self._make_verifier(1, 'right', []) 580 top = self._make_verifier(0, 'top', [left, right]) 581 failures = self._make_expected_failures(left, right) 582 for count in self._generate_verify_count(top): 583 expected_exception = hosts.AutoservVerifyDependencyError 584 with self.assertRaises(expected_exception) as e: 585 top._verify_host(self._fake_host, silent) 586 self.assertEqual(e.exception.failures, failures) 587 self.assertEqual(top.verify_count, 0) 588 self.assertEqual(left.verify_count, count) 589 self.assertEqual(right.verify_count, count) 590 self._check_log_records(silent, 591 ('left', 'FAIL'), 592 ('right', 'FAIL')) 593 594 595 def test_two_dependencies_mixed(self): 596 """ 597 Test proper handling with mixed dependencies. 598 599 Construct and call a three-node verification with one node 600 dependent on the other two, where one dependency will pass, 601 and one will fail. Assert the following: 602 * The verification exception is `AutoservVerifyDependencyError`, 603 and the exception argument has the descriptions of the 604 single failed node. 605 * The `verify()` method for each dependency is called once, 606 and for the parent node not at all. 607 * The expected 'GOOD' and 'FAIL' records are logged via 608 `Host.record()` for the dependencies. 609 * If `_verify_host()` is called more than once, there are no 610 visible side-effects after the first call. 611 * Calling `_reverify()` clears all cached results. 612 """ 613 for silent in self._generate_silent(): 614 left = self._make_verifier(1, 'left', []) 615 right = self._make_verifier(0, 'right', []) 616 top = self._make_verifier(0, 'top', [left, right]) 617 failures = self._make_expected_failures(left) 618 for count in self._generate_verify_count(top): 619 expected_exception = hosts.AutoservVerifyDependencyError 620 with self.assertRaises(expected_exception) as e: 621 top._verify_host(self._fake_host, silent) 622 self.assertEqual(e.exception.failures, failures) 623 self.assertEqual(top.verify_count, 0) 624 self.assertEqual(left.verify_count, count) 625 self.assertEqual(right.verify_count, count) 626 self._check_log_records(silent, 627 ('left', 'FAIL'), 628 ('right', 'GOOD')) 629 630 631 def test_diamond_pass(self): 632 """ 633 Test a "diamond" structure DAG with all nodes passing. 634 635 Construct and call a "diamond" structure DAG where all nodes 636 will pass: 637 638 TOP 639 / \ 640 LEFT RIGHT 641 \ / 642 BOTTOM 643 644 Assert the following: 645 * The `verify()` method for all nodes is called once. 646 * The expected 'GOOD' records are logged via `Host.record()` 647 for all nodes. 648 * If `_verify_host()` is called more than once, there are no 649 visible side-effects after the first call. 650 * Calling `_reverify()` clears all cached results. 651 """ 652 for silent in self._generate_silent(): 653 bottom = self._make_verifier(0, 'bottom', []) 654 left = self._make_verifier(0, 'left', [bottom]) 655 right = self._make_verifier(0, 'right', [bottom]) 656 top = self._make_verifier(0, 'top', [left, right]) 657 for count in self._generate_verify_count(top): 658 top._verify_host(self._fake_host, silent) 659 self.assertEqual(top.verify_count, count) 660 self.assertEqual(left.verify_count, count) 661 self.assertEqual(right.verify_count, count) 662 self.assertEqual(bottom.verify_count, count) 663 self._check_log_records(silent, 664 ('bottom', 'GOOD'), 665 ('left', 'GOOD'), 666 ('right', 'GOOD'), 667 ('top', 'GOOD')) 668 669 670 def test_diamond_fail(self): 671 """ 672 Test a "diamond" structure DAG with the bottom node failing. 673 674 Construct and call a "diamond" structure DAG where the bottom 675 node will fail: 676 677 TOP 678 / \ 679 LEFT RIGHT 680 \ / 681 BOTTOM 682 683 Assert the following: 684 * The verification exception is `AutoservVerifyDependencyError`, 685 and the exception argument has the description of the 686 "bottom" node. 687 * The `verify()` method for the "bottom" node is called once, 688 and for the other nodes not at all. 689 * The expected 'FAIL' record is logged via `Host.record()` 690 for the "bottom" node. 691 * If `_verify_host()` is called more than once, there are no 692 visible side-effects after the first call. 693 * Calling `_reverify()` clears all cached results. 694 """ 695 for silent in self._generate_silent(): 696 bottom = self._make_verifier(1, 'bottom', []) 697 left = self._make_verifier(0, 'left', [bottom]) 698 right = self._make_verifier(0, 'right', [bottom]) 699 top = self._make_verifier(0, 'top', [left, right]) 700 failures = self._make_expected_failures(bottom) 701 for count in self._generate_verify_count(top): 702 expected_exception = hosts.AutoservVerifyDependencyError 703 with self.assertRaises(expected_exception) as e: 704 top._verify_host(self._fake_host, silent) 705 self.assertEqual(e.exception.failures, failures) 706 self.assertEqual(top.verify_count, 0) 707 self.assertEqual(left.verify_count, 0) 708 self.assertEqual(right.verify_count, 0) 709 self.assertEqual(bottom.verify_count, count) 710 self._check_log_records(silent, ('bottom', 'FAIL')) 711 712 713class RepairActionTests(_DependencyNodeTestCase): 714 """ 715 Unit tests for `RepairAction`. 716 717 The tests in this class test the fundamental behaviors of the 718 `RepairAction` class: 719 * Repair doesn't run unless all dependencies pass. 720 * Repair doesn't run unless at least one trigger fails. 721 * Repair reports the expected value of `status` for metrics. 722 * The `_repair_host()` method makes the expected calls to 723 `Host.record()` for every call to the `repair()` method. 724 725 The test cases don't use `RepairStrategy` to build repair 726 graphs, but instead rely on custom-built structures. 727 """ 728 729 def test_repair_not_triggered(self): 730 """ 731 Test a repair that doesn't trigger. 732 733 Construct and call a repair action with a verification trigger 734 that passes. Assert the following: 735 * The `verify()` method for the trigger is called. 736 * The `repair()` method is not called. 737 * The repair action's `status` field is 'untriggered'. 738 * The verifier logs the expected 'GOOD' message with 739 `Host.record()`. 740 * The repair action logs no messages with `Host.record()`. 741 """ 742 for silent in self._generate_silent(): 743 verifier = self._make_verifier(0, 'check', []) 744 repair_action = self._make_repair_action(True, 'unneeded', 745 [], [verifier]) 746 repair_action._repair_host(self._fake_host, silent) 747 self.assertEqual(verifier.verify_count, 1) 748 self.assertEqual(repair_action.repair_count, 0) 749 self.assertEqual(repair_action.status, 'skipped') 750 self._check_log_records(silent, ('check', 'GOOD')) 751 752 753 def test_repair_fails(self): 754 """ 755 Test a repair that triggers and fails. 756 757 Construct and call a repair action with a verification trigger 758 that fails. The repair fails by raising `_StubRepairFailure`. 759 Assert the following: 760 * The repair action fails with the `_StubRepairFailure` raised 761 by `repair()`. 762 * The `verify()` method for the trigger is called once. 763 * The `repair()` method is called once. 764 * The repair action's `status` field is 'failed-action'. 765 * The expected 'START', 'FAIL', and 'END FAIL' messages are 766 logged with `Host.record()` for the failed verifier and the 767 failed repair. 768 """ 769 for silent in self._generate_silent(): 770 verifier = self._make_verifier(1, 'fail', []) 771 repair_action = self._make_repair_action(False, 'nofix', 772 [], [verifier]) 773 with self.assertRaises(_StubRepairFailure) as e: 774 repair_action._repair_host(self._fake_host, silent) 775 self.assertEqual(repair_action.message, str(e.exception)) 776 self.assertEqual(verifier.verify_count, 1) 777 self.assertEqual(repair_action.repair_count, 1) 778 self.assertEqual(repair_action.status, 'repair_failure') 779 self._check_log_records(silent, 780 ('fail', 'FAIL'), 781 ('nofix', 'START'), 782 ('nofix', 'FAIL'), 783 ('nofix', 'END FAIL')) 784 785 786 def test_repair_success(self): 787 """ 788 Test a repair that fixes its trigger. 789 790 Construct and call a repair action that raises no exceptions, 791 using a repair trigger that fails first, then passes after 792 repair. Assert the following: 793 * The `repair()` method is called once. 794 * The trigger's `verify()` method is called twice. 795 * The repair action's `status` field is 'repaired'. 796 * The expected 'START', 'FAIL', 'GOOD', and 'END GOOD' 797 messages are logged with `Host.record()` for the verifier 798 and the repair. 799 """ 800 for silent in self._generate_silent(): 801 verifier = self._make_verifier(1, 'fail', []) 802 repair_action = self._make_repair_action(True, 'fix', 803 [], [verifier]) 804 repair_action._repair_host(self._fake_host, silent) 805 self.assertEqual(repair_action.repair_count, 1) 806 self.assertEqual(verifier.verify_count, 2) 807 self.assertEqual(repair_action.status, 'repaired') 808 self._check_log_records(silent, 809 ('fail', 'FAIL'), 810 ('fix', 'START'), 811 ('fail', 'GOOD'), 812 ('fix', 'END GOOD')) 813 814 815 def test_repair_noop(self): 816 """ 817 Test a repair that doesn't fix a failing trigger. 818 819 Construct and call a repair action with a trigger that fails. 820 The repair action raises no exceptions, and after repair, the 821 trigger still fails. Assert the following: 822 * The `_repair_host()` call fails with `AutoservRepairError`. 823 * The `repair()` method is called once. 824 * The trigger's `verify()` method is called twice. 825 * The repair action's `status` field is 'failed-trigger'. 826 * The expected 'START', 'FAIL', and 'END FAIL' messages are 827 logged with `Host.record()` for the verifier and the repair. 828 """ 829 for silent in self._generate_silent(): 830 verifier = self._make_verifier(2, 'fail', []) 831 repair_action = self._make_repair_action(True, 'nofix', 832 [], [verifier]) 833 with self.assertRaises(hosts.AutoservRepairError) as e: 834 repair_action._repair_host(self._fake_host, silent) 835 self.assertEqual(repair_action.repair_count, 1) 836 self.assertEqual(verifier.verify_count, 2) 837 self.assertEqual(repair_action.status, 'verify_failure') 838 self._check_log_records(silent, 839 ('fail', 'FAIL'), 840 ('nofix', 'START'), 841 ('fail', 'FAIL'), 842 ('nofix', 'END FAIL')) 843 844 845 def test_dependency_pass(self): 846 """ 847 Test proper handling of repair dependencies that pass. 848 849 Construct and call a repair action with a dependency and a 850 trigger. The dependency will pass and the trigger will fail and 851 be repaired. Assert the following: 852 * Repair passes. 853 * The `verify()` method for the dependency is called once. 854 * The `verify()` method for the trigger is called twice. 855 * The `repair()` method is called once. 856 * The repair action's `status` field is 'repaired'. 857 * The expected records are logged via `Host.record()` 858 for the successful dependency, the failed trigger, and 859 the successful repair. 860 """ 861 for silent in self._generate_silent(): 862 dep = self._make_verifier(0, 'dep', []) 863 trigger = self._make_verifier(1, 'trig', []) 864 repair = self._make_repair_action(True, 'fixit', 865 [dep], [trigger]) 866 repair._repair_host(self._fake_host, silent) 867 self.assertEqual(dep.verify_count, 1) 868 self.assertEqual(trigger.verify_count, 2) 869 self.assertEqual(repair.repair_count, 1) 870 self.assertEqual(repair.status, 'repaired') 871 self._check_log_records(silent, 872 ('dep', 'GOOD'), 873 ('trig', 'FAIL'), 874 ('fixit', 'START'), 875 ('trig', 'GOOD'), 876 ('fixit', 'END GOOD')) 877 878 879 def test_dependency_fail(self): 880 """ 881 Test proper handling of repair dependencies that fail. 882 883 Construct and call a repair action with a dependency and a 884 trigger, both of which fail. Assert the following: 885 * Repair fails with `AutoservVerifyDependencyError`, 886 and the exception argument is the description of the failed 887 dependency. 888 * The `verify()` method for the failing dependency is called 889 once. 890 * The trigger and the repair action aren't invoked at all. 891 * The repair action's `status` field is 'blocked'. 892 * The expected 'FAIL' record is logged via `Host.record()` 893 for the single failed dependency. 894 """ 895 for silent in self._generate_silent(): 896 dep = self._make_verifier(1, 'dep', []) 897 trigger = self._make_verifier(1, 'trig', []) 898 repair = self._make_repair_action(True, 'fixit', 899 [dep], [trigger]) 900 expected_exception = hosts.AutoservVerifyDependencyError 901 with self.assertRaises(expected_exception) as e: 902 repair._repair_host(self._fake_host, silent) 903 self.assertEqual(e.exception.failures, 904 self._make_expected_failures(dep)) 905 self.assertEqual(dep.verify_count, 1) 906 self.assertEqual(trigger.verify_count, 0) 907 self.assertEqual(repair.repair_count, 0) 908 self.assertEqual(repair.status, 'blocked') 909 self._check_log_records(silent, ('dep', 'FAIL')) 910 911 912class _RepairStrategyTestCase(_DependencyNodeTestCase): 913 """Shared base class for testing `RepairStrategy` methods.""" 914 915 def _make_verify_data(self, *input_data): 916 """ 917 Create `verify_data` for the `RepairStrategy` constructor. 918 919 `RepairStrategy` expects `verify_data` as a list of tuples 920 of the form `(constructor, tag, deps)`. Each item in 921 `input_data` is a tuple of the form `(tag, count, deps)` that 922 creates one entry in the returned list of `verify_data` tuples 923 as follows: 924 * `count` is used to create a constructor function that calls 925 `self._make_verifier()` with that value plus plus the 926 arguments provided by the `RepairStrategy` constructor. 927 * `tag` and `deps` will be passed as-is to the `RepairStrategy` 928 constructor. 929 930 @param input_data A list of tuples, each representing one 931 tuple in the `verify_data` list. 932 @return A list suitable to be the `verify_data` parameter for 933 the `RepairStrategy` constructor. 934 """ 935 strategy_data = [] 936 for tag, count, deps in input_data: 937 construct = functools.partial(self._make_verifier, count) 938 strategy_data.append((construct, tag, deps)) 939 return strategy_data 940 941 942 def _make_repair_data(self, *input_data): 943 """ 944 Create `repair_data` for the `RepairStrategy` constructor. 945 946 `RepairStrategy` expects `repair_data` as a list of tuples 947 of the form `(constructor, tag, deps, triggers)`. Each item in 948 `input_data` is a tuple of the form `(tag, success, deps, triggers)` 949 that creates one entry in the returned list of `repair_data` 950 tuples as follows: 951 * `success` is used to create a constructor function that calls 952 `self._make_verifier()` with that value plus plus the 953 arguments provided by the `RepairStrategy` constructor. 954 * `tag`, `deps`, and `triggers` will be passed as-is to the 955 `RepairStrategy` constructor. 956 957 @param input_data A list of tuples, each representing one 958 tuple in the `repair_data` list. 959 @return A list suitable to be the `repair_data` parameter for 960 the `RepairStrategy` constructor. 961 """ 962 strategy_data = [] 963 for tag, success, deps, triggers in input_data: 964 construct = functools.partial(self._make_repair_action, success) 965 strategy_data.append((construct, tag, deps, triggers)) 966 return strategy_data 967 968 969 def _make_strategy(self, verify_input, repair_input): 970 """ 971 Create a `RepairStrategy` from the given arguments. 972 973 @param verify_input As for `input_data` in 974 `_make_verify_data()`. 975 @param repair_input As for `input_data` in 976 `_make_repair_data()`. 977 """ 978 verify_data = self._make_verify_data(*verify_input) 979 repair_data = self._make_repair_data(*repair_input) 980 return hosts.RepairStrategy(verify_data, repair_data, 'unittest') 981 982 def _check_silent_records(self, silent): 983 """ 984 Check that logging honored the `silent` parameter. 985 986 Asserts that logging with `Host.record()` occurred (or did not 987 occur) in accordance with the value of `silent`. 988 989 This method only asserts the presence or absence of log records. 990 Coverage for the contents of the log records is handled in other 991 test cases. 992 993 @param silent When true, there should be no log records; 994 otherwise there should be records present. 995 """ 996 log_records = self._fake_host.get_log_records() 997 if silent: 998 self.assertEqual(log_records, []) 999 else: 1000 self.assertNotEqual(log_records, []) 1001 1002 1003class RepairStrategyVerifyTests(_RepairStrategyTestCase): 1004 """ 1005 Unit tests for `RepairStrategy.verify()`. 1006 1007 These unit tests focus on verifying that the `RepairStrategy` 1008 constructor creates the expected DAG structure from given 1009 `verify_data`. Functional testing here is mainly confined to 1010 asserting that `RepairStrategy.verify()` properly distinguishes 1011 success from failure. Testing the behavior of specific DAG 1012 structures is left to tests in `VerifyTests`. 1013 """ 1014 1015 def test_single_node(self): 1016 """ 1017 Test construction of a single-node verification DAG. 1018 1019 Assert that the structure looks like this: 1020 1021 Root Node -> Main Node 1022 """ 1023 verify_data = self._make_verify_data(('main', 0, ())) 1024 strategy = hosts.RepairStrategy(verify_data, [], 'unittest') 1025 verifier = self.nodes['main'] 1026 self.assertEqual( 1027 strategy._verify_root._dependency_list, 1028 [verifier]) 1029 self.assertEqual(verifier._dependency_list, []) 1030 1031 1032 def test_single_dependency(self): 1033 """ 1034 Test construction of a two-node dependency chain. 1035 1036 Assert that the structure looks like this: 1037 1038 Root Node -> Parent Node -> Child Node 1039 """ 1040 verify_data = self._make_verify_data( 1041 ('child', 0, ()), 1042 ('parent', 0, ('child',))) 1043 strategy = hosts.RepairStrategy(verify_data, [], 'unittest') 1044 parent = self.nodes['parent'] 1045 child = self.nodes['child'] 1046 self.assertEqual( 1047 strategy._verify_root._dependency_list, [parent]) 1048 self.assertEqual( 1049 parent._dependency_list, [child]) 1050 self.assertEqual( 1051 child._dependency_list, []) 1052 1053 1054 def test_two_nodes_and_dependency(self): 1055 """ 1056 Test construction of two nodes with a shared dependency. 1057 1058 Assert that the structure looks like this: 1059 1060 Root Node -> Left Node ---\ 1061 \ -> Bottom Node 1062 -> Right Node / 1063 """ 1064 verify_data = self._make_verify_data( 1065 ('bottom', 0, ()), 1066 ('left', 0, ('bottom',)), 1067 ('right', 0, ('bottom',))) 1068 strategy = hosts.RepairStrategy(verify_data, [], 'unittest') 1069 bottom = self.nodes['bottom'] 1070 left = self.nodes['left'] 1071 right = self.nodes['right'] 1072 self.assertEqual( 1073 strategy._verify_root._dependency_list, 1074 [left, right]) 1075 self.assertEqual(left._dependency_list, [bottom]) 1076 self.assertEqual(right._dependency_list, [bottom]) 1077 self.assertEqual(bottom._dependency_list, []) 1078 1079 1080 def test_three_nodes(self): 1081 """ 1082 Test construction of three nodes with no dependencies. 1083 1084 Assert that the structure looks like this: 1085 1086 -> Node One 1087 / 1088 Root Node -> Node Two 1089 \ 1090 -> Node Three 1091 1092 N.B. This test exists to enforce ordering expectations of 1093 root-level DAG nodes. Three nodes are used to make it unlikely 1094 that randomly ordered roots will match expectations. 1095 """ 1096 verify_data = self._make_verify_data( 1097 ('one', 0, ()), 1098 ('two', 0, ()), 1099 ('three', 0, ())) 1100 strategy = hosts.RepairStrategy(verify_data, [], 'unittest') 1101 one = self.nodes['one'] 1102 two = self.nodes['two'] 1103 three = self.nodes['three'] 1104 self.assertEqual( 1105 strategy._verify_root._dependency_list, 1106 [one, two, three]) 1107 self.assertEqual(one._dependency_list, []) 1108 self.assertEqual(two._dependency_list, []) 1109 self.assertEqual(three._dependency_list, []) 1110 1111 1112 def test_verify(self): 1113 """ 1114 Test behavior of the `verify()` method. 1115 1116 Build a `RepairStrategy` with a single verifier. Assert the 1117 following: 1118 * If the verifier passes, `verify()` passes. 1119 * If the verifier fails, `verify()` fails. 1120 * The verifier is reinvoked with every call to `verify()`; 1121 cached results are not re-used. 1122 """ 1123 verify_data = self._make_verify_data(('tester', 0, ())) 1124 strategy = hosts.RepairStrategy(verify_data, [], 'unittest') 1125 verifier = self.nodes['tester'] 1126 count = 0 1127 for silent in self._generate_silent(): 1128 for i in range(0, 2): 1129 for j in range(0, 2): 1130 strategy.verify(self._fake_host, silent) 1131 self._check_silent_records(silent) 1132 count += 1 1133 self.assertEqual(verifier.verify_count, count) 1134 verifier.unrepair() 1135 for j in range(0, 2): 1136 with self.assertRaises(Exception) as e: 1137 strategy.verify(self._fake_host, silent) 1138 self._check_silent_records(silent) 1139 count += 1 1140 self.assertEqual(verifier.verify_count, count) 1141 verifier.try_repair() 1142 1143 1144class RepairStrategyRepairTests(_RepairStrategyTestCase): 1145 """ 1146 Unit tests for `RepairStrategy.repair()`. 1147 1148 These unit tests focus on verifying that the `RepairStrategy` 1149 constructor creates the expected repair list from given 1150 `repair_data`. Functional testing here is confined to asserting 1151 that `RepairStrategy.repair()` properly distinguishes success from 1152 failure. Testing the behavior of specific repair structures is left 1153 to tests in `RepairActionTests`. 1154 """ 1155 1156 def _check_common_trigger(self, strategy, repair_tags, triggers): 1157 self.assertEqual(strategy._repair_actions, 1158 [self.nodes[tag] for tag in repair_tags]) 1159 for tag in repair_tags: 1160 self.assertEqual(self.nodes[tag]._trigger_list, 1161 triggers) 1162 self.assertEqual(self.nodes[tag]._dependency_list, []) 1163 1164 1165 def test_single_repair_with_trigger(self): 1166 """ 1167 Test constructing a strategy with a single repair trigger. 1168 1169 Build a `RepairStrategy` with a single repair action and a 1170 single trigger. Assert that the trigger graph looks like this: 1171 1172 Repair -> Trigger 1173 1174 Assert that there are no repair dependencies. 1175 """ 1176 verify_input = (('base', 0, ()),) 1177 repair_input = (('fixit', True, (), ('base',)),) 1178 strategy = self._make_strategy(verify_input, repair_input) 1179 self._check_common_trigger(strategy, 1180 ['fixit'], 1181 [self.nodes['base']]) 1182 1183 1184 def test_repair_with_root_trigger(self): 1185 """ 1186 Test construction of a repair triggering on the root verifier. 1187 1188 Build a `RepairStrategy` with a single repair action that 1189 triggers on the root verifier. Assert that the trigger graph 1190 looks like this: 1191 1192 Repair -> Root Verifier 1193 1194 Assert that there are no repair dependencies. 1195 """ 1196 root_tag = hosts.RepairStrategy.ROOT_TAG 1197 repair_input = (('fixit', True, (), (root_tag,)),) 1198 strategy = self._make_strategy([], repair_input) 1199 self._check_common_trigger(strategy, 1200 ['fixit'], 1201 [strategy._verify_root]) 1202 1203 1204 def test_three_repairs(self): 1205 """ 1206 Test constructing a strategy with three repair actions. 1207 1208 Build a `RepairStrategy` with a three repair actions sharing a 1209 single trigger. Assert that the trigger graph looks like this: 1210 1211 Repair A -> Trigger 1212 Repair B -> Trigger 1213 Repair C -> Trigger 1214 1215 Assert that there are no repair dependencies. 1216 1217 N.B. This test exists to enforce ordering expectations of 1218 repair nodes. Three nodes are used to make it unlikely that 1219 randomly ordered actions will match expectations. 1220 """ 1221 verify_input = (('base', 0, ()),) 1222 repair_tags = ['a', 'b', 'c'] 1223 repair_input = ( 1224 (tag, True, (), ('base',)) for tag in repair_tags) 1225 strategy = self._make_strategy(verify_input, repair_input) 1226 self._check_common_trigger(strategy, 1227 repair_tags, 1228 [self.nodes['base']]) 1229 1230 1231 def test_repair_dependency(self): 1232 """ 1233 Test construction of a repair with a dependency. 1234 1235 Build a `RepairStrategy` with a single repair action that 1236 depends on a single verifier. Assert that the dependency graph 1237 looks like this: 1238 1239 Repair -> Verifier 1240 1241 Assert that there are no repair triggers. 1242 """ 1243 verify_input = (('base', 0, ()),) 1244 repair_input = (('fixit', True, ('base',), ()),) 1245 strategy = self._make_strategy(verify_input, repair_input) 1246 self.assertEqual(strategy._repair_actions, 1247 [self.nodes['fixit']]) 1248 self.assertEqual(self.nodes['fixit']._trigger_list, []) 1249 self.assertEqual(self.nodes['fixit']._dependency_list, 1250 [self.nodes['base']]) 1251 1252 1253 def _check_repair_failure(self, strategy, silent): 1254 """ 1255 Check the effects of a call to `repair()` that fails. 1256 1257 For the given strategy object, call the `repair()` method; the 1258 call is expected to fail and all repair actions are expected to 1259 trigger. 1260 1261 Assert the following: 1262 * The call raises an exception. 1263 * For each repair action in the strategy, its `repair()` 1264 method is called exactly once. 1265 1266 @param strategy The strategy to be tested. 1267 """ 1268 action_counts = [(a, a.repair_count) 1269 for a in strategy._repair_actions] 1270 with self.assertRaises(Exception) as e: 1271 strategy.repair(self._fake_host, silent) 1272 self._check_silent_records(silent) 1273 for action, count in action_counts: 1274 self.assertEqual(action.repair_count, count + 1) 1275 1276 1277 def _check_repair_success(self, strategy, silent): 1278 """ 1279 Check the effects of a call to `repair()` that succeeds. 1280 1281 For the given strategy object, call the `repair()` method; the 1282 call is expected to succeed without raising an exception and all 1283 repair actions are expected to trigger. 1284 1285 Assert that for each repair action in the strategy, its 1286 `repair()` method is called exactly once. 1287 1288 @param strategy The strategy to be tested. 1289 """ 1290 action_counts = [(a, a.repair_count) 1291 for a in strategy._repair_actions] 1292 strategy.repair(self._fake_host, silent) 1293 self._check_silent_records(silent) 1294 for action, count in action_counts: 1295 self.assertEqual(action.repair_count, count + 1) 1296 1297 1298 def test_repair(self): 1299 """ 1300 Test behavior of the `repair()` method. 1301 1302 Build a `RepairStrategy` with two repair actions each depending 1303 on its own verifier. Set up calls to `repair()` for each of 1304 the following conditions: 1305 * Both repair actions trigger and fail. 1306 * Both repair actions trigger and succeed. 1307 * Both repair actions trigger; the first one fails, but the 1308 second one succeeds. 1309 * Both repair actions trigger; the first one succeeds, but the 1310 second one fails. 1311 1312 Assert the following: 1313 * When both repair actions succeed, `repair()` succeeds. 1314 * When either repair action fails, `repair()` fails. 1315 * After each call to the strategy's `repair()` method, each 1316 repair action triggered exactly once. 1317 """ 1318 verify_input = (('a', 2, ()), ('b', 2, ())) 1319 repair_input = (('afix', True, (), ('a',)), 1320 ('bfix', True, (), ('b',))) 1321 strategy = self._make_strategy(verify_input, repair_input) 1322 1323 for silent in self._generate_silent(): 1324 # call where both 'afix' and 'bfix' fail 1325 self._check_repair_failure(strategy, silent) 1326 # repair counts are now 1 for both verifiers 1327 1328 # call where both 'afix' and 'bfix' succeed 1329 self._check_repair_success(strategy, silent) 1330 # repair counts are now 0 for both verifiers 1331 1332 # call where 'afix' fails and 'bfix' succeeds 1333 for tag in ['a', 'a', 'b']: 1334 self.nodes[tag].unrepair() 1335 self._check_repair_failure(strategy, silent) 1336 # 'a' repair count is 1; 'b' count is 0 1337 1338 # call where 'afix' succeeds and 'bfix' fails 1339 for tag in ['b', 'b']: 1340 self.nodes[tag].unrepair() 1341 self._check_repair_failure(strategy, silent) 1342 # 'a' repair count is 0; 'b' count is 1 1343 1344 for tag in ['a', 'a', 'b']: 1345 self.nodes[tag].unrepair() 1346 # repair counts are now 2 for both verifiers 1347 1348 1349class VerifierResultTestCases(_DependencyNodeTestCase): 1350 """ 1351 Test to check that we can find correct verifier and 1352 get the result of it 1353 """ 1354 def test_find_verifier_by_tag(self): 1355 """Test to find correct verifier by tag""" 1356 verify_data = [ 1357 (_GoodVerifier, 'v1', []), 1358 (_GoodVerifier, 'v2', ['v1']), 1359 (_BadVerifier, 'v3', []), 1360 (_BadVerifier, 'v4', []), 1361 (_SkipVerifier, 'v5', ['v4']), 1362 (_SkipVerifier, 'v6', ['v2', 'v3']) 1363 ] 1364 strategy = hosts.RepairStrategy(verify_data, (), 'unittest') 1365 1366 for v in range(1,6): 1367 tag = 'v%s' % v 1368 verifier = strategy._verify_root._get_node_by_tag(tag) 1369 self.assertIsNotNone(verifier) 1370 self.assertEqual(tag, verifier.tag) 1371 1372 verifier = strategy._verify_root._get_node_by_tag('v0') 1373 self.assertEqual(repair.VERIFY_NOT_RUN, verifier) 1374 1375 def test_run_verifier_and_get_results(self): 1376 """Check the result of verifiers""" 1377 verify_data = [ 1378 (_GoodVerifier, 'v1', []), 1379 (_BadVerifier, 'v2', []), 1380 (_SkipVerifier, 'v3', []), 1381 ] 1382 strategy = hosts.RepairStrategy(verify_data, (), 'unittest') 1383 try: 1384 strategy.verify(self._fake_host, silent=True) 1385 except Exception as e: 1386 pass 1387 self.assertEqual(repair.VERIFY_NOT_RUN, 1388 strategy.verifier_is_good('v0')) 1389 self.assertEqual(repair.VERIFY_SUCCESS, 1390 strategy.verifier_is_good('v1')) 1391 self.assertEqual(repair.VERIFY_FAILED, strategy.verifier_is_good('v2')) 1392 self.assertEqual(repair.VERIFY_NOT_RUN, 1393 strategy.verifier_is_good('v3')) 1394 self.assertEqual(repair.VERIFY_NOT_RUN, 1395 strategy.verifier_is_good('v4')) 1396 1397 def test_run_verifier_with_dependencies(self): 1398 """Check the result if dependency fail or not applicable.""" 1399 verify_data = [ 1400 (_GoodVerifier, 'v1', []), 1401 (_BadVerifier, 'v2', []), 1402 (_SkipVerifier, 'v3', []), 1403 (_GoodVerifier, 'v4', ['v2']), 1404 (_GoodVerifier, 'v5', ['v3']), 1405 ] 1406 strategy = hosts.RepairStrategy(verify_data, (), 'unittest') 1407 try: 1408 strategy.verify(self._fake_host, silent=True) 1409 except Exception as e: 1410 pass 1411 self.assertEqual(repair.VERIFY_NOT_RUN, 1412 strategy.verifier_is_good('v0')) 1413 self.assertEqual(repair.VERIFY_SUCCESS, 1414 strategy.verifier_is_good('v1')) 1415 self.assertEqual(repair.VERIFY_FAILED, strategy.verifier_is_good('v2')) 1416 self.assertEqual(repair.VERIFY_NOT_RUN, 1417 strategy.verifier_is_good('v3')) 1418 # if dependencies fail then the verifier mark as not run 1419 self.assertEqual(repair.VERIFY_NOT_RUN, 1420 strategy.verifier_is_good('v4')) 1421 # if dependencies not applicable then run only verifier 1422 self.assertEqual(repair.VERIFY_SUCCESS, 1423 strategy.verifier_is_good('v5')) 1424 self.assertEqual(repair.VERIFY_NOT_RUN, 1425 strategy.verifier_is_good('v6')) 1426 1427 1428if __name__ == '__main__': 1429 unittest.main() 1430