1#!/usr/bin/env python 2# Copyright (c) 2011 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. 5 6"""Unit tests for Crocodile.""" 7 8import os 9import StringIO 10import unittest 11import croc 12 13 14class TestCoverageStats(unittest.TestCase): 15 """Tests for croc.CoverageStats.""" 16 17 def testAdd(self): 18 """Test Add().""" 19 c = croc.CoverageStats() 20 21 # Initially empty 22 self.assertEqual(c, {}) 23 24 # Add items 25 c['a'] = 1 26 c['b'] = 0 27 self.assertEqual(c, {'a': 1, 'b': 0}) 28 29 # Add dict with non-overlapping items 30 c.Add({'c': 5}) 31 self.assertEqual(c, {'a': 1, 'b': 0, 'c': 5}) 32 33 # Add dict with overlapping items 34 c.Add({'a': 4, 'd': 3}) 35 self.assertEqual(c, {'a': 5, 'b': 0, 'c': 5, 'd': 3}) 36 37 38class TestCoveredFile(unittest.TestCase): 39 """Tests for croc.CoveredFile.""" 40 41 def setUp(self): 42 self.cov_file = croc.CoveredFile('bob.cc', group='source', language='C++') 43 44 def testInit(self): 45 """Test init.""" 46 f = self.cov_file 47 48 # Check initial values 49 self.assertEqual(f.filename, 'bob.cc') 50 self.assertEqual(f.attrs, {'group': 'source', 'language': 'C++'}) 51 self.assertEqual(f.lines, {}) 52 self.assertEqual(f.stats, {}) 53 self.assertEqual(f.local_path, None) 54 self.assertEqual(f.in_lcov, False) 55 56 def testUpdateCoverageEmpty(self): 57 """Test updating coverage when empty.""" 58 f = self.cov_file 59 f.UpdateCoverage() 60 self.assertEqual(f.stats, { 61 'lines_executable': 0, 62 'lines_instrumented': 0, 63 'lines_covered': 0, 64 'files_executable': 1, 65 }) 66 67 def testUpdateCoverageExeOnly(self): 68 """Test updating coverage when no lines are instrumented.""" 69 f = self.cov_file 70 f.lines = {1: None, 2: None, 4: None} 71 f.UpdateCoverage() 72 self.assertEqual(f.stats, { 73 'lines_executable': 3, 74 'lines_instrumented': 0, 75 'lines_covered': 0, 76 'files_executable': 1, 77 }) 78 79 # Now mark the file instrumented via in_lcov 80 f.in_lcov = True 81 f.UpdateCoverage() 82 self.assertEqual(f.stats, { 83 'lines_executable': 3, 84 'lines_instrumented': 0, 85 'lines_covered': 0, 86 'files_executable': 1, 87 'files_instrumented': 1, 88 }) 89 90 def testUpdateCoverageExeAndInstr(self): 91 """Test updating coverage when no lines are covered.""" 92 f = self.cov_file 93 f.lines = {1: None, 2: None, 4: 0, 5: 0, 7: None} 94 f.UpdateCoverage() 95 self.assertEqual(f.stats, { 96 'lines_executable': 5, 97 'lines_instrumented': 2, 98 'lines_covered': 0, 99 'files_executable': 1, 100 'files_instrumented': 1, 101 }) 102 103 def testUpdateCoverageWhenCovered(self): 104 """Test updating coverage when lines are covered.""" 105 f = self.cov_file 106 f.lines = {1: None, 2: None, 3: 1, 4: 0, 5: 0, 6: 1, 7: None} 107 f.UpdateCoverage() 108 self.assertEqual(f.stats, { 109 'lines_executable': 7, 110 'lines_instrumented': 4, 111 'lines_covered': 2, 112 'files_executable': 1, 113 'files_instrumented': 1, 114 'files_covered': 1, 115 }) 116 117 118class TestCoveredDir(unittest.TestCase): 119 """Tests for croc.CoveredDir.""" 120 121 def setUp(self): 122 self.cov_dir = croc.CoveredDir('/a/b/c') 123 124 def testInit(self): 125 """Test init.""" 126 d = self.cov_dir 127 128 # Check initial values 129 self.assertEqual(d.dirpath, '/a/b/c') 130 self.assertEqual(d.files, {}) 131 self.assertEqual(d.subdirs, {}) 132 self.assertEqual(d.stats_by_group, {'all': {}}) 133 134 def testGetTreeEmpty(self): 135 """Test getting empty tree.""" 136 d = self.cov_dir 137 self.assertEqual(d.GetTree(), 'c/') 138 139 def testGetTreeStats(self): 140 """Test getting tree with stats.""" 141 d = self.cov_dir 142 d.stats_by_group['all'] = croc.CoverageStats( 143 lines_executable=50, lines_instrumented=30, lines_covered=20) 144 d.stats_by_group['bar'] = croc.CoverageStats( 145 lines_executable=0, lines_instrumented=0, lines_covered=0) 146 d.stats_by_group['foo'] = croc.CoverageStats( 147 lines_executable=33, lines_instrumented=22, lines_covered=11) 148 # 'bar' group is skipped because it has no executable lines 149 self.assertEqual( 150 d.GetTree(), 151 'c/ all:20/30/50 foo:11/22/33') 152 153 def testGetTreeSubdir(self): 154 """Test getting tree with subdirs.""" 155 d1 = self.cov_dir = croc.CoveredDir('/a') 156 d2 = self.cov_dir = croc.CoveredDir('/a/b') 157 d3 = self.cov_dir = croc.CoveredDir('/a/c') 158 d4 = self.cov_dir = croc.CoveredDir('/a/b/d') 159 d5 = self.cov_dir = croc.CoveredDir('/a/b/e') 160 d1.subdirs = {'/a/b': d2, '/a/c': d3} 161 d2.subdirs = {'/a/b/d': d4, '/a/b/e': d5} 162 self.assertEqual(d1.GetTree(), 'a/\n b/\n d/\n e/\n c/') 163 164 165class TestCoverage(unittest.TestCase): 166 """Tests for croc.Coverage.""" 167 168 def MockWalk(self, src_dir): 169 """Mock for os.walk(). 170 171 Args: 172 src_dir: Source directory to walk. 173 174 Returns: 175 A list of (dirpath, dirnames, filenames) tuples. 176 """ 177 self.mock_walk_calls.append(src_dir) 178 return self.mock_walk_return 179 180 def MockScanFile(self, filename, language): 181 """Mock for croc_scan.ScanFile(). 182 183 Args: 184 filename: Path to file to scan. 185 language: Language for file. 186 187 Returns: 188 A list of executable lines. 189 """ 190 self.mock_scan_calls.append([filename, language]) 191 if filename in self.mock_scan_return: 192 return self.mock_scan_return[filename] 193 else: 194 return self.mock_scan_return['default'] 195 196 def setUp(self): 197 """Per-test setup.""" 198 199 # Empty coverage object 200 self.cov = croc.Coverage() 201 202 # Coverage object with minimal setup 203 self.cov_minimal = croc.Coverage() 204 self.cov_minimal.AddRoot('/src') 205 self.cov_minimal.AddRoot('c:\\source') 206 self.cov_minimal.AddRule('^_/', include=1, group='my') 207 self.cov_minimal.AddRule('.*\\.c$', language='C') 208 self.cov_minimal.AddRule('.*\\.c##$', language='C##') # sharper than thou 209 210 # Data for MockWalk() 211 self.mock_walk_calls = [] 212 self.mock_walk_return = [] 213 214 # Data for MockScanFile() 215 self.mock_scan_calls = [] 216 self.mock_scan_return = {'default': [1]} 217 218 def testInit(self): 219 """Test init.""" 220 c = self.cov 221 self.assertEqual(c.files, {}) 222 self.assertEqual(c.root_dirs, []) 223 self.assertEqual(c.print_stats, []) 224 self.assertEqual(c.rules, []) 225 226 def testAddRoot(self): 227 """Test AddRoot() and CleanupFilename().""" 228 c = self.cov 229 230 # Check for identity on already-clean filenames 231 self.assertEqual(c.CleanupFilename(''), '') 232 self.assertEqual(c.CleanupFilename('a'), 'a') 233 self.assertEqual(c.CleanupFilename('.a'), '.a') 234 self.assertEqual(c.CleanupFilename('..a'), '..a') 235 self.assertEqual(c.CleanupFilename('a.b'), 'a.b') 236 self.assertEqual(c.CleanupFilename('a/b/c'), 'a/b/c') 237 self.assertEqual(c.CleanupFilename('a/b/c/'), 'a/b/c/') 238 239 # Backslash to forward slash 240 self.assertEqual(c.CleanupFilename('a\\b\\c'), 'a/b/c') 241 242 # Handle relative paths 243 self.assertEqual(c.CleanupFilename('.'), 244 c.CleanupFilename(os.path.abspath('.'))) 245 self.assertEqual(c.CleanupFilename('..'), 246 c.CleanupFilename(os.path.abspath('..'))) 247 self.assertEqual(c.CleanupFilename('./foo/bar'), 248 c.CleanupFilename(os.path.abspath('./foo/bar'))) 249 self.assertEqual(c.CleanupFilename('../../a/b/c'), 250 c.CleanupFilename(os.path.abspath('../../a/b/c'))) 251 252 # Replace alt roots 253 c.AddRoot('foo') 254 self.assertEqual(c.CleanupFilename('foo'), '_') 255 self.assertEqual(c.CleanupFilename('foo/bar/baz'), '_/bar/baz') 256 self.assertEqual(c.CleanupFilename('aaa/foo'), 'aaa/foo') 257 258 # Alt root replacement is applied for all roots 259 c.AddRoot('foo/bar', '_B') 260 self.assertEqual(c.CleanupFilename('foo/bar/baz'), '_B/baz') 261 262 # Can use previously defined roots in cleanup 263 c.AddRoot('_/nom/nom/nom', '_CANHAS') 264 self.assertEqual(c.CleanupFilename('foo/nom/nom/nom/cheezburger'), 265 '_CANHAS/cheezburger') 266 267 # Verify roots starting with UNC paths or drive letters work, and that 268 # more than one root can point to the same alt_name 269 c.AddRoot('/usr/local/foo', '_FOO') 270 c.AddRoot('D:\\my\\foo', '_FOO') 271 self.assertEqual(c.CleanupFilename('/usr/local/foo/a/b'), '_FOO/a/b') 272 self.assertEqual(c.CleanupFilename('D:\\my\\foo\\c\\d'), '_FOO/c/d') 273 274 # Cannot specify a blank alt_name 275 self.assertRaises(ValueError, c.AddRoot, 'some_dir', '') 276 277 def testAddRule(self): 278 """Test AddRule() and ClassifyFile().""" 279 c = self.cov 280 281 # With only the default rule, nothing gets kept 282 self.assertEqual(c.ClassifyFile('_/src/'), {}) 283 self.assertEqual(c.ClassifyFile('_/src/a.c'), {}) 284 285 # Add rules to include a tree and set a default group 286 c.AddRule('^_/src/', include=1, group='source') 287 self.assertEqual(c.ClassifyFile('_/src/'), 288 {'include': 1, 'group': 'source'}) 289 self.assertEqual(c.ClassifyFile('_/notsrc/'), {}) 290 self.assertEqual(c.ClassifyFile('_/src/a.c'), 291 {'include': 1, 'group': 'source'}) 292 293 # Define some languages and groups 294 c.AddRule('.*\\.(c|h)$', language='C') 295 c.AddRule('.*\\.py$', language='Python') 296 c.AddRule('.*_test\\.', group='test') 297 self.assertEqual(c.ClassifyFile('_/src/a.c'), 298 {'include': 1, 'group': 'source', 'language': 'C'}) 299 self.assertEqual(c.ClassifyFile('_/src/a.h'), 300 {'include': 1, 'group': 'source', 'language': 'C'}) 301 self.assertEqual(c.ClassifyFile('_/src/a.cpp'), 302 {'include': 1, 'group': 'source'}) 303 self.assertEqual(c.ClassifyFile('_/src/a_test.c'), 304 {'include': 1, 'group': 'test', 'language': 'C'}) 305 self.assertEqual(c.ClassifyFile('_/src/test_a.c'), 306 {'include': 1, 'group': 'source', 'language': 'C'}) 307 self.assertEqual(c.ClassifyFile('_/src/foo/bar.py'), 308 {'include': 1, 'group': 'source', 'language': 'Python'}) 309 self.assertEqual(c.ClassifyFile('_/src/test.py'), 310 {'include': 1, 'group': 'source', 'language': 'Python'}) 311 312 # Exclude a path (for example, anything in a build output dir) 313 c.AddRule('.*/build/', include=0) 314 # But add back in a dir which matched the above rule but isn't a build 315 # output dir 316 c.AddRule('_/src/tools/build/', include=1) 317 self.assertEqual(c.ClassifyFile('_/src/build.c').get('include'), 1) 318 self.assertEqual(c.ClassifyFile('_/src/build/').get('include'), 0) 319 self.assertEqual(c.ClassifyFile('_/src/build/a.c').get('include'), 0) 320 self.assertEqual(c.ClassifyFile('_/src/tools/build/').get('include'), 1) 321 self.assertEqual(c.ClassifyFile('_/src/tools/build/t.c').get('include'), 1) 322 323 def testGetCoveredFile(self): 324 """Test GetCoveredFile().""" 325 c = self.cov_minimal 326 327 # Not currently any covered files 328 self.assertEqual(c.GetCoveredFile('_/a.c'), None) 329 330 # Add some files 331 a_c = c.GetCoveredFile('_/a.c', add=True) 332 b_c = c.GetCoveredFile('_/b.c##', add=True) 333 self.assertEqual(a_c.filename, '_/a.c') 334 self.assertEqual(a_c.attrs, {'include': 1, 'group': 'my', 'language': 'C'}) 335 self.assertEqual(b_c.filename, '_/b.c##') 336 self.assertEqual(b_c.attrs, 337 {'include': 1, 'group': 'my', 'language': 'C##'}) 338 339 # Specifying the same filename should return the existing object 340 self.assertEqual(c.GetCoveredFile('_/a.c'), a_c) 341 self.assertEqual(c.GetCoveredFile('_/a.c', add=True), a_c) 342 343 # Filenames get cleaned on the way in, as do root paths 344 self.assertEqual(c.GetCoveredFile('/src/a.c'), a_c) 345 self.assertEqual(c.GetCoveredFile('c:\\source\\a.c'), a_c) 346 347 # TODO: Make sure that covered files require language, group, and include 348 # (since that checking is now done in GetCoveredFile() rather than 349 # ClassifyFile()) 350 351 def testRemoveCoveredFile(self): 352 """Test RemoveCoveredFile().""" 353 # TODO: TEST ME! 354 355 def testParseLcov(self): 356 """Test ParseLcovData().""" 357 c = self.cov_minimal 358 359 c.ParseLcovData([ 360 '# Ignore unknown lines', 361 # File we should include' 362 'SF:/src/a.c', 363 'DA:10,1', 364 'DA:11,0', 365 'DA:12,1 \n', # Trailing whitespace should get stripped 366 'end_of_record', 367 # File we should ignore 368 'SF:/not_src/a.c', 369 'DA:20,1', 370 'end_of_record', 371 # Same as first source file, but alternate root 372 'SF:c:\\source\\a.c', 373 'DA:30,1', 374 'end_of_record', 375 # Ignore extra end of record 376 'end_of_record', 377 # Ignore data points after end of record 378 'DA:40,1', 379 # Instrumented but uncovered file 380 'SF:/src/b.c', 381 'DA:50,0', 382 'end_of_record', 383 # Empty file (instrumented but no executable lines) 384 'SF:c:\\source\\c.c', 385 'end_of_record', 386 ]) 387 388 # We should know about three files 389 self.assertEqual(sorted(c.files), ['_/a.c', '_/b.c', '_/c.c']) 390 391 # Check expected contents 392 a_c = c.GetCoveredFile('_/a.c') 393 self.assertEqual(a_c.lines, {10: 1, 11: 0, 12: 1, 30: 1}) 394 self.assertEqual(a_c.stats, { 395 'files_executable': 1, 396 'files_instrumented': 1, 397 'files_covered': 1, 398 'lines_instrumented': 4, 399 'lines_executable': 4, 400 'lines_covered': 3, 401 }) 402 self.assertEqual(a_c.in_lcov, True) 403 404 b_c = c.GetCoveredFile('_/b.c') 405 self.assertEqual(b_c.lines, {50: 0}) 406 self.assertEqual(b_c.stats, { 407 'files_executable': 1, 408 'files_instrumented': 1, 409 'lines_instrumented': 1, 410 'lines_executable': 1, 411 'lines_covered': 0, 412 }) 413 self.assertEqual(b_c.in_lcov, True) 414 415 c_c = c.GetCoveredFile('_/c.c') 416 self.assertEqual(c_c.lines, {}) 417 self.assertEqual(c_c.stats, { 418 'files_executable': 1, 419 'files_instrumented': 1, 420 'lines_instrumented': 0, 421 'lines_executable': 0, 422 'lines_covered': 0, 423 }) 424 self.assertEqual(c_c.in_lcov, True) 425 426 # TODO: Test that files are marked as instrumented if they come from lcov, 427 # even if they don't have any instrumented lines. (and that in_lcov is set 428 # for those files - probably should set that via some method rather than 429 # directly...) 430 431 def testGetStat(self): 432 """Test GetStat() and PrintStat().""" 433 c = self.cov 434 435 # Add some stats, so there's something to report 436 c.tree.stats_by_group = { 437 'all': { 438 'count_a': 10, 439 'count_b': 4, 440 'foo': 'bar', 441 }, 442 'tests': { 443 'count_a': 2, 444 'count_b': 5, 445 'baz': 'bob', 446 }, 447 } 448 449 # Test missing stats and groups 450 self.assertRaises(croc.CrocStatError, c.GetStat, 'nosuch') 451 self.assertRaises(croc.CrocStatError, c.GetStat, 'baz') 452 self.assertRaises(croc.CrocStatError, c.GetStat, 'foo', group='tests') 453 self.assertRaises(croc.CrocStatError, c.GetStat, 'foo', group='nosuch') 454 455 # Test returning defaults 456 self.assertEqual(c.GetStat('nosuch', default=13), 13) 457 self.assertEqual(c.GetStat('baz', default='aaa'), 'aaa') 458 self.assertEqual(c.GetStat('foo', group='tests', default=0), 0) 459 self.assertEqual(c.GetStat('foo', group='nosuch', default=''), '') 460 461 # Test getting stats 462 self.assertEqual(c.GetStat('count_a'), 10) 463 self.assertEqual(c.GetStat('count_a', group='tests'), 2) 464 self.assertEqual(c.GetStat('foo', default='baz'), 'bar') 465 466 # Test stat math (eval) 467 self.assertEqual(c.GetStat('count_a - count_b'), 6) 468 self.assertEqual(c.GetStat('100.0 * count_a / count_b', group='tests'), 469 40.0) 470 # Should catch eval errors 471 self.assertRaises(croc.CrocStatError, c.GetStat, '100 / 0') 472 self.assertRaises(croc.CrocStatError, c.GetStat, 'count_a -') 473 474 # Test nested stats via S() 475 self.assertEqual(c.GetStat('count_a - S("count_a", group="tests")'), 8) 476 self.assertRaises(croc.CrocStatError, c.GetStat, 'S()') 477 self.assertRaises(croc.CrocStatError, c.GetStat, 'S("nosuch")') 478 479 # Test PrintStat() 480 # We won't see the first print, but at least verify it doesn't assert 481 c.PrintStat('count_a', format='(test to stdout: %s)') 482 # Send subsequent prints to a file 483 f = StringIO.StringIO() 484 c.PrintStat('count_b', outfile=f) 485 # Test specifying output format 486 c.PrintStat('count_a', format='Count A = %05d', outfile=f) 487 # Test specifing additional keyword args 488 c.PrintStat('count_a', group='tests', outfile=f) 489 c.PrintStat('nosuch', default=42, outfile=f) 490 self.assertEqual(f.getvalue(), ("""\ 491GetStat('count_b') = 4 492Count A = 00010 493GetStat('count_a') = 2 494GetStat('nosuch') = 42 495""")) 496 f.close() 497 498 def testAddConfigEmpty(self): 499 """Test AddConfig() with empty config.""" 500 c = self.cov 501 # Most minimal config is an empty dict; should do nothing 502 c.AddConfig('{} # And we ignore comments') 503 504 def testAddConfig(self): 505 """Test AddConfig().""" 506 c = self.cov 507 lcov_queue = [] 508 addfiles_queue = [] 509 510 c.AddConfig("""{ 511 'roots' : [ 512 {'root' : '/foo'}, 513 {'root' : '/bar', 'altname' : 'BAR'}, 514 ], 515 'rules' : [ 516 {'regexp' : '^_/', 'group' : 'apple'}, 517 {'regexp' : 're2', 'include' : 1, 'language' : 'elvish'}, 518 ], 519 'lcov_files' : ['a.lcov', 'b.lcov'], 520 'add_files' : ['/src', 'BAR/doo'], 521 'print_stats' : [ 522 {'stat' : 'count_a'}, 523 {'stat' : 'count_b', 'group' : 'tests'}, 524 ], 525 'extra_key' : 'is ignored', 526 }""", lcov_queue=lcov_queue, addfiles_queue=addfiles_queue) 527 528 self.assertEqual(lcov_queue, ['a.lcov', 'b.lcov']) 529 self.assertEqual(addfiles_queue, ['/src', 'BAR/doo']) 530 self.assertEqual(c.root_dirs, [['/foo', '_'], ['/bar', 'BAR']]) 531 self.assertEqual(c.print_stats, [ 532 {'stat': 'count_a'}, 533 {'stat': 'count_b', 'group': 'tests'}, 534 ]) 535 # Convert compiled re's back to patterns for comparison 536 rules = [[r[0].pattern] + r[1:] for r in c.rules] 537 self.assertEqual(rules, [ 538 ['^_/', {'group': 'apple'}], 539 ['re2', {'include': 1, 'language': 'elvish'}], 540 ]) 541 542 def testAddFilesSimple(self): 543 """Test AddFiles() simple call.""" 544 c = self.cov_minimal 545 c.add_files_walk = self.MockWalk 546 c.scan_file = self.MockScanFile 547 548 c.AddFiles('/a/b/c') 549 self.assertEqual(self.mock_walk_calls, ['/a/b/c']) 550 self.assertEqual(self.mock_scan_calls, []) 551 self.assertEqual(c.files, {}) 552 553 def testAddFilesRootMap(self): 554 """Test AddFiles() with root mappings.""" 555 c = self.cov_minimal 556 c.add_files_walk = self.MockWalk 557 c.scan_file = self.MockScanFile 558 559 c.AddRoot('_/subdir', 'SUBDIR') 560 561 # AddFiles() should replace the 'SUBDIR' alt_name, then match both 562 # possible roots for the '_' alt_name. 563 c.AddFiles('SUBDIR/foo') 564 self.assertEqual(self.mock_walk_calls, 565 ['/src/subdir/foo', 'c:/source/subdir/foo']) 566 self.assertEqual(self.mock_scan_calls, []) 567 self.assertEqual(c.files, {}) 568 569 def testAddFilesNonEmpty(self): 570 """Test AddFiles() where files are returned.""" 571 572 c = self.cov_minimal 573 c.add_files_walk = self.MockWalk 574 c.scan_file = self.MockScanFile 575 576 # Add a rule to exclude a subdir 577 c.AddRule('^_/proj1/excluded/', include=0) 578 579 # Add a rule to exclude adding some fiels 580 c.AddRule('.*noscan.c$', add_if_missing=0) 581 582 # Set data for mock walk and scan 583 self.mock_walk_return = [ 584 [ 585 '/src/proj1', 586 ['excluded', 'subdir'], 587 ['a.c', 'no.f', 'yes.c', 'noexe.c', 'bob_noscan.c'], 588 ], 589 [ 590 '/src/proj1/subdir', 591 [], 592 ['cherry.c'], 593 ], 594 ] 595 596 # Add a file with no executable lines; it should be scanned but not added 597 self.mock_scan_return['/src/proj1/noexe.c'] = [] 598 599 c.AddFiles('/src/proj1') 600 601 self.assertEqual(self.mock_walk_calls, ['/src/proj1']) 602 self.assertEqual(self.mock_scan_calls, [ 603 ['/src/proj1/a.c', 'C'], 604 ['/src/proj1/yes.c', 'C'], 605 ['/src/proj1/noexe.c', 'C'], 606 ['/src/proj1/subdir/cherry.c', 'C'], 607 ]) 608 609 # Include files from the main dir and subdir 610 self.assertEqual(sorted(c.files), [ 611 '_/proj1/a.c', 612 '_/proj1/subdir/cherry.c', 613 '_/proj1/yes.c']) 614 615 # Excluded dir should have been pruned from the mock walk data dirnames. 616 # In the real os.walk() call this prunes the walk. 617 self.assertEqual(self.mock_walk_return[0][1], ['subdir']) 618 619 620 def testEmptyTreeStats(self): 621 """Make sure we don't choke when absolutely nothing happened. 622 623 How we might hit this: bot compile error.""" 624 c = self.cov_minimal 625 t = c.tree 626 t.stats_by_group['all'].AddDefaults() 627 self.assertEqual(t.stats_by_group, { 628 'all': { 'files_covered': 0, 629 'files_instrumented': 0, 630 'files_executable': 0, 631 'lines_covered': 0, 632 'lines_instrumented': 0, 633 'lines_executable': 0 }}) 634 635 def testUpdateTreeStats(self): 636 """Test UpdateTreeStats().""" 637 638 c = self.cov_minimal 639 c.AddRule('.*_test', group='test') 640 641 # Fill the files list 642 c.ParseLcovData([ 643 'SF:/src/a.c', 644 'DA:10,1', 'DA:11,1', 'DA:20,0', 645 'end_of_record', 646 'SF:/src/a_test.c', 647 'DA:10,1', 'DA:11,1', 'DA:12,1', 648 'end_of_record', 649 'SF:/src/foo/b.c', 650 'DA:10,1', 'DA:11,1', 'DA:20,0', 'DA:21,0', 'DA:30,0', 651 'end_of_record', 652 'SF:/src/foo/b_test.c', 653 'DA:20,0', 'DA:21,0', 'DA:22,0', 654 'end_of_record', 655 ]) 656 c.UpdateTreeStats() 657 658 t = c.tree 659 self.assertEqual(t.dirpath, '') 660 self.assertEqual(sorted(t.files), []) 661 self.assertEqual(sorted(t.subdirs), ['_']) 662 self.assertEqual(t.stats_by_group, { 663 'all': { 664 'files_covered': 3, 665 'files_executable': 4, 666 'lines_executable': 14, 667 'lines_covered': 7, 668 'lines_instrumented': 14, 669 'files_instrumented': 4, 670 }, 671 'my': { 672 'files_covered': 2, 673 'files_executable': 2, 674 'lines_executable': 8, 675 'lines_covered': 4, 676 'lines_instrumented': 8, 677 'files_instrumented': 2, 678 }, 679 'test': { 680 'files_covered': 1, 681 'files_executable': 2, 682 'lines_executable': 6, 683 'lines_covered': 3, 684 'lines_instrumented': 6, 685 'files_instrumented': 2, 686 }, 687 }) 688 689 t = t.subdirs['_'] 690 self.assertEqual(t.dirpath, '_') 691 self.assertEqual(sorted(t.files), ['a.c', 'a_test.c']) 692 self.assertEqual(sorted(t.subdirs), ['foo']) 693 self.assertEqual(t.stats_by_group, { 694 'all': { 695 'files_covered': 3, 696 'files_executable': 4, 697 'lines_executable': 14, 698 'lines_covered': 7, 699 'lines_instrumented': 14, 700 'files_instrumented': 4, 701 }, 702 'my': { 703 'files_covered': 2, 704 'files_executable': 2, 705 'lines_executable': 8, 706 'lines_covered': 4, 707 'lines_instrumented': 8, 708 'files_instrumented': 2, 709 }, 710 'test': { 711 'files_covered': 1, 712 'files_executable': 2, 713 'lines_executable': 6, 714 'lines_covered': 3, 715 'lines_instrumented': 6, 716 'files_instrumented': 2, 717 }, 718 }) 719 720 t = t.subdirs['foo'] 721 self.assertEqual(t.dirpath, '_/foo') 722 self.assertEqual(sorted(t.files), ['b.c', 'b_test.c']) 723 self.assertEqual(sorted(t.subdirs), []) 724 self.assertEqual(t.stats_by_group, { 725 'test': { 726 'files_executable': 1, 727 'files_instrumented': 1, 728 'lines_executable': 3, 729 'lines_instrumented': 3, 730 'lines_covered': 0, 731 }, 732 'all': { 733 'files_covered': 1, 734 'files_executable': 2, 735 'lines_executable': 8, 736 'lines_covered': 2, 737 'lines_instrumented': 8, 738 'files_instrumented': 2, 739 }, 740 'my': { 741 'files_covered': 1, 742 'files_executable': 1, 743 'lines_executable': 5, 744 'lines_covered': 2, 745 'lines_instrumented': 5, 746 'files_instrumented': 1, 747 } 748 }) 749 750 # TODO: test: less important, since these are thin wrappers around other 751 # tested methods. 752 # ParseConfig() 753 # ParseLcovFile() 754 # PrintTree() 755 756 757if __name__ == '__main__': 758 unittest.main() 759