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