chromium/third_party/blink/tools/blinkpy/web_tests/controllers/web_test_finder_unittest.py

# Copyright 2015 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import optparse
import unittest

from blinkpy.common.host_mock import MockHost
from blinkpy.common.system.filesystem_mock import MockFileSystem
from blinkpy.web_tests.controllers import web_test_finder
from blinkpy.web_tests.models import test_expectations


class WebTestFinderTests(unittest.TestCase):
    def test_skip_tests_expectations(self):
        """Tests that tests are skipped based on to expectations and options."""
        host = MockHost()
        port = host.port_factory.get('test-win-win7', None)

        all_tests = [
            'fast/css/passes.html',
            'fast/css/fails.html',
            'fast/css/times_out.html',
            'fast/css/skip.html',
        ]

        # Patch port.tests() to return our tests
        port.tests = lambda paths: paths or all_tests

        options = optparse.Values({
            'no_expectations': False,
            'enable_sanitizer': False,
            'skipped': 'default',
            'skip_timeouts': False,
            'skip_failing_tests': False,
        })
        finder = web_test_finder.WebTestFinder(port, options)

        expectations = test_expectations.TestExpectations(port)
        expectations.merge_raw_expectations(
            ('# results: [ Failure Timeout Skip ]'
             '\nfast/css/fails.html [ Failure ]'
             '\nfast/css/times_out.html [ Timeout ]'
             '\nfast/css/skip.html [ Skip ]'))

        # When run with default settings, we only skip the tests marked Skip.
        tests = finder.skip_tests([], all_tests, expectations)
        self.assertEqual(tests, set(['fast/css/skip.html']))

        # Specify test on the command line; by default should not skip.
        tests = finder.skip_tests(['fast/css/skip.html'], all_tests,
                                  expectations)
        self.assertEqual(tests, set())

        # Specify test on the command line, but always skip.
        finder._options.skipped = 'always'
        tests = finder.skip_tests(['fast/css/skip.html'], all_tests,
                                  expectations)
        self.assertEqual(tests, set(['fast/css/skip.html']))
        finder._options.skipped = 'default'

        # Only run skip tests, aka skip all non-skipped tests.
        finder._options.skipped = 'only'
        tests = finder.skip_tests([], all_tests, expectations)
        self.assertEqual(
            tests,
            set([
                'fast/css/passes.html', 'fast/css/fails.html',
                'fast/css/times_out.html'
            ]))
        finder._options.skipped = 'default'

        # Ignore any skip entries, aka never skip anything.
        finder._options.skipped = 'ignore'
        tests = finder.skip_tests([], all_tests, expectations)
        self.assertEqual(tests, set())
        finder._options.skipped = 'default'

        # Skip tests that are marked TIMEOUT.
        finder._options.skip_timeouts = True
        tests = finder.skip_tests([], all_tests, expectations)
        self.assertEqual(
            tests, set(['fast/css/times_out.html', 'fast/css/skip.html']))
        finder._options.skip_timeouts = False

        # Skip tests that are marked FAILURE
        finder._options.skip_failing_tests = True
        tests = finder.skip_tests([], all_tests, expectations)
        self.assertEqual(tests,
                         set(['fast/css/fails.html', 'fast/css/skip.html']))
        finder._options.skip_failing_tests = False

        # Disable expectations entirely; nothing should be skipped by default.
        finder._options.no_expectations = True
        tests = finder.skip_tests([], all_tests, None)
        self.assertEqual(tests, set())

    def test_skip_tests_idlharness(self):
        """Tests that idlharness tests are skipped on MSAN/ASAN runs.

        See https://crbug.com/856601
        """
        host = MockHost()
        port = host.port_factory.get('test-win-win7', None)

        non_idlharness_test = 'external/wpt/dir1/dir2/foo.html'
        idlharness_test_1 = 'external/wpt/dir1/dir2/idlharness.any.html'
        idlharness_test_2 = 'external/wpt/dir1/dir2/idlharness.any.worker.html'
        all_tests = [
            non_idlharness_test,
            idlharness_test_1,
            idlharness_test_2,
        ]

        # Patch port.tests() to return our tests
        port.tests = lambda paths: paths or all_tests

        options = optparse.Values({
            'no_expectations': False,
            'enable_sanitizer': False,
            'skipped': 'default',
            'skip_timeouts': False,
            'skip_failing_tests': False,
        })
        finder = web_test_finder.WebTestFinder(port, options)

        # Default case; not MSAN/ASAN so should not skip anything.
        expectations = test_expectations.TestExpectations(port)
        tests = finder.skip_tests([], all_tests, expectations)
        self.assertEqual(tests, set())

        # MSAN/ASAN, with no paths specified explicitly, so should skip both
        # idlharness tests.
        expectations = test_expectations.TestExpectations(port)
        finder._options.enable_sanitizer = True
        tests = finder.skip_tests([], all_tests, expectations)
        self.assertEqual(tests, set([idlharness_test_1, idlharness_test_2]))

        # Disable expectations entirely; we should still skip the idlharness
        # tests but shouldn't touch the expectations parameter.
        finder._options.no_expectations = True
        tests = finder.skip_tests([], all_tests, None)
        self.assertEqual(tests, set([idlharness_test_1, idlharness_test_2]))

        # MSAN/ASAN, with one of the tests specified explicitly (and
        # --skipped=default), so should skip only the unspecified test.
        expectations = test_expectations.TestExpectations(port)
        tests = finder.skip_tests([idlharness_test_1], all_tests, expectations)
        self.assertEqual(tests, set([idlharness_test_2]))

    def test_find_fastest_tests(self):
        host = MockHost()
        port = host.port_factory.get('test-win-win7', None)

        all_tests = [
            'path/test.html',
            'new/test.html',
            'fast/css/1.html',
            'fast/css/2.html',
            'fast/css/3.html',
            'fast/css/skip1.html',
            'fast/css/skip2.html',
            'fast/css/skip3.html',
            'fast/css/skip4.html',
            'fast/css/skip5.html',
        ]

        port.tests = lambda paths: paths or all_tests

        finder = web_test_finder.WebTestFinder(port, {})
        finder._times_trie = lambda: {
            'fast': {
                'css': {
                    '1.html': 1,
                    '2.html': 2,
                    '3.html': 3,
                    'skip1.html': 0,
                    'skip2.html': 0,
                    'skip3.html': 0,
                    'skip4.html': 0,
                    'skip5.html': 0,
                }
            },
            'path': {
                'test.html': 4,
            }
        }

        tests = finder.find_tests(fastest_percentile=50, args=[])
        self.assertEqual(
            set(tests[1]),
            set(['fast/css/1.html', 'fast/css/2.html', 'new/test.html']))

        tests = finder.find_tests(
            fastest_percentile=50, args=['path/test.html'])
        self.assertEqual(
            set(tests[1]),
            set([
                'fast/css/1.html', 'fast/css/2.html', 'path/test.html',
                'new/test.html'
            ]))

        tests = finder.find_tests(args=[])
        self.assertEqual(tests[1], all_tests)

        tests = finder.find_tests(args=['path/test.html'])
        self.assertEqual(tests[1], ['path/test.html'])

    def test_find_fastest_tests_excludes_deleted_tests(self):
        host = MockHost()
        port = host.port_factory.get('test-win-win7', None)

        all_tests = [
            'fast/css/1.html',
            'fast/css/2.html',
        ]

        port.tests = lambda paths: paths or all_tests

        finder = web_test_finder.WebTestFinder(port, {})

        finder._times_trie = lambda: {
            'fast': {
                'css': {
                    '1.html': 1,
                    '2.html': 2,
                    'non-existant.html': 1,
                }
            },
        }

        tests = finder.find_tests(fastest_percentile=90, args=[])
        self.assertEqual(set(tests[1]), set(['fast/css/1.html']))

    def test_split_chunks(self):
        split = web_test_finder.WebTestFinder._split_into_chunks  # pylint: disable=protected-access

        tests = ['1', '2', '3', '4']
        self.assertEqual(['1', '2', '3', '4'], split(tests, 0, 1))

        self.assertEqual(['3', '4'], split(tests, 0, 2))
        self.assertEqual(['1', '2'], split(tests, 1, 2))

        self.assertEqual(['1', '2', '4'], split(tests, 0, 3))
        self.assertEqual([], split(tests, 1, 3))
        self.assertEqual(['3'], split(tests, 2, 3))

        tests = ['1', '2', '3', '4', '5']
        self.assertEqual(['1', '2', '3', '4', '5'], split(tests, 0, 1))

        self.assertEqual(['3', '4'], split(tests, 0, 2))
        self.assertEqual(['1', '2', '5'], split(tests, 1, 2))

        self.assertEqual(['1', '2', '4'], split(tests, 0, 3))
        self.assertEqual(['5'], split(tests, 1, 3))
        self.assertEqual(['3'], split(tests, 2, 3))

        tests = ['1', '2', '3', '4', '5', '6']
        self.assertEqual(['1', '2', '3', '4', '5', '6'], split(tests, 0, 1))

        self.assertEqual(['3', '4'], split(tests, 0, 2))
        self.assertEqual(['1', '2', '5', '6'], split(tests, 1, 2))

        self.assertEqual(['1', '2', '4'], split(tests, 0, 3))
        self.assertEqual(['5', '6'], split(tests, 1, 3))
        self.assertEqual(['3'], split(tests, 2, 3))

    def test_test_list_find_tests(self):
        host = MockHost()
        port = host.port_factory.get('test-win-win7', None)
        mock_files = {'test-list.txt': \
            'path/test.html\n'\
            'virtual/path/test.html'}
        host.filesystem = MockFileSystem(files=mock_files)

        port_tests = [
            'path/test.html',
            'not/in/test/list.html',
        ]

        port.tests = lambda paths: paths or port_tests

        finder = web_test_finder.WebTestFinder(port, {})

        tests = finder.find_tests(args=[], test_lists=['test-list.txt'])
        self.assertEqual(
            set(tests[1]),
            set(['path/test.html','virtual/path/test.html',]))

    def test_inverted_test_filter_find_tests(self):
        host = MockHost()
        port = host.port_factory.get('test-win-win7', None)
        mock_files = {
            'test-list.txt': 'path/test.html\nvirtual/path/test.html',
            'inverted-filter.txt': 'path/test.html'
        }
        host.filesystem = MockFileSystem(files=mock_files)

        port_tests = [
            'path/test.html',
            'not/in/test/list.html',
        ]

        port.tests = lambda paths: paths or port_tests

        finder = web_test_finder.WebTestFinder(port, {})

        tests = finder.find_tests(
            args=[],
            test_lists=['test-list.txt'],
            inverted_filter_files=['inverted-filter.txt'])
        self.assertEqual(set(tests[1]), set([
            'virtual/path/test.html',
        ]))

class FilterTestsTests(unittest.TestCase):
    simple_test_filter = ['a/a1.html', 'a/a2.html', 'b/b1.html']

    def check(self, tests, filters, expected_tests):
        self.assertEqual(expected_tests,
                         web_test_finder.filter_tests(tests, filters))

    def test_no_filters(self):
        self.check(self.simple_test_filter, [], self.simple_test_filter)

    def test_empty_glob_is_rejected(self):
        self.assertRaises(ValueError, self.check, self.simple_test_filter,
                          [['']], [])
        self.assertRaises(ValueError, self.check, self.simple_test_filter,
                          [['-']], [])

    def test_one_all_positive_filter(self):
        self.check(self.simple_test_filter, [['a*']],
                   ['a/a1.html', 'a/a2.html'])
        self.check(self.simple_test_filter, [['+a*']],
                   ['a/a1.html', 'a/a2.html'])

        self.check(self.simple_test_filter, [['a*', 'b*']],
                   self.simple_test_filter)

    def test_one_exact_positive_filter(self):
        self.check(self.simple_test_filter, [['a/a1.html']], ['a/a1.html'])
        self.check(self.simple_test_filter, [['+a/a1.html']], ['a/a1.html'])

    def test_one_all_negative_filter(self):
        self.check(self.simple_test_filter, [['-c*']], self.simple_test_filter)

    def test_one_exact_negative_filter(self):
        self.check(self.simple_test_filter, [['-a/a1.html']],
                   ['a/a2.html', 'b/b1.html'])

    def test_one_mixed_filter(self):
        self.check(self.simple_test_filter, [['a*', '-c*']],
                   ['a/a1.html', 'a/a2.html'])

    def test_two_all_positive_filters(self):
        self.check(self.simple_test_filter, [['a*'], ['b*']], [])

    def test_two_all_negative_filters(self):
        self.check(self.simple_test_filter, [['-a*'], ['-b*']], [])

        self.check(self.simple_test_filter, [['-a*'], ['-c*']], ['b/b1.html'])

    def test_two_mixed_filters(self):
        self.check(self.simple_test_filter, [['a*'], ['-b*']],
                   ['a/a1.html', 'a/a2.html'])

    def test_longest_glob_wins(self):
        # These test that if two matching globs are specified as
        # part of the same filter expression, the longest matching
        # glob wins (takes precedence). The order of the two globs
        # must not matter.
        self.check(self.simple_test_filter, [['a/a*', '-a/a2*']],
                   ['a/a1.html'])
        self.check(self.simple_test_filter, [['-a/a*', 'a/a2*']],
                   ['a/a2.html'])

        # In this test, the positive and negative globs are in
        # separate filter expressions, so a2 should be filtered out
        # and nothing should run (tests should only be run if they
        # would be run by every filter individually).
        self.check(self.simple_test_filter, [['-a/a*'], ['a/a2*']], [])

    def test_only_trailing_unescaped_globs_work(self):
        self.check(self.simple_test_filter, [['a*']],
                   ['a/a1.html', 'a/a2.html'])
        # These test that if you have a glob that contains a "*" that isn't
        # at the end, it is rejected; only globs at the end should work.
        self.assertRaises(ValueError, self.check, self.simple_test_filter,
                          [['*1.html']], [])
        self.assertRaises(ValueError, self.check, self.simple_test_filter,
                          [['a*.html']], [])

    def test_escaped_globs_allowed(self):
        self.check(self.simple_test_filter + ['a\\*1'], [['-a\\*1']],
                   self.simple_test_filter)