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
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
= {
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.
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>'
44 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">'
46 '<TABLE CELLSPACING="0" WIDTH="100%">'
48 '<TABLE CLASS="it" CELLSPACING="0">'
50 '<TABLE CELLSPACING="0" WIDTH="100%">'
52 '<TH CLASS="f">name</TH>'
59 '<TD><A HREF="_files/0.html"'
60 '>org.chromium.chrome.browser</A></TD>'
61 '<TD CLASS="h">0% (0/3)</TD>'
64 '<TD><A HREF="_files/1.html"'
65 '>org.chromium.chrome.browser.tabmodel</A></TD>'
66 '<TD CLASS="h">0% (0/8)</TD>'
69 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">'
74 self
.package_1_class_list_html
= (
77 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">'
79 '<TABLE CELLSPACING="0" WIDTH="100%">'
81 '<TABLE CELLSPACING="0" WIDTH="100%">'
83 '<TH CLASS="f">name</TH>'
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>'
97 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">'
102 self
.package_2_class_list_html
= (
105 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">'
107 '<TABLE CELLSPACING="0" WIDTH="100%">'
109 '<TABLE CELLSPACING="0" WIDTH="100%">'
111 '<TH CLASS="f">name</TH>'
118 '<TD><A HREF="1f.html">ContentSetting.java</A></TD>'
119 '<TD CLASS="h">0% (0/1)</TD>'
122 '<TD><A HREF="20.html">DevToolsServer.java</A></TD>'
125 '<TD><A HREF="21.html">FileProviderHelper.java</A></TD>'
128 '<TD><A HREF="22.html">ContextualMenuBar.java</A></TD>'
131 '<TD><A HREF="23.html">AccessibilityUtil.java</A></TD>'
134 '<TD><A HREF="24.html">NavigationPopup.java</A></TD>'
137 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">'
142 self
.partially_covered_tr_html
= (
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>'
149 self
.covered_tr_html
= (
151 '<TD CLASS="l">110</TD>'
152 '<TD> if (mSelectors.get(index) != null) {</TD>'
155 self
.not_executable_tr_html
= (
157 '<TD CLASS="l">109</TD>'
161 self
.tr_with_extra_a_tag
= (
164 '<A name="1f">54</A>'
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
):
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.
283 html_elements: List of strings each representing an HTML element.
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."""
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',
320 self
.simple_coverage
= emma_coverage_stats
._EmmaCoverageStats
(
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):
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):
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):
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(
365 self
.assertEqual(covered
, 3.05)
366 self
.assertEqual(total
, 5)
368 def testGetSourceFileToEmmaFileDict(self
):
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'
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
404 'line': line_coverage
[0].source
,
405 'coverage': line_coverage
[0].covered_status
,
407 'fractional_coverage': line_coverage
[0].fractional_line_coverage
,
410 'line': line_coverage
[1].source
,
411 'coverage': line_coverage
[1].covered_status
,
413 'fractional_coverage': line_coverage
[1].fractional_line_coverage
,
416 'line': line_coverage
[2].source
,
417 'coverage': line_coverage
[2].covered_status
,
419 'fractional_coverage': line_coverage
[2].fractional_line_coverage
,
422 'line': line_coverage
[3].source
,
423 'coverage': line_coverage
[3].covered_status
,
425 'fractional_coverage': line_coverage
[3].fractional_line_coverage
,
428 'line': line_coverage
[4].source
,
429 'coverage': line_coverage
[4].covered_status
,
431 'fractional_coverage': line_coverage
[4].fractional_line_coverage
,
434 'line': line_coverage
[5].source
,
435 'coverage': line_coverage
[5].covered_status
,
437 'fractional_coverage': line_coverage
[5].fractional_line_coverage
,
441 result_dict
= self
.simple_coverage
.GetCoverageDictForFile(
443 self
.assertDictEqual(result_dict
, expected_dict
)
445 def testGetCoverageDictForFile_emptyCoverage(self
):
447 'absolute': {'covered': 0, 'total': 0},
448 'incremental': {'covered': 0, 'total': 0},
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'
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)
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)
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
='',
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.
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|.
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)