OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/python |
| 2 # Copyright 2015 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. |
| 5 |
| 6 import os |
| 7 import sys |
| 8 import unittest |
| 9 from xml.etree import ElementTree |
| 10 |
| 11 import emma_coverage_stats |
| 12 from pylib import constants |
| 13 |
| 14 sys.path.append(os.path.join( |
| 15 constants.DIR_SOURCE_ROOT, 'third_party', 'pymock')) |
| 16 import mock # pylint: disable=F0401 |
| 17 |
| 18 |
| 19 class _EmmaHtmlParserTest(unittest.TestCase): |
| 20 """Tests for _EmmaHtmlParser. |
| 21 |
| 22 Uses modified EMMA report HTML that contains only the subset of tags needed |
| 23 for test verification. |
| 24 """ |
| 25 |
| 26 def setUp(self): |
| 27 self.emma_dir = 'fake/dir/' |
| 28 self.parser = emma_coverage_stats._EmmaHtmlParser(self.emma_dir) |
| 29 self.simple_html = '<TR><TD CLASS="p">Test HTML</TD></TR>' |
| 30 self.index_html = ( |
| 31 '<HTML>' |
| 32 '<BODY>' |
| 33 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">' |
| 34 '</TABLE>' |
| 35 '<TABLE CELLSPACING="0" WIDTH="100%">' |
| 36 '</TABLE>' |
| 37 '<TABLE CLASS="it" CELLSPACING="0">' |
| 38 '</TABLE>' |
| 39 '<TABLE CELLSPACING="0" WIDTH="100%">' |
| 40 '<TR>' |
| 41 '<TH CLASS="f">name</TH>' |
| 42 '<TH>class, %</TH>' |
| 43 '<TH>method, %</TH>' |
| 44 '<TH>block, %</TH>' |
| 45 '<TH>line, %</TH>' |
| 46 '</TR>' |
| 47 '<TR CLASS="o">' |
| 48 '<TD><A HREF="_files/0.html"' |
| 49 '>org.chromium.chrome.browser</A></TD>' |
| 50 '<TD CLASS="h">0% (0/3)</TD>' |
| 51 '</TR>' |
| 52 '<TR>' |
| 53 '<TD><A HREF="_files/1.html"' |
| 54 '>org.chromium.chrome.browser.tabmodel</A></TD>' |
| 55 '<TD CLASS="h">0% (0/8)</TD>' |
| 56 '</TR>' |
| 57 '</TABLE>' |
| 58 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">' |
| 59 '</TABLE>' |
| 60 '</BODY>' |
| 61 '</HTML>' |
| 62 ) |
| 63 self.package_1_class_list_html = ( |
| 64 '<HTML>' |
| 65 '<BODY>' |
| 66 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">' |
| 67 '</TABLE>' |
| 68 '<TABLE CELLSPACING="0" WIDTH="100%">' |
| 69 '</TABLE>' |
| 70 '<TABLE CELLSPACING="0" WIDTH="100%">' |
| 71 '<TR>' |
| 72 '<TH CLASS="f">name</TH>' |
| 73 '<TH>class, %</TH>' |
| 74 '<TH>method, %</TH>' |
| 75 '<TH>block, %</TH>' |
| 76 '<TH>line, %</TH>' |
| 77 '</TR>' |
| 78 '<TR CLASS="o">' |
| 79 '<TD><A HREF="1e.html">IntentHelper.java</A></TD>' |
| 80 '<TD CLASS="h">0% (0/3)</TD>' |
| 81 '<TD CLASS="h">0% (0/9)</TD>' |
| 82 '<TD CLASS="h">0% (0/97)</TD>' |
| 83 '<TD CLASS="h">0% (0/26)</TD>' |
| 84 '</TR>' |
| 85 '</TABLE>' |
| 86 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">' |
| 87 '</TABLE>' |
| 88 '</BODY>' |
| 89 '</HTML>' |
| 90 ) |
| 91 self.package_2_class_list_html = ( |
| 92 '<HTML>' |
| 93 '<BODY>' |
| 94 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">' |
| 95 '</TABLE>' |
| 96 '<TABLE CELLSPACING="0" WIDTH="100%">' |
| 97 '</TABLE>' |
| 98 '<TABLE CELLSPACING="0" WIDTH="100%">' |
| 99 '<TR>' |
| 100 '<TH CLASS="f">name</TH>' |
| 101 '<TH>class, %</TH>' |
| 102 '<TH>method, %</TH>' |
| 103 '<TH>block, %</TH>' |
| 104 '<TH>line, %</TH>' |
| 105 '</TR>' |
| 106 '<TR CLASS="o">' |
| 107 '<TD><A HREF="1f.html">ContentSetting.java</A></TD>' |
| 108 '<TD CLASS="h">0% (0/1)</TD>' |
| 109 '</TR>' |
| 110 '<TR>' |
| 111 '<TD><A HREF="20.html">DevToolsServer.java</A></TD>' |
| 112 '</TR>' |
| 113 '<TR CLASS="o">' |
| 114 '<TD><A HREF="21.html">FileProviderHelper.java</A></TD>' |
| 115 '</TR>' |
| 116 '<TR>' |
| 117 '<TD><A HREF="22.html">ContextualMenuBar.java</A></TD>' |
| 118 '</TR>' |
| 119 '<TR CLASS="o">' |
| 120 '<TD><A HREF="23.html">AccessibilityUtil.java</A></TD>' |
| 121 '</TR>' |
| 122 '<TR>' |
| 123 '<TD><A HREF="24.html">NavigationPopup.java</A></TD>' |
| 124 '</TR>' |
| 125 '</TABLE>' |
| 126 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">' |
| 127 '</TABLE>' |
| 128 '</BODY>' |
| 129 '</HTML>' |
| 130 ) |
| 131 self.partially_covered_tr_html = ( |
| 132 '<TR CLASS="p">' |
| 133 '<TD CLASS="l" TITLE="78% line coverage (7 out of 9)">108</TD>' |
| 134 '<TD TITLE="78% line coverage (7 out of 9 instructions)">' |
| 135 'if (index < 0 || index = mSelectors.size()) index = 0;</TD>' |
| 136 '</TR>' |
| 137 ) |
| 138 self.covered_tr_html = ( |
| 139 '<TR CLASS="c">' |
| 140 '<TD CLASS="l">110</TD>' |
| 141 '<TD> if (mSelectors.get(index) != null) {</TD>' |
| 142 '</TR>' |
| 143 ) |
| 144 self.not_executable_tr_html = ( |
| 145 '<TR>' |
| 146 '<TD CLASS="l">109</TD>' |
| 147 '<TD> </TD>' |
| 148 '</TR>' |
| 149 ) |
| 150 self.tr_with_extra_a_tag = ( |
| 151 '<TR CLASS="z">' |
| 152 '<TD CLASS="l">' |
| 153 '<A name="1f">54</A>' |
| 154 '</TD>' |
| 155 '<TD> }</TD>' |
| 156 '</TR>' |
| 157 ) |
| 158 |
| 159 def testInit(self): |
| 160 emma_dir = self.emma_dir |
| 161 parser = emma_coverage_stats._EmmaHtmlParser(emma_dir) |
| 162 self.assertEqual(parser._base_dir, emma_dir) |
| 163 self.assertEqual(parser._emma_files_path, 'fake/dir/_files') |
| 164 self.assertEqual(parser._index_path, 'fake/dir/index.html') |
| 165 |
| 166 def testFindElements_basic(self): |
| 167 read_values = [self.simple_html] |
| 168 found, _ = MockOpenForFunction(self.parser._FindElements, read_values, |
| 169 file_path='fake', xpath_selector='.//TD') |
| 170 self.assertIs(type(found), list) |
| 171 self.assertIs(type(found[0]), ElementTree.Element) |
| 172 self.assertEqual(found[0].text, 'Test HTML') |
| 173 |
| 174 def testFindElements_multipleElements(self): |
| 175 multiple_trs = self.not_executable_tr_html + self.covered_tr_html |
| 176 read_values = ['<div>' + multiple_trs + '</div>'] |
| 177 found, _ = MockOpenForFunction(self.parser._FindElements, read_values, |
| 178 file_path='fake', xpath_selector='.//TR') |
| 179 self.assertEquals(2, len(found)) |
| 180 |
| 181 def testFindElements_noMatch(self): |
| 182 read_values = [self.simple_html] |
| 183 found, _ = MockOpenForFunction(self.parser._FindElements, read_values, |
| 184 file_path='fake', xpath_selector='.//TR') |
| 185 self.assertEqual(found, []) |
| 186 |
| 187 def testFindElements_badFilePath(self): |
| 188 with self.assertRaises(IOError): |
| 189 with mock.patch('os.path.exists', return_value=False): |
| 190 self.parser._FindElements('fake', xpath_selector='//tr') |
| 191 |
| 192 def testGetPackageNameToEmmaFileDict_basic(self): |
| 193 expected_dict = { |
| 194 'org.chromium.chrome.browser.AccessibilityUtil.java': |
| 195 'fake/dir/_files/23.html', |
| 196 'org.chromium.chrome.browser.ContextualMenuBar.java': |
| 197 'fake/dir/_files/22.html', |
| 198 'org.chromium.chrome.browser.tabmodel.IntentHelper.java': |
| 199 'fake/dir/_files/1e.html', |
| 200 'org.chromium.chrome.browser.ContentSetting.java': |
| 201 'fake/dir/_files/1f.html', |
| 202 'org.chromium.chrome.browser.DevToolsServer.java': |
| 203 'fake/dir/_files/20.html', |
| 204 'org.chromium.chrome.browser.NavigationPopup.java': |
| 205 'fake/dir/_files/24.html', |
| 206 'org.chromium.chrome.browser.FileProviderHelper.java': |
| 207 'fake/dir/_files/21.html'} |
| 208 |
| 209 read_values = [self.index_html, self.package_1_class_list_html, |
| 210 self.package_2_class_list_html] |
| 211 return_dict, mock_open = MockOpenForFunction( |
| 212 self.parser.GetPackageNameToEmmaFileDict, read_values) |
| 213 |
| 214 self.assertDictEqual(return_dict, expected_dict) |
| 215 self.assertEqual(mock_open.call_count, 3) |
| 216 calls = [mock.call('fake/dir/index.html'), |
| 217 mock.call('fake/dir/_files/1.html'), |
| 218 mock.call('fake/dir/_files/0.html')] |
| 219 mock_open.assert_has_calls(calls) |
| 220 |
| 221 def testGetPackageNameToEmmaFileDict_noPackageElements(self): |
| 222 self.parser._FindElements = mock.Mock(return_value=[]) |
| 223 return_dict = self.parser.GetPackageNameToEmmaFileDict() |
| 224 self.assertDictEqual({}, return_dict) |
| 225 |
| 226 def testGetLineCoverage_status_basic(self): |
| 227 line_coverage = self.GetLineCoverageWithFakeElements([self.covered_tr_html]) |
| 228 self.assertEqual(line_coverage[0].covered_status, |
| 229 emma_coverage_stats.COVERED) |
| 230 |
| 231 def testGetLineCoverage_status_statusMissing(self): |
| 232 line_coverage = self.GetLineCoverageWithFakeElements( |
| 233 [self.not_executable_tr_html]) |
| 234 self.assertEqual(line_coverage[0].covered_status, |
| 235 emma_coverage_stats.NOT_EXECUTABLE) |
| 236 |
| 237 def testGetLineCoverage_fractionalCoverage_basic(self): |
| 238 line_coverage = self.GetLineCoverageWithFakeElements([self.covered_tr_html]) |
| 239 self.assertEqual(line_coverage[0].fractional_line_coverage, 1.0) |
| 240 |
| 241 def testGetLineCoverage_fractionalCoverage_partial(self): |
| 242 line_coverage = self.GetLineCoverageWithFakeElements( |
| 243 [self.partially_covered_tr_html]) |
| 244 self.assertEqual(line_coverage[0].fractional_line_coverage, 0.78) |
| 245 |
| 246 def testGetLineCoverage_lineno_basic(self): |
| 247 line_coverage = self.GetLineCoverageWithFakeElements([self.covered_tr_html]) |
| 248 self.assertEqual(line_coverage[0].lineno, 110) |
| 249 |
| 250 def testGetLineCoverage_lineno_withAlternativeHtml(self): |
| 251 line_coverage = self.GetLineCoverageWithFakeElements( |
| 252 [self.tr_with_extra_a_tag]) |
| 253 self.assertEqual(line_coverage[0].lineno, 54) |
| 254 |
| 255 def testGetLineCoverage_source(self): |
| 256 self.parser._FindElements = mock.Mock( |
| 257 return_value=[ElementTree.fromstring(self.covered_tr_html)]) |
| 258 line_coverage = self.parser.GetLineCoverage('fake_path') |
| 259 self.assertEqual(line_coverage[0].source, |
| 260 ' if (mSelectors.get(index) != null) {') |
| 261 |
| 262 def testGetLineCoverage_multipleElements(self): |
| 263 line_coverage = self.GetLineCoverageWithFakeElements( |
| 264 [self.covered_tr_html, self.partially_covered_tr_html, |
| 265 self.tr_with_extra_a_tag]) |
| 266 self.assertEqual(len(line_coverage), 3) |
| 267 |
| 268 def GetLineCoverageWithFakeElements(self, html_elements): |
| 269 """Wraps GetLineCoverage to work with extra whitespace characters. |
| 270 |
| 271 The test HTML strings include extra whitespace characters to make the HTML |
| 272 human readable. This isn't the case with EMMA HTML files, so we need to |
| 273 remove all the unnecessary whitespace. |
| 274 |
| 275 Args: |
| 276 html_elements: List of strings each representing an HTML element. |
| 277 |
| 278 Returns: |
| 279 A list of LineCoverage objects. |
| 280 """ |
| 281 elements = [ElementTree.fromstring(string) for string in html_elements] |
| 282 with mock.patch('emma_coverage_stats._EmmaHtmlParser._FindElements', |
| 283 return_value=elements): |
| 284 return self.parser.GetLineCoverage('fake_path') |
| 285 |
| 286 |
| 287 def MockOpenForFunction(func, side_effects, **kwargs): |
| 288 """Allows easy mock open and read for callables that open multiple files. |
| 289 |
| 290 Args: |
| 291 func: The callable to invoke once mock files are setup. |
| 292 side_effects: A list of return values for each file to return once read. |
| 293 Length of list should be equal to the number calls to open in |func|. |
| 294 **kwargs: Keyword arguments to be passed to |func|. |
| 295 |
| 296 Returns: |
| 297 A tuple containing the return value of |func| and the MagicMock object used |
| 298 to mock all calls to open respectively. |
| 299 """ |
| 300 mock_open = mock.mock_open() |
| 301 mock_open.side_effect = [mock.mock_open(read_data=side_effect).return_value |
| 302 for side_effect in side_effects] |
| 303 with mock.patch('__builtin__.open', mock_open): |
| 304 return func(**kwargs), mock_open |
| 305 |
| 306 |
| 307 if __name__ == '__main__': |
| 308 unittest.main() |
OLD | NEW |