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