test_output.py 23.0 KB
Newer Older
1 2
import json
import tempfile
3
import os
4
import re
5
import shutil
6
import unittest
7
from xml.dom import minidom
8

9 10
import pkg_resources

11
from avocado.core import exit_codes
12
from avocado.core.output import TermSupport
13
from avocado.utils import process
14
from avocado.utils import script
15
from avocado.utils import path as utils_path
16

17 18 19
basedir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..')
basedir = os.path.abspath(basedir)

20 21
AVOCADO = os.environ.get("UNITTEST_AVOCADO_CMD", "./scripts/avocado")

22

23 24 25
PERL_TAP_PARSER_SNIPPET = """#!/bin/env perl
use TAP::Parser;

26
my $parser = TAP::Parser->new( { exec => ['%s', 'run', 'passtest.py', 'errortest.py', 'warntest.py', '--tap', '-', '--sysinfo', 'off', '--job-results-dir', '%%s'] } );
27 28 29 30 31 32 33

while ( my $result = $parser->next ) {
        $result->is_unknown && die "Unknown line \\"" . $result->as_string . "\\" in the TAP output!\n";
}
$parser->parse_errors == 0 || die "Parser errors!\n";
$parser->is_good_plan || die "Plan is not a good plan!\n";
$parser->plan eq '1..3' || die "Plan does not match what was expected!\n";
34
""" % AVOCADO
35 36


37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
OUTPUT_TEST_CONTENT = """#!/bin/env python
import sys

from avocado import Test
from avocado.utils import process

print "top_print"
sys.stdout.write("top_stdout\\n")
sys.stderr.write("top_stderr\\n")
process.run("/bin/echo top_process")

class OutputTest(Test):
    def __init__(self, *args, **kwargs):
        super(OutputTest, self).__init__(*args, **kwargs)
        print "init_print"
        sys.stdout.write("init_stdout\\n")
        sys.stderr.write("init_stderr\\n")
        process.run("/bin/echo init_process")

    def test(self):
        print "test_print"
        sys.stdout.write("test_stdout\\n")
        sys.stderr.write("test_stderr\\n")
60
        process.run("/bin/echo -n test_process")
61 62 63 64 65

    def __del__(self):
        print "del_print"
        sys.stdout.write("del_stdout\\n")
        sys.stderr.write("del_stderr\\n")
66
        process.run("/bin/echo -n del_process")
67 68 69
"""


70 71 72 73 74 75 76 77
def image_output_uncapable():
    try:
        import PIL
        return False
    except ImportError:
        return True


78 79
def html_uncapable():
    try:
80
        pkg_resources.require('avocado-framework-plugin-result-html')
81 82 83 84 85
        return False
    except pkg_resources.DistributionNotFound:
        return True


86 87 88 89
def perl_tap_parser_uncapable():
    return os.system("perl -e 'use TAP::Parser;'") != 0


90 91 92 93 94 95 96 97
def missing_binary(binary):
    try:
        utils_path.find_command(binary)
        return False
    except utils_path.CmdNotFoundError:
        return True


98 99
class OutputTest(unittest.TestCase):

100
    def setUp(self):
101
        self.tmpdir = tempfile.mkdtemp(prefix='avocado_' + __name__)
102
        os.chdir(basedir)
103

104 105
    @unittest.skipIf(missing_binary('cc'),
                     "C compiler is required by the underlying doublefree.py test")
106
    def test_output_doublefree(self):
107 108
        cmd_line = ('%s run --job-results-dir %s --sysinfo=off '
                    'doublefree.py' % (AVOCADO, self.tmpdir))
109
        result = process.run(cmd_line, ignore_status=True)
110
        expected_rc = exit_codes.AVOCADO_ALL_OK
111 112 113 114 115 116 117 118 119
        output = result.stdout + result.stderr
        self.assertEqual(result.exit_status, expected_rc,
                         "Avocado did not return rc %d:\n%s" %
                         (expected_rc, result))
        bad_string = 'double free or corruption'
        self.assertNotIn(bad_string, output,
                         "Libc double free can be seen in avocado "
                         "doublefree output:\n%s" % output)

120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
    def test_print_to_std(self):
        def _check_output(path, exps, name):
            i = 0
            end = len(exps)
            for line in open(path):
                if exps[i] in line:
                    i += 1
                    if i == end:
                        break
            self.assertEqual(i, end, "Failed to find %sth message from\n%s\n"
                             "\nin the %s. Either it's missing or in wrong "
                             "order.\n%s" % (i, "\n".join(exps), name,
                                             open(path).read()))
        test = script.Script(os.path.join(self.tmpdir, "output_test.py"),
                             OUTPUT_TEST_CONTENT)
        test.save()
        result = process.run("%s run --job-results-dir %s --sysinfo=off "
                             "--json - -- %s" % (AVOCADO, self.tmpdir, test))
        res = json.loads(result.stdout)
        joblog = res["debuglog"]
        exps = ["[stdout] top_print", "[stdout] top_stdout",
                "[stderr] top_stderr", "[stdout] top_process",
                "[stdout] init_print", "[stdout] init_stdout",
                "[stderr] init_stderr", "[stdout] init_process",
                "[stdout] test_print", "[stdout] test_stdout",
                "[stderr] test_stderr", "[stdout] test_process"]
        _check_output(joblog, exps, "job.log")
        testdir = res["tests"][0]["logdir"]
148
        self.assertEqual("test_print\ntest_stdout\ntest_process",
149
                         open(os.path.join(testdir, "stdout")).read())
150
        self.assertEqual("test_stderr\n",
151
                         open(os.path.join(testdir, "stderr")).read())
152 153
        self.assertEqual("test_print\ntest_stdout\ntest_stderr\n"
                         "test_process",
154
                         open(os.path.join(testdir, "output")).read())
155

156 157 158
    def tearDown(self):
        shutil.rmtree(self.tmpdir)

159

160 161
class OutputPluginTest(unittest.TestCase):

162
    def setUp(self):
163
        self.tmpdir = tempfile.mkdtemp(prefix='avocado_' + __name__)
164
        os.chdir(basedir)
165

166 167 168 169 170 171 172 173
    def check_output_files(self, debug_log):
        base_dir = os.path.dirname(debug_log)
        json_output = os.path.join(base_dir, 'results.json')
        self.assertTrue(os.path.isfile(json_output))
        with open(json_output, 'r') as fp:
            json.load(fp)
        xunit_output = os.path.join(base_dir, 'results.xml')
        self.assertTrue(os.path.isfile(json_output))
174 175
        try:
            minidom.parse(xunit_output)
C
Cleber Rosa 已提交
176
        except Exception as details:
177 178
            raise AssertionError("Unable to parse xunit output: %s\n\n%s"
                                 % (details, open(xunit_output).read()))
179 180 181 182 183
        tap_output = os.path.join(base_dir, "results.tap")
        self.assertTrue(os.path.isfile(tap_output))
        tap = open(tap_output).read()
        self.assertIn("..", tap)
        self.assertIn("\n# debug.log of ", tap)
184

185
    def test_output_incompatible_setup(self):
186 187
        cmd_line = ('%s run --job-results-dir %s --sysinfo=off '
                    '--xunit - --json - passtest.py' % (AVOCADO, self.tmpdir))
188
        result = process.run(cmd_line, ignore_status=True)
189
        expected_rc = exit_codes.AVOCADO_FAIL
190 191 192
        self.assertEqual(result.exit_status, expected_rc,
                         "Avocado did not return rc %d:\n%s" %
                         (expected_rc, result))
193 194 195 196 197 198 199
        error_regex = re.compile(r'avocado run: error: argument ((--json)|'
                                 '(--xunit)): Options ((--xunit --json)|'
                                 '(--json --xunit)) are trying to use stdout '
                                 'simultaneously\n')
        self.assertIsNotNone(error_regex.match(result.stderr),
                             "Missing error message from output:\n%s" %
                             result.stderr)
200

201 202
    @unittest.skipIf(html_uncapable(),
                     "Uncapable of Avocado Result HTML plugin")
203
    def test_output_incompatible_setup_2(self):
204 205
        cmd_line = ('%s run --job-results-dir %s --sysinfo=off '
                    '--html - passtest.py' % (AVOCADO, self.tmpdir))
206
        result = process.run(cmd_line, ignore_status=True)
207
        expected_rc = exit_codes.AVOCADO_JOB_FAIL
208 209 210 211 212 213 214 215
        output = result.stdout + result.stderr
        self.assertEqual(result.exit_status, expected_rc,
                         "Avocado did not return rc %d:\n%s" %
                         (expected_rc, result))
        error_excerpt = "HTML to stdout not supported"
        self.assertIn(error_excerpt, output,
                      "Missing excerpt error message from output:\n%s" % output)

216 217
    def test_output_compatible_setup(self):
        tmpfile = tempfile.mktemp()
218
        cmd_line = ('%s run --job-results-dir %s --sysinfo=off '
219
                    '--journal --xunit %s --json - passtest.py' %
220
                    (AVOCADO, self.tmpdir, tmpfile))
221 222
        result = process.run(cmd_line, ignore_status=True)
        output = result.stdout + result.stderr
223
        expected_rc = exit_codes.AVOCADO_ALL_OK
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
        try:
            self.assertEqual(result.exit_status, expected_rc,
                             "Avocado did not return rc %d:\n%s" %
                             (expected_rc, result))
            # Check if we are producing valid outputs
            json.loads(output)
            minidom.parse(tmpfile)
        finally:
            try:
                os.remove(tmpfile)
            except OSError:
                pass

    def test_output_compatible_setup_2(self):
        tmpfile = tempfile.mktemp()
239
        cmd_line = ('%s run --job-results-dir %s --sysinfo=off '
240
                    '--xunit - --json %s passtest.py' %
241
                    (AVOCADO, self.tmpdir, tmpfile))
242 243
        result = process.run(cmd_line, ignore_status=True)
        output = result.stdout + result.stderr
244
        expected_rc = exit_codes.AVOCADO_ALL_OK
245 246 247 248 249 250
        try:
            self.assertEqual(result.exit_status, expected_rc,
                             "Avocado did not return rc %d:\n%s" %
                             (expected_rc, result))
            # Check if we are producing valid outputs
            with open(tmpfile, 'r') as fp:
251 252 253
                json_results = json.load(fp)
                debug_log = json_results['debuglog']
                self.check_output_files(debug_log)
254 255 256 257 258 259 260
            minidom.parseString(output)
        finally:
            try:
                os.remove(tmpfile)
            except OSError:
                pass

261 262
    @unittest.skipIf(html_uncapable(),
                     "Uncapable of Avocado Result HTML plugin")
263
    def test_output_compatible_setup_3(self):
264 265 266
        tmpfile = tempfile.mktemp(prefix='avocado_' + __name__)
        tmpfile2 = tempfile.mktemp(prefix='avocado_' + __name__)
        tmpdir = tempfile.mkdtemp(prefix='avocado_' + __name__)
267
        tmpfile3 = os.path.join(tmpdir, "result.html")
268 269 270
        cmd_line = ('%s run --job-results-dir %s --sysinfo=off '
                    '--xunit %s --json %s --html %s passtest.py'
                    % (AVOCADO, self.tmpdir, tmpfile, tmpfile2, tmpfile3))
271 272
        result = process.run(cmd_line, ignore_status=True)
        output = result.stdout + result.stderr
273
        expected_rc = exit_codes.AVOCADO_ALL_OK
274
        tmpdir_contents = os.listdir(tmpdir)
275 276 277
        self.assertEqual(len(tmpdir_contents), 1, "Html plugin generated "
                         "extra files in the result dir: %s"
                         % tmpdir_contents)
278 279 280 281
        try:
            self.assertEqual(result.exit_status, expected_rc,
                             "Avocado did not return rc %d:\n%s" %
                             (expected_rc, result))
282
            self.assertNotEqual(output, "", "Output is empty")
283
            # Check if we are producing valid outputs
284 285 286 287 288 289 290 291 292
            with open(tmpfile2, 'r') as fp:
                json_results = json.load(fp)
                debug_log = json_results['debuglog']
                self.check_output_files(debug_log)
            minidom.parse(tmpfile)
        finally:
            try:
                os.remove(tmpfile)
                os.remove(tmpfile2)
293
                shutil.rmtree(tmpdir)
294 295 296 297 298 299
            except OSError:
                pass

    def test_output_compatible_setup_nooutput(self):
        tmpfile = tempfile.mktemp()
        tmpfile2 = tempfile.mktemp()
300
        # Verify --silent can be supplied as app argument
301 302 303
        cmd_line = ('%s --silent run --job-results-dir %s '
                    '--sysinfo=off --xunit %s --json %s passtest.py'
                    % (AVOCADO, self.tmpdir, tmpfile, tmpfile2))
304 305
        result = process.run(cmd_line, ignore_status=True)
        output = result.stdout + result.stderr
306
        expected_rc = exit_codes.AVOCADO_ALL_OK
307 308 309 310 311 312
        try:
            self.assertEqual(result.exit_status, expected_rc,
                             "Avocado did not return rc %d:\n%s" %
                             (expected_rc, result))
            self.assertEqual(output, "", "Output is not empty:\n%s" % output)
            # Check if we are producing valid outputs
313
            with open(tmpfile2, 'r') as fp:
314 315 316
                json_results = json.load(fp)
                debug_log = json_results['debuglog']
                self.check_output_files(debug_log)
317 318 319 320 321 322 323 324
            minidom.parse(tmpfile)
        finally:
            try:
                os.remove(tmpfile)
                os.remove(tmpfile2)
            except OSError:
                pass

325
    def test_nonprintable_chars(self):
326
        cmd_line = ("%s run --external-runner /bin/ls "
327
                    "'NON_EXISTING_FILE_WITH_NONPRINTABLE_CHARS_IN_HERE\x1b' "
328 329
                    "--job-results-dir %s --sysinfo=off"
                    % (AVOCADO, self.tmpdir))
330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
        result = process.run(cmd_line, ignore_status=True)
        output = result.stdout + result.stderr
        expected_rc = exit_codes.AVOCADO_TESTS_FAIL
        self.assertEqual(result.exit_status, expected_rc,
                         "Avocado did not return rc %d:\n%s" %
                         (expected_rc, result))
        debug_log = None
        for line in output.splitlines():
            if "JOB LOG" in line:
                debug_log = line.split(':', 1)[-1].strip()
                break
        self.assertTrue(debug_log, "Unable to get JOB LOG from output:\n%s"
                        % output)
        self.check_output_files(debug_log)

345
    def test_show_job_log(self):
346 347
        cmd_line = ('%s run --job-results-dir %s --sysinfo=off '
                    'passtest.py --show-job-log' % (AVOCADO, self.tmpdir))
348
        result = process.run(cmd_line, ignore_status=True)
349
        expected_rc = exit_codes.AVOCADO_ALL_OK
350 351 352
        self.assertEqual(result.exit_status, expected_rc,
                         "Avocado did not return rc %d:\n%s" %
                         (expected_rc, result))
353
        job_id_list = re.findall('Job ID: (.*)', result.stdout,
354
                                 re.MULTILINE)
355 356
        self.assertTrue(job_id_list, 'No Job ID in stdout:\n%s' %
                        result.stdout)
357 358
        job_id = job_id_list[0]
        self.assertEqual(len(job_id), 40)
359 360

    def test_silent_trumps_show_job_log(self):
361
        # Also verify --silent can be supplied as run option
362 363 364
        cmd_line = ('%s run --silent --job-results-dir %s '
                    '--sysinfo=off passtest.py --show-job-log'
                    % (AVOCADO, self.tmpdir))
365 366
        result = process.run(cmd_line, ignore_status=True)
        output = result.stdout + result.stderr
367
        expected_rc = exit_codes.AVOCADO_ALL_OK
368 369 370 371 372
        self.assertEqual(result.exit_status, expected_rc,
                         "Avocado did not return rc %d:\n%s" %
                         (expected_rc, result))
        self.assertEqual(output, "")

373
    def test_default_enabled_plugins(self):
374 375
        cmd_line = ('%s run --job-results-dir %s --sysinfo=off '
                    'passtest.py' % (AVOCADO, self.tmpdir))
376 377
        result = process.run(cmd_line, ignore_status=True)
        output = result.stdout + result.stderr
378
        expected_rc = exit_codes.AVOCADO_ALL_OK
379 380 381 382
        self.assertEqual(result.exit_status, expected_rc,
                         "Avocado did not return rc %d:\n%s" %
                         (expected_rc, result))
        output_lines = output.splitlines()
C
Cleber Rosa 已提交
383 384 385 386 387
        # The current human output produces 6 lines when running a single test,
        # with an optional 7th line when the HTML report generation is enabled
        self.assertGreaterEqual(len(output_lines), 6,
                                ('Basic human interface did not produce the '
                                 'expect output. Output produced: "%s"' % output))
388 389
        second_line = output_lines[1]
        debug_log = second_line.split()[-1]
390 391
        self.check_output_files(debug_log)

392 393 394
    def test_verify_whiteboard_save(self):
        tmpfile = tempfile.mktemp()
        try:
395 396 397 398
            config = os.path.join(self.tmpdir, "conf.ini")
            content = ("[datadir.paths]\nlogs_dir = %s"
                       % os.path.relpath(self.tmpdir, "."))
            script.Script(config, content).save()
399 400 401
            cmd_line = ('%s --config %s --show all run '
                        '--sysinfo=off whiteboard.py --json %s'
                        % (AVOCADO, config, tmpfile))
402
            result = process.run(cmd_line, ignore_status=True)
403
            expected_rc = exit_codes.AVOCADO_ALL_OK
404 405 406 407 408
            self.assertEqual(result.exit_status, expected_rc,
                             "Avocado did not return rc %d:\n%s" %
                             (expected_rc, result))
            with open(tmpfile, 'r') as fp:
                json_results = json.load(fp)
409 410 411
                logfile = json_results['tests'][0]['logfile']
                debug_dir = os.path.dirname(logfile)
                whiteboard_path = os.path.join(debug_dir, 'whiteboard')
412 413 414 415 416 417 418
                self.assertTrue(os.path.exists(whiteboard_path),
                                'Missing whiteboard file %s' % whiteboard_path)
        finally:
            try:
                os.remove(tmpfile)
            except OSError:
                pass
419

420
    @unittest.skipIf(image_output_uncapable(),
421
                     "Uncapable of generating images with PIL library")
422 423 424
    def test_gendata(self):
        tmpfile = tempfile.mktemp()
        try:
425
            cmd_line = ("%s run --job-results-dir %s "
426
                        "--sysinfo=off gendata.py --json %s" %
427
                        (AVOCADO, self.tmpdir, tmpfile))
428
            result = process.run(cmd_line, ignore_status=True)
429
            expected_rc = exit_codes.AVOCADO_ALL_OK
430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459
            self.assertEqual(result.exit_status, expected_rc,
                             "Avocado did not return rc %d:\n%s" %
                             (expected_rc, result))
            with open(tmpfile, 'r') as fp:
                json_results = json.load(fp)
                bsod_dir = None
                json_dir = None
                for test in json_results['tests']:
                    if "test_bsod" in test['url']:
                        bsod_dir = test['logfile']
                    elif "test_json" in test['url']:
                        json_dir = test['logfile']
                self.assertTrue(bsod_dir, "Failed to get test_bsod output "
                                "directory")
                self.assertTrue(json_dir, "Failed to get test_json output "
                                "directory")
                bsod_dir = os.path.join(os.path.dirname(bsod_dir), "data",
                                        "bsod.png")
                json_dir = os.path.join(os.path.dirname(json_dir), "data",
                                        "test.json")
                self.assertTrue(os.path.exists(bsod_dir), "File %s produced by"
                                "test does not exist" % bsod_dir)
                self.assertTrue(os.path.exists(json_dir), "File %s produced by"
                                "test does not exist" % json_dir)
        finally:
            try:
                os.remove(tmpfile)
            except OSError:
                pass

460 461 462
    def test_redirect_output(self):
        redirected_output_path = tempfile.mktemp()
        try:
463 464 465
            cmd_line = ('%s run --job-results-dir %s '
                        '--sysinfo=off passtest.py > %s'
                        % (AVOCADO, self.tmpdir, redirected_output_path))
466 467
            result = process.run(cmd_line, ignore_status=True, shell=True)
            output = result.stdout + result.stderr
468
            expected_rc = exit_codes.AVOCADO_ALL_OK
469 470 471
            self.assertEqual(result.exit_status, expected_rc,
                             "Avocado did not return rc %d:\n%s" %
                             (expected_rc, result))
472 473
            self.assertEqual(output, '',
                             'After redirecting to file, output is not empty: %s' % output)
474 475 476
            with open(redirected_output_path, 'r') as redirected_output_file_obj:
                redirected_output = redirected_output_file_obj.read()
                for code in TermSupport.ESCAPE_CODES:
R
Rudá Moura 已提交
477
                    self.assertNotIn(code, redirected_output,
478 479 480 481 482 483 484 485
                                     'Found terminal support code %s in redirected output\n%s' %
                                     (code, redirected_output))
        finally:
            try:
                os.remove(redirected_output_path)
            except OSError:
                pass

486 487 488 489
    @unittest.skipIf(perl_tap_parser_uncapable(),
                     "Uncapable of using Perl TAP::Parser library")
    def test_tap_parser(self):
        perl_script = script.TemporaryScript("tap_parser.pl",
490 491
                                             PERL_TAP_PARSER_SNIPPET
                                             % self.tmpdir)
492 493 494
        perl_script.save()
        process.run("perl %s" % perl_script)

A
Amador Pahim 已提交
495
    def test_tap_totaltests(self):
496
        cmd_line = ("%s run passtest.py "
A
Amador Pahim 已提交
497 498
                    "-m examples/tests/sleeptest.py.data/sleeptest.yaml "
                    "--job-results-dir %s "
499
                    "--tap -" % (AVOCADO, self.tmpdir))
A
Amador Pahim 已提交
500 501 502 503 504
        result = process.run(cmd_line)
        expr = '1..4'
        self.assertIn(expr, result.stdout, "'%s' not found in:\n%s"
                      % (expr, result.stdout))

505
    def test_broken_pipe(self):
506
        cmd_line = "(%s run --help | whacky-unknown-command)" % AVOCADO
507 508
        result = process.run(cmd_line, shell=True, ignore_status=True,
                             env={"LC_ALL": "C"})
509 510 511 512 513 514 515 516 517
        expected_rc = 127
        self.assertEqual(result.exit_status, expected_rc,
                         ("avocado run to broken pipe did not return "
                          "rc %d:\n%s" % (expected_rc, result)))
        self.assertEqual(len(result.stderr.splitlines()), 1)
        self.assertIn("whacky-unknown-command", result.stderr)
        self.assertIn("not found", result.stderr)
        self.assertNotIn("Avocado crashed", result.stderr)

A
Amador Pahim 已提交
518 519 520
    def test_results_plugins_no_tests(self):
        cmd_line = ("%s run UNEXISTING --job-results-dir %s"
                    % (AVOCADO, self.tmpdir))
521 522
        result = process.run(cmd_line, ignore_status=True)
        self.assertEqual(result.exit_status, exit_codes.AVOCADO_JOB_FAIL)
A
Amador Pahim 已提交
523 524 525 526 527 528 529 530 531 532

        xunit_results = os.path.join(self.tmpdir, 'latest', 'results.xml')
        self.assertFalse(os.path.exists(xunit_results))

        json_results = os.path.join(self.tmpdir, 'latest', 'results.json')
        self.assertFalse(os.path.exists(json_results))

        tap_results = os.path.join(self.tmpdir, 'latest', 'results.tap')
        self.assertFalse(os.path.exists(tap_results))

533 534 535 536
        # Check that no UI output was generated
        self.assertNotIn("RESULTS    : PASS ", result.stdout)
        self.assertNotIn("JOB TIME   :", result.stdout)

537 538 539
        # Check that plugins do not produce errors
        self.assertNotIn("Error running method ", result.stderr)

540 541 542
    def tearDown(self):
        shutil.rmtree(self.tmpdir)

543

544 545
if __name__ == '__main__':
    unittest.main()