Update CrOS OOBE throbber to MD throbber; delete old asset
[chromium-blink-merge.git] / build / android / emma_coverage_stats_test.py
blob111f24aec194d64b2fc864fb7013b7a54e10c6d6
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 import os
7 import sys
8 import unittest
9 from xml.etree import ElementTree
11 import emma_coverage_stats
12 from pylib import constants
14 sys.path.append(os.path.join(
15 constants.DIR_SOURCE_ROOT, 'third_party', 'pymock'))
16 import mock # pylint: disable=F0401
18 EMPTY_COVERAGE_STATS_DICT = {
19 'files': {},
20 'patch': {
21 'incremental': {
22 'covered': 0, 'total': 0
28 class _EmmaHtmlParserTest(unittest.TestCase):
29 """Tests for _EmmaHtmlParser.
31 Uses modified EMMA report HTML that contains only the subset of tags needed
32 for test verification.
33 """
35 def setUp(self):
36 self.emma_dir = 'fake/dir/'
37 self.parser = emma_coverage_stats._EmmaHtmlParser(self.emma_dir)
38 self.simple_html = '<TR><TD CLASS="p">Test HTML</TD></TR>'
39 self.index_html = (
40 '<HTML>'
41 '<BODY>'
42 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">'
43 '</TABLE>'
44 '<TABLE CELLSPACING="0" WIDTH="100%">'
45 '</TABLE>'
46 '<TABLE CLASS="it" CELLSPACING="0">'
47 '</TABLE>'
48 '<TABLE CELLSPACING="0" WIDTH="100%">'
49 '<TR>'
50 '<TH CLASS="f">name</TH>'
51 '<TH>class, %</TH>'
52 '<TH>method, %</TH>'
53 '<TH>block, %</TH>'
54 '<TH>line, %</TH>'
55 '</TR>'
56 '<TR CLASS="o">'
57 '<TD><A HREF="_files/0.html"'
58 '>org.chromium.chrome.browser</A></TD>'
59 '<TD CLASS="h">0% (0/3)</TD>'
60 '</TR>'
61 '<TR>'
62 '<TD><A HREF="_files/1.html"'
63 '>org.chromium.chrome.browser.tabmodel</A></TD>'
64 '<TD CLASS="h">0% (0/8)</TD>'
65 '</TR>'
66 '</TABLE>'
67 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">'
68 '</TABLE>'
69 '</BODY>'
70 '</HTML>'
72 self.package_1_class_list_html = (
73 '<HTML>'
74 '<BODY>'
75 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">'
76 '</TABLE>'
77 '<TABLE CELLSPACING="0" WIDTH="100%">'
78 '</TABLE>'
79 '<TABLE CELLSPACING="0" WIDTH="100%">'
80 '<TR>'
81 '<TH CLASS="f">name</TH>'
82 '<TH>class, %</TH>'
83 '<TH>method, %</TH>'
84 '<TH>block, %</TH>'
85 '<TH>line, %</TH>'
86 '</TR>'
87 '<TR CLASS="o">'
88 '<TD><A HREF="1e.html">IntentHelper.java</A></TD>'
89 '<TD CLASS="h">0% (0/3)</TD>'
90 '<TD CLASS="h">0% (0/9)</TD>'
91 '<TD CLASS="h">0% (0/97)</TD>'
92 '<TD CLASS="h">0% (0/26)</TD>'
93 '</TR>'
94 '</TABLE>'
95 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">'
96 '</TABLE>'
97 '</BODY>'
98 '</HTML>'
100 self.package_2_class_list_html = (
101 '<HTML>'
102 '<BODY>'
103 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">'
104 '</TABLE>'
105 '<TABLE CELLSPACING="0" WIDTH="100%">'
106 '</TABLE>'
107 '<TABLE CELLSPACING="0" WIDTH="100%">'
108 '<TR>'
109 '<TH CLASS="f">name</TH>'
110 '<TH>class, %</TH>'
111 '<TH>method, %</TH>'
112 '<TH>block, %</TH>'
113 '<TH>line, %</TH>'
114 '</TR>'
115 '<TR CLASS="o">'
116 '<TD><A HREF="1f.html">ContentSetting.java</A></TD>'
117 '<TD CLASS="h">0% (0/1)</TD>'
118 '</TR>'
119 '<TR>'
120 '<TD><A HREF="20.html">DevToolsServer.java</A></TD>'
121 '</TR>'
122 '<TR CLASS="o">'
123 '<TD><A HREF="21.html">FileProviderHelper.java</A></TD>'
124 '</TR>'
125 '<TR>'
126 '<TD><A HREF="22.html">ContextualMenuBar.java</A></TD>'
127 '</TR>'
128 '<TR CLASS="o">'
129 '<TD><A HREF="23.html">AccessibilityUtil.java</A></TD>'
130 '</TR>'
131 '<TR>'
132 '<TD><A HREF="24.html">NavigationPopup.java</A></TD>'
133 '</TR>'
134 '</TABLE>'
135 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">'
136 '</TABLE>'
137 '</BODY>'
138 '</HTML>'
140 self.partially_covered_tr_html = (
141 '<TR CLASS="p">'
142 '<TD CLASS="l" TITLE="78% line coverage (7 out of 9)">108</TD>'
143 '<TD TITLE="78% line coverage (7 out of 9 instructions)">'
144 'if (index &lt; 0 || index = mSelectors.size()) index = 0;</TD>'
145 '</TR>'
147 self.covered_tr_html = (
148 '<TR CLASS="c">'
149 '<TD CLASS="l">110</TD>'
150 '<TD> if (mSelectors.get(index) != null) {</TD>'
151 '</TR>'
153 self.not_executable_tr_html = (
154 '<TR>'
155 '<TD CLASS="l">109</TD>'
156 '<TD> </TD>'
157 '</TR>'
159 self.tr_with_extra_a_tag = (
160 '<TR CLASS="z">'
161 '<TD CLASS="l">'
162 '<A name="1f">54</A>'
163 '</TD>'
164 '<TD> }</TD>'
165 '</TR>'
168 def testInit(self):
169 emma_dir = self.emma_dir
170 parser = emma_coverage_stats._EmmaHtmlParser(emma_dir)
171 self.assertEqual(parser._base_dir, emma_dir)
172 self.assertEqual(parser._emma_files_path, 'fake/dir/_files')
173 self.assertEqual(parser._index_path, 'fake/dir/index.html')
175 def testFindElements_basic(self):
176 read_values = [self.simple_html]
177 found, _ = MockOpenForFunction(self.parser._FindElements, read_values,
178 file_path='fake', xpath_selector='.//TD')
179 self.assertIs(type(found), list)
180 self.assertIs(type(found[0]), ElementTree.Element)
181 self.assertEqual(found[0].text, 'Test HTML')
183 def testFindElements_multipleElements(self):
184 multiple_trs = self.not_executable_tr_html + self.covered_tr_html
185 read_values = ['<div>' + multiple_trs + '</div>']
186 found, _ = MockOpenForFunction(self.parser._FindElements, read_values,
187 file_path='fake', xpath_selector='.//TR')
188 self.assertEquals(2, len(found))
190 def testFindElements_noMatch(self):
191 read_values = [self.simple_html]
192 found, _ = MockOpenForFunction(self.parser._FindElements, read_values,
193 file_path='fake', xpath_selector='.//TR')
194 self.assertEqual(found, [])
196 def testFindElements_badFilePath(self):
197 with self.assertRaises(IOError):
198 with mock.patch('os.path.exists', return_value=False):
199 self.parser._FindElements('fake', xpath_selector='//tr')
201 def testGetPackageNameToEmmaFileDict_basic(self):
202 expected_dict = {
203 'org.chromium.chrome.browser.AccessibilityUtil.java':
204 'fake/dir/_files/23.html',
205 'org.chromium.chrome.browser.ContextualMenuBar.java':
206 'fake/dir/_files/22.html',
207 'org.chromium.chrome.browser.tabmodel.IntentHelper.java':
208 'fake/dir/_files/1e.html',
209 'org.chromium.chrome.browser.ContentSetting.java':
210 'fake/dir/_files/1f.html',
211 'org.chromium.chrome.browser.DevToolsServer.java':
212 'fake/dir/_files/20.html',
213 'org.chromium.chrome.browser.NavigationPopup.java':
214 'fake/dir/_files/24.html',
215 'org.chromium.chrome.browser.FileProviderHelper.java':
216 'fake/dir/_files/21.html'}
218 read_values = [self.index_html, self.package_1_class_list_html,
219 self.package_2_class_list_html]
220 return_dict, mock_open = MockOpenForFunction(
221 self.parser.GetPackageNameToEmmaFileDict, read_values)
223 self.assertDictEqual(return_dict, expected_dict)
224 self.assertEqual(mock_open.call_count, 3)
225 calls = [mock.call('fake/dir/index.html'),
226 mock.call('fake/dir/_files/1.html'),
227 mock.call('fake/dir/_files/0.html')]
228 mock_open.assert_has_calls(calls)
230 def testGetPackageNameToEmmaFileDict_noPackageElements(self):
231 self.parser._FindElements = mock.Mock(return_value=[])
232 return_dict = self.parser.GetPackageNameToEmmaFileDict()
233 self.assertDictEqual({}, return_dict)
235 def testGetLineCoverage_status_basic(self):
236 line_coverage = self.GetLineCoverageWithFakeElements([self.covered_tr_html])
237 self.assertEqual(line_coverage[0].covered_status,
238 emma_coverage_stats.COVERED)
240 def testGetLineCoverage_status_statusMissing(self):
241 line_coverage = self.GetLineCoverageWithFakeElements(
242 [self.not_executable_tr_html])
243 self.assertEqual(line_coverage[0].covered_status,
244 emma_coverage_stats.NOT_EXECUTABLE)
246 def testGetLineCoverage_fractionalCoverage_basic(self):
247 line_coverage = self.GetLineCoverageWithFakeElements([self.covered_tr_html])
248 self.assertEqual(line_coverage[0].fractional_line_coverage, 1.0)
250 def testGetLineCoverage_fractionalCoverage_partial(self):
251 line_coverage = self.GetLineCoverageWithFakeElements(
252 [self.partially_covered_tr_html])
253 self.assertEqual(line_coverage[0].fractional_line_coverage, 0.78)
255 def testGetLineCoverage_lineno_basic(self):
256 line_coverage = self.GetLineCoverageWithFakeElements([self.covered_tr_html])
257 self.assertEqual(line_coverage[0].lineno, 110)
259 def testGetLineCoverage_lineno_withAlternativeHtml(self):
260 line_coverage = self.GetLineCoverageWithFakeElements(
261 [self.tr_with_extra_a_tag])
262 self.assertEqual(line_coverage[0].lineno, 54)
264 def testGetLineCoverage_source(self):
265 self.parser._FindElements = mock.Mock(
266 return_value=[ElementTree.fromstring(self.covered_tr_html)])
267 line_coverage = self.parser.GetLineCoverage('fake_path')
268 self.assertEqual(line_coverage[0].source,
269 ' if (mSelectors.get(index) != null) {')
271 def testGetLineCoverage_multipleElements(self):
272 line_coverage = self.GetLineCoverageWithFakeElements(
273 [self.covered_tr_html, self.partially_covered_tr_html,
274 self.tr_with_extra_a_tag])
275 self.assertEqual(len(line_coverage), 3)
277 def GetLineCoverageWithFakeElements(self, html_elements):
278 """Wraps GetLineCoverage so mock HTML can easily be used.
280 Args:
281 html_elements: List of strings each representing an HTML element.
283 Returns:
284 A list of LineCoverage objects.
286 elements = [ElementTree.fromstring(string) for string in html_elements]
287 with mock.patch('emma_coverage_stats._EmmaHtmlParser._FindElements',
288 return_value=elements):
289 return self.parser.GetLineCoverage('fake_path')
292 class _EmmaCoverageStatsTest(unittest.TestCase):
293 """Tests for _EmmaCoverageStats."""
295 def setUp(self):
296 self.good_source_to_emma = {
297 '/path/to/1/File1.java': '/emma/1.html',
298 '/path/2/File2.java': '/emma/2.html',
299 '/path/2/File3.java': '/emma/3.html'
301 self.line_coverage = [
302 emma_coverage_stats.LineCoverage(
303 1, '', emma_coverage_stats.COVERED, 1.0),
304 emma_coverage_stats.LineCoverage(
305 2, '', emma_coverage_stats.COVERED, 1.0),
306 emma_coverage_stats.LineCoverage(
307 3, '', emma_coverage_stats.NOT_EXECUTABLE, 1.0),
308 emma_coverage_stats.LineCoverage(
309 4, '', emma_coverage_stats.NOT_COVERED, 1.0),
310 emma_coverage_stats.LineCoverage(
311 5, '', emma_coverage_stats.PARTIALLY_COVERED, 0.85),
312 emma_coverage_stats.LineCoverage(
313 6, '', emma_coverage_stats.PARTIALLY_COVERED, 0.20)
315 self.lines_for_coverage = [1, 3, 5, 6]
316 with mock.patch('emma_coverage_stats._EmmaHtmlParser._FindElements',
317 return_value=[]):
318 self.simple_coverage = emma_coverage_stats._EmmaCoverageStats(
319 'fake_dir', {})
321 def testInit(self):
322 coverage_stats = self.simple_coverage
323 self.assertIsInstance(coverage_stats._emma_parser,
324 emma_coverage_stats._EmmaHtmlParser)
325 self.assertIsInstance(coverage_stats._source_to_emma, dict)
327 def testNeedsCoverage_withExistingJavaFile(self):
328 test_file = '/path/to/file/File.java'
329 with mock.patch('os.path.exists', return_value=True):
330 self.assertTrue(
331 emma_coverage_stats._EmmaCoverageStats.NeedsCoverage(test_file))
333 def testNeedsCoverage_withNonJavaFile(self):
334 test_file = '/path/to/file/File.c'
335 with mock.patch('os.path.exists', return_value=True):
336 self.assertFalse(
337 emma_coverage_stats._EmmaCoverageStats.NeedsCoverage(test_file))
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(
343 emma_coverage_stats._EmmaCoverageStats.NeedsCoverage(test_file))
345 def testGetPackageNameFromFile_basic(self):
346 test_file_text = """// Test Copyright
347 package org.chromium.chrome.browser;
348 import android.graphics.RectF;"""
349 result_package, _ = MockOpenForFunction(
350 emma_coverage_stats._EmmaCoverageStats.GetPackageNameFromFile,
351 [test_file_text], file_path='/path/to/file/File.java')
352 self.assertEqual(result_package, 'org.chromium.chrome.browser.File.java')
354 def testGetPackageNameFromFile_noPackageStatement(self):
355 result_package, _ = MockOpenForFunction(
356 emma_coverage_stats._EmmaCoverageStats.GetPackageNameFromFile,
357 ['not a package statement'], file_path='/path/to/file/File.java')
358 self.assertIsNone(result_package)
360 def testGetSummaryStatsForLines_basic(self):
361 covered, total = self.simple_coverage.GetSummaryStatsForLines(
362 self.line_coverage)
363 self.assertEqual(covered, 3.05)
364 self.assertEqual(total, 5)
366 def testGetSourceFileToEmmaFileDict(self):
367 package_names = {
368 '/path/to/1/File1.java': 'org.fake.one.File1.java',
369 '/path/2/File2.java': 'org.fake.File2.java',
370 '/path/2/File3.java': 'org.fake.File3.java'
372 package_to_emma = {
373 'org.fake.one.File1.java': '/emma/1.html',
374 'org.fake.File2.java': '/emma/2.html',
375 'org.fake.File3.java': '/emma/3.html'
377 with mock.patch('os.path.exists', return_value=True):
378 coverage_stats = self.simple_coverage
379 coverage_stats._emma_parser.GetPackageNameToEmmaFileDict = mock.MagicMock(
380 return_value=package_to_emma)
381 coverage_stats.GetPackageNameFromFile = lambda x: package_names[x]
382 result_dict = coverage_stats._GetSourceFileToEmmaFileDict(
383 package_names.keys())
384 self.assertDictEqual(result_dict, self.good_source_to_emma)
386 def testGetCoverageDictForFile(self):
387 line_coverage = self.line_coverage
388 self.simple_coverage._emma_parser.GetLineCoverage = lambda x: line_coverage
389 self.simple_coverage._source_to_emma = {'/fake/src': 'fake/emma'}
390 lines = self.lines_for_coverage
391 expected_dict = {
392 'absolute': {
393 'covered': 3.05,
394 'total': 5
396 'incremental': {
397 'covered': 2.05,
398 'total': 3
400 'source': [
402 'line': line_coverage[0].source,
403 'coverage': line_coverage[0].covered_status,
404 'changed': True,
405 'fractional_coverage': line_coverage[0].fractional_line_coverage,
408 'line': line_coverage[1].source,
409 'coverage': line_coverage[1].covered_status,
410 'changed': False,
411 'fractional_coverage': line_coverage[1].fractional_line_coverage,
414 'line': line_coverage[2].source,
415 'coverage': line_coverage[2].covered_status,
416 'changed': True,
417 'fractional_coverage': line_coverage[2].fractional_line_coverage,
420 'line': line_coverage[3].source,
421 'coverage': line_coverage[3].covered_status,
422 'changed': False,
423 'fractional_coverage': line_coverage[3].fractional_line_coverage,
426 'line': line_coverage[4].source,
427 'coverage': line_coverage[4].covered_status,
428 'changed': True,
429 'fractional_coverage': line_coverage[4].fractional_line_coverage,
432 'line': line_coverage[5].source,
433 'coverage': line_coverage[5].covered_status,
434 'changed': True,
435 'fractional_coverage': line_coverage[5].fractional_line_coverage,
439 result_dict = self.simple_coverage.GetCoverageDictForFile(
440 '/fake/src', lines)
441 self.assertDictEqual(result_dict, expected_dict)
443 def testGetCoverageDictForFile_emptyCoverage(self):
444 expected_dict = {
445 'absolute': {'covered': 0, 'total': 0},
446 'incremental': {'covered': 0, 'total': 0},
447 'source': []
449 self.simple_coverage._emma_parser.GetLineCoverage = lambda x: []
450 self.simple_coverage._source_to_emma = {'fake_dir': 'fake/emma'}
451 result_dict = self.simple_coverage.GetCoverageDictForFile('fake_dir', {})
452 self.assertDictEqual(result_dict, expected_dict)
454 def testGetCoverageDictForFile_missingCoverage(self):
455 self.simple_coverage._source_to_emma = {}
456 result_dict = self.simple_coverage.GetCoverageDictForFile('fake_file', {})
457 self.assertIsNone(result_dict)
459 def testGetCoverageDict_basic(self):
460 files_for_coverage = {
461 '/path/to/1/File1.java': [1, 3, 4],
462 '/path/2/File2.java': [1, 2]
464 self.simple_coverage._source_to_emma = {
465 '/path/to/1/File1.java': 'emma_1',
466 '/path/2/File2.java': 'emma_2'
468 coverage_info = {
469 'emma_1': [
470 emma_coverage_stats.LineCoverage(
471 1, '', emma_coverage_stats.COVERED, 1.0),
472 emma_coverage_stats.LineCoverage(
473 2, '', emma_coverage_stats.PARTIALLY_COVERED, 0.5),
474 emma_coverage_stats.LineCoverage(
475 3, '', emma_coverage_stats.NOT_EXECUTABLE, 1.0),
476 emma_coverage_stats.LineCoverage(
477 4, '', emma_coverage_stats.COVERED, 1.0)
479 'emma_2': [
480 emma_coverage_stats.LineCoverage(
481 1, '', emma_coverage_stats.NOT_COVERED, 1.0),
482 emma_coverage_stats.LineCoverage(
483 2, '', emma_coverage_stats.COVERED, 1.0)
486 expected_dict = {
487 'files': {
488 '/path/2/File2.java': {
489 'absolute': {'covered': 1, 'total': 2},
490 'incremental': {'covered': 1, 'total': 2},
491 'source': [{'changed': True, 'coverage': 0,
492 'line': '', 'fractional_coverage': 1.0},
493 {'changed': True, 'coverage': 1,
494 'line': '', 'fractional_coverage': 1.0}]
496 '/path/to/1/File1.java': {
497 'absolute': {'covered': 2.5, 'total': 3},
498 'incremental': {'covered': 2, 'total': 2},
499 'source': [{'changed': True, 'coverage': 1,
500 'line': '', 'fractional_coverage': 1.0},
501 {'changed': False, 'coverage': 2,
502 'line': '', 'fractional_coverage': 0.5},
503 {'changed': True, 'coverage': -1,
504 'line': '', 'fractional_coverage': 1.0},
505 {'changed': True, 'coverage': 1,
506 'line': '', 'fractional_coverage': 1.0}]
509 'patch': {'incremental': {'covered': 3, 'total': 4}}
511 # Return the relevant coverage info for each file.
512 self.simple_coverage._emma_parser.GetLineCoverage = (
513 lambda x: coverage_info[x])
514 result_dict = self.simple_coverage.GetCoverageDict(files_for_coverage)
515 self.assertDictEqual(result_dict, expected_dict)
517 def testGetCoverageDict_noCoverage(self):
518 result_dict = self.simple_coverage.GetCoverageDict({})
519 self.assertDictEqual(result_dict, EMPTY_COVERAGE_STATS_DICT)
522 class EmmaCoverageStatsGenerateCoverageReport(unittest.TestCase):
523 """Tests for GenerateCoverageReport."""
525 def testGenerateCoverageReport_missingJsonFile(self):
526 with self.assertRaises(IOError):
527 with mock.patch('os.path.exists', return_value=False):
528 emma_coverage_stats.GenerateCoverageReport('', '', '')
530 def testGenerateCoverageReport_invalidJsonFile(self):
531 with self.assertRaises(ValueError):
532 with mock.patch('os.path.exists', return_value=True):
533 MockOpenForFunction(emma_coverage_stats.GenerateCoverageReport, [''],
534 line_coverage_file='', out_file_path='',
535 coverage_dir='')
538 def MockOpenForFunction(func, side_effects, **kwargs):
539 """Allows easy mock open and read for callables that open multiple files.
541 Will mock the python open function in a way such that each time read() is
542 called on an open file, the next element in |side_effects| is returned. This
543 makes it easier to test functions that call open() multiple times.
545 Args:
546 func: The callable to invoke once mock files are setup.
547 side_effects: A list of return values for each file to return once read.
548 Length of list should be equal to the number calls to open in |func|.
549 **kwargs: Keyword arguments to be passed to |func|.
551 Returns:
552 A tuple containing the return value of |func| and the MagicMock object used
553 to mock all calls to open respectively.
555 mock_open = mock.mock_open()
556 mock_open.side_effect = [mock.mock_open(read_data=side_effect).return_value
557 for side_effect in side_effects]
558 with mock.patch('__builtin__.open', mock_open):
559 return func(**kwargs), mock_open
562 if __name__ == '__main__':
563 # Suppress logging messages.
564 unittest.main(buffer=True)