diff options
Diffstat (limited to 'deps/v8/build/android/emma_coverage_stats_test.py')
-rwxr-xr-x | deps/v8/build/android/emma_coverage_stats_test.py | 563 |
1 files changed, 563 insertions, 0 deletions
diff --git a/deps/v8/build/android/emma_coverage_stats_test.py b/deps/v8/build/android/emma_coverage_stats_test.py new file mode 100755 index 0000000000..44f6dc3586 --- /dev/null +++ b/deps/v8/build/android/emma_coverage_stats_test.py @@ -0,0 +1,563 @@ +#!/usr/bin/env python +# Copyright 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# pylint: disable=protected-access + +import unittest +from xml.etree import ElementTree + +import emma_coverage_stats +from pylib.constants import host_paths + +with host_paths.SysPath(host_paths.PYMOCK_PATH): + import mock # pylint: disable=import-error + +EMPTY_COVERAGE_STATS_DICT = { + 'files': {}, + 'patch': { + 'incremental': { + 'covered': 0, 'total': 0 + } + } +} + + +class _EmmaHtmlParserTest(unittest.TestCase): + """Tests for _EmmaHtmlParser. + + Uses modified EMMA report HTML that contains only the subset of tags needed + for test verification. + """ + + def setUp(self): + self.emma_dir = 'fake/dir/' + self.parser = emma_coverage_stats._EmmaHtmlParser(self.emma_dir) + self.simple_html = '<TR><TD CLASS="p">Test HTML</TD></TR>' + self.index_html = ( + '<HTML>' + '<BODY>' + '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">' + '</TABLE>' + '<TABLE CELLSPACING="0" WIDTH="100%">' + '</TABLE>' + '<TABLE CLASS="it" CELLSPACING="0">' + '</TABLE>' + '<TABLE CELLSPACING="0" WIDTH="100%">' + '<TR>' + '<TH CLASS="f">name</TH>' + '<TH>class, %</TH>' + '<TH>method, %</TH>' + '<TH>block, %</TH>' + '<TH>line, %</TH>' + '</TR>' + '<TR CLASS="o">' + '<TD><A HREF="_files/0.html"' + '>org.chromium.chrome.browser</A></TD>' + '<TD CLASS="h">0% (0/3)</TD>' + '</TR>' + '<TR>' + '<TD><A HREF="_files/1.html"' + '>org.chromium.chrome.browser.tabmodel</A></TD>' + '<TD CLASS="h">0% (0/8)</TD>' + '</TR>' + '</TABLE>' + '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">' + '</TABLE>' + '</BODY>' + '</HTML>' + ) + self.package_1_class_list_html = ( + '<HTML>' + '<BODY>' + '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">' + '</TABLE>' + '<TABLE CELLSPACING="0" WIDTH="100%">' + '</TABLE>' + '<TABLE CELLSPACING="0" WIDTH="100%">' + '<TR>' + '<TH CLASS="f">name</TH>' + '<TH>class, %</TH>' + '<TH>method, %</TH>' + '<TH>block, %</TH>' + '<TH>line, %</TH>' + '</TR>' + '<TR CLASS="o">' + '<TD><A HREF="1e.html">IntentHelper.java</A></TD>' + '<TD CLASS="h">0% (0/3)</TD>' + '<TD CLASS="h">0% (0/9)</TD>' + '<TD CLASS="h">0% (0/97)</TD>' + '<TD CLASS="h">0% (0/26)</TD>' + '</TR>' + '</TABLE>' + '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">' + '</TABLE>' + '</BODY>' + '</HTML>' + ) + self.package_2_class_list_html = ( + '<HTML>' + '<BODY>' + '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">' + '</TABLE>' + '<TABLE CELLSPACING="0" WIDTH="100%">' + '</TABLE>' + '<TABLE CELLSPACING="0" WIDTH="100%">' + '<TR>' + '<TH CLASS="f">name</TH>' + '<TH>class, %</TH>' + '<TH>method, %</TH>' + '<TH>block, %</TH>' + '<TH>line, %</TH>' + '</TR>' + '<TR CLASS="o">' + '<TD><A HREF="1f.html">ContentSetting.java</A></TD>' + '<TD CLASS="h">0% (0/1)</TD>' + '</TR>' + '<TR>' + '<TD><A HREF="20.html">DevToolsServer.java</A></TD>' + '</TR>' + '<TR CLASS="o">' + '<TD><A HREF="21.html">FileProviderHelper.java</A></TD>' + '</TR>' + '<TR>' + '<TD><A HREF="22.html">ContextualMenuBar.java</A></TD>' + '</TR>' + '<TR CLASS="o">' + '<TD><A HREF="23.html">AccessibilityUtil.java</A></TD>' + '</TR>' + '<TR>' + '<TD><A HREF="24.html">NavigationPopup.java</A></TD>' + '</TR>' + '</TABLE>' + '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">' + '</TABLE>' + '</BODY>' + '</HTML>' + ) + self.partially_covered_tr_html = ( + '<TR CLASS="p">' + '<TD CLASS="l" TITLE="78% line coverage (7 out of 9)">108</TD>' + '<TD TITLE="78% line coverage (7 out of 9 instructions)">' + 'if (index < 0 || index = mSelectors.size()) index = 0;</TD>' + '</TR>' + ) + self.covered_tr_html = ( + '<TR CLASS="c">' + '<TD CLASS="l">110</TD>' + '<TD> if (mSelectors.get(index) != null) {</TD>' + '</TR>' + ) + self.not_executable_tr_html = ( + '<TR>' + '<TD CLASS="l">109</TD>' + '<TD> </TD>' + '</TR>' + ) + self.tr_with_extra_a_tag = ( + '<TR CLASS="z">' + '<TD CLASS="l">' + '<A name="1f">54</A>' + '</TD>' + '<TD> }</TD>' + '</TR>' + ) + + def testInit(self): + emma_dir = self.emma_dir + parser = emma_coverage_stats._EmmaHtmlParser(emma_dir) + self.assertEqual(parser._base_dir, emma_dir) + self.assertEqual(parser._emma_files_path, 'fake/dir/_files') + self.assertEqual(parser._index_path, 'fake/dir/index.html') + + def testFindElements_basic(self): + read_values = [self.simple_html] + found, _ = MockOpenForFunction(self.parser._FindElements, read_values, + file_path='fake', xpath_selector='.//TD') + self.assertIs(type(found), list) + self.assertIs(type(found[0]), ElementTree.Element) + self.assertEqual(found[0].text, 'Test HTML') + + def testFindElements_multipleElements(self): + multiple_trs = self.not_executable_tr_html + self.covered_tr_html + read_values = ['<div>' + multiple_trs + '</div>'] + found, _ = MockOpenForFunction(self.parser._FindElements, read_values, + file_path='fake', xpath_selector='.//TR') + self.assertEquals(2, len(found)) + + def testFindElements_noMatch(self): + read_values = [self.simple_html] + found, _ = MockOpenForFunction(self.parser._FindElements, read_values, + file_path='fake', xpath_selector='.//TR') + self.assertEqual(found, []) + + def testFindElements_badFilePath(self): + with self.assertRaises(IOError): + with mock.patch('os.path.exists', return_value=False): + self.parser._FindElements('fake', xpath_selector='//tr') + + def testGetPackageNameToEmmaFileDict_basic(self): + expected_dict = { + 'org.chromium.chrome.browser.AccessibilityUtil.java': + 'fake/dir/_files/23.html', + 'org.chromium.chrome.browser.ContextualMenuBar.java': + 'fake/dir/_files/22.html', + 'org.chromium.chrome.browser.tabmodel.IntentHelper.java': + 'fake/dir/_files/1e.html', + 'org.chromium.chrome.browser.ContentSetting.java': + 'fake/dir/_files/1f.html', + 'org.chromium.chrome.browser.DevToolsServer.java': + 'fake/dir/_files/20.html', + 'org.chromium.chrome.browser.NavigationPopup.java': + 'fake/dir/_files/24.html', + 'org.chromium.chrome.browser.FileProviderHelper.java': + 'fake/dir/_files/21.html'} + + read_values = [self.index_html, self.package_1_class_list_html, + self.package_2_class_list_html] + return_dict, mock_open = MockOpenForFunction( + self.parser.GetPackageNameToEmmaFileDict, read_values) + + self.assertDictEqual(return_dict, expected_dict) + self.assertEqual(mock_open.call_count, 3) + calls = [mock.call('fake/dir/index.html'), + mock.call('fake/dir/_files/1.html'), + mock.call('fake/dir/_files/0.html')] + mock_open.assert_has_calls(calls) + + def testGetPackageNameToEmmaFileDict_noPackageElements(self): + self.parser._FindElements = mock.Mock(return_value=[]) + return_dict = self.parser.GetPackageNameToEmmaFileDict() + self.assertDictEqual({}, return_dict) + + def testGetLineCoverage_status_basic(self): + line_coverage = self.GetLineCoverageWithFakeElements([self.covered_tr_html]) + self.assertEqual(line_coverage[0].covered_status, + emma_coverage_stats.COVERED) + + def testGetLineCoverage_status_statusMissing(self): + line_coverage = self.GetLineCoverageWithFakeElements( + [self.not_executable_tr_html]) + self.assertEqual(line_coverage[0].covered_status, + emma_coverage_stats.NOT_EXECUTABLE) + + def testGetLineCoverage_fractionalCoverage_basic(self): + line_coverage = self.GetLineCoverageWithFakeElements([self.covered_tr_html]) + self.assertEqual(line_coverage[0].fractional_line_coverage, 1.0) + + def testGetLineCoverage_fractionalCoverage_partial(self): + line_coverage = self.GetLineCoverageWithFakeElements( + [self.partially_covered_tr_html]) + self.assertEqual(line_coverage[0].fractional_line_coverage, 0.78) + + def testGetLineCoverage_lineno_basic(self): + line_coverage = self.GetLineCoverageWithFakeElements([self.covered_tr_html]) + self.assertEqual(line_coverage[0].lineno, 110) + + def testGetLineCoverage_lineno_withAlternativeHtml(self): + line_coverage = self.GetLineCoverageWithFakeElements( + [self.tr_with_extra_a_tag]) + self.assertEqual(line_coverage[0].lineno, 54) + + def testGetLineCoverage_source(self): + self.parser._FindElements = mock.Mock( + return_value=[ElementTree.fromstring(self.covered_tr_html)]) + line_coverage = self.parser.GetLineCoverage('fake_path') + self.assertEqual(line_coverage[0].source, + ' if (mSelectors.get(index) != null) {') + + def testGetLineCoverage_multipleElements(self): + line_coverage = self.GetLineCoverageWithFakeElements( + [self.covered_tr_html, self.partially_covered_tr_html, + self.tr_with_extra_a_tag]) + self.assertEqual(len(line_coverage), 3) + + def GetLineCoverageWithFakeElements(self, html_elements): + """Wraps GetLineCoverage so mock HTML can easily be used. + + Args: + html_elements: List of strings each representing an HTML element. + + Returns: + A list of LineCoverage objects. + """ + elements = [ElementTree.fromstring(string) for string in html_elements] + with mock.patch('emma_coverage_stats._EmmaHtmlParser._FindElements', + return_value=elements): + return self.parser.GetLineCoverage('fake_path') + + +class _EmmaCoverageStatsTest(unittest.TestCase): + """Tests for _EmmaCoverageStats.""" + + def setUp(self): + self.good_source_to_emma = { + '/path/to/1/File1.java': '/emma/1.html', + '/path/2/File2.java': '/emma/2.html', + '/path/2/File3.java': '/emma/3.html' + } + self.line_coverage = [ + emma_coverage_stats.LineCoverage( + 1, '', emma_coverage_stats.COVERED, 1.0), + emma_coverage_stats.LineCoverage( + 2, '', emma_coverage_stats.COVERED, 1.0), + emma_coverage_stats.LineCoverage( + 3, '', emma_coverage_stats.NOT_EXECUTABLE, 1.0), + emma_coverage_stats.LineCoverage( + 4, '', emma_coverage_stats.NOT_COVERED, 1.0), + emma_coverage_stats.LineCoverage( + 5, '', emma_coverage_stats.PARTIALLY_COVERED, 0.85), + emma_coverage_stats.LineCoverage( + 6, '', emma_coverage_stats.PARTIALLY_COVERED, 0.20) + ] + self.lines_for_coverage = [1, 3, 5, 6] + with mock.patch('emma_coverage_stats._EmmaHtmlParser._FindElements', + return_value=[]): + self.simple_coverage = emma_coverage_stats._EmmaCoverageStats( + 'fake_dir', {}) + + def testInit(self): + coverage_stats = self.simple_coverage + self.assertIsInstance(coverage_stats._emma_parser, + emma_coverage_stats._EmmaHtmlParser) + self.assertIsInstance(coverage_stats._source_to_emma, dict) + + def testNeedsCoverage_withExistingJavaFile(self): + test_file = '/path/to/file/File.java' + with mock.patch('os.path.exists', return_value=True): + self.assertTrue( + emma_coverage_stats._EmmaCoverageStats.NeedsCoverage(test_file)) + + def testNeedsCoverage_withNonJavaFile(self): + test_file = '/path/to/file/File.c' + with mock.patch('os.path.exists', return_value=True): + self.assertFalse( + emma_coverage_stats._EmmaCoverageStats.NeedsCoverage(test_file)) + + def testNeedsCoverage_fileDoesNotExist(self): + test_file = '/path/to/file/File.java' + with mock.patch('os.path.exists', return_value=False): + self.assertFalse( + emma_coverage_stats._EmmaCoverageStats.NeedsCoverage(test_file)) + + def testGetPackageNameFromFile_basic(self): + test_file_text = """// Test Copyright + package org.chromium.chrome.browser; + import android.graphics.RectF;""" + result_package, _ = MockOpenForFunction( + emma_coverage_stats._EmmaCoverageStats.GetPackageNameFromFile, + [test_file_text], file_path='/path/to/file/File.java') + self.assertEqual(result_package, 'org.chromium.chrome.browser.File.java') + + def testGetPackageNameFromFile_noPackageStatement(self): + result_package, _ = MockOpenForFunction( + emma_coverage_stats._EmmaCoverageStats.GetPackageNameFromFile, + ['not a package statement'], file_path='/path/to/file/File.java') + self.assertIsNone(result_package) + + def testGetSummaryStatsForLines_basic(self): + covered, total = self.simple_coverage.GetSummaryStatsForLines( + self.line_coverage) + self.assertEqual(covered, 3.05) + self.assertEqual(total, 5) + + def testGetSourceFileToEmmaFileDict(self): + package_names = { + '/path/to/1/File1.java': 'org.fake.one.File1.java', + '/path/2/File2.java': 'org.fake.File2.java', + '/path/2/File3.java': 'org.fake.File3.java' + } + package_to_emma = { + 'org.fake.one.File1.java': '/emma/1.html', + 'org.fake.File2.java': '/emma/2.html', + 'org.fake.File3.java': '/emma/3.html' + } + with mock.patch('os.path.exists', return_value=True): + coverage_stats = self.simple_coverage + coverage_stats._emma_parser.GetPackageNameToEmmaFileDict = mock.MagicMock( + return_value=package_to_emma) + coverage_stats.GetPackageNameFromFile = lambda x: package_names[x] + result_dict = coverage_stats._GetSourceFileToEmmaFileDict( + package_names.keys()) + self.assertDictEqual(result_dict, self.good_source_to_emma) + + def testGetCoverageDictForFile(self): + line_coverage = self.line_coverage + self.simple_coverage._emma_parser.GetLineCoverage = lambda x: line_coverage + self.simple_coverage._source_to_emma = {'/fake/src': 'fake/emma'} + lines = self.lines_for_coverage + expected_dict = { + 'absolute': { + 'covered': 3.05, + 'total': 5 + }, + 'incremental': { + 'covered': 2.05, + 'total': 3 + }, + 'source': [ + { + 'line': line_coverage[0].source, + 'coverage': line_coverage[0].covered_status, + 'changed': True, + 'fractional_coverage': line_coverage[0].fractional_line_coverage, + }, + { + 'line': line_coverage[1].source, + 'coverage': line_coverage[1].covered_status, + 'changed': False, + 'fractional_coverage': line_coverage[1].fractional_line_coverage, + }, + { + 'line': line_coverage[2].source, + 'coverage': line_coverage[2].covered_status, + 'changed': True, + 'fractional_coverage': line_coverage[2].fractional_line_coverage, + }, + { + 'line': line_coverage[3].source, + 'coverage': line_coverage[3].covered_status, + 'changed': False, + 'fractional_coverage': line_coverage[3].fractional_line_coverage, + }, + { + 'line': line_coverage[4].source, + 'coverage': line_coverage[4].covered_status, + 'changed': True, + 'fractional_coverage': line_coverage[4].fractional_line_coverage, + }, + { + 'line': line_coverage[5].source, + 'coverage': line_coverage[5].covered_status, + 'changed': True, + 'fractional_coverage': line_coverage[5].fractional_line_coverage, + } + ] + } + result_dict = self.simple_coverage.GetCoverageDictForFile( + '/fake/src', lines) + self.assertDictEqual(result_dict, expected_dict) + + def testGetCoverageDictForFile_emptyCoverage(self): + expected_dict = { + 'absolute': {'covered': 0, 'total': 0}, + 'incremental': {'covered': 0, 'total': 0}, + 'source': [] + } + self.simple_coverage._emma_parser.GetLineCoverage = lambda x: [] + self.simple_coverage._source_to_emma = {'fake_dir': 'fake/emma'} + result_dict = self.simple_coverage.GetCoverageDictForFile('fake_dir', {}) + self.assertDictEqual(result_dict, expected_dict) + + def testGetCoverageDictForFile_missingCoverage(self): + self.simple_coverage._source_to_emma = {} + result_dict = self.simple_coverage.GetCoverageDictForFile('fake_file', {}) + self.assertIsNone(result_dict) + + def testGetCoverageDict_basic(self): + files_for_coverage = { + '/path/to/1/File1.java': [1, 3, 4], + '/path/2/File2.java': [1, 2] + } + self.simple_coverage._source_to_emma = { + '/path/to/1/File1.java': 'emma_1', + '/path/2/File2.java': 'emma_2' + } + coverage_info = { + 'emma_1': [ + emma_coverage_stats.LineCoverage( + 1, '', emma_coverage_stats.COVERED, 1.0), + emma_coverage_stats.LineCoverage( + 2, '', emma_coverage_stats.PARTIALLY_COVERED, 0.5), + emma_coverage_stats.LineCoverage( + 3, '', emma_coverage_stats.NOT_EXECUTABLE, 1.0), + emma_coverage_stats.LineCoverage( + 4, '', emma_coverage_stats.COVERED, 1.0) + ], + 'emma_2': [ + emma_coverage_stats.LineCoverage( + 1, '', emma_coverage_stats.NOT_COVERED, 1.0), + emma_coverage_stats.LineCoverage( + 2, '', emma_coverage_stats.COVERED, 1.0) + ] + } + expected_dict = { + 'files': { + '/path/2/File2.java': { + 'absolute': {'covered': 1, 'total': 2}, + 'incremental': {'covered': 1, 'total': 2}, + 'source': [{'changed': True, 'coverage': 0, + 'line': '', 'fractional_coverage': 1.0}, + {'changed': True, 'coverage': 1, + 'line': '', 'fractional_coverage': 1.0}] + }, + '/path/to/1/File1.java': { + 'absolute': {'covered': 2.5, 'total': 3}, + 'incremental': {'covered': 2, 'total': 2}, + 'source': [{'changed': True, 'coverage': 1, + 'line': '', 'fractional_coverage': 1.0}, + {'changed': False, 'coverage': 2, + 'line': '', 'fractional_coverage': 0.5}, + {'changed': True, 'coverage': -1, + 'line': '', 'fractional_coverage': 1.0}, + {'changed': True, 'coverage': 1, + 'line': '', 'fractional_coverage': 1.0}] + } + }, + 'patch': {'incremental': {'covered': 3, 'total': 4}} + } + # Return the relevant coverage info for each file. + self.simple_coverage._emma_parser.GetLineCoverage = ( + lambda x: coverage_info[x]) + result_dict = self.simple_coverage.GetCoverageDict(files_for_coverage) + self.assertDictEqual(result_dict, expected_dict) + + def testGetCoverageDict_noCoverage(self): + result_dict = self.simple_coverage.GetCoverageDict({}) + self.assertDictEqual(result_dict, EMPTY_COVERAGE_STATS_DICT) + + +class EmmaCoverageStatsGenerateCoverageReport(unittest.TestCase): + """Tests for GenerateCoverageReport.""" + + def testGenerateCoverageReport_missingJsonFile(self): + with self.assertRaises(IOError): + with mock.patch('os.path.exists', return_value=False): + emma_coverage_stats.GenerateCoverageReport('', '', '') + + def testGenerateCoverageReport_invalidJsonFile(self): + with self.assertRaises(ValueError): + with mock.patch('os.path.exists', return_value=True): + MockOpenForFunction(emma_coverage_stats.GenerateCoverageReport, [''], + line_coverage_file='', out_file_path='', + coverage_dir='') + + +def MockOpenForFunction(func, side_effects, **kwargs): + """Allows easy mock open and read for callables that open multiple files. + + Will mock the python open function in a way such that each time read() is + called on an open file, the next element in |side_effects| is returned. This + makes it easier to test functions that call open() multiple times. + + Args: + func: The callable to invoke once mock files are setup. + side_effects: A list of return values for each file to return once read. + Length of list should be equal to the number calls to open in |func|. + **kwargs: Keyword arguments to be passed to |func|. + + Returns: + A tuple containing the return value of |func| and the MagicMock object used + to mock all calls to open respectively. + """ + mock_open = mock.mock_open() + mock_open.side_effect = [mock.mock_open(read_data=side_effect).return_value + for side_effect in side_effects] + with mock.patch('__builtin__.open', mock_open): + return func(**kwargs), mock_open + + +if __name__ == '__main__': + # Suppress logging messages. + unittest.main(buffer=True) |