Enable swapping a frame back in to its parent process
[chromium-blink-merge.git] / tools / code_coverage / croc_test.py
blob7c2521ca3dfd6be9d143c4750813852ebe490774
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.
6 """Unit tests for Crocodile."""
8 import os
9 import StringIO
10 import unittest
11 import croc
14 class TestCoverageStats(unittest.TestCase):
15 """Tests for croc.CoverageStats."""
17 def testAdd(self):
18 """Test Add()."""
19 c = croc.CoverageStats()
21 # Initially empty
22 self.assertEqual(c, {})
24 # Add items
25 c['a'] = 1
26 c['b'] = 0
27 self.assertEqual(c, {'a': 1, 'b': 0})
29 # Add dict with non-overlapping items
30 c.Add({'c': 5})
31 self.assertEqual(c, {'a': 1, 'b': 0, 'c': 5})
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})
38 class TestCoveredFile(unittest.TestCase):
39 """Tests for croc.CoveredFile."""
41 def setUp(self):
42 self.cov_file = croc.CoveredFile('bob.cc', group='source', language='C++')
44 def testInit(self):
45 """Test init."""
46 f = self.cov_file
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)
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,
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,
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,
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,
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,
118 class TestCoveredDir(unittest.TestCase):
119 """Tests for croc.CoveredDir."""
121 def setUp(self):
122 self.cov_dir = croc.CoveredDir('/a/b/c')
124 def testInit(self):
125 """Test init."""
126 d = self.cov_dir
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': {}})
134 def testGetTreeEmpty(self):
135 """Test getting empty tree."""
136 d = self.cov_dir
137 self.assertEqual(d.GetTree(), 'c/')
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')
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/')
165 class TestCoverage(unittest.TestCase):
166 """Tests for croc.Coverage."""
168 def MockWalk(self, src_dir):
169 """Mock for os.walk().
171 Args:
172 src_dir: Source directory to walk.
174 Returns:
175 A list of (dirpath, dirnames, filenames) tuples.
177 self.mock_walk_calls.append(src_dir)
178 return self.mock_walk_return
180 def MockScanFile(self, filename, language):
181 """Mock for croc_scan.ScanFile().
183 Args:
184 filename: Path to file to scan.
185 language: Language for file.
187 Returns:
188 A list of executable lines.
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']
196 def setUp(self):
197 """Per-test setup."""
199 # Empty coverage object
200 self.cov = croc.Coverage()
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
210 # Data for MockWalk()
211 self.mock_walk_calls = []
212 self.mock_walk_return = []
214 # Data for MockScanFile()
215 self.mock_scan_calls = []
216 self.mock_scan_return = {'default': [1]}
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, [])
226 def testAddRoot(self):
227 """Test AddRoot() and CleanupFilename()."""
228 c = self.cov
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/')
239 # Backslash to forward slash
240 self.assertEqual(c.CleanupFilename('a\\b\\c'), 'a/b/c')
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')))
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')
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')
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')
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')
274 # Cannot specify a blank alt_name
275 self.assertRaises(ValueError, c.AddRoot, 'some_dir', '')
277 def testAddRule(self):
278 """Test AddRule() and ClassifyFile()."""
279 c = self.cov
281 # With only the default rule, nothing gets kept
282 self.assertEqual(c.ClassifyFile('_/src/'), {})
283 self.assertEqual(c.ClassifyFile('_/src/a.c'), {})
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'})
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'})
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)
323 def testGetCoveredFile(self):
324 """Test GetCoveredFile()."""
325 c = self.cov_minimal
327 # Not currently any covered files
328 self.assertEqual(c.GetCoveredFile('_/a.c'), None)
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##'})
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)
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)
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())
351 def testRemoveCoveredFile(self):
352 """Test RemoveCoveredFile()."""
353 # TODO: TEST ME!
355 def testParseLcov(self):
356 """Test ParseLcovData()."""
357 c = self.cov_minimal
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',
388 # We should know about three files
389 self.assertEqual(sorted(c.files), ['_/a.c', '_/b.c', '_/c.c'])
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,
402 self.assertEqual(a_c.in_lcov, True)
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,
413 self.assertEqual(b_c.in_lcov, True)
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,
424 self.assertEqual(c_c.in_lcov, True)
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...)
431 def testGetStat(self):
432 """Test GetStat() and PrintStat()."""
433 c = self.cov
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',
442 'tests': {
443 'count_a': 2,
444 'count_b': 5,
445 'baz': 'bob',
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')
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=''), '')
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')
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 -')
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")')
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(), ("""\
491 GetStat('count_b') = 4
492 Count A = 00010
493 GetStat('count_a') = 2
494 GetStat('nosuch') = 42
495 """))
496 f.close()
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')
504 def testAddConfig(self):
505 """Test AddConfig()."""
506 c = self.cov
507 lcov_queue = []
508 addfiles_queue = []
510 c.AddConfig("""{
511 'roots' : [
512 {'root' : '/foo'},
513 {'root' : '/bar', 'altname' : 'BAR'},
515 'rules' : [
516 {'regexp' : '^_/', 'group' : 'apple'},
517 {'regexp' : 're2', 'include' : 1, 'language' : 'elvish'},
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'},
525 'extra_key' : 'is ignored',
526 }""", lcov_queue=lcov_queue, addfiles_queue=addfiles_queue)
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'},
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'}],
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
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, {})
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
559 c.AddRoot('_/subdir', 'SUBDIR')
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, {})
569 def testAddFilesNonEmpty(self):
570 """Test AddFiles() where files are returned."""
572 c = self.cov_minimal
573 c.add_files_walk = self.MockWalk
574 c.scan_file = self.MockScanFile
576 # Add a rule to exclude a subdir
577 c.AddRule('^_/proj1/excluded/', include=0)
579 # Add a rule to exclude adding some fiels
580 c.AddRule('.*noscan.c$', add_if_missing=0)
582 # Set data for mock walk and scan
583 self.mock_walk_return = [
585 '/src/proj1',
586 ['excluded', 'subdir'],
587 ['a.c', 'no.f', 'yes.c', 'noexe.c', 'bob_noscan.c'],
590 '/src/proj1/subdir',
592 ['cherry.c'],
596 # Add a file with no executable lines; it should be scanned but not added
597 self.mock_scan_return['/src/proj1/noexe.c'] = []
599 c.AddFiles('/src/proj1')
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'],
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'])
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'])
620 def testEmptyTreeStats(self):
621 """Make sure we don't choke when absolutely nothing happened.
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 }})
635 def testUpdateTreeStats(self):
636 """Test UpdateTreeStats()."""
638 c = self.cov_minimal
639 c.AddRule('.*_test', group='test')
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',
656 c.UpdateTreeStats()
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,
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,
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,
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,
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,
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,
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,
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,
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,
750 # TODO: test: less important, since these are thin wrappers around other
751 # tested methods.
752 # ParseConfig()
753 # ParseLcovFile()
754 # PrintTree()
757 if __name__ == '__main__':
758 unittest.main()