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