Index: build/android/emma_coverage_stats_test.py |
diff --git a/build/android/emma_coverage_stats_test.py b/build/android/emma_coverage_stats_test.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..4ee2d01056c23d65165d9a27e555ac2a400f6d4d |
--- /dev/null |
+++ b/build/android/emma_coverage_stats_test.py |
@@ -0,0 +1,308 @@ |
+#!/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. |
+ |
+import os |
+import sys |
+import unittest |
+from xml.etree import ElementTree |
+ |
+import emma_coverage_stats |
+from pylib import constants |
+ |
+sys.path.append(os.path.join( |
+ constants.DIR_SOURCE_ROOT, 'third_party', 'pymock')) |
+import mock # pylint: disable=F0401 |
+ |
+ |
+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 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: |
+ 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') |
+ |
+ |
+def MockOpenForFunction(func, side_effects, **kwargs): |
+ """Allows easy mock open and read for callables that open multiple files. |
+ |
+ 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__': |
+ unittest.main() |