OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # Copyright 2015 The Chromium Authors. All rights reserved. | 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 | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
| 6 import json |
6 import os | 7 import os |
7 import sys | 8 import sys |
8 import unittest | 9 import unittest |
9 from xml.etree import ElementTree | 10 from xml.etree import ElementTree |
10 | 11 |
11 import emma_coverage_stats | 12 import emma_coverage_stats |
12 from pylib import constants | 13 from pylib import constants |
13 | 14 |
14 sys.path.append(os.path.join( | 15 sys.path.append(os.path.join( |
15 constants.DIR_SOURCE_ROOT, 'third_party', 'pymock')) | 16 constants.DIR_SOURCE_ROOT, 'third_party', 'pymock')) |
16 import mock # pylint: disable=F0401 | 17 import mock # pylint: disable=F0401 |
17 | 18 |
| 19 EMPTY_COVERAGE_STATS_DICT = { |
| 20 'files': {}, |
| 21 'patch': { |
| 22 'incremental': { |
| 23 'covered': 0, 'total': 0 |
| 24 } |
| 25 } |
| 26 } |
| 27 |
18 | 28 |
19 class _EmmaHtmlParserTest(unittest.TestCase): | 29 class _EmmaHtmlParserTest(unittest.TestCase): |
20 """Tests for _EmmaHtmlParser. | 30 """Tests for _EmmaHtmlParser. |
21 | 31 |
22 Uses modified EMMA report HTML that contains only the subset of tags needed | 32 Uses modified EMMA report HTML that contains only the subset of tags needed |
23 for test verification. | 33 for test verification. |
24 """ | 34 """ |
25 | 35 |
26 def setUp(self): | 36 def setUp(self): |
27 self.emma_dir = 'fake/dir/' | 37 self.emma_dir = 'fake/dir/' |
(...skipping 188 matching lines...) Loading... |
216 calls = [mock.call('fake/dir/index.html'), | 226 calls = [mock.call('fake/dir/index.html'), |
217 mock.call('fake/dir/_files/1.html'), | 227 mock.call('fake/dir/_files/1.html'), |
218 mock.call('fake/dir/_files/0.html')] | 228 mock.call('fake/dir/_files/0.html')] |
219 mock_open.assert_has_calls(calls) | 229 mock_open.assert_has_calls(calls) |
220 | 230 |
221 def testGetPackageNameToEmmaFileDict_noPackageElements(self): | 231 def testGetPackageNameToEmmaFileDict_noPackageElements(self): |
222 self.parser._FindElements = mock.Mock(return_value=[]) | 232 self.parser._FindElements = mock.Mock(return_value=[]) |
223 return_dict = self.parser.GetPackageNameToEmmaFileDict() | 233 return_dict = self.parser.GetPackageNameToEmmaFileDict() |
224 self.assertDictEqual({}, return_dict) | 234 self.assertDictEqual({}, return_dict) |
225 | 235 |
| 236 def testGetPackageNameToEmmaFileDict_badFilePath(self): |
| 237 self.parser._FindElements = mock.Mock(return_value=[]) |
| 238 return_dict = self.parser.GetPackageNameToEmmaFileDict() |
| 239 self.assertEqual(return_dict, {}) |
| 240 |
226 def testGetLineCoverage_status_basic(self): | 241 def testGetLineCoverage_status_basic(self): |
227 line_coverage = self.GetLineCoverageWithFakeElements([self.covered_tr_html]) | 242 line_coverage = self.GetLineCoverageWithFakeElements([self.covered_tr_html]) |
228 self.assertEqual(line_coverage[0].covered_status, | 243 self.assertEqual(line_coverage[0].covered_status, |
229 emma_coverage_stats.COVERED) | 244 emma_coverage_stats.COVERED) |
230 | 245 |
231 def testGetLineCoverage_status_statusMissing(self): | 246 def testGetLineCoverage_status_statusMissing(self): |
232 line_coverage = self.GetLineCoverageWithFakeElements( | 247 line_coverage = self.GetLineCoverageWithFakeElements( |
233 [self.not_executable_tr_html]) | 248 [self.not_executable_tr_html]) |
234 self.assertEqual(line_coverage[0].covered_status, | 249 self.assertEqual(line_coverage[0].covered_status, |
235 emma_coverage_stats.NOT_EXECUTABLE) | 250 emma_coverage_stats.NOT_EXECUTABLE) |
(...skipping 41 matching lines...) Loading... |
277 | 292 |
278 Returns: | 293 Returns: |
279 A list of LineCoverage objects. | 294 A list of LineCoverage objects. |
280 """ | 295 """ |
281 elements = [ElementTree.fromstring(string) for string in html_elements] | 296 elements = [ElementTree.fromstring(string) for string in html_elements] |
282 with mock.patch('emma_coverage_stats._EmmaHtmlParser._FindElements', | 297 with mock.patch('emma_coverage_stats._EmmaHtmlParser._FindElements', |
283 return_value=elements): | 298 return_value=elements): |
284 return self.parser.GetLineCoverage('fake_path') | 299 return self.parser.GetLineCoverage('fake_path') |
285 | 300 |
286 | 301 |
| 302 class _EmmaCoverageStatsTest(unittest.TestCase): |
| 303 """Tests for _EmmaCoverageStats.""" |
| 304 |
| 305 def setUp(self): |
| 306 self.good_source_to_emma = { |
| 307 '/path/to/1/File1.java': '/emma/1.html', |
| 308 '/path/2/File2.java': '/emma/2.html', |
| 309 '/path/2/File3.java': '/emma/3.html' |
| 310 } |
| 311 self.line_coverage = [ |
| 312 emma_coverage_stats.LineCoverage( |
| 313 1, '', emma_coverage_stats.COVERED, 1.0), |
| 314 emma_coverage_stats.LineCoverage( |
| 315 2, '', emma_coverage_stats.COVERED, 1.0), |
| 316 emma_coverage_stats.LineCoverage( |
| 317 3, '', emma_coverage_stats.NOT_EXECUTABLE, 1.0), |
| 318 emma_coverage_stats.LineCoverage( |
| 319 4, '', emma_coverage_stats.NOT_COVERED, 1.0), |
| 320 emma_coverage_stats.LineCoverage( |
| 321 5, '', emma_coverage_stats.PARTIALLY_COVERED, 0.85), |
| 322 emma_coverage_stats.LineCoverage( |
| 323 6, '', emma_coverage_stats.PARTIALLY_COVERED, 0.20) |
| 324 ] |
| 325 self.lines_for_coverage = [1, 3, 5, 6] |
| 326 with mock.patch('emma_coverage_stats._EmmaHtmlParser._FindElements', |
| 327 return_value=[]): |
| 328 self.simple_coverage = emma_coverage_stats._EmmaCoverageStats( |
| 329 'fake_dir', {}) |
| 330 |
| 331 def testInit(self): |
| 332 coverage_stats = self.simple_coverage |
| 333 self.assertIsInstance(coverage_stats._emma_parser, |
| 334 emma_coverage_stats._EmmaHtmlParser) |
| 335 self.assertIsInstance(coverage_stats._source_to_emma, dict) |
| 336 |
| 337 def testNeedsCoverage_withExistingJavaFile(self): |
| 338 test_file = '/path/to/file/File.java' |
| 339 with mock.patch('os.path.exists', return_value=True): |
| 340 self.assertTrue( |
| 341 emma_coverage_stats._EmmaCoverageStats.NeedsCoverage(test_file)) |
| 342 |
| 343 def testNeedsCoverage_withNonJavaFile(self): |
| 344 test_file = '/path/to/file/File.c' |
| 345 with mock.patch('os.path.exists', return_value=True): |
| 346 self.assertFalse( |
| 347 emma_coverage_stats._EmmaCoverageStats.NeedsCoverage(test_file)) |
| 348 |
| 349 def testNeedsCoverage_fileDoesNotExist(self): |
| 350 test_file = '/path/to/file/File.java' |
| 351 with mock.patch('os.path.exists', return_value=False): |
| 352 self.assertFalse( |
| 353 emma_coverage_stats._EmmaCoverageStats.NeedsCoverage(test_file)) |
| 354 |
| 355 def testGetPackageNameFromFile_basic(self): |
| 356 test_file_text = """// Test Copyright |
| 357 package org.chromium.chrome.browser; |
| 358 import android.graphics.RectF;""" |
| 359 result_package, _ = MockOpenForFunction( |
| 360 emma_coverage_stats._EmmaCoverageStats.GetPackageNameFromFile, |
| 361 [test_file_text], file_path='/path/to/file/File.java') |
| 362 self.assertEqual(result_package, 'org.chromium.chrome.browser.File.java') |
| 363 |
| 364 def testGetPackageNameFromFile_noPackageStatement(self): |
| 365 result_package, _ = MockOpenForFunction( |
| 366 emma_coverage_stats._EmmaCoverageStats.GetPackageNameFromFile, |
| 367 ['not a package statement'], file_path='/path/to/file/File.java') |
| 368 self.assertIsNone(result_package) |
| 369 |
| 370 def testGetSummaryStatsForLines_basic(self): |
| 371 covered, total = self.simple_coverage.GetSummaryStatsForLines( |
| 372 self.line_coverage) |
| 373 self.assertEqual(covered, 3.05) |
| 374 self.assertEqual(total, 5) |
| 375 |
| 376 def testGetSourceFileToEmmaFileDict(self): |
| 377 package_names = { |
| 378 '/path/to/1/File1.java': 'org.fake.one.File1.java', |
| 379 '/path/2/File2.java': 'org.fake.File2.java', |
| 380 '/path/2/File3.java': 'org.fake.File3.java' |
| 381 } |
| 382 package_to_emma = { |
| 383 'org.fake.one.File1.java': '/emma/1.html', |
| 384 'org.fake.File2.java': '/emma/2.html', |
| 385 'org.fake.File3.java': '/emma/3.html' |
| 386 } |
| 387 with mock.patch('os.path.exists', return_value=True): |
| 388 coverage_stats = self.simple_coverage |
| 389 coverage_stats._emma_parser.GetPackageNameToEmmaFileDict = mock.MagicMock( |
| 390 return_value=package_to_emma) |
| 391 coverage_stats.GetPackageNameFromFile = lambda x: package_names[x] |
| 392 result_dict = coverage_stats._GetSourceFileToEmmaFileDict( |
| 393 package_names.keys()) |
| 394 self.assertDictEqual(result_dict, self.good_source_to_emma) |
| 395 |
| 396 def testGetLineCoverageForFile_basic(self): |
| 397 java_file_path = '/path/to/1/File1.java' |
| 398 line_coverage = emma_coverage_stats.LineCoverage( |
| 399 1, '', emma_coverage_stats.COVERED, 1.0) |
| 400 expected_line_coverage = list(line_coverage) |
| 401 coverage_stats = self.simple_coverage |
| 402 coverage_stats._source_to_emma = self.good_source_to_emma |
| 403 coverage_stats._emma_parser.GetLineCoverage = mock.MagicMock( |
| 404 return_value=expected_line_coverage) |
| 405 coverage_info = coverage_stats._GetLineCoverageForFile(java_file_path) |
| 406 self.assertListEqual(coverage_info, expected_line_coverage) |
| 407 |
| 408 def testGetLineCoverageForFile_noInfo(self): |
| 409 with mock.patch('os.path.exists', return_value=False): |
| 410 coverage_info = self.simple_coverage._GetLineCoverageForFile('fake_path') |
| 411 self.assertIsNone(coverage_info) |
| 412 |
| 413 def testGetCoverageDictForFile(self): |
| 414 line_coverage = self.line_coverage |
| 415 self.simple_coverage._GetLineCoverageForFile = mock.Mock( |
| 416 return_value=line_coverage) |
| 417 lines = self.lines_for_coverage |
| 418 expected_dict = { |
| 419 'absolute': { |
| 420 'covered': 3.05, |
| 421 'total': 5 |
| 422 }, |
| 423 'incremental': { |
| 424 'covered': 2.05, |
| 425 'total': 3 |
| 426 }, |
| 427 'source': [ |
| 428 { |
| 429 'line': line_coverage[0].source, |
| 430 'coverage': line_coverage[0].covered_status, |
| 431 'changed': True |
| 432 }, |
| 433 { |
| 434 'line': line_coverage[1].source, |
| 435 'coverage': line_coverage[1].covered_status, |
| 436 'changed': False |
| 437 }, |
| 438 { |
| 439 'line': line_coverage[2].source, |
| 440 'coverage': line_coverage[2].covered_status, |
| 441 'changed': True |
| 442 }, |
| 443 { |
| 444 'line': line_coverage[3].source, |
| 445 'coverage': line_coverage[3].covered_status, |
| 446 'changed': False |
| 447 }, |
| 448 { |
| 449 'line': line_coverage[4].source, |
| 450 'coverage': line_coverage[4].covered_status, |
| 451 'changed': True |
| 452 }, |
| 453 { |
| 454 'line': line_coverage[5].source, |
| 455 'coverage': line_coverage[5].covered_status, |
| 456 'changed': True |
| 457 } |
| 458 ] |
| 459 } |
| 460 result_dict = self.simple_coverage.GetCoverageDictForFile( |
| 461 line_coverage, lines) |
| 462 self.assertDictEqual(result_dict, expected_dict) |
| 463 |
| 464 def testGetCoverageDictForFile_emptyCoverage(self): |
| 465 expected_dict = { |
| 466 'absolute': {'covered': 0, 'total': 0}, |
| 467 'incremental': {'covered': 0, 'total': 0}, |
| 468 'source': [] |
| 469 } |
| 470 self.simple_coverage._GetLineCoverageForFile = mock.Mock(return_value=[]) |
| 471 result_dict = self.simple_coverage.GetCoverageDictForFile('fake_dir', {}) |
| 472 self.assertDictEqual(result_dict, expected_dict) |
| 473 |
| 474 def testGetCoverageDictFor_basic(self): |
| 475 files_for_coverage = { |
| 476 '/path/to/1/File1.java': [1, 3, 4], |
| 477 '/path/2/File2.java': [1, 2] |
| 478 } |
| 479 coverage_info = { |
| 480 '/path/to/1/File1.java': [ |
| 481 emma_coverage_stats.LineCoverage( |
| 482 1, '', emma_coverage_stats.COVERED, 1.0), |
| 483 emma_coverage_stats.LineCoverage( |
| 484 2, '', emma_coverage_stats.PARTIALLY_COVERED, 0.5), |
| 485 emma_coverage_stats.LineCoverage( |
| 486 3, '', emma_coverage_stats.NOT_EXECUTABLE, 1.0), |
| 487 emma_coverage_stats.LineCoverage( |
| 488 4, '', emma_coverage_stats.COVERED, 1.0) |
| 489 ], |
| 490 '/path/2/File2.java': [ |
| 491 emma_coverage_stats.LineCoverage( |
| 492 1, '', emma_coverage_stats.NOT_COVERED, 1.0), |
| 493 emma_coverage_stats.LineCoverage( |
| 494 2, '', emma_coverage_stats.COVERED, 1.0) |
| 495 ] |
| 496 } |
| 497 expected_dict = { |
| 498 'files': { |
| 499 '/path/2/File2.java': { |
| 500 'absolute': {'covered': 1, 'total': 2}, |
| 501 'incremental': {'covered': 1, 'total': 2}, |
| 502 'source': [{'changed': True, 'coverage': 0, 'line': ''}, |
| 503 {'changed': True, 'coverage': 1, 'line': ''}] |
| 504 }, |
| 505 '/path/to/1/File1.java': { |
| 506 'absolute': {'covered': 2.5, 'total': 3}, |
| 507 'incremental': {'covered': 2, 'total': 2}, |
| 508 'source': [{'changed': True, 'coverage': 1, 'line': ''}, |
| 509 {'changed': False, 'coverage': 2, 'line': ''}, |
| 510 {'changed': True, 'coverage': -1, 'line': ''}, |
| 511 {'changed': True, 'coverage': 1, 'line': ''}] |
| 512 } |
| 513 }, |
| 514 'patch': {'incremental': {'covered': 3, 'total': 4}} |
| 515 } |
| 516 # Return the relevant coverage info for each file. We aren't testing |
| 517 # _GetCoverageStatusForFile here. |
| 518 self.simple_coverage._GetLineCoverageForFile = lambda x: coverage_info[x] |
| 519 result_dict = self.simple_coverage.GetCoverageDict( |
| 520 files_for_coverage) |
| 521 self.assertDictEqual(result_dict, expected_dict) |
| 522 |
| 523 def testGetCoverageDict_noCoverage(self): |
| 524 result_dict = self.simple_coverage.GetCoverageDict({}) |
| 525 self.assertDictEqual(result_dict, EMPTY_COVERAGE_STATS_DICT) |
| 526 |
| 527 |
| 528 class EmmaCoverageStatsGenerateCoverageReport(unittest.TestCase): |
| 529 """Tests for GenerateCoverageReport.""" |
| 530 |
| 531 def testGenerateCoverageReport_missingJsonFile(self): |
| 532 with self.assertRaises(IOError): |
| 533 with mock.patch('os.path.exists', return_value=False): |
| 534 emma_coverage_stats.GenerateCoverageReport('', '', '') |
| 535 |
| 536 def testGenerateCoverageReport_invalidJsonFile(self): |
| 537 with self.assertRaises(ValueError): |
| 538 with mock.patch('os.path.exists', return_value=True): |
| 539 MockOpenForFunction(emma_coverage_stats.GenerateCoverageReport, [''], |
| 540 line_coverage_file='', out_file_path='', |
| 541 coverage_dir='') |
| 542 |
| 543 |
287 def MockOpenForFunction(func, side_effects, **kwargs): | 544 def MockOpenForFunction(func, side_effects, **kwargs): |
288 """Allows easy mock open and read for callables that open multiple files. | 545 """Allows easy mock open and read for callables that open multiple files. |
289 | 546 |
| 547 Will mock the python open function in a way such that each time read() is |
| 548 called on an open file, the next element in |side_effects| is returned. This |
| 549 makes it easier to test functions that call open() multiple times. |
| 550 |
290 Args: | 551 Args: |
291 func: The callable to invoke once mock files are setup. | 552 func: The callable to invoke once mock files are setup. |
292 side_effects: A list of return values for each file to return once read. | 553 side_effects: A list of return values for each file to return once read. |
293 Length of list should be equal to the number calls to open in |func|. | 554 Length of list should be equal to the number calls to open in |func|. |
294 **kwargs: Keyword arguments to be passed to |func|. | 555 **kwargs: Keyword arguments to be passed to |func|. |
295 | 556 |
296 Returns: | 557 Returns: |
297 A tuple containing the return value of |func| and the MagicMock object used | 558 A tuple containing the return value of |func| and the MagicMock object used |
298 to mock all calls to open respectively. | 559 to mock all calls to open respectively. |
299 """ | 560 """ |
300 mock_open = mock.mock_open() | 561 mock_open = mock.mock_open() |
301 mock_open.side_effect = [mock.mock_open(read_data=side_effect).return_value | 562 mock_open.side_effect = [mock.mock_open(read_data=side_effect).return_value |
302 for side_effect in side_effects] | 563 for side_effect in side_effects] |
303 with mock.patch('__builtin__.open', mock_open): | 564 with mock.patch('__builtin__.open', mock_open): |
304 return func(**kwargs), mock_open | 565 return func(**kwargs), mock_open |
305 | 566 |
306 | 567 |
307 if __name__ == '__main__': | 568 if __name__ == '__main__': |
308 unittest.main() | 569 # Suppress logging messages. |
| 570 unittest.main(buffer=True) |
OLD | NEW |