xref: /aosp_15_r20/external/autotest/client/common_lib/hosts/repair_unittest.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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