예제 #1
0
    def test_invalid_dict(self):

        # Pass in some invalid data
        for invalid in ["", "invalid", ["list"], 5, None]:
            with self.assertRaises(ValueError):
                coverage_data = CoverageData()
                coverage_data.load_from_dict('root_dir', '', invalid)
예제 #2
0
    def test_get_relative_unknown_path(self):

        # Load the data
        coverage_data = CoverageData()
        coverage_data.load_from_dict('/root_dir', '', self.TEST_COVERAGE_DICT)

        # Unknown path returns None, even if in the root dir
        self.assertIs(coverage_data.rel_src_path('unknown'), None)
        self.assertIs(coverage_data.rel_src_path('/root_dir/unknown'), None)
예제 #3
0
    def test_missing_key(self):

        # Load data with a JSON dict that is missing the line keys
        coverage_data = CoverageData()
        coverage_data.load_from_dict('root_dir', '',
                                     {'/src1': {'missing key': True}})

        # Expect that no coverage information is loaded
        self.assertEqual(coverage_data.src_list(), [])
        self.assertIs(coverage_data.line_dict_for_src('root_dir/src1'), None)
예제 #4
0
    def test_get_relative_src_path(self):

        # Load the data
        coverage_data = CoverageData()
        coverage_data.load_from_dict('/root_dir', 'prepend/to/path', self.TEST_COVERAGE_DICT)

        # Check that we can retrieve the relative source path
        self.assertEqual(coverage_data.rel_src_path(u'/root_dir/src1.js'),
                         'prepend/to/path/src1.js')
        self.assertEqual(coverage_data.rel_src_path(u'/root_dir/subdir/src2.js'),
                         'prepend/to/path/subdir/src2.js')
예제 #5
0
    def test_covered_and_uncovered_src(self, mock_num_file_lines):

        # Set an expected source file
        coverage_data = CoverageData()
        coverage_data.add_expected_src('/root_dir', 'uncovered.js')

        # Configure the num file lines for the source file
        num_lines = 10
        mock_num_file_lines.return_value = num_lines

        # Provide coverage info for other source files,
        # but not the one we passed to the constructor
        coverage_data.load_from_dict('/root_dir', '', self.TEST_COVERAGE_DICT)

        # Expect that all sources are reported (including the uncovered src)
        self.assertEqual(coverage_data.src_list(), 
                         [u'/root_dir/src1.js',
                          u'/root_dir/subdir/src2.js',
                          u'/root_dir/uncovered.js'])

        # Expect that the source is reported as 0% covered
        self.assertEqual(coverage_data.line_dict_for_src('/root_dir/uncovered.js'),
                         {line_num: False for line_num in range(num_lines)})

        # Expect that total coverage is smaller because
        # of the uncovered file.
        expected_coverage = 6.0 / (8.0 + num_lines)
        self.assertEqual(coverage_data.total_coverage(), expected_coverage)
예제 #6
0
    def start(self):
        """
        Start serving pages on an open local port.
        """
        server_thread = threading.Thread(target=self.serve_forever)
        server_thread.daemon = True
        server_thread.start()

        # If we're collecting coverage information
        if self._jscover_path is not None:

            # Create an object to store coverage data we receive
            self.coverage_data = CoverageData()

            # Start each SrcInstrumenter instance if we know where JSCover is
            for suite_name, desc in self.desc_dict.iteritems():

                # Inform the coverage data that we expect this source
                # (report it as 0% if no info received).
                for rel_path in desc.src_paths():
                    self.coverage_data.add_expected_src(desc.root_dir(), rel_path)

                # Create an instrumenter serving files
                # in the suite description root directory
                instr = SrcInstrumenter(desc.root_dir(),
                                        tool_path=self._jscover_path)

                # Start the instrumenter service
                instr.start()

                # Associate the instrumenter with its suite description
                self.src_instr_dict[suite_name] = instr

        else:
            self.src_instr_dict = {}
예제 #7
0
    def assert_output_equals(self, coverage_dict, expected_output):
        """
        Asserts that the output from the coverage reporter
        is equal to `expected_output`.

        `coverage_dict` is a dict of the form:

            { SRC_PATH: [ COVER_INFO, ...]

        where COVER_INFO is either:

        * None: no coverage info
        * integer: number of times line was hit

        for example:

            { 'src.js': [ 1, 0, None, 1]}

        This assumes that the output is XML-parseable; it will
        parse the XML to ignore whitespace between elements.
        """

        # Munge the dict into the right format
        coverage_dict = {src_path: {'lineData': line_data}
                         for src_path, line_data in coverage_dict.items()}

        # Create a `CoverageData` instance.
        # Since this involves no network or filesystem access
        # we don't bother mocking it.
        data = CoverageData()
        data.load_from_dict('root_dir', '', coverage_dict)

        # Write the report to the output file in the temp directory
        self.reporter.write_report(data)

        # Read the data back in from the output file
        with open(self.OUTPUT_FILE_NAME) as output_file:
            output_str = output_file.read()

        # Run the reports through the XML parser to normalize format
        output_str = etree.tostring(etree.fromstring(output_str))
        expected_output = etree.tostring(etree.fromstring(expected_output))

        # Check that the the output matches what we expect
        self.assertEqual(output_str, expected_output)
예제 #8
0
    def test_suite_name_list(self):

        # Report data from two suites
        coverage_data = CoverageData()
        coverage_data.add_suite_name(2)
        coverage_data.add_suite_name(3)
        coverage_data.add_suite_name(3)

        self.assertEqual(coverage_data.suite_name_list(), [2, 3])
예제 #9
0
    def test_multiple_load_from_dict(self):

        # Load the data
        coverage_data = CoverageData()
        coverage_data.load_from_dict('/root_dir', '', self.TEST_COVERAGE_DICT)

        # Load additional data covering the lines that were uncovered
        lines = [0, 1, 0, 1, 1, None]
        coverage_data.load_from_dict('/root_dir', '', {'/src1.js': {'lineData': lines}})

        # Check that the two sources are combined correctly
        expected = {0: True, 1: True, 2: True, 3: True, 4: True, 5: True}
        self.assertEqual(coverage_data.line_dict_for_src('/root_dir/src1.js'), expected)
예제 #10
0
    def test_load_from_dict(self):

        # Load the data
        coverage_data = CoverageData()
        coverage_data.load_from_dict('/root_dir', '', self.TEST_COVERAGE_DICT)

        # Check that it gets parsed correctly
        self.assertEqual(coverage_data.src_list(),
                         [u'/root_dir/src1.js', u'/root_dir/subdir/src2.js'])

        self.assertEqual(coverage_data.line_dict_for_src('/root_dir/src1.js'),
                         {0: True, 2: True, 3: False, 5: True})

        self.assertEqual(coverage_data.line_dict_for_src('/root_dir/subdir/src2.js'),
                         {0: True, 1: True, 2: True, 3: False})
예제 #11
0
    def test_different_root_dirs(self):

        # Load data from two different root dirs
        coverage_data = CoverageData()
        coverage_data.load_from_dict('/root_1', '', self.TEST_COVERAGE_DICT)
        coverage_data.load_from_dict('/root_2', '', self.TEST_COVERAGE_DICT)

        # We should get two separate sources
        self.assertEqual(coverage_data.src_list(),
                         ['/root_1/src1.js', '/root_1/subdir/src2.js',
                          '/root_2/src1.js', '/root_2/subdir/src2.js'])

        # But the data in each should be the same
        expected = {0: True, 2: True, 3: False, 5: True}
        self.assertEqual(coverage_data.line_dict_for_src('/root_1/src1.js'),
                         expected)
        self.assertEqual(coverage_data.line_dict_for_src('/root_2/src1.js'),
                         expected)
예제 #12
0
    def test_uncovered_src(self, mock_num_file_lines):

        # Set an expected source file
        coverage_data = CoverageData()
        coverage_data.add_expected_src('/root_dir', 'src1.js')

        # Configure the num file lines for the source file
        num_lines = 10
        mock_num_file_lines.return_value = num_lines

        # Provide no coverage information (did NOT call `load_from_dict()`)

        # Expect that the source is still reported
        self.assertEqual(coverage_data.src_list(), ['/root_dir/src1.js'])
        self.assertEqual(coverage_data.rel_src_path('/root_dir/src1.js'), 'src1.js')

        # Expect that the source is reported as 0% covered
        self.assertEqual(coverage_data.line_dict_for_src('/root_dir/src1.js'),
                         {line_num: False for line_num in range(num_lines)})

        # Expect that total coverage is 0%
        self.assertEqual(coverage_data.total_coverage(), 0.0)
예제 #13
0
class SuitePageServer(ThreadingMixIn, HTTPServer):
    """
    Serve test suite pages and included JavaScript files.
    """

    protocol_version = 'HTTP/1.1'

    # Request response timeout
    timeout = 5

    # Amount of time to wait for clients to POST coverage info
    # back to the server before timing out.
    COVERAGE_TIMEOUT = 2.0

    # Amount of time to wait between checks that the we
    # have all the coverage info
    COVERAGE_WAIT_TIME = 0.1

    # Returns the `CoverageData` instance used by the server
    # to store coverage data received from the test suites.
    # Since `CoverageData` is thread-safe, it is okay for
    # other processes to write to it asynchronously.
    coverage_data = None

    def __init__(self, suite_desc_list, suite_renderer, jscover_path=None, port=0):
        """
        Initialize the server to serve test runner pages
        and dependencies described by `suite_desc_list`
        (list of `SuiteDescription` instances).

        `jscover_path` is the path to the JSCover JAR file.  If not
        specified, no coverage information will be collected.

        Use `suite_renderer` (a `SuiteRenderer` instance) to
        render the test suite pages.
        """

        # Store dependencies
        self.desc_dict = self._suite_dict_from_list(suite_desc_list)
        self.renderer = suite_renderer
        self._jscover_path = jscover_path

        # Create a dict for source instrumenter services
        # (One for each suite description)
        self.src_instr_dict = {}

        address = ('0.0.0.0', port)
        HTTPServer.__init__(self, address, SuitePageRequestHandler)

    def start(self):
        """
        Start serving pages on an open local port.
        """
        server_thread = threading.Thread(target=self.serve_forever)
        server_thread.daemon = True
        server_thread.start()

        # If we're collecting coverage information
        if self._jscover_path is not None:

            # Create an object to store coverage data we receive
            self.coverage_data = CoverageData()

            # Start each SrcInstrumenter instance if we know where JSCover is
            for suite_name, desc in self.desc_dict.iteritems():

                # Inform the coverage data that we expect this source
                # (report it as 0% if no info received).
                for rel_path in desc.src_paths():
                    self.coverage_data.add_expected_src(desc.root_dir(), rel_path)

                # Create an instrumenter serving files
                # in the suite description root directory
                instr = SrcInstrumenter(desc.root_dir(),
                                        tool_path=self._jscover_path)

                # Start the instrumenter service
                instr.start()

                # Associate the instrumenter with its suite description
                self.src_instr_dict[suite_name] = instr

        else:
            self.src_instr_dict = {}

    def stop(self):
        """
        Stop the server and free the port.
        """

        # Stop each instrumenter service that we started
        for instr in self.src_instr_dict.values():
            instr.stop()

        # Stop the page server and free the port
        self.shutdown()
        self.socket.close()

    def suite_url_list(self):
        """
        Return a list of URLs (unicode strings), where each URL
        is a test suite page containing the JS code to run
        the JavaScript tests.
        """
        return [self.root_url() + u'suite/{}'.format(suite_name)
                for suite_name in self.desc_dict.keys()]

    def root_url(self):
        """
        Return the root URL (including host and port) for the server
        as a unicode string.
        """
        host, port = self.server_address
        return u"http://{}:{}/".format(host, port)

    def all_coverage_data(self):
        """
        Returns a `CoverageData` instance containing all coverage data
        received from running the tests.

        Blocks until all suites have reported coverage data.  If it
        times out waiting for all data, raises a `TimeoutException`.

        If we are not collecting coverage, returns None.
        """
        if self.coverage_data is not None:
            self._block_until(self._has_all_coverage)
            return self.coverage_data

        else:
            return None

    def _block_until(self, success_func):
        """
        Block until `success_func` returns True.
        `success_func` should be a lambda with no argument.
        """

        # Remember when we started
        start_time = time.time()

        # Until we are successful
        while not success_func():

            # See if we've timed out
            if time.time() - start_time > self.COVERAGE_TIMEOUT:
                raise TimeoutError()

            # Wait a little bit before checking again
            time.sleep(self.COVERAGE_WAIT_TIME)

    def _has_all_coverage(self):
        """
        Returns True if and only if every suite
        has coverage information.
        """
        # Retrieve the indices of each suite for which coverage
        # information was reported.
        suite_name_list = self.coverage_data.suite_name_list()

        # Check that we have an index for every suite
        # (This is not the most efficient way to do this --
        # if it becomes a bottleneck, we can revisit.)
        return (sorted(suite_name_list) == sorted(self.desc_dict.keys()))

    @classmethod
    def _suite_dict_from_list(cls, suite_desc_list):
        """
        Given a list of `SuiteDescription` instances, construct
        a dictionary mapping suite names to the instances.

        Raises a `DuplicateSuiteNameError` if two suites have
        the same name.
        """
        suite_dict = {
            suite.suite_name(): suite
            for suite in suite_desc_list
        }

        # Check that we haven't repeated keys
        duplicates = cls._duplicates([suite.suite_name() for suite in suite_desc_list])

        if len(duplicates) > 0:
            msg = "Duplicate suite name(s): {}".format(",".join(duplicates))
            raise DuplicateSuiteNameError(msg)

        return suite_dict

    @classmethod
    def _duplicates(cls, name_list):
        """
        Given a list of strings, return a set of duplicates in the list.
        """
        seen = set()
        duplicates = set()

        for name in name_list:

            # Check if we've already seen the name; if so, add it
            # to the list of duplicates
            if name in seen:
                duplicates.add(name)

            # Add the name to the list of names we've already seen
            else:
                seen.add(name)

        return duplicates
예제 #14
0
    def test_total_coverage(self):
        coverage_data = CoverageData()
        coverage_data.load_from_dict('root_dir', '', self.TEST_COVERAGE_DICT)

        # Total coverage is 6/8 = 0.75
        self.assertEqual(coverage_data.total_coverage(), 0.75)
예제 #15
0
    def test_coverage_for_src(self):
        coverage_data = CoverageData()
        coverage_data.load_from_dict('root_dir', '', self.TEST_COVERAGE_DICT)

        # The coverage for root_dir/src1.js is 3/4 = 0.75
        self.assertEqual(coverage_data.coverage_for_src('root_dir/src1.js'), 0.75)