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.
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
= {
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.
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>'
42 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">'
44 '<TABLE CELLSPACING="0" WIDTH="100%">'
46 '<TABLE CLASS="it" CELLSPACING="0">'
48 '<TABLE CELLSPACING="0" WIDTH="100%">'
50 '<TH CLASS="f">name</TH>'
57 '<TD><A HREF="_files/0.html"'
58 '>org.chromium.chrome.browser</A></TD>'
59 '<TD CLASS="h">0% (0/3)</TD>'
62 '<TD><A HREF="_files/1.html"'
63 '>org.chromium.chrome.browser.tabmodel</A></TD>'
64 '<TD CLASS="h">0% (0/8)</TD>'
67 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">'
72 self
.package_1_class_list_html
= (
75 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">'
77 '<TABLE CELLSPACING="0" WIDTH="100%">'
79 '<TABLE CELLSPACING="0" WIDTH="100%">'
81 '<TH CLASS="f">name</TH>'
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>'
95 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">'
100 self
.package_2_class_list_html
= (
103 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">'
105 '<TABLE CELLSPACING="0" WIDTH="100%">'
107 '<TABLE CELLSPACING="0" WIDTH="100%">'
109 '<TH CLASS="f">name</TH>'
116 '<TD><A HREF="1f.html">ContentSetting.java</A></TD>'
117 '<TD CLASS="h">0% (0/1)</TD>'
120 '<TD><A HREF="20.html">DevToolsServer.java</A></TD>'
123 '<TD><A HREF="21.html">FileProviderHelper.java</A></TD>'
126 '<TD><A HREF="22.html">ContextualMenuBar.java</A></TD>'
129 '<TD><A HREF="23.html">AccessibilityUtil.java</A></TD>'
132 '<TD><A HREF="24.html">NavigationPopup.java</A></TD>'
135 '<TABLE CLASS="hdft" CELLSPACING="0" WIDTH="100%">'
140 self
.partially_covered_tr_html
= (
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 < 0 || index = mSelectors.size()) index = 0;</TD>'
147 self
.covered_tr_html
= (
149 '<TD CLASS="l">110</TD>'
150 '<TD> if (mSelectors.get(index) != null) {</TD>'
153 self
.not_executable_tr_html
= (
155 '<TD CLASS="l">109</TD>'
159 self
.tr_with_extra_a_tag
= (
162 '<A name="1f">54</A>'
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
):
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.
281 html_elements: List of strings each representing an HTML element.
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."""
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',
318 self
.simple_coverage
= emma_coverage_stats
._EmmaCoverageStats
(
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):
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):
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):
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(
363 self
.assertEqual(covered
, 3.05)
364 self
.assertEqual(total
, 5)
366 def testGetSourceFileToEmmaFileDict(self
):
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'
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
402 'line': line_coverage
[0].source
,
403 'coverage': line_coverage
[0].covered_status
,
405 'fractional_coverage': line_coverage
[0].fractional_line_coverage
,
408 'line': line_coverage
[1].source
,
409 'coverage': line_coverage
[1].covered_status
,
411 'fractional_coverage': line_coverage
[1].fractional_line_coverage
,
414 'line': line_coverage
[2].source
,
415 'coverage': line_coverage
[2].covered_status
,
417 'fractional_coverage': line_coverage
[2].fractional_line_coverage
,
420 'line': line_coverage
[3].source
,
421 'coverage': line_coverage
[3].covered_status
,
423 'fractional_coverage': line_coverage
[3].fractional_line_coverage
,
426 'line': line_coverage
[4].source
,
427 'coverage': line_coverage
[4].covered_status
,
429 'fractional_coverage': line_coverage
[4].fractional_line_coverage
,
432 'line': line_coverage
[5].source
,
433 'coverage': line_coverage
[5].covered_status
,
435 'fractional_coverage': line_coverage
[5].fractional_line_coverage
,
439 result_dict
= self
.simple_coverage
.GetCoverageDictForFile(
441 self
.assertDictEqual(result_dict
, expected_dict
)
443 def testGetCoverageDictForFile_emptyCoverage(self
):
445 'absolute': {'covered': 0, 'total': 0},
446 'incremental': {'covered': 0, 'total': 0},
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'
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)
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)
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
='',
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.
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|.
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)