1#!/usr/bin/python
2# pylint: disable=missing-docstring
3
4import logging
5import os
6import shutil
7import StringIO
8import sys
9import tempfile
10import unittest
11
12import common
13from autotest_lib.client.bin import job, sysinfo, harness
14from autotest_lib.client.bin import utils
15from autotest_lib.client.common_lib import error
16from autotest_lib.client.common_lib import logging_manager, logging_config
17from autotest_lib.client.common_lib import base_job_unittest
18from autotest_lib.client.common_lib.test_utils import mock
19
20
21class job_test_case(unittest.TestCase):
22    """Generic job TestCase class that defines a standard job setUp and
23    tearDown, with some standard stubs."""
24
25    job_class = job.base_client_job
26
27    def setUp(self):
28        self.god = mock.mock_god(ut=self)
29        self.god.stub_with(job.base_client_job, '_get_environ_autodir',
30                           classmethod(lambda cls: '/adir'))
31        self.job = self.job_class.__new__(self.job_class)
32        self.job._job_directory = base_job_unittest.stub_job_directory
33
34        _, self.control_file = tempfile.mkstemp()
35
36
37    def tearDown(self):
38        self.god.unstub_all()
39        os.remove(self.control_file)
40
41
42class test_find_base_directories(
43        base_job_unittest.test_find_base_directories.generic_tests,
44        job_test_case):
45
46    def test_autodir_equals_clientdir(self):
47        autodir, clientdir, _ = self.job._find_base_directories()
48        self.assertEqual(autodir, '/adir')
49        self.assertEqual(clientdir, '/adir')
50
51
52    def test_serverdir_is_none(self):
53        _, _, serverdir = self.job._find_base_directories()
54        self.assertEqual(serverdir, None)
55
56
57class abstract_test_init(base_job_unittest.test_init.generic_tests):
58    """Generic client job mixin used when defining variations on the
59    job.__init__ generic tests."""
60    OPTIONAL_ATTRIBUTES = (
61        base_job_unittest.test_init.generic_tests.OPTIONAL_ATTRIBUTES
62        - set(['control', 'harness']))
63
64
65class test_init_minimal_options(abstract_test_init, job_test_case):
66
67    def call_init(self):
68        # TODO(jadmanski): refactor more of the __init__ code to not need to
69        # stub out countless random APIs
70        self.god.stub_function_to_return(job.os, 'mkdir', None)
71        self.god.stub_function_to_return(job.os.path, 'exists', True)
72        self.god.stub_function_to_return(self.job, '_load_state', None)
73        self.god.stub_function_to_return(self.job, 'record', None)
74        self.god.stub_function_to_return(job.shutil, 'copyfile', None)
75        self.god.stub_function_to_return(job.logging_manager,
76                                         'configure_logging', None)
77        class manager:
78            def start_logging(self):
79                return None
80        self.god.stub_function_to_return(job.logging_manager,
81                                         'get_logging_manager', manager())
82        class stub_sysinfo:
83            def log_per_reboot_data(self):
84                return None
85        self.god.stub_function_to_return(job.sysinfo, 'sysinfo',
86                                         stub_sysinfo())
87        class stub_harness:
88            run_start = lambda self: None
89        self.god.stub_function_to_return(job.harness, 'select', stub_harness())
90        class options:
91            tag = ''
92            verbose = False
93            cont = False
94            harness = 'stub'
95            harness_args = None
96            hostname = None
97            user = None
98            log = False
99            args = ''
100            output_dir = ''
101        self.god.stub_function_to_return(job.utils, 'drop_caches', None)
102
103        self.job._job_state = base_job_unittest.stub_job_state
104        self.job.__init__(self.control_file, options)
105
106
107class dummy(object):
108    """A simple placeholder for attributes"""
109    pass
110
111
112class first_line_comparator(mock.argument_comparator):
113    def __init__(self, first_line):
114        self.first_line = first_line
115
116
117    def is_satisfied_by(self, parameter):
118        return self.first_line == parameter.splitlines()[0]
119
120
121class test_base_job(unittest.TestCase):
122    def setUp(self):
123        # make god
124        self.god = mock.mock_god(ut=self)
125
126        # need to set some environ variables
127        self.autodir = "autodir"
128        os.environ['AUTODIR'] = self.autodir
129
130        # set up some variables
131        _, self.control = tempfile.mkstemp()
132        self.jobtag = "jobtag"
133
134        # get rid of stdout and logging
135        sys.stdout = StringIO.StringIO()
136        logging_manager.configure_logging(logging_config.TestingConfig())
137        logging.disable(logging.CRITICAL)
138        def dummy_configure_logging(*args, **kwargs):
139            pass
140        self.god.stub_with(logging_manager, 'configure_logging',
141                           dummy_configure_logging)
142        real_get_logging_manager = logging_manager.get_logging_manager
143        def get_logging_manager_no_fds(manage_stdout_and_stderr=False,
144                                       redirect_fds=False):
145            return real_get_logging_manager(manage_stdout_and_stderr, False)
146        self.god.stub_with(logging_manager, 'get_logging_manager',
147                           get_logging_manager_no_fds)
148
149        # stub out some stuff
150        self.god.stub_function(os.path, 'exists')
151        self.god.stub_function(os.path, 'isdir')
152        self.god.stub_function(os, 'makedirs')
153        self.god.stub_function(os, 'mkdir')
154        self.god.stub_function(os, 'remove')
155        self.god.stub_function(shutil, 'rmtree')
156        self.god.stub_function(shutil, 'copyfile')
157        self.god.stub_function(job, 'open')
158        self.god.stub_function(utils, 'system')
159        self.god.stub_function(utils, 'drop_caches')
160        self.god.stub_function(harness, 'select')
161        self.god.stub_function(sysinfo, 'log_per_reboot_data')
162
163        self.god.stub_class(job.local_host, 'LocalHost')
164        self.god.stub_class(sysinfo, 'sysinfo')
165
166        self.god.stub_class_method(job.base_client_job,
167                                   '_cleanup_debugdir_files')
168        self.god.stub_class_method(job.base_client_job, '_cleanup_results_dir')
169
170        self.god.stub_with(job.base_job.job_directory, '_ensure_valid',
171                           lambda *_: None)
172
173
174    def tearDown(self):
175        sys.stdout = sys.__stdout__
176        self.god.unstub_all()
177        os.remove(self.control)
178
179
180    def _setup_pre_record_init(self, cont):
181        self.god.stub_function(self.job, '_load_state')
182
183        resultdir = os.path.join(self.autodir, 'results', self.jobtag)
184        tmpdir = os.path.join(self.autodir, 'tmp')
185        if not cont:
186            job.base_client_job._cleanup_debugdir_files.expect_call()
187            job.base_client_job._cleanup_results_dir.expect_call()
188
189        self.job._load_state.expect_call()
190
191        my_harness = self.god.create_mock_class(harness.harness,
192                                                'my_harness')
193        harness.select.expect_call(None,
194                                   self.job,
195                                   None).and_return(my_harness)
196
197        return resultdir, my_harness
198
199
200    def _setup_post_record_init(self, cont, resultdir, my_harness):
201        # now some specific stubs
202        self.god.stub_function(self.job, 'config_get')
203        self.god.stub_function(self.job, 'config_set')
204        self.god.stub_function(self.job, 'record')
205
206        # other setup
207        results = os.path.join(self.autodir, 'results')
208        download = os.path.join(self.autodir, 'tests', 'download')
209        pkgdir = os.path.join(self.autodir, 'packages')
210
211        utils.drop_caches.expect_call()
212        job_sysinfo = sysinfo.sysinfo.expect_new(resultdir)
213        if not cont:
214            os.path.exists.expect_call(download).and_return(False)
215            os.mkdir.expect_call(download)
216            shutil.copyfile.expect_call(mock.is_string_comparator(),
217                                 os.path.join(resultdir, 'control'))
218
219        job.local_host.LocalHost.expect_new(hostname='localhost')
220        job_sysinfo.log_per_reboot_data.expect_call()
221        if not cont:
222            self.job.record.expect_call('START', None, None)
223
224        my_harness.run_start.expect_call()
225
226
227    def construct_job(self, cont):
228        # will construct class instance using __new__
229        self.job = job.base_client_job.__new__(job.base_client_job)
230
231        # record
232        resultdir, my_harness = self._setup_pre_record_init(cont)
233        self._setup_post_record_init(cont, resultdir, my_harness)
234
235        # finish constructor
236        options = dummy()
237        options.tag = self.jobtag
238        options.cont = cont
239        options.harness = None
240        options.harness_args = None
241        options.log = False
242        options.verbose = False
243        options.hostname = 'localhost'
244        options.user = 'my_user'
245        options.args = ''
246        options.output_dir = ''
247        self.job.__init__(self.control, options)
248
249        # check
250        self.god.check_playback()
251
252
253    def get_partition_mock(self, devname):
254        """
255        Create a mock of a partition object and return it.
256        """
257        class mock(object):
258            device = devname
259            get_mountpoint = self.god.create_mock_function('get_mountpoint')
260        return mock
261
262
263    def test_constructor_first_run(self):
264        self.construct_job(False)
265
266
267    def test_constructor_continuation(self):
268        self.construct_job(True)
269
270
271    def test_constructor_post_record_failure(self):
272        """
273        Test post record initialization failure.
274        """
275        self.job = job.base_client_job.__new__(job.base_client_job)
276        options = dummy()
277        options.tag = self.jobtag
278        options.cont = False
279        options.harness = None
280        options.harness_args = None
281        options.log = False
282        options.verbose = False
283        options.hostname = 'localhost'
284        options.user = 'my_user'
285        options.args = ''
286        options.output_dir = ''
287        error = Exception('fail')
288
289        self.god.stub_function(self.job, '_post_record_init')
290        self.god.stub_function(self.job, 'record')
291
292        self._setup_pre_record_init(False)
293        self.job._post_record_init.expect_call(
294                self.control, options, True).and_raises(error)
295        self.job.record.expect_call(
296                'ABORT', None, None,'client.bin.job.__init__ failed: %s' %
297                str(error))
298
299        self.assertRaises(
300                Exception, self.job.__init__, self.control, options,
301                drop_caches=True)
302
303        # check
304        self.god.check_playback()
305
306
307    def test_control_functions(self):
308        self.construct_job(True)
309        control_file = "blah"
310        self.job.control_set(control_file)
311        self.assertEquals(self.job.control_get(), os.path.abspath(control_file))
312
313
314    def test_harness_select(self):
315        self.construct_job(True)
316
317        # record
318        which = "which"
319        harness_args = ''
320        harness.select.expect_call(which, self.job,
321                                   harness_args).and_return(None)
322
323        # run and test
324        self.job.harness_select(which, harness_args)
325        self.god.check_playback()
326
327
328    def test_setup_dirs_raise(self):
329        self.construct_job(True)
330
331        # setup
332        results_dir = 'foo'
333        tmp_dir = 'bar'
334
335        # record
336        os.path.exists.expect_call(tmp_dir).and_return(True)
337        os.path.isdir.expect_call(tmp_dir).and_return(False)
338
339        # test
340        self.assertRaises(ValueError, self.job.setup_dirs, results_dir, tmp_dir)
341        self.god.check_playback()
342
343
344    def test_setup_dirs(self):
345        self.construct_job(True)
346
347        # setup
348        results_dir1 = os.path.join(self.job.resultdir, 'build')
349        results_dir2 = os.path.join(self.job.resultdir, 'build.2')
350        results_dir3 = os.path.join(self.job.resultdir, 'build.3')
351        tmp_dir = 'bar'
352
353        # record
354        os.path.exists.expect_call(tmp_dir).and_return(False)
355        os.mkdir.expect_call(tmp_dir)
356        os.path.isdir.expect_call(tmp_dir).and_return(True)
357        os.path.exists.expect_call(results_dir1).and_return(True)
358        os.path.exists.expect_call(results_dir2).and_return(True)
359        os.path.exists.expect_call(results_dir3).and_return(False)
360        os.path.exists.expect_call(results_dir3).and_return(False)
361        os.mkdir.expect_call(results_dir3)
362
363        # test
364        self.assertEqual(self.job.setup_dirs(None, tmp_dir),
365                         (results_dir3, tmp_dir))
366        self.god.check_playback()
367
368
369    def test_run_test_logs_test_error_from_unhandled_error(self):
370        self.construct_job(True)
371
372        # set up stubs
373        self.god.stub_function(self.job.pkgmgr, 'get_package_name')
374        self.god.stub_function(self.job, "_runtest")
375
376        # create an unhandled error object
377        class MyError(error.TestError):
378            pass
379        real_error = MyError("this is the real error message")
380        unhandled_error = error.UnhandledTestError(real_error)
381
382        # set up the recording
383        testname = "error_test"
384        outputdir = os.path.join(self.job.resultdir, testname)
385        self.job.pkgmgr.get_package_name.expect_call(
386            testname, 'test').and_return(("", testname))
387        os.path.exists.expect_call(outputdir).and_return(False)
388        self.job.record.expect_call("START", testname, testname,
389                                    optional_fields=None)
390        self.job._runtest.expect_call(testname, "", None, (), {}).and_raises(
391            unhandled_error)
392        self.job.record.expect_call("ERROR", testname, testname,
393                                    first_line_comparator(str(real_error)))
394        self.job.record.expect_call("END ERROR", testname, testname)
395        self.job.harness.run_test_complete.expect_call()
396        utils.drop_caches.expect_call()
397
398        # run and check
399        self.job.run_test(testname)
400        self.god.check_playback()
401
402
403    def test_run_test_logs_non_test_error_from_unhandled_error(self):
404        self.construct_job(True)
405
406        # set up stubs
407        self.god.stub_function(self.job.pkgmgr, 'get_package_name')
408        self.god.stub_function(self.job, "_runtest")
409
410        # create an unhandled error object
411        class MyError(Exception):
412            pass
413        real_error = MyError("this is the real error message")
414        unhandled_error = error.UnhandledTestError(real_error)
415        reason = first_line_comparator("Unhandled MyError: %s" % real_error)
416
417        # set up the recording
418        testname = "error_test"
419        outputdir = os.path.join(self.job.resultdir, testname)
420        self.job.pkgmgr.get_package_name.expect_call(
421            testname, 'test').and_return(("", testname))
422        os.path.exists.expect_call(outputdir).and_return(False)
423        self.job.record.expect_call("START", testname, testname,
424                                    optional_fields=None)
425        self.job._runtest.expect_call(testname, "", None, (), {}).and_raises(
426            unhandled_error)
427        self.job.record.expect_call("ERROR", testname, testname, reason)
428        self.job.record.expect_call("END ERROR", testname, testname)
429        self.job.harness.run_test_complete.expect_call()
430        utils.drop_caches.expect_call()
431
432        # run and check
433        self.job.run_test(testname)
434        self.god.check_playback()
435
436
437    def test_report_reboot_failure(self):
438        self.construct_job(True)
439
440        # record
441        self.job.record.expect_call("ABORT", "sub", "reboot.verify",
442                                    "boot failure")
443        self.job.record.expect_call("END ABORT", "sub", "reboot",
444                                    optional_fields={"kernel": "2.6.15-smp"})
445
446        # playback
447        self.job._record_reboot_failure("sub", "reboot.verify", "boot failure",
448                                        running_id="2.6.15-smp")
449        self.god.check_playback()
450
451
452    def _setup_check_post_reboot(self, mount_info, cpu_count):
453        # setup
454        self.god.stub_function(job.partition_lib, "get_partition_list")
455        self.god.stub_function(utils, "count_cpus")
456
457        part_list = [self.get_partition_mock("/dev/hda1"),
458                     self.get_partition_mock("/dev/hdb1")]
459        mount_list = ["/mnt/hda1", "/mnt/hdb1"]
460
461        # record
462        job.partition_lib.get_partition_list.expect_call(
463                self.job, exclude_swap=False).and_return(part_list)
464        for i in xrange(len(part_list)):
465            part_list[i].get_mountpoint.expect_call().and_return(mount_list[i])
466        if cpu_count is not None:
467            utils.count_cpus.expect_call().and_return(cpu_count)
468        self.job._state.set('client', 'mount_info', mount_info)
469        self.job._state.set('client', 'cpu_count', 8)
470
471
472    def test_check_post_reboot_success(self):
473        self.construct_job(True)
474
475        mount_info = set([("/dev/hda1", "/mnt/hda1"),
476                          ("/dev/hdb1", "/mnt/hdb1")])
477        self._setup_check_post_reboot(mount_info, 8)
478
479        # playback
480        self.job._check_post_reboot("sub")
481        self.god.check_playback()
482
483
484    def test_check_post_reboot_mounts_failure(self):
485        self.construct_job(True)
486
487        mount_info = set([("/dev/hda1", "/mnt/hda1")])
488        self._setup_check_post_reboot(mount_info, None)
489
490        self.god.stub_function(self.job, "_record_reboot_failure")
491        self.job._record_reboot_failure.expect_call("sub",
492                "reboot.verify_config", "mounted partitions are different after"
493                " reboot (old entries: set([]), new entries: set([('/dev/hdb1',"
494                " '/mnt/hdb1')]))", running_id=None)
495
496        # playback
497        self.assertRaises(error.JobError, self.job._check_post_reboot, "sub")
498        self.god.check_playback()
499
500
501    def test_check_post_reboot_cpu_failure(self):
502        self.construct_job(True)
503
504        mount_info = set([("/dev/hda1", "/mnt/hda1"),
505                          ("/dev/hdb1", "/mnt/hdb1")])
506        self._setup_check_post_reboot(mount_info, 4)
507
508        self.god.stub_function(self.job, "_record_reboot_failure")
509        self.job._record_reboot_failure.expect_call(
510            'sub', 'reboot.verify_config',
511            'Number of CPUs changed after reboot (old count: 8, new count: 4)',
512            running_id=None)
513
514        # playback
515        self.assertRaises(error.JobError, self.job._check_post_reboot, "sub")
516        self.god.check_playback()
517
518
519    def test_parse_args(self):
520        test_set = {"a='foo bar baz' b='moo apt'":
521                    ["a='foo bar baz'", "b='moo apt'"],
522                    "a='foo bar baz' only=gah":
523                    ["a='foo bar baz'", "only=gah"],
524                    "a='b c d' no=argh":
525                    ["a='b c d'", "no=argh"]}
526        for t in test_set:
527            parsed_args = job.base_client_job._parse_args(t)
528            expected_args = test_set[t]
529            self.assertEqual(parsed_args, expected_args)
530
531
532    def test_run_test_timeout_parameter_is_propagated(self):
533        self.construct_job(True)
534
535        # set up stubs
536        self.god.stub_function(self.job.pkgmgr, 'get_package_name')
537        self.god.stub_function(self.job, "_runtest")
538
539        # create an unhandled error object
540        #class MyError(error.TestError):
541        #    pass
542        #real_error = MyError("this is the real error message")
543        #unhandled_error = error.UnhandledTestError(real_error)
544
545        # set up the recording
546        testname = "test"
547        outputdir = os.path.join(self.job.resultdir, testname)
548        self.job.pkgmgr.get_package_name.expect_call(
549            testname, 'test').and_return(("", testname))
550        os.path.exists.expect_call(outputdir).and_return(False)
551        timeout = 60
552        optional_fields = {}
553        optional_fields['timeout'] = timeout
554        self.job.record.expect_call("START", testname, testname,
555                                    optional_fields=optional_fields)
556        self.job._runtest.expect_call(testname, "", timeout, (), {})
557        self.job.record.expect_call("GOOD", testname, testname,
558                                    "completed successfully")
559        self.job.record.expect_call("END GOOD", testname, testname)
560        self.job.harness.run_test_complete.expect_call()
561        utils.drop_caches.expect_call()
562
563        # run and check
564        self.job.run_test(testname, timeout=timeout)
565        self.god.check_playback()
566
567
568if __name__ == "__main__":
569    unittest.main()
570