Chromium Code Reviews| Index: build/android/coverage_test.py |
| diff --git a/build/android/coverage_test.py b/build/android/coverage_test.py |
| new file mode 100755 |
| index 0000000000000000000000000000000000000000..14933835ee6187f18d080405a76bacd851edfc29 |
| --- /dev/null |
| +++ b/build/android/coverage_test.py |
| @@ -0,0 +1,322 @@ |
| +#!/usr/bin/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. |
| + |
| +from lxml import etree |
| +from lxml import html |
| +import os |
| +import sys |
| +import unittest |
| + |
| +import coverage |
| +from pylib import constants |
| + |
| +sys.path.append(os.path.join( |
| + constants.DIR_SOURCE_ROOT, 'third_party', 'pymock')) |
| +import mock # pylint: disable=F0401 |
| + |
| + |
| +class LineCoverageTest(unittest.TestCase): |
| + """Tests for LineCoverage.""" |
| + |
| + def testInit(self): |
| + line_coverage = coverage.LineCoverage(10, 'i++;', |
| + coverage.LineCoverage.COVERED, 1.0) |
| + self.assertEqual(line_coverage.lineno, 10) |
| + self.assertEqual(line_coverage.source, 'i++;') |
| + self.assertEqual(line_coverage.covered_status, |
| + coverage.LineCoverage.COVERED) |
| + self.assertEqual(line_coverage.fractional_line_coverage, 1.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 = coverage._EmmaHtmlParser(self.emma_dir) |
| + self.simple_html = '<td class="p">Test HTML</td>' |
| + 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 = coverage._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): |
| + found, _ = MockOpenForFunction(self.parser._FindElements, self.simple_html, |
| + file_path='fake', _path='//td') |
| + self.assertIs(type(found), list) |
| + self.assertIs(type(found[0]), html.HtmlElement) |
| + self.assertEqual(found[0].text, 'Test HTML') |
| + |
| + def testFindElements_noMatch(self): |
| + found, _ = MockOpenForFunction(self.parser._FindElements, self.simple_html, |
| + file_path='fake', _path='//tr') |
| + self.assertEqual(found, []) |
| + |
| + def testFindElements_badFilePath(self): |
| + # Ensure that os.path.exists will fail. |
| + with mock.patch('os.path.exists', return_value=False): |
| + found = self.parser._FindElements('fake', _path='//tr') |
| + self.assertEqual(found, []) |
| + |
| + 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'} |
| + |
| + return_dict, mock_open = MockOpenForFunction( |
| + self.parser.GetPackageNameToEmmaFileDict, |
| + self.index_html, self.package_1_class_list_html, |
| + self.package_2_class_list_html) |
| + |
| + 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_badFilePath(self): |
| + file_path = self.parser.GetPackageNameToEmmaFileDict() |
| + self.assertEqual(file_path, {}) |
| + |
| + def testGetLineCoverage_status_basic(self): |
| + line_coverage = self.GetLineCoverageWithFakeElements(self.covered_tr_html) |
| + self.assertEqual(line_coverage[0].covered_status, |
| + coverage.LineCoverage.COVERED) |
| + |
| + def testGetLineCoverage_status_statusMissing(self): |
| + line_coverage = self.GetLineCoverageWithFakeElements( |
| + self.not_executable_tr_html) |
| + self.assertEqual(line_coverage[0].covered_status, |
| + coverage.LineCoverage.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=[html.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, *args): |
|
mikecase (-- gone --)
2015/07/07 17:12:33
Can you just change this to...
GetLineCoverageWith
estevenson1
2015/07/07 23:54:56
Done.
|
| + """Wraps GetLineCoverage to work with extra whitespace characters. |
| + |
| + The test HTML strings include extra whitespace characters to make the HTML |
| + human readable. This isn't the case with EMMA HTML files, so we need to |
| + remove all the unnecessary whitespace. |
| + |
| + Args: |
| + *args: Each is a string representing an HTML element. |
| + |
| + Returns: |
| + A list of LineCoverage objects. |
| + """ |
| + html_elements = [self.MakeElementsWithoutWhitespace(arg) for arg in args] |
| + with mock.patch('coverage._EmmaHtmlParser._FindElements', |
| + return_value=html_elements): |
| + return self.parser.GetLineCoverage('fake_path') |
| + |
| + def MakeElementsWithoutWhitespace(self, html_string): |
| + """Helper to make HtmlElements with excess whitespace removed. |
| + |
| + Args: |
| + html_string: String representing HTML to be used to create elements. |
| + |
| + Returns: |
| + The root element of |html_string| with all extra whitespace removed. |
| + """ |
| + parser = etree.HTMLParser(remove_blank_text=True) |
| + root = html.fromstring(html_string, parser=parser) |
| + for elem in root.iter('*'): |
|
mikecase (-- gone --)
2015/07/07 17:12:33
nit: s/elem/element
estevenson1
2015/07/07 23:54:56
Done.
|
| + if elem.text is not None: |
| + elem.text = elem.text.strip() |
| + return root |
| + |
| + |
| +def MockOpenForFunction(func, *args, **kwargs): |
|
mikecase (-- gone --)
2015/07/07 17:12:33
Can you change this to...
MockOpenForFunction(func
estevenson1
2015/07/07 23:54:56
Done.
|
| + """Allows easy mock open and read for callables that open multiple files. |
| + |
| + Args: |
| + func: The callable to invoke once mock files are setup. |
| + *args: 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=arg).return_value |
| + for arg in args] |
| + with mock.patch('__builtin__.open', mock_open): |
| + return func(**kwargs), mock_open |
| + |
| + |
| +if __name__ == '__main__': |
| + unittest.main() |