Revert of [Android] Don't use a device blacklist if one isn't provided. (RELAND)...
[chromium-blink-merge.git] / build / android / emma_coverage_stats_test.py
blobc39dd4718fa633fc5d74c50bbcfe2ef2bf0ec1a0
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.
6 # pylint: disable=protected-access
8 import os
9 import sys
10 import unittest
11 from xml.etree import ElementTree
13 import emma_coverage_stats
14 from pylib import constants
16 sys.path.append(os.path.join(
17 constants.DIR_SOURCE_ROOT, 'third_party', 'pymock'))
18 import mock # pylint: disable=import-error
20 EMPTY_COVERAGE_STATS_DICT = {
21 'files': {},
22 'patch': {
23 'incremental': {
24 'covered': 0, 'total': 0
30 class _EmmaHtmlParserTest(unittest.TestCase):
31 """Tests for _EmmaHtmlParser.
33 Uses modified EMMA report HTML that contains only the subset of tags needed
34 for test verification.
35 """
37 def setUp(self):
38 self.emma_dir = 'fake/dir/'
39 self.parser = emma_coverage_stats._EmmaHtmlParser(self.emma_dir)
40 self.simple_html = '<TR><TD CLASS="p">Test HTML</TD></TR>'
41 self.index_html = (
42 '<HTML>'
43 '<BODY>'
44 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">'
45 '</TABLE>'
46 '<TABLE CELLSPACING="0" WIDTH="100%">'
47 '</TABLE>'
48 '<TABLE CLASS="it" CELLSPACING="0">'
49 '</TABLE>'
50 '<TABLE CELLSPACING="0" WIDTH="100%">'
51 '<TR>'
52 '<TH CLASS="f">name</TH>'
53 '<TH>class, %</TH>'
54 '<TH>method, %</TH>'
55 '<TH>block, %</TH>'
56 '<TH>line, %</TH>'
57 '</TR>'
58 '<TR CLASS="o">'
59 '<TD><A HREF="_files/0.html"'
60 '>org.chromium.chrome.browser</A></TD>'
61 '<TD CLASS="h">0% (0/3)</TD>'
62 '</TR>'
63 '<TR>'
64 '<TD><A HREF="_files/1.html"'
65 '>org.chromium.chrome.browser.tabmodel</A></TD>'
66 '<TD CLASS="h">0% (0/8)</TD>'
67 '</TR>'
68 '</TABLE>'
69 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">'
70 '</TABLE>'
71 '</BODY>'
72 '</HTML>'
74 self.package_1_class_list_html = (
75 '<HTML>'
76 '<BODY>'
77 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">'
78 '</TABLE>'
79 '<TABLE CELLSPACING="0" WIDTH="100%">'
80 '</TABLE>'
81 '<TABLE CELLSPACING="0" WIDTH="100%">'
82 '<TR>'
83 '<TH CLASS="f">name</TH>'
84 '<TH>class, %</TH>'
85 '<TH>method, %</TH>'
86 '<TH>block, %</TH>'
87 '<TH>line, %</TH>'
88 '</TR>'
89 '<TR CLASS="o">'
90 '<TD><A HREF="1e.html">IntentHelper.java</A></TD>'
91 '<TD CLASS="h">0% (0/3)</TD>'
92 '<TD CLASS="h">0% (0/9)</TD>'
93 '<TD CLASS="h">0% (0/97)</TD>'
94 '<TD CLASS="h">0% (0/26)</TD>'
95 '</TR>'
96 '</TABLE>'
97 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">'
98 '</TABLE>'
99 '</BODY>'
100 '</HTML>'
102 self.package_2_class_list_html = (
103 '<HTML>'
104 '<BODY>'
105 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">'
106 '</TABLE>'
107 '<TABLE CELLSPACING="0" WIDTH="100%">'
108 '</TABLE>'
109 '<TABLE CELLSPACING="0" WIDTH="100%">'
110 '<TR>'
111 '<TH CLASS="f">name</TH>'
112 '<TH>class, %</TH>'
113 '<TH>method, %</TH>'
114 '<TH>block, %</TH>'
115 '<TH>line, %</TH>'
116 '</TR>'
117 '<TR CLASS="o">'
118 '<TD><A HREF="1f.html">ContentSetting.java</A></TD>'
119 '<TD CLASS="h">0% (0/1)</TD>'
120 '</TR>'
121 '<TR>'
122 '<TD><A HREF="20.html">DevToolsServer.java</A></TD>'
123 '</TR>'
124 '<TR CLASS="o">'
125 '<TD><A HREF="21.html">FileProviderHelper.java</A></TD>'
126 '</TR>'
127 '<TR>'
128 '<TD><A HREF="22.html">ContextualMenuBar.java</A></TD>'
129 '</TR>'
130 '<TR CLASS="o">'
131 '<TD><A HREF="23.html">AccessibilityUtil.java</A></TD>'
132 '</TR>'
133 '<TR>'
134 '<TD><A HREF="24.html">NavigationPopup.java</A></TD>'
135 '</TR>'
136 '</TABLE>'
137 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">'
138 '</TABLE>'
139 '</BODY>'
140 '</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 &lt; 0 || index = mSelectors.size()) index = 0;</TD>'
147 '</TR>'
149 self.covered_tr_html = (
150 '<TR CLASS="c">'
151 '<TD CLASS="l">110</TD>'
152 '<TD> if (mSelectors.get(index) != null) {</TD>'
153 '</TR>'
155 self.not_executable_tr_html = (
156 '<TR>'
157 '<TD CLASS="l">109</TD>'
158 '<TD> </TD>'
159 '</TR>'
161 self.tr_with_extra_a_tag = (
162 '<TR CLASS="z">'
163 '<TD CLASS="l">'
164 '<A name="1f">54</A>'
165 '</TD>'
166 '<TD> }</TD>'
167 '</TR>'
170 def testInit(self):
171 emma_dir = self.emma_dir
172 parser = emma_coverage_stats._EmmaHtmlParser(emma_dir)
173 self.assertEqual(parser._base_dir, emma_dir)
174 self.assertEqual(parser._emma_files_path, 'fake/dir/_files')
175 self.assertEqual(parser._index_path, 'fake/dir/index.html')
177 def testFindElements_basic(self):
178 read_values = [self.simple_html]
179 found, _ = MockOpenForFunction(self.parser._FindElements, read_values,
180 file_path='fake', xpath_selector='.//TD')
181 self.assertIs(type(found), list)
182 self.assertIs(type(found[0]), ElementTree.Element)
183 self.assertEqual(found[0].text, 'Test HTML')
185 def testFindElements_multipleElements(self):
186 multiple_trs = self.not_executable_tr_html + self.covered_tr_html
187 read_values = ['<div>' + multiple_trs + '</div>']
188 found, _ = MockOpenForFunction(self.parser._FindElements, read_values,
189 file_path='fake', xpath_selector='.//TR')
190 self.assertEquals(2, len(found))
192 def testFindElements_noMatch(self):
193 read_values = [self.simple_html]
194 found, _ = MockOpenForFunction(self.parser._FindElements, read_values,
195 file_path='fake', xpath_selector='.//TR')
196 self.assertEqual(found, [])
198 def testFindElements_badFilePath(self):
199 with self.assertRaises(IOError):
200 with mock.patch('os.path.exists', return_value=False):
201 self.parser._FindElements('fake', xpath_selector='//tr')
203 def testGetPackageNameToEmmaFileDict_basic(self):
204 expected_dict = {
205 'org.chromium.chrome.browser.AccessibilityUtil.java':
206 'fake/dir/_files/23.html',
207 'org.chromium.chrome.browser.ContextualMenuBar.java':
208 'fake/dir/_files/22.html',
209 'org.chromium.chrome.browser.tabmodel.IntentHelper.java':
210 'fake/dir/_files/1e.html',
211 'org.chromium.chrome.browser.ContentSetting.java':
212 'fake/dir/_files/1f.html',
213 'org.chromium.chrome.browser.DevToolsServer.java':
214 'fake/dir/_files/20.html',
215 'org.chromium.chrome.browser.NavigationPopup.java':
216 'fake/dir/_files/24.html',
217 'org.chromium.chrome.browser.FileProviderHelper.java':
218 'fake/dir/_files/21.html'}
220 read_values = [self.index_html, self.package_1_class_list_html,
221 self.package_2_class_list_html]
222 return_dict, mock_open = MockOpenForFunction(
223 self.parser.GetPackageNameToEmmaFileDict, read_values)
225 self.assertDictEqual(return_dict, expected_dict)
226 self.assertEqual(mock_open.call_count, 3)
227 calls = [mock.call('fake/dir/index.html'),
228 mock.call('fake/dir/_files/1.html'),
229 mock.call('fake/dir/_files/0.html')]
230 mock_open.assert_has_calls(calls)
232 def testGetPackageNameToEmmaFileDict_noPackageElements(self):
233 self.parser._FindElements = mock.Mock(return_value=[])
234 return_dict = self.parser.GetPackageNameToEmmaFileDict()
235 self.assertDictEqual({}, return_dict)
237 def testGetLineCoverage_status_basic(self):
238 line_coverage = self.GetLineCoverageWithFakeElements([self.covered_tr_html])
239 self.assertEqual(line_coverage[0].covered_status,
240 emma_coverage_stats.COVERED)
242 def testGetLineCoverage_status_statusMissing(self):
243 line_coverage = self.GetLineCoverageWithFakeElements(
244 [self.not_executable_tr_html])
245 self.assertEqual(line_coverage[0].covered_status,
246 emma_coverage_stats.NOT_EXECUTABLE)
248 def testGetLineCoverage_fractionalCoverage_basic(self):
249 line_coverage = self.GetLineCoverageWithFakeElements([self.covered_tr_html])
250 self.assertEqual(line_coverage[0].fractional_line_coverage, 1.0)
252 def testGetLineCoverage_fractionalCoverage_partial(self):
253 line_coverage = self.GetLineCoverageWithFakeElements(
254 [self.partially_covered_tr_html])
255 self.assertEqual(line_coverage[0].fractional_line_coverage, 0.78)
257 def testGetLineCoverage_lineno_basic(self):
258 line_coverage = self.GetLineCoverageWithFakeElements([self.covered_tr_html])
259 self.assertEqual(line_coverage[0].lineno, 110)
261 def testGetLineCoverage_lineno_withAlternativeHtml(self):
262 line_coverage = self.GetLineCoverageWithFakeElements(
263 [self.tr_with_extra_a_tag])
264 self.assertEqual(line_coverage[0].lineno, 54)
266 def testGetLineCoverage_source(self):
267 self.parser._FindElements = mock.Mock(
268 return_value=[ElementTree.fromstring(self.covered_tr_html)])
269 line_coverage = self.parser.GetLineCoverage('fake_path')
270 self.assertEqual(line_coverage[0].source,
271 ' if (mSelectors.get(index) != null) {')
273 def testGetLineCoverage_multipleElements(self):
274 line_coverage = self.GetLineCoverageWithFakeElements(
275 [self.covered_tr_html, self.partially_covered_tr_html,
276 self.tr_with_extra_a_tag])
277 self.assertEqual(len(line_coverage), 3)
279 def GetLineCoverageWithFakeElements(self, html_elements):
280 """Wraps GetLineCoverage so mock HTML can easily be used.
282 Args:
283 html_elements: List of strings each representing an HTML element.
285 Returns:
286 A list of LineCoverage objects.
288 elements = [ElementTree.fromstring(string) for string in html_elements]
289 with mock.patch('emma_coverage_stats._EmmaHtmlParser._FindElements',
290 return_value=elements):
291 return self.parser.GetLineCoverage('fake_path')
294 class _EmmaCoverageStatsTest(unittest.TestCase):
295 """Tests for _EmmaCoverageStats."""
297 def setUp(self):
298 self.good_source_to_emma = {
299 '/path/to/1/File1.java': '/emma/1.html',
300 '/path/2/File2.java': '/emma/2.html',
301 '/path/2/File3.java': '/emma/3.html'
303 self.line_coverage = [
304 emma_coverage_stats.LineCoverage(
305 1, '', emma_coverage_stats.COVERED, 1.0),
306 emma_coverage_stats.LineCoverage(
307 2, '', emma_coverage_stats.COVERED, 1.0),
308 emma_coverage_stats.LineCoverage(
309 3, '', emma_coverage_stats.NOT_EXECUTABLE, 1.0),
310 emma_coverage_stats.LineCoverage(
311 4, '', emma_coverage_stats.NOT_COVERED, 1.0),
312 emma_coverage_stats.LineCoverage(
313 5, '', emma_coverage_stats.PARTIALLY_COVERED, 0.85),
314 emma_coverage_stats.LineCoverage(
315 6, '', emma_coverage_stats.PARTIALLY_COVERED, 0.20)
317 self.lines_for_coverage = [1, 3, 5, 6]
318 with mock.patch('emma_coverage_stats._EmmaHtmlParser._FindElements',
319 return_value=[]):
320 self.simple_coverage = emma_coverage_stats._EmmaCoverageStats(
321 'fake_dir', {})
323 def testInit(self):
324 coverage_stats = self.simple_coverage
325 self.assertIsInstance(coverage_stats._emma_parser,
326 emma_coverage_stats._EmmaHtmlParser)
327 self.assertIsInstance(coverage_stats._source_to_emma, dict)
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(
333 emma_coverage_stats._EmmaCoverageStats.NeedsCoverage(test_file))
335 def testNeedsCoverage_withNonJavaFile(self):
336 test_file = '/path/to/file/File.c'
337 with mock.patch('os.path.exists', return_value=True):
338 self.assertFalse(
339 emma_coverage_stats._EmmaCoverageStats.NeedsCoverage(test_file))
341 def testNeedsCoverage_fileDoesNotExist(self):
342 test_file = '/path/to/file/File.java'
343 with mock.patch('os.path.exists', return_value=False):
344 self.assertFalse(
345 emma_coverage_stats._EmmaCoverageStats.NeedsCoverage(test_file))
347 def testGetPackageNameFromFile_basic(self):
348 test_file_text = """// Test Copyright
349 package org.chromium.chrome.browser;
350 import android.graphics.RectF;"""
351 result_package, _ = MockOpenForFunction(
352 emma_coverage_stats._EmmaCoverageStats.GetPackageNameFromFile,
353 [test_file_text], file_path='/path/to/file/File.java')
354 self.assertEqual(result_package, 'org.chromium.chrome.browser.File.java')
356 def testGetPackageNameFromFile_noPackageStatement(self):
357 result_package, _ = MockOpenForFunction(
358 emma_coverage_stats._EmmaCoverageStats.GetPackageNameFromFile,
359 ['not a package statement'], file_path='/path/to/file/File.java')
360 self.assertIsNone(result_package)
362 def testGetSummaryStatsForLines_basic(self):
363 covered, total = self.simple_coverage.GetSummaryStatsForLines(
364 self.line_coverage)
365 self.assertEqual(covered, 3.05)
366 self.assertEqual(total, 5)
368 def testGetSourceFileToEmmaFileDict(self):
369 package_names = {
370 '/path/to/1/File1.java': 'org.fake.one.File1.java',
371 '/path/2/File2.java': 'org.fake.File2.java',
372 '/path/2/File3.java': 'org.fake.File3.java'
374 package_to_emma = {
375 'org.fake.one.File1.java': '/emma/1.html',
376 'org.fake.File2.java': '/emma/2.html',
377 'org.fake.File3.java': '/emma/3.html'
379 with mock.patch('os.path.exists', return_value=True):
380 coverage_stats = self.simple_coverage
381 coverage_stats._emma_parser.GetPackageNameToEmmaFileDict = mock.MagicMock(
382 return_value=package_to_emma)
383 coverage_stats.GetPackageNameFromFile = lambda x: package_names[x]
384 result_dict = coverage_stats._GetSourceFileToEmmaFileDict(
385 package_names.keys())
386 self.assertDictEqual(result_dict, self.good_source_to_emma)
388 def testGetCoverageDictForFile(self):
389 line_coverage = self.line_coverage
390 self.simple_coverage._emma_parser.GetLineCoverage = lambda x: line_coverage
391 self.simple_coverage._source_to_emma = {'/fake/src': 'fake/emma'}
392 lines = self.lines_for_coverage
393 expected_dict = {
394 'absolute': {
395 'covered': 3.05,
396 'total': 5
398 'incremental': {
399 'covered': 2.05,
400 'total': 3
402 'source': [
404 'line': line_coverage[0].source,
405 'coverage': line_coverage[0].covered_status,
406 'changed': True,
407 'fractional_coverage': line_coverage[0].fractional_line_coverage,
410 'line': line_coverage[1].source,
411 'coverage': line_coverage[1].covered_status,
412 'changed': False,
413 'fractional_coverage': line_coverage[1].fractional_line_coverage,
416 'line': line_coverage[2].source,
417 'coverage': line_coverage[2].covered_status,
418 'changed': True,
419 'fractional_coverage': line_coverage[2].fractional_line_coverage,
422 'line': line_coverage[3].source,
423 'coverage': line_coverage[3].covered_status,
424 'changed': False,
425 'fractional_coverage': line_coverage[3].fractional_line_coverage,
428 'line': line_coverage[4].source,
429 'coverage': line_coverage[4].covered_status,
430 'changed': True,
431 'fractional_coverage': line_coverage[4].fractional_line_coverage,
434 'line': line_coverage[5].source,
435 'coverage': line_coverage[5].covered_status,
436 'changed': True,
437 'fractional_coverage': line_coverage[5].fractional_line_coverage,
441 result_dict = self.simple_coverage.GetCoverageDictForFile(
442 '/fake/src', lines)
443 self.assertDictEqual(result_dict, expected_dict)
445 def testGetCoverageDictForFile_emptyCoverage(self):
446 expected_dict = {
447 'absolute': {'covered': 0, 'total': 0},
448 'incremental': {'covered': 0, 'total': 0},
449 'source': []
451 self.simple_coverage._emma_parser.GetLineCoverage = lambda x: []
452 self.simple_coverage._source_to_emma = {'fake_dir': 'fake/emma'}
453 result_dict = self.simple_coverage.GetCoverageDictForFile('fake_dir', {})
454 self.assertDictEqual(result_dict, expected_dict)
456 def testGetCoverageDictForFile_missingCoverage(self):
457 self.simple_coverage._source_to_emma = {}
458 result_dict = self.simple_coverage.GetCoverageDictForFile('fake_file', {})
459 self.assertIsNone(result_dict)
461 def testGetCoverageDict_basic(self):
462 files_for_coverage = {
463 '/path/to/1/File1.java': [1, 3, 4],
464 '/path/2/File2.java': [1, 2]
466 self.simple_coverage._source_to_emma = {
467 '/path/to/1/File1.java': 'emma_1',
468 '/path/2/File2.java': 'emma_2'
470 coverage_info = {
471 'emma_1': [
472 emma_coverage_stats.LineCoverage(
473 1, '', emma_coverage_stats.COVERED, 1.0),
474 emma_coverage_stats.LineCoverage(
475 2, '', emma_coverage_stats.PARTIALLY_COVERED, 0.5),
476 emma_coverage_stats.LineCoverage(
477 3, '', emma_coverage_stats.NOT_EXECUTABLE, 1.0),
478 emma_coverage_stats.LineCoverage(
479 4, '', emma_coverage_stats.COVERED, 1.0)
481 'emma_2': [
482 emma_coverage_stats.LineCoverage(
483 1, '', emma_coverage_stats.NOT_COVERED, 1.0),
484 emma_coverage_stats.LineCoverage(
485 2, '', emma_coverage_stats.COVERED, 1.0)
488 expected_dict = {
489 'files': {
490 '/path/2/File2.java': {
491 'absolute': {'covered': 1, 'total': 2},
492 'incremental': {'covered': 1, 'total': 2},
493 'source': [{'changed': True, 'coverage': 0,
494 'line': '', 'fractional_coverage': 1.0},
495 {'changed': True, 'coverage': 1,
496 'line': '', 'fractional_coverage': 1.0}]
498 '/path/to/1/File1.java': {
499 'absolute': {'covered': 2.5, 'total': 3},
500 'incremental': {'covered': 2, 'total': 2},
501 'source': [{'changed': True, 'coverage': 1,
502 'line': '', 'fractional_coverage': 1.0},
503 {'changed': False, 'coverage': 2,
504 'line': '', 'fractional_coverage': 0.5},
505 {'changed': True, 'coverage': -1,
506 'line': '', 'fractional_coverage': 1.0},
507 {'changed': True, 'coverage': 1,
508 'line': '', 'fractional_coverage': 1.0}]
511 'patch': {'incremental': {'covered': 3, 'total': 4}}
513 # Return the relevant coverage info for each file.
514 self.simple_coverage._emma_parser.GetLineCoverage = (
515 lambda x: coverage_info[x])
516 result_dict = self.simple_coverage.GetCoverageDict(files_for_coverage)
517 self.assertDictEqual(result_dict, expected_dict)
519 def testGetCoverageDict_noCoverage(self):
520 result_dict = self.simple_coverage.GetCoverageDict({})
521 self.assertDictEqual(result_dict, EMPTY_COVERAGE_STATS_DICT)
524 class EmmaCoverageStatsGenerateCoverageReport(unittest.TestCase):
525 """Tests for GenerateCoverageReport."""
527 def testGenerateCoverageReport_missingJsonFile(self):
528 with self.assertRaises(IOError):
529 with mock.patch('os.path.exists', return_value=False):
530 emma_coverage_stats.GenerateCoverageReport('', '', '')
532 def testGenerateCoverageReport_invalidJsonFile(self):
533 with self.assertRaises(ValueError):
534 with mock.patch('os.path.exists', return_value=True):
535 MockOpenForFunction(emma_coverage_stats.GenerateCoverageReport, [''],
536 line_coverage_file='', out_file_path='',
537 coverage_dir='')
540 def MockOpenForFunction(func, side_effects, **kwargs):
541 """Allows easy mock open and read for callables that open multiple files.
543 Will mock the python open function in a way such that each time read() is
544 called on an open file, the next element in |side_effects| is returned. This
545 makes it easier to test functions that call open() multiple times.
547 Args:
548 func: The callable to invoke once mock files are setup.
549 side_effects: A list of return values for each file to return once read.
550 Length of list should be equal to the number calls to open in |func|.
551 **kwargs: Keyword arguments to be passed to |func|.
553 Returns:
554 A tuple containing the return value of |func| and the MagicMock object used
555 to mock all calls to open respectively.
557 mock_open = mock.mock_open()
558 mock_open.side_effect = [mock.mock_open(read_data=side_effect).return_value
559 for side_effect in side_effects]
560 with mock.patch('__builtin__.open', mock_open):
561 return func(**kwargs), mock_open
564 if __name__ == '__main__':
565 # Suppress logging messages.
566 unittest.main(buffer=True)