-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCoverStoryCoverageData.m
More file actions
644 lines (584 loc) · 22.4 KB
/
Copy pathCoverStoryCoverageData.m
File metadata and controls
644 lines (584 loc) · 22.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
//
// CoverStoryCoverageData.m
// CoverStory
//
// Created by dmaclach on 12/24/06.
// Copyright 2006-2008 Google Inc.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy
// of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
//
#import "CoverStoryCoverageData.h"
#import "GTMRegex.h"
// helper for building the string to make sure rounding doesn't get us
static float codeCoverage(NSInteger codeLines, NSInteger hitCodeLines,
NSString **outCoverageString) {
float coverage = 0.0f;
if (codeLines > 0) {
coverage = (float)hitCodeLines/(float)codeLines * 100.0f;
}
if (outCoverageString) {
*outCoverageString = [NSString stringWithFormat:@"%.1f", coverage];
// make sure we never round to 100% if it's not 100%
if ([*outCoverageString isEqual:@"100.0"]) {
if (hitCodeLines == codeLines) {
*outCoverageString = @"100";
} else {
*outCoverageString = @"99.9";
}
}
}
return coverage;
}
@implementation NSEnumerator (CodeCoverage)
- (void)coverageTotalLines:(NSInteger *)outTotal
codeLines:(NSInteger *)outCode
hitCodeLines:(NSInteger *)outHitCode
nonFeasibleLines:(NSInteger *)outNonFeasible
coverageString:(NSString **)outCoverageString
coverage:(float *)outCoverage {
// collect the data
NSInteger sumTotal = 0;
NSInteger sumCode = 0;
NSInteger sumHitCode = 0;
NSInteger sumNonFeasible = 0;
id<CoverStoryLineCoverageProtocol> data;
while ((data = [self nextObject])) {
NSInteger localTotal = 0;
NSInteger localCode = 0;
NSInteger localHitCode = 0;
NSInteger localNonFeasible = 0;
[data coverageTotalLines:&localTotal
codeLines:&localCode
hitCodeLines:&localHitCode
nonFeasibleLines:&localNonFeasible
coverageString:NULL
coverage:NULL];
sumTotal += localTotal;
sumCode += localCode;
sumHitCode += localHitCode;
sumNonFeasible += localNonFeasible;
}
if (outTotal) {
*outTotal = sumTotal;
}
if (outCode) {
*outCode = sumCode;
}
if (outHitCode) {
*outHitCode = sumHitCode;
}
if (outNonFeasible) {
*outNonFeasible = sumNonFeasible;
}
if (outCoverageString || outCoverage) {
float coverage = codeCoverage(sumCode, sumHitCode, outCoverageString);
if (outCoverage) {
*outCoverage = coverage;
}
}
}
@end
@interface CoverStoryCoverageFileData ()
@property (readwrite, nonatomic, assign) NSInteger hitLines;
@property (readwrite, nonatomic, assign) NSInteger codeLines;
@property (readwrite, nonatomic, assign) NSInteger nonfeasible;
- (void)updateCounts;
- (NSArray *)queuedWarnings;
@end
@interface CoverStoryCoverageSet ()
@property (readonly, nonatomic, retain) NSMutableArray *fileDatas;
@end
@interface CoverStoryCoverageFileData (ScriptingMethods)
- (NSScriptObjectSpecifier *)objectSpecifierForLineData:(CoverStoryCoverageLineData*)data;
@end
@interface CoverStoryCoverageLineData ()
@property (readwrite, nonatomic, assign) NSInteger hitCount;
@end
@interface CoverStoryCoverageLineData (ScriptingMethods)
@end
@implementation CoverStoryCoverageFileData
@synthesize document = document_;
@synthesize sourcePath = sourcePath_;
@synthesize lines = lines_;
@synthesize hitLines = hitLines_;
@synthesize codeLines = codeLines_;
@synthesize nonfeasible = nonfeasible_;
@dynamic coverage;
+ (id)coverageFileDataFromPath:(NSString *)path
document:(CoverStoryDocument *)document
messageReceiver:(id<CoverStoryCoverageProcessingProtocol>)receiver {
return [[[self alloc] initWithPath:path
document:document
messageReceiver:receiver] autorelease];
}
- (id)initWithPath:(NSString *)path
document:(CoverStoryDocument *)document
messageReceiver:(id<CoverStoryCoverageProcessingProtocol>)receiver {
if ((self = [super init])) {
document_ = document;
lines_ = [[NSMutableArray alloc] init];
// The dirty secret: we queue up warnings and don't report them in realtime.
// Why? if we report them now, then if the directory with multiple arches
// we'll send the warning for each arch, and if the file occures in more
// then one set of gcda/gcno (say for headers w/ inlines), the we'll report
// each time we read that header. So instead we queue them, and don't
// send them over to the receiver until it's added to a set, and only if
// it's new, this way we're sure we only send them once.
warnings_ = [[NSMutableArray alloc] init];
// Scan in our data and create up out CoverStoryCoverageLineData objects.
// TODO(dmaclach): make this routine a little more "error tolerant"
// Let NSString try to handle the encoding when opening
NSStringEncoding encoding;
NSError *error;
NSString *string = [NSString stringWithContentsOfFile:path
usedEncoding:&encoding
error:&error];
if (!string) {
// Sadly, NSString doesn't detect/handle NSMacOSRomanStringEncoding very
// well, so we have to manually try Roman. math.h in the system headers
// is in MacRoman easily causes us errors w/o this extra code.
// (we don't care about the error here, we'll just report the parent
// error)
string = [NSString stringWithContentsOfFile:path
encoding:NSMacOSRomanStringEncoding
error:NULL];
}
if (!string) {
[receiver coverageErrorForPath:path message:@"failed to open file %@", error];
[self release];
self = nil;
} else {
NSCharacterSet *linefeeds = [NSCharacterSet characterSetWithCharactersInString:@"\n\r"];
GTMRegex *nfLineRegex = [GTMRegex regexWithPattern:@"//[[:blank:]]*COV_NF_LINE"];
GTMRegex *nfRangeStartRegex = [GTMRegex regexWithPattern:@"//[[:blank:]]*COV_NF_START"];
GTMRegex *nfRangeEndRegex = [GTMRegex regexWithPattern:@"//[[:blank:]]*COV_NF_END"];
BOOL inNonFeasibleRange = NO;
NSScanner *scanner = [NSScanner scannerWithString:string];
[scanner setCharactersToBeSkipped:nil];
while (![scanner isAtEnd]) {
NSString *segment;
// scan in hit count
BOOL goodScan = [scanner scanUpToString:@":" intoString:&segment];
[scanner setScanLocation:[scanner scanLocation] + 1];
NSInteger hitCount = 0;
if (goodScan) {
hitCount = [segment intValue];
if (hitCount == 0) {
if ([segment characterAtIndex:[segment length] - 1] != '#') {
hitCount = kCoverStoryNotExecutedMarker;
}
}
}
// scan in line number
goodScan = [scanner scanUpToString:@":" intoString:&segment];
if (goodScan) {
[scanner setScanLocation:[scanner scanLocation] + 1];
// scan in the code line
goodScan = [scanner scanUpToCharactersFromSet:linefeeds
intoString:&segment];
}
if (!goodScan) {
segment = @"";
}
// skip over the end of line marker (CR, LF, CRLF), and it's possible
// on the end of the file that there is none of them.
[scanner scanCharactersFromSet:linefeeds intoString:NULL];
// handle the non feasible markers
if (inNonFeasibleRange) {
if (hitCount > 0) {
NSString *warning =
[NSString stringWithFormat:@"Line %lu is in a Non Feasible block,"
" but was executed.",
(unsigned long)[lines_ count] - 4];
[warnings_ addObject:warning];
}
// if the line was gonna count, mark it as non feasible (we only mark
// the lines that would have counted so the total number of non
// feasible lines isn't too high (otherwise comment lines, blank
// lines, etc. count as non feasible).
if (hitCount != kCoverStoryNotExecutedMarker) {
hitCount = kCoverStoryNonFeasibleMarker;
}
// if it has the end marker, clear our state
if ([nfRangeEndRegex matchesSubStringInString:segment]) {
inNonFeasibleRange = NO;
}
} else {
// if it matches the line marker, don't count it
if ([nfLineRegex matchesSubStringInString:segment]) {
if (hitCount > 0) {
NSString *warning =
[NSString stringWithFormat:@"Line %lu is marked as a Non"
" Feasible line, but was executed.",
(unsigned long)[lines_ count] - 4];
[warnings_ addObject:warning];
}
hitCount = kCoverStoryNonFeasibleMarker;
}
// if it matches the start marker, don't count it and set state
else if ([nfRangeStartRegex matchesSubStringInString:segment]) {
if (hitCount > 0) {
NSString *warning =
[NSString stringWithFormat:@"Line %lu is in a Non Feasible"
" block, but was executed.",
(unsigned long)[lines_ count] - 4];
[warnings_ addObject:warning];
}
// if the line was gonna count, mark it as non feasible (we only mark
// the lines that would have counted so the total number of non
// feasible lines isn't too high (otherwise comment lines, blank
// lines, etc. count as non feasible).
if (hitCount != kCoverStoryNotExecutedMarker) {
hitCount = kCoverStoryNonFeasibleMarker;
}
inNonFeasibleRange = YES;
}
}
[lines_ addObject:[CoverStoryCoverageLineData coverageLineDataWithLine:segment
hitCount:hitCount
coverageFile:self]];
}
// The first five lines are not data we want to show to the user
if ([lines_ count] > 5) {
// The first line contains the path to our source. Most projects use
// paths relative to the project, so just incase they walk into a
// neighbor directory, resolve them.
NSString *srcPath = [[[lines_ objectAtIndex:0] line] substringFromIndex:7];
sourcePath_ = [[srcPath stringByStandardizingPath] retain];
[lines_ removeObjectsInRange:NSMakeRange(0,5)];
// get out counts
[self updateCounts];
} else {
[receiver coverageErrorForPath:path message:@"illegal file format"];
// something bad
[self release];
self = nil;
}
}
}
return self;
}
- (void)dealloc {
[lines_ release];
[sourcePath_ release];
[warnings_ release];
[super dealloc];
}
- (BOOL)isEqual:(id)object {
BOOL equal = NO;
if ([object isKindOfClass:[self class]]) {
equal = [[object sourcePath] isEqual:[self sourcePath]];
}
return equal;
}
- (NSUInteger)hash {
return [sourcePath_ hash];
}
- (void)updateCounts {
NSInteger hitLines = 0;
NSInteger codeLines = 0;
NSInteger nonfeasible = 0;
for (CoverStoryCoverageLineData *dataPoint in lines_) {
NSInteger hitCount = [dataPoint hitCount];
switch (hitCount) {
case kCoverStoryNonFeasibleMarker:
++nonfeasible;
break;
case kCoverStoryNotExecutedMarker:
// doesn't count;
break;
case 0:
// line of code that wasn't hit
++codeLines;
break;
default:
// line of code w/ hits
++hitLines;
++codeLines;
break;
}
}
[self setCodeLines:codeLines];
[self setHitLines:hitLines];
[self setNonfeasible:nonfeasible];
}
- (NSArray *)queuedWarnings {
return warnings_;
}
- (NSNumber *)coverage {
float result = 0.0f;
[self coverageTotalLines:NULL
codeLines:NULL
hitCodeLines:NULL
nonFeasibleLines:NULL
coverageString:NULL
coverage:&result];
return [NSNumber numberWithFloat:result];
}
- (void)coverageTotalLines:(NSInteger *)outTotal
codeLines:(NSInteger *)outCode
hitCodeLines:(NSInteger *)outHitCode
nonFeasibleLines:(NSInteger *)outNonFeasible
coverageString:(NSString **)outCoverageString
coverage:(float *)outCoverage {
if (outTotal) {
*outTotal = [lines_ count];
}
if (outCode) {
*outCode = [self codeLines];
}
if (outHitCode) {
*outHitCode = [self hitLines];
}
if (outNonFeasible) {
*outNonFeasible = [self nonfeasible];
}
if (outCoverageString || outCoverage) {
float coverage = codeCoverage(codeLines_, hitLines_, outCoverageString);
if (outCoverage) {
*outCoverage = coverage;
}
}
}
- (BOOL)addFileData:(CoverStoryCoverageFileData *)fileData
messageReceiver:(id<CoverStoryCoverageProcessingProtocol>)receiver {
// must be for the same paths
if (![[fileData sourcePath] isEqual:sourcePath_]) {
if (receiver) {
[receiver coverageErrorForPath:sourcePath_
message:@"coverage is for different source path:%@",
[fileData sourcePath]];
}
return NO;
}
// make sure the source file lines actually match
NSArray *newLines = [fileData lines];
if ([newLines count] != [lines_ count]) {
if (receiver) {
[receiver coverageErrorForPath:sourcePath_
message:@"coverage source (%@) has different line count '%d' vs '%d'",
[fileData sourcePath], [newLines count], [lines_ count]];
}
return NO;
}
for (NSUInteger x = 0, max = [newLines count] ; x < max ; ++x ) {
CoverStoryCoverageLineData *lineNew = [newLines objectAtIndex:x];
CoverStoryCoverageLineData *lineMe = [lines_ objectAtIndex:x];
// string match the lines (since the Non Feasible support is via comments,
// this makes sure they also match)
if (![[lineNew line] isEqual:[lineMe line]]) {
if (receiver) {
[receiver coverageErrorForPath:sourcePath_
message:@"coverage source (%@) line %d doesn't match, '%@' vs '%@'",
[fileData sourcePath], x, [lineNew line], [lineMe line]];
}
return NO;
}
// we don't check if the lines weren't hit between the two because we could
// be processing big and little endian runs, and w/ ifdefs one set of lines
// would be ignored in one run, but not in the other.
}
// spin though once more summing the counts
for (NSUInteger x = 0, max = [newLines count] ; x < max ; ++x ) {
CoverStoryCoverageLineData *lineNew = [newLines objectAtIndex:x];
CoverStoryCoverageLineData *lineMe = [lines_ objectAtIndex:x];
[lineMe addHits:[lineNew hitCount]];
}
// then add the number
[self updateCounts];
return YES;
}
- (NSString *)description {
return [NSString stringWithFormat:
@"%@: %d total lines, %d lines non-feasible, "
@"%d lines of code, %d lines hit",
sourcePath_, [lines_ count], nonfeasible_, codeLines_, hitLines_];
}
@end
@implementation CoverStoryCoverageSet
@synthesize fileDatas = fileDatas_;
- (id)init {
if ((self = [super init])) {
fileDatas_ = [[NSMutableArray alloc] init];
}
return self;
}
- (void)dealloc {
[fileDatas_ release];
[super dealloc];
}
- (BOOL)addFileData:(CoverStoryCoverageFileData *)fileData
messageReceiver:(id<CoverStoryCoverageProcessingProtocol>)receiver {
BOOL wasGood = NO;
NSUInteger idx = [fileDatas_ indexOfObject:fileData];
if (idx != NSNotFound) {
CoverStoryCoverageFileData *currentData = [fileDatas_ objectAtIndex:idx];
// we need to merge them
// (this is needed for headers w/ inlines where if you process >1 gcno/gcda
// then you could get that header reported >1 time)
wasGood = [currentData addFileData:fileData messageReceiver:receiver];
} else {
// it's new, save it
NSUInteger index = [fileDatas_ count];
[self willChange:NSKeyValueChangeInsertion
valuesAtIndexes:[NSIndexSet indexSetWithIndex:index]
forKey:@"fileDatas"];
[fileDatas_ insertObject:fileData atIndex:index];
[self didChange:NSKeyValueChangeInsertion
valuesAtIndexes:[NSIndexSet indexSetWithIndex:index]
forKey:@"fileDatas"];
// send the queued up warnings since this is the first time we've seen the
// file.
// TODO: this is really a hack, we would be better (since these currently
// are line specific) is to extend the structure to allow warnings to be
// hung on the line data along w/ the hit counts. Then w/in the UI indicate
// how many warnings are on a file in the files list, and in the source
// display show the warnings inline (sorta like Xcode 3). The other option
// would be to keep this basic structure, but be able to relay info w/ the
// warning so our warning/error ui could take clicks and open to the right
// file/line so the user can take action on the message.
for (NSString *warning in [fileData queuedWarnings]) {
[receiver coverageWarningForPath:[fileData sourcePath]
message:@"%@", warning];
}
wasGood = YES;
}
return wasGood;
}
- (void)coverageTotalLines:(NSInteger *)outTotal
codeLines:(NSInteger *)outCode
hitCodeLines:(NSInteger *)outHitCode
nonFeasibleLines:(NSInteger *)outNonFeasible
coverageString:(NSString **)outCoverageString
coverage:(float *)outCoverage {
// use the enum helper
NSEnumerator *enumerator = [fileDatas_ objectEnumerator];
[enumerator coverageTotalLines:outTotal
codeLines:outCode
hitCodeLines:outHitCode
nonFeasibleLines:outNonFeasible
coverageString:outCoverageString
coverage:outCoverage];
}
- (void)removeAllData {
NSRange fullRange = NSMakeRange(0, [fileDatas_ count]);
NSIndexSet *fullSet = [NSIndexSet indexSetWithIndexesInRange:fullRange];
[self willChange:NSKeyValueChangeRemoval
valuesAtIndexes:fullSet
forKey:@"fileDatas"];
[fileDatas_ removeAllObjects];
[self didChange:NSKeyValueChangeRemoval
valuesAtIndexes:fullSet
forKey:@"fileDatas"];
}
- (NSString *)description {
return [NSString stringWithFormat:@"%@ <%p>: %u items in set",
[self class], self, [fileDatas_ count]];
}
@end
@implementation CoverStoryCoverageLineData
@synthesize hitCount = hitCount_;
@synthesize line = line_;
@synthesize coverageFile = coverageFile_;
+ (id)coverageLineDataWithLine:(NSString*)line
hitCount:(NSInteger)hitCount
coverageFile:(CoverStoryCoverageFileData *)coverageFile {
return [[[self alloc] initWithLine:line
hitCount:hitCount
coverageFile:coverageFile] autorelease];
}
- (id)initWithLine:(NSString*)line
hitCount:(NSInteger)hitCount
coverageFile:(CoverStoryCoverageFileData *)coverageFile {
if ((self = [super init])) {
hitCount_ = hitCount;
line_ = [line copy];
coverageFile_ = coverageFile;
}
return self;
}
- (void)dealloc {
[line_ release];
[super dealloc];
}
- (void)addHits:(NSInteger)newHits {
// we could be processing big and little endian runs, and w/ ifdefs one set of
// lines would be ignored in one run, but not in the other. so... if we were
// a not hit line, we just take the new hits, otherwise we add any real hits
// to our count.
if (self.hitCount == kCoverStoryNotExecutedMarker) {
self.hitCount = newHits;
} else if (newHits > 0) {
NSAssert1(self.hitCount >= 0,
@"how was it not feasible in only one version? (hitCount_ = %d)",
hitCount_);
self.hitCount += newHits;
}
}
- (NSString*)description {
return [NSString stringWithFormat:@"%d %@", self.hitCount, self.line];
}
@end
@implementation CoverStoryCoverageFileData (ScriptingMethods)
- (NSScriptObjectSpecifier *)objectSpecifier {
NSScriptObjectSpecifier *containerSpec = [[self document] objectSpecifier];
NSScriptClassDescription *containterClassDesc
= [containerSpec keyClassDescription];
NSString *name = [self sourcePath];
return [[[NSNameSpecifier alloc] initWithContainerClassDescription:containterClassDesc
containerSpecifier:containerSpec
key:@"fileDatas"
name:name]
autorelease];
}
- (NSScriptObjectSpecifier *)objectSpecifierForLineData:(CoverStoryCoverageLineData*)data {
NSScriptObjectSpecifier *containerSpec = [self objectSpecifier];
NSScriptClassDescription *containterClassDesc
= [containerSpec keyClassDescription];
NSInteger index = [[self lines] indexOfObject:data];
return [[[NSIndexSpecifier alloc] initWithContainerClassDescription:containterClassDesc
containerSpecifier:containerSpec
key:@"lines"
index:index]
autorelease];
}
@end
@implementation CoverStoryCoverageLineData (ScriptingMethods)
- (NSScriptObjectSpecifier *)objectSpecifier {
return [[self coverageFile] objectSpecifierForLineData:self];
}
// For scripting, we don't want to return any negative hit counts
- (NSInteger)adjustedHitCount {
NSInteger hitCount = [self hitCount];
if (hitCount < 0) hitCount = 0;
return hitCount;
}
- (NSString *)coverageType {
NSInteger hitCount = [self hitCount];
NSString *type = nil;
switch (hitCount) {
case 0:
type = @"missed";
break;
case kCoverStoryNotExecutedMarker:
type = @"non-executable";
break;
case kCoverStoryNonFeasibleMarker:
type = @"non-feasible";
break;
default:
type = @"executed";
break;
}
return type;
}
@end