Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(108)

Side by Side Diff: build/android/coverage_test.py

Issue 1216033009: Updated script to capture useful coverage stats. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« build/android/coverage.py ('K') | « build/android/coverage.py ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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):
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('*'):
296 if elem.text is not None:
297 elem.text = elem.text.strip()
298 return root
299
300
301 class _EmmaCoverageStatsTest(unittest.TestCase):
302 """Tests for _EmmaCoverageStats."""
303
304 def setUp(self):
305 self.good_source_to_emma = {
306 '/path/to/1/File1.java': '/emma/1.html',
307 '/path/2/File2.java': '/emma/2.html',
308 '/path/2/File3.java': '/emma/3.html'
309 }
310 self.line_coverage = [
311 coverage.LineCoverage(1, '', coverage.LineCoverage.COVERED, 1.0),
312 coverage.LineCoverage(2, '', coverage.LineCoverage.COVERED, 1.0),
313 coverage.LineCoverage(3, '', coverage.LineCoverage.NOT_EXECUTABLE, 1.0),
314 coverage.LineCoverage(4, '', coverage.LineCoverage.NOT_COVERED, 1.0),
315 coverage.LineCoverage(5, '',
316 coverage.LineCoverage.PARTIALLY_COVERED, 0.85),
317 coverage.LineCoverage(6, '',
318 coverage.LineCoverage.PARTIALLY_COVERED, 0.20)
319 ]
320 self.lines_for_coverage = [1, 3, 5, 6]
321 self.simple_coverage = coverage._EmmaCoverageStats('fake_dir', {})
322
323 def testInit(self):
324 coverage_stats = self.simple_coverage
325 self.assertIsInstance(coverage_stats._emma_parser,
326 coverage._EmmaHtmlParser)
327 self.assertIsInstance(coverage_stats._source_to_emma, dict)
328
329 def testNeedsCoverage_withExistingJavaFile(self):
330 test_file = '/path/to/file/File.java'
331 with mock.patch('os.path.exists', return_value=True):
332 self.assertTrue(coverage._EmmaCoverageStats.NeedsCoverage(test_file))
333
334 def testNeedsCoverage_withNonJavaFile(self):
335 test_file = '/path/to/file/File.c'
336 with mock.patch('os.path.exists', return_value=True):
337 self.assertFalse(coverage._EmmaCoverageStats.NeedsCoverage(test_file))
338
339 def testNeedsCoverage_fileDoesNotExist(self):
340 test_file = '/path/to/file/File.java'
341 with mock.patch('os.path.exists', return_value=False):
342 self.assertFalse(coverage._EmmaCoverageStats.NeedsCoverage(test_file))
343
344 def testGetPackageNameFromFile_basic(self):
345 test_file_text = """// Test Copyright
346 package org.chromium.chrome.browser;
347 import android.graphics.RectF;"""
348 result_package, _ = MockOpenForFunction(
349 coverage._EmmaCoverageStats.GetPackageNameFromFile, test_file_text,
350 file_path='/path/to/file/File.java')
351 self.assertEqual(result_package, 'org.chromium.chrome.browser.File.java')
352
353 def testGetPackageNameFromFile_noPackageStatement(self):
354 result_package, _ = MockOpenForFunction(
355 coverage._EmmaCoverageStats.GetPackageNameFromFile,
356 'not a package statement', file_path='/path/to/file/File.java')
357 self.assertEqual(result_package, None)
358
359 def testGetStatsForLines_basic(self):
360 covered, total = self.simple_coverage._GetStatsForLines(
361 self.line_coverage, self.lines_for_coverage)
362 self.assertEqual(covered, 2.05)
363 self.assertEqual(total, 3)
364
365 def testGetStatsForLines_noLineNumbersProvided(self):
366 covered, total = self.simple_coverage._GetStatsForLines(self.line_coverage)
367 self.assertEqual(covered, 3.05)
368 self.assertEqual(total, 5)
369
370 def testGetSourceFileToEmmaFileDict(self):
371 package_names = {
372 '/path/to/1/File1.java': 'org.fake.one.File1.java',
373 '/path/2/File2.java': 'org.fake.File2.java',
374 '/path/2/File3.java': 'org.fake.File3.java'
375 }
376 package_to_emma = {
377 'org.fake.one.File1.java': '/emma/1.html',
378 'org.fake.File2.java': '/emma/2.html',
379 'org.fake.File3.java': '/emma/3.html'
380 }
381 with mock.patch('os.path.exists', return_value=True):
382 coverage_stats = self.simple_coverage
383 coverage_stats._emma_parser.GetPackageNameToEmmaFileDict = mock.MagicMock(
384 return_value=package_to_emma)
385 coverage_stats.GetPackageNameFromFile = lambda x: package_names[x]
386 result_dict = coverage_stats._GetSourceFileToEmmaFileDict(
387 package_names.keys())
388 self.assertDictEqual(result_dict, self.good_source_to_emma)
389
390 def testGetCoverageStatusFromFile_basic(self):
391 java_file_path = '/path/to/1/File1.java'
392 line_coverage = [
393 coverage.LineCoverage(1, '', coverage.LineCoverage.COVERED, 1.0)]
394 coverage_stats = self.simple_coverage
395 coverage_stats._source_to_emma = self.good_source_to_emma
396 coverage_stats._emma_parser.GetLineCoverage = mock.MagicMock(
397 return_value=line_coverage)
398 coverage_info = coverage_stats._GetCoverageStatusForFile(java_file_path)
399 self.assertDictEqual(coverage_info[0].__dict__, line_coverage[0].__dict__)
400
401 def testGetCoverageStatusFromFile_noInfo(self):
402 coverage_info = self.simple_coverage._GetCoverageStatusForFile('fake_path')
403 self.assertIsNone(coverage_info)
404
405 def testGetCoverageReportForLines(self):
406 line_coverage = self.line_coverage
407 lines = self.lines_for_coverage
408 expected_dict = {
409 'absolute': {
410 'covered': 3.05,
411 'total': 5
412 },
413 'incremental': {
414 'covered': 2.05,
415 'total': 3
416 },
417 'source': [
418 {
419 'line': line_coverage[0].source,
420 'coverage': line_coverage[0].covered_status,
421 'changed': True
422 },
423 {
424 'line': line_coverage[1].source,
425 'coverage': line_coverage[1].covered_status,
426 'changed': False
427 },
428 {
429 'line': line_coverage[2].source,
430 'coverage': line_coverage[2].covered_status,
431 'changed': True
432 },
433 {
434 'line': line_coverage[3].source,
435 'coverage': line_coverage[3].covered_status,
436 'changed': False
437 },
438 {
439 'line': line_coverage[4].source,
440 'coverage': line_coverage[4].covered_status,
441 'changed': True
442 },
443 {
444 'line': line_coverage[5].source,
445 'coverage': line_coverage[5].covered_status,
446 'changed': True
447 }
448 ]
449 }
450 result_dict = self.simple_coverage.GetCoverageReportForLines(line_coverage,
451 lines)
452 self.assertDictEqual(result_dict, expected_dict)
453
454 def testGetCoverageReportForLines_emptyCoverage(self):
455 expected_dict = {
456 'absolute': {'covered': 0, 'total': 0},
457 'incremental': {'covered': 0, 'total': 0},
458 'source': []
459 }
460 result_dict = self.simple_coverage.GetCoverageReportForLines({}, [])
461 self.assertDictEqual(result_dict, expected_dict)
462
463 def testGetCoverageDictForFiles_basic(self):
464 files_for_coverage = {
465 '/path/to/1/File1.java': [1, 3, 4],
466 '/path/2/File2.java': [1, 2]
467 }
468 coverage_info = {
469 '/path/to/1/File1.java': [
470 coverage.LineCoverage(1, '', coverage.LineCoverage.COVERED, 1.0),
471 coverage.LineCoverage(2, '',
472 coverage.LineCoverage.PARTIALLY_COVERED, 0.5),
473 coverage.LineCoverage(3, '',
474 coverage.LineCoverage.NOT_EXECUTABLE, 1.0),
475 coverage.LineCoverage(4, '', coverage.LineCoverage.COVERED, 1.0)
476 ],
477 '/path/2/File2.java': [
478 coverage.LineCoverage(1, '', coverage.LineCoverage.NOT_COVERED, 1.0),
479 coverage.LineCoverage(2, '', coverage.LineCoverage.COVERED, 1.0)
480 ]
481 }
482 expected_dict = {
483 'files': {
484 '/path/2/File2.java': {
485 'absolute': {'covered': 1, 'total': 2},
486 'incremental': {'covered': 1, 'total': 2},
487 'source': [{'changed': True, 'coverage': 0, 'line': ''},
488 {'changed': True, 'coverage': 1, 'line': ''}]
489 },
490 '/path/to/1/File1.java': {
491 'absolute': {'covered': 2.5, 'total': 3},
492 'incremental': {'covered': 2, 'total': 2},
493 'source': [{'changed': True, 'coverage': 1, 'line': ''},
494 {'changed': False, 'coverage': 2, 'line': ''},
495 {'changed': True, 'coverage': -1, 'line': ''},
496 {'changed': True, 'coverage': 1, 'line': ''}]
497 }
498 },
499 'patch': {'incremental': {'covered': 3, 'total': 4}}
500 }
501 self.simple_coverage._GetCoverageStatusForFile = lambda x: coverage_info[x]
502 result_dict = self.simple_coverage.GetCoverageDictForFiles(
503 files_for_coverage)
504 self.assertDictEqual(result_dict, expected_dict)
505
506 def testGetCoverageDictForFiles_noCoverage(self):
507 expected_dict = {
508 'files': {},
509 'patch': {
510 'incremental': {
511 'covered': 0, 'total': 0
512 }
513 }
514 }
515 result_dict = self.simple_coverage.GetCoverageDictForFiles({})
516 self.assertDictEqual(result_dict, expected_dict)
517
518
519 class CoverageGenerateCoverageReportTest(unittest.TestCase):
520 """Tests for GenerateCoverageReport."""
521
522 def testGenerateCoverageReport_missingLineCoverageFile(self):
523 with self.assertRaises(IOError):
524 with mock.patch('os.path.exists', return_value=False):
525 coverage.GenerateCoverageReport('', '', '')
526
527 def testGenerateCoverageReport_noCoverageRequired(self):
528 with self.assertRaises(SystemExit) as cm:
529 with mock.patch('os.path.exists', return_value=True):
530 MockOpenForFunction(coverage.GenerateCoverageReport, '{}',
531 line_coverage_file='', out_file_path='',
532 coverage_dir='')
533 self.assertEqual(cm.exception.code, 0)
534
535
536 def MockOpenForFunction(func, *args, **kwargs):
537 """Allows easy mock open and read for callables that open multiple files.
538
539 Args:
540 func: The callable to invoke once mock files are setup.
541 *args: A list of return values for each file to return once read. Length of
542 list should be equal to the number calls to open in |func|.
543 **kwargs: Keyword arguments to be passed to |func|.
544
545 Returns:
546 A tuple containing the return value of |func| and the MagicMock object used
547 to mock all calls to open respectively.
548 """
549 mock_open = mock.mock_open()
550 mock_open.side_effect = [mock.mock_open(read_data=arg).return_value
551 for arg in args]
552 with mock.patch('__builtin__.open', mock_open):
553 return func(**kwargs), mock_open
554
555
556 if __name__ == '__main__':
557 # Suppress logging messages.
558 unittest.main(buffer=True)
OLDNEW
« build/android/coverage.py ('K') | « build/android/coverage.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698