├── resources ├── scr1.png ├── scr2.png ├── scr3.png ├── header.png ├── example_cropped.gif └── example │ ├── resources │ ├── Screenshot_47A75F84-354F-404E-B959-AD58198B938F.png │ ├── Screenshot_A343C7F1-A5DA-49EE-B3FB-3F2A61188752.png │ └── Screenshot_C55265E9-4AD5-4E6C-B740-AB146893BD23.png │ └── result.html ├── .github └── CODEOWNERS ├── XCSummary ├── Templates │ ├── Template.html │ ├── TestCaseTemplate.html │ ├── TemplateHeader.h │ ├── TestCaseTemplateFailed.html │ ├── ActivityTemplateWithoutImage.html │ ├── ActivityTemplateWithImage.html │ ├── SummaryTemplate.html │ ├── CMHTMLReportBuilder.h │ ├── TemplateGeneratedHeader.h │ └── CMHTMLReportBuilder.m ├── Models │ ├── CMAttachment.h │ ├── CMEntity.h │ ├── CMEntity.m │ ├── CMAttachment.m │ ├── CMActivitySummary.h │ ├── CMTestableSummary.h │ ├── CMTest.h │ ├── CMActivitySummary.m │ ├── CMTestableSummary.m │ └── CMTest.m ├── Parser │ ├── CMTestSummaryParser.h │ └── CMTestSummaryParser.m ├── Additions │ ├── NSArrayAdditions.h │ └── NSArrayAdditions.m └── main.m ├── XCSummary.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── project.pbxproj ├── .infrastructure └── azp │ └── security-pipeline.yml ├── .gitignore ├── LICENSE └── README.md /resources/scr1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacPaw/xcsummary/HEAD/resources/scr1.png -------------------------------------------------------------------------------- /resources/scr2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacPaw/xcsummary/HEAD/resources/scr2.png -------------------------------------------------------------------------------- /resources/scr3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacPaw/xcsummary/HEAD/resources/scr3.png -------------------------------------------------------------------------------- /resources/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacPaw/xcsummary/HEAD/resources/header.png -------------------------------------------------------------------------------- /resources/example_cropped.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacPaw/xcsummary/HEAD/resources/example_cropped.gif -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Security checks owner is Security team 2 | .infrastructure/azp/security-pipeline.yml @MacPaw/security -------------------------------------------------------------------------------- /resources/example/resources/Screenshot_47A75F84-354F-404E-B959-AD58198B938F.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacPaw/xcsummary/HEAD/resources/example/resources/Screenshot_47A75F84-354F-404E-B959-AD58198B938F.png -------------------------------------------------------------------------------- /resources/example/resources/Screenshot_A343C7F1-A5DA-49EE-B3FB-3F2A61188752.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacPaw/xcsummary/HEAD/resources/example/resources/Screenshot_A343C7F1-A5DA-49EE-B3FB-3F2A61188752.png -------------------------------------------------------------------------------- /resources/example/resources/Screenshot_C55265E9-4AD5-4E6C-B740-AB146893BD23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacPaw/xcsummary/HEAD/resources/example/resources/Screenshot_C55265E9-4AD5-4E6C-B740-AB146893BD23.png -------------------------------------------------------------------------------- /XCSummary/Templates/Template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | %@ 9 | 10 | 11 | -------------------------------------------------------------------------------- /XCSummary.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /XCSummary/Models/CMAttachment.h: -------------------------------------------------------------------------------- 1 | // 2 | // CMAttachment.h 3 | // XCSummary 4 | // 5 | // Created by Volodymyr Borodavchuk on 10/3/18. 6 | // Copyright © 2018 MacPaw inc. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "CMEntity.h" 11 | 12 | @interface CMAttachment : CMEntity 13 | 14 | @property (nonatomic, copy, readonly) NSString *filename; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /XCSummary/Models/CMEntity.h: -------------------------------------------------------------------------------- 1 | // 2 | // CMEntity.h 3 | // xcsummary 4 | // 5 | // Created by Kryvoblotskyi Sergii on 12/12/16. 6 | // Copyright © 2016 MacPaw inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface CMEntity : NSObject 14 | 15 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /XCSummary/Templates/TestCaseTemplate.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 |
%@
(%2.2f sec)
8 |
9 | 10 |
11 | -------------------------------------------------------------------------------- /XCSummary/Templates/TemplateHeader.h: -------------------------------------------------------------------------------- 1 | #define TestCaseTemplate @"XCTestCaseTemplateXC" 2 | 3 | 4 | #define TestCaseTemplateFailed @"XCTestCaseTemplateFailedXC" 5 | 6 | 7 | #define ActivityTemplateWithoutImage @"XCActivityTemplateWithoutImageXC" 8 | 9 | 10 | #define ActivityTemplateWithImage @"XCActivityTemplateWithImageXC" 11 | 12 | 13 | #define Template @"XCTemplateXC" 14 | 15 | 16 | #define SummaryTemplate @"XCSummaryTemplateXC" 17 | -------------------------------------------------------------------------------- /XCSummary/Templates/TestCaseTemplateFailed.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 |
%@
(%2.2f sec)
8 |
9 | 10 |
11 | -------------------------------------------------------------------------------- /XCSummary/Models/CMEntity.m: -------------------------------------------------------------------------------- 1 | // 2 | // CMEntity.m 3 | // xcsummary 4 | // 5 | // Created by Kryvoblotskyi Sergii on 12/12/16. 6 | // Copyright © 2016 MacPaw inc. All rights reserved. 7 | // 8 | 9 | #import "CMEntity.h" 10 | 11 | @implementation CMEntity 12 | 13 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary 14 | { 15 | self = [super init]; 16 | if (self) 17 | { 18 | //No-op 19 | } 20 | return self; 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /XCSummary/Templates/ActivityTemplateWithoutImage.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 |
%@
(%2.2f sec)
8 |
9 | 10 |
11 | -------------------------------------------------------------------------------- /XCSummary/Templates/ActivityTemplateWithImage.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 |
%@
(%2.2f sec)
9 |
10 | 11 |
12 | -------------------------------------------------------------------------------- /.infrastructure/azp/security-pipeline.yml: -------------------------------------------------------------------------------- 1 | name: security-scan 2 | trigger: none 3 | pr: 4 | - master 5 | 6 | variables: 7 | - group: vault 8 | - group: github 9 | 10 | resources: 11 | repositories: 12 | - repository: sec-azp-pipelines 13 | type: github 14 | name: MacPaw/sec-azp-pipelines 15 | ref: refs/heads/main 16 | endpoint: MacPaw 17 | 18 | stages: 19 | - stage: main 20 | pool: 21 | name: default-sre 22 | displayName: scan 23 | jobs: 24 | - template: azp/templates/base-security-checks.yaml@sec-azp-pipelines 25 | -------------------------------------------------------------------------------- /XCSummary/Models/CMAttachment.m: -------------------------------------------------------------------------------- 1 | // 2 | // CMAttachment.m 3 | // xcsummary 4 | // 5 | // Created by Volodymyr Borodavchuk on 10/3/18. 6 | // Copyright © 2018 MacPaw inc. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "CMAttachment.h" 11 | #import "NSArrayAdditions.h" 12 | 13 | @implementation CMAttachment 14 | 15 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary 16 | { 17 | self = [super init]; 18 | if (self) 19 | { 20 | _filename = dictionary[@"Filename"]; 21 | } 22 | return self; 23 | } 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | DerivedData/ 7 | 8 | ## Various settings 9 | *.pbxuser 10 | !default.pbxuser 11 | *.mode1v3 12 | !default.mode1v3 13 | *.mode2v3 14 | !default.mode2v3 15 | *.perspectivev3 16 | !default.perspectivev3 17 | xcuserdata/ 18 | 19 | ## Other 20 | *.moved-aside 21 | *.xcuserstate 22 | 23 | ## Obj-C/Swift specific 24 | *.hmap 25 | *.ipa 26 | *.dSYM.zip 27 | *.dSYM 28 | 29 | /XCSummary/Templates/TemplateGeneratedHeader.h 30 | /build/XCSummary.build 31 | -------------------------------------------------------------------------------- /XCSummary/Parser/CMTestSummaryParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // CMTestSummaryParser.h 3 | // xcsummary 4 | // 5 | // Created by Kryvoblotskyi Sergii on 12/12/16. 6 | // Copyright © 2016 MacPaw inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @class CMTestableSummary; 14 | @interface CMTestSummaryParser : NSObject 15 | 16 | /** 17 | @param path NSString * The path of the 'summary'.plist. Commonly located at DerivedData/Project/Logs/Test 18 | @return CMTestSummaryParser 19 | */ 20 | - (instancetype)initWithPath:(NSString *)path; 21 | 22 | @property (nonatomic, copy, readonly) NSString *path; 23 | 24 | /** 25 | @return NSArray * an array of the test summaries. 26 | */ 27 | - (NSArray *)testSummaries; 28 | 29 | @end 30 | 31 | NS_ASSUME_NONNULL_END 32 | -------------------------------------------------------------------------------- /XCSummary/Models/CMActivitySummary.h: -------------------------------------------------------------------------------- 1 | // 2 | // CMActivitySummary.h 3 | // xcsummary 4 | // 5 | // Created by Kryvoblotskyi Sergii on 12/12/16. 6 | // Copyright © 2016 MacPaw inc. All rights reserved. 7 | // 8 | 9 | #import "CMEntity.h" 10 | #import "CMAttachment.h" 11 | 12 | @interface CMActivitySummary : CMEntity 13 | 14 | @property (nonatomic, copy, readonly) NSString *title; 15 | @property (nonatomic, strong, readonly) NSUUID *uuid; 16 | 17 | @property (nonatomic, readonly) NSTimeInterval startTimeInterval; 18 | @property (nonatomic, readonly) NSTimeInterval finishTimeInterval; 19 | 20 | @property (nonatomic, readonly) BOOL hasElementsOfInterest; 21 | @property (nonatomic, readonly) BOOL hasScreenshotData; 22 | 23 | @property (nonatomic, strong, readonly) NSArray *subActivities; 24 | @property (nonatomic, strong, readonly) NSArray *attachments; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /XCSummary/Models/CMTestableSummary.h: -------------------------------------------------------------------------------- 1 | // 2 | // CMTestableSummary.h 3 | // xcsummary 4 | // 5 | // Created by Kryvoblotskyi Sergii on 12/12/16. 6 | // Copyright © 2016 MacPaw inc. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "CMEntity.h" 11 | 12 | @class CMTest; 13 | @interface CMTestableSummary : CMEntity 14 | 15 | @property (nonatomic, copy, readonly) NSString *projectPath; 16 | @property (nonatomic, copy, readonly) NSString *targetName; 17 | @property (nonatomic, copy, readonly) NSString *testName; 18 | @property (nonatomic, copy, readonly) NSString *testObjectClass; 19 | 20 | @property (nonatomic, readonly) NSUInteger numberOfSuccessfulTests; 21 | @property (nonatomic, readonly) NSUInteger numberOfFailedTests; 22 | @property (nonatomic, readonly) NSTimeInterval totalDuration; 23 | 24 | @property (nonatomic, strong, readonly) NSArray *tests; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /XCSummary/Templates/SummaryTemplate.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 16 | 17 |
Test Results: 7 |
8 | %d Tests 9 |
10 | (%@) 11 |
12 |
13 |
%d successful
14 |
%d failed
15 |
18 |
19 | 20 | 21 |
22 | -------------------------------------------------------------------------------- /XCSummary/Parser/CMTestSummaryParser.m: -------------------------------------------------------------------------------- 1 | // 2 | // CMTestSummaryParser.m 3 | // xcsummary 4 | // 5 | // Created by Kryvoblotskyi Sergii on 12/12/16. 6 | // Copyright © 2016 MacPaw inc. All rights reserved. 7 | // 8 | 9 | #import "CMTestSummaryParser.h" 10 | #import "NSArrayAdditions.h" 11 | #import "CMTestableSummary.h" 12 | 13 | @implementation CMTestSummaryParser 14 | 15 | - (instancetype)initWithPath:(NSString *)path 16 | { 17 | self = [super init]; 18 | if (self) 19 | { 20 | _path = path; 21 | } 22 | return self; 23 | } 24 | 25 | - (NSArray *)testSummaries 26 | { 27 | NSDictionary *summaryInfo = [NSDictionary dictionaryWithContentsOfFile:self.path]; 28 | NSArray *testSummaries = summaryInfo[@"TestableSummaries"]; 29 | return [testSummaries map:^id(NSDictionary *testSummary, NSUInteger index, BOOL *stop) { 30 | return [[CMTestableSummary alloc] initWithDictionary:testSummary]; 31 | }]; 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /XCSummary/Models/CMTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // CMTest.h 3 | // xcsummary 4 | // 5 | // Created by Kryvoblotskyi Sergii on 12/12/16. 6 | // Copyright © 2016 MacPaw inc. All rights reserved. 7 | // 8 | 9 | #import "CMEntity.h" 10 | 11 | typedef NS_ENUM(NSInteger, CMTestStatus) 12 | { 13 | CMTestStatusUnknown = 0, 14 | CMTestStatusSuccess = 1, 15 | CMTestStatusFailure = 2 16 | }; 17 | 18 | @class CMActivitySummary; 19 | @interface CMTest : CMEntity 20 | 21 | @property (nonatomic, strong, readonly) NSUUID *testSummaryGUID; 22 | @property (nonatomic, readonly) NSTimeInterval duration; 23 | @property (nonatomic, readonly) CMTestStatus status; 24 | @property (nonatomic, copy, readonly) NSString *testIdentifier; 25 | @property (nonatomic, copy, readonly) NSString *testName; 26 | @property (nonatomic, copy, readonly) NSString *testObjectClass; 27 | 28 | @property (nonatomic, strong, readonly) NSArray *subTests; 29 | @property (nonatomic, strong, readonly) NSArray *activities; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 MacPaw Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /XCSummary/Templates/CMHTMLReportBuilder.h: -------------------------------------------------------------------------------- 1 | // 2 | // CMHTMLReportBuilder.h 3 | // xcsummary 4 | // 5 | // Created by Kryvoblotskyi Sergii on 12/13/16. 6 | // Copyright © 2016 MacPaw inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class CMTestableSummary, CMTest; 12 | @interface CMHTMLReportBuilder : NSObject 13 | 14 | /** 15 | Designated initializer 16 | 17 | @param path NSString attachments path 18 | @param resultsPath NSString 19 | @param showSuccessTests BOOL 20 | @return CMHTMLReportBuilder 21 | */ 22 | - (instancetype)initWithAttachmentsPath:(NSString *)path 23 | resultsPath:(NSString *)resultsPath 24 | showSuccessTests:(BOOL)showSuccessTests; 25 | 26 | /** 27 | Appends summaries info as a header 28 | 29 | @param summaries NSArray * 30 | */ 31 | - (void)appendSummaries:(NSArray *)summaries; 32 | 33 | /** 34 | Appends tests to the html template 35 | 36 | @param tests NSArray 37 | @param indentation CGGloat 38 | */ 39 | - (void)appendTests:(NSArray *)tests indentation:(CGFloat)indentation; 40 | 41 | /** 42 | Builds a compilted html page 43 | 44 | @return NSString 45 | */ 46 | - (NSString *)build; 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /XCSummary/Additions/NSArrayAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray+Additions.h 3 | // xcsummary 4 | // 5 | // Created by Anton Mironov on 1/13/15. 6 | // Copyright (c) 2015 MacPaw. All rights reserved. 7 | // 8 | 9 | @import Foundation; 10 | 11 | @interface NSArray<__covariant ObjectType> (Extensions) 12 | 13 | - (NSMutableArray *)map:(id(^)(ObjectType object, NSUInteger index, BOOL *stop))mappingBlock; 14 | - (NSMutableArray *)flatMap:(NSArray *(^)(id object, NSUInteger index, BOOL *stop))mappingBlock; 15 | - (NSMutableArray *)filter:(BOOL(^)(ObjectType object, NSUInteger index, BOOL *stop))filteringBlock; 16 | 17 | @property (readonly) ObjectType singleObject; // non-observable 18 | @property (readonly) ObjectType singleObjectOrNil; // non-observable 19 | 20 | - (ObjectType)safeObjectAtIndex:(NSInteger)index; 21 | 22 | @end 23 | 24 | 25 | @interface NSMutableArray (Extensions) 26 | 27 | - (BOOL)safeAddObject:(ObjectType)object; 28 | - (void)filterIn:(BOOL(^)(ObjectType object, NSUInteger index, BOOL *stop))filteringBlock; 29 | 30 | - (NSArray *)takeObjectsAtIndexes:(NSIndexSet *)indexes; 31 | - (ObjectType)takeObjectAtIndex:(NSUInteger)index; 32 | - (ObjectType)takeLastObject; 33 | - (ObjectType)takeFirstObject; 34 | 35 | @end 36 | 37 | @interface NSArray (CAAdditions) 38 | 39 | - (instancetype)normalizedKeyTimes; 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /XCSummary/Models/CMActivitySummary.m: -------------------------------------------------------------------------------- 1 | // 2 | // CMActivitySummary.m 3 | // xcsummary 4 | // 5 | // Created by Kryvoblotskyi Sergii on 12/12/16. 6 | // Copyright © 2016 MacPaw inc. All rights reserved. 7 | // 8 | 9 | #import "CMActivitySummary.h" 10 | #import "NSArrayAdditions.h" 11 | 12 | @implementation CMActivitySummary 13 | 14 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary 15 | { 16 | self = [super init]; 17 | if (self) 18 | { 19 | NSString *uuidString = dictionary[@"UUID"]; 20 | NSArray *subActivitesInfo = dictionary[@"SubActivities"]; 21 | NSArray *attachmetnsInfo = dictionary[@"Attachments"]; 22 | _title = dictionary[@"Title"]; 23 | _uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; 24 | _startTimeInterval = [dictionary[@"StartTimeInterval"] doubleValue]; 25 | _finishTimeInterval = [dictionary[@"FinishTimeInterval"] doubleValue]; 26 | _hasScreenshotData = [dictionary[@"HasScreenshotData"] boolValue]; 27 | _hasElementsOfInterest = [dictionary[@"HasElementsOfInterest"] boolValue]; 28 | _subActivities = [subActivitesInfo map:^id(NSDictionary *activityInfo, NSUInteger index, BOOL *stop) { 29 | return [[CMActivitySummary alloc] initWithDictionary:activityInfo]; 30 | }]; 31 | _attachments = [attachmetnsInfo map:^id(NSDictionary *attachmetnsInfo, NSUInteger index, BOOL *stop) { 32 | return [[CMAttachment alloc] initWithDictionary:attachmetnsInfo]; 33 | }]; 34 | } 35 | return self; 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /XCSummary/Models/CMTestableSummary.m: -------------------------------------------------------------------------------- 1 | // 2 | // CMTestableSummary.m 3 | // xcsummary 4 | // 5 | // Created by Kryvoblotskyi Sergii on 12/12/16. 6 | // Copyright © 2016 MacPaw inc. All rights reserved. 7 | // 8 | 9 | #import "CMTestableSummary.h" 10 | #import "NSArrayAdditions.h" 11 | #import "CMTest.h" 12 | 13 | @implementation CMTestableSummary 14 | 15 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary 16 | { 17 | self = [super initWithDictionary:dictionary]; 18 | if (self) 19 | { 20 | NSArray *rawTests = dictionary[@"Tests"]; 21 | _projectPath = dictionary[@"ProjectPath"]; 22 | _targetName = dictionary[@"TargetName"]; 23 | _testName = dictionary[@"TestName"]; 24 | _testObjectClass = dictionary[@"TestObjectClass"]; 25 | _tests = [rawTests map:^id(NSDictionary *rawTest, NSUInteger index, BOOL *stop) { 26 | return [[CMTest alloc] initWithDictionary:rawTest]; 27 | }]; 28 | _numberOfSuccessfulTests = [self _countOfTests:self.tests withStatus:CMTestStatusSuccess]; 29 | _numberOfFailedTests = [self _countOfTests:self.tests withStatus:CMTestStatusFailure]; 30 | _totalDuration = [[self.tests valueForKeyPath:@"@sum.duration"] doubleValue]; 31 | } 32 | return self; 33 | } 34 | 35 | - (NSInteger)_countOfTests:(NSArray *)tests withStatus:(CMTestStatus)status 36 | { 37 | __block NSInteger result = 0; 38 | [tests enumerateObjectsUsingBlock:^(CMTest * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 39 | if (obj.status == status) { 40 | result++; 41 | } 42 | result += [self _countOfTests:obj.subTests withStatus:status]; 43 | }]; 44 | return result; 45 | } 46 | 47 | - (NSString *)description 48 | { 49 | return [NSString stringWithFormat:@"\n%@ %@ %@\n", [super description], self.testName, self.tests]; 50 | } 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /XCSummary/Models/CMTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // CMTest.m 3 | // xcsummary 4 | // 5 | // Created by Kryvoblotskyi Sergii on 12/12/16. 6 | // Copyright © 2016 MacPaw inc. All rights reserved. 7 | // 8 | 9 | #import "CMTest.h" 10 | #import "NSArrayAdditions.h" 11 | #import "CMActivitySummary.h" 12 | 13 | CMTestStatus CMTestStatusFromString(NSString * string); 14 | 15 | @implementation CMTest 16 | 17 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary 18 | { 19 | self = [super initWithDictionary:dictionary]; 20 | if (self) 21 | { 22 | NSArray *rawTests = dictionary[@"Subtests"]; 23 | NSString *status = dictionary[@"TestStatus"]; 24 | NSArray *activities = dictionary[@"ActivitySummaries"]; 25 | NSString *summaryGUIDString = dictionary[@"TestSummaryGUID"]; 26 | _testName = dictionary[@"TestName"]; 27 | _testIdentifier = dictionary[@"TestIdentifier"]; 28 | _testObjectClass = dictionary[@"TestObjectClass"]; 29 | _duration = [dictionary[@"Duration"] doubleValue]; 30 | _subTests = [rawTests map:^id(NSDictionary *rawTest, NSUInteger index, BOOL *stop) { 31 | return [[CMTest alloc] initWithDictionary:rawTest]; 32 | }]; 33 | _activities = [activities map:^id(NSDictionary *rawActivity, NSUInteger index, BOOL *stop) { 34 | return [[CMActivitySummary alloc] initWithDictionary:rawActivity]; 35 | }]; 36 | _status = CMTestStatusFromString(status); 37 | _testSummaryGUID = [[NSUUID alloc] initWithUUIDString:summaryGUIDString]; 38 | } 39 | return self; 40 | } 41 | 42 | - (NSString *)description 43 | { 44 | return [NSString stringWithFormat:@"\n%@ %@ %@\n", [super description], self.testName, self.subTests]; 45 | } 46 | 47 | @end 48 | 49 | CMTestStatus CMTestStatusFromString(NSString * string) 50 | { 51 | CMTestStatus status = CMTestStatusUnknown; 52 | if ([string isEqualToString:@"Failure"]) 53 | { 54 | status = CMTestStatusFailure; 55 | } 56 | else if ([string isEqualToString:@"Success"]) 57 | { 58 | status = CMTestStatusSuccess; 59 | } 60 | return status; 61 | } 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![GitHub Logo](/resources/header.png) 2 | 3 | `xcsummary` is a macOS command line tool that parses `xcbodebuild` test results and generates html output with activity screenshots. We use it as a part of our CI Server and find it very useful and helpful. We'd like to share it. 4 | 5 | * [Installation](#installation) 6 | * [Usage](#usage) 7 | * [Example](#example) 8 | * [Output](#output) 9 | * [Inspiration](#inspiration) 10 | * [Links](#links) 11 | * [Contribution](#contribution) 12 | 13 | ## Installation 14 | 15 | Open terminal and do the following: 16 | 17 | * Clone the project. 18 | ```shell 19 | git clone https://github.com/MacPaw/xcsummary.git 20 | ``` 21 | 22 | * Navigate to source folder 23 | ```shell 24 | cd xcsummary 25 | ``` 26 | 27 | * Copy binary to the folder you prefer. For example /usr/local/bin/ 28 | ```shell 29 | cp build/xcsummary /usr/local/bin/xcsummary 30 | ``` 31 | ## Usage 32 | 33 | As easy as pie: 34 | ```shell 35 | xcsummary -in -out 36 | ``` 37 | 38 | Optional -show_success argument can be used to expand green test cases as well. 39 | 40 | ## Example 41 | 42 | Tip: we use `-resultBundlePath` parameter in xcodebuild to know the exact location for every single build. 43 | ```shell 44 | #Run your test as usual 45 | xcodebuild test -workspace YourProject.xcworkspace -scheme YourProjectScheme -resultBundlePath '' 46 | #Here we suggest to browse folder to check the folders structure. 47 | #... and generate the report 48 | xcsummary -in /1_Test/action_TestSummaries.plist -out ~/Desktop/result.html 49 | ``` 50 | 51 | ![script](/resources/example_cropped.gif) 52 | 53 | ## Screenshots 54 | 55 | ![screen1](/resources/scr1.png) 56 | ![screen2](/resources/scr2.png) 57 | ![screen3](/resources/scr3.png) 58 | 59 | ## Output 60 | 61 | [Live HTML Example](http://htmlpreview.github.io/?https://github.com/MacPaw/xcsummary/blob/master/resources/example/result.html) 62 | 63 | ## Inspiration 64 | xcsummary is completely inspired by [xcpretty](https://github.com/supermarin/xcpretty), we really love it and use it. But since xcpretty has some [issues](https://github.com/supermarin/xcpretty/issues/251) with screenshots collection, we decided to create our own one :) 65 | 66 | ## Links 67 | Great article to understand [Test Logs in Xcode](http://michele.io/test-logs-in-xcode) 68 | 69 | ## Contribution 70 | Feel free to open issues and pull requests. 71 | -------------------------------------------------------------------------------- /XCSummary/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // xcsummary 4 | // 5 | // Created by Kryvoblotskyi Sergii on 12/13/16. 6 | // Copyright © 2016 MacPaw inc. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "CMTestSummaryParser.h" 11 | #import "CMHTMLReportBuilder.h" 12 | #import "CMTestableSummary.h" 13 | #import "CMTest.h" 14 | 15 | NSString *CMSummaryGetValue(NSArray *arguments, NSString *argument); 16 | BOOL CMSummaryValueExists(NSArray *arguments, NSString *argument); 17 | 18 | int main(int argc, const char * argv[]) { 19 | @autoreleasepool { 20 | 21 | NSArray *arguments = [[NSProcessInfo processInfo] arguments]; 22 | if (arguments.count < 3) { 23 | NSLog(@"Not enough arguments %@", arguments); 24 | return EXIT_FAILURE; 25 | } 26 | 27 | NSString *input = CMSummaryGetValue(arguments, @"-in"); 28 | NSString *output = CMSummaryGetValue(arguments, @"-out"); 29 | if (!input || !output) { 30 | NSLog(@"-in or -out was not provided %@", arguments); 31 | return EXIT_FAILURE; 32 | } 33 | 34 | NSString *summaryPath = [input stringByExpandingTildeInPath]; 35 | NSString *attachmentsPath = [[summaryPath stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"Attachments"]; 36 | 37 | CMTestSummaryParser *parser = [[CMTestSummaryParser alloc] initWithPath:summaryPath]; 38 | NSArray *summaries = [parser testSummaries]; 39 | 40 | BOOL showSuccess = CMSummaryValueExists(arguments, @"-show_success"); 41 | CMHTMLReportBuilder *builder = [[CMHTMLReportBuilder alloc] initWithAttachmentsPath:attachmentsPath 42 | resultsPath:output.stringByExpandingTildeInPath 43 | showSuccessTests:showSuccess]; 44 | [builder appendSummaries:summaries]; 45 | [summaries enumerateObjectsUsingBlock:^(CMTestableSummary *summary, NSUInteger idx, BOOL * _Nonnull stop) { 46 | [builder appendTests:summary.tests indentation:10.0f]; 47 | }]; 48 | 49 | NSString *htmlResult = [builder build]; 50 | return [[htmlResult dataUsingEncoding:NSUTF8StringEncoding] writeToFile:output.stringByExpandingTildeInPath 51 | atomically:YES] == YES ? EXIT_SUCCESS : EXIT_FAILURE; 52 | } 53 | return 0; 54 | } 55 | 56 | NSString *CMSummaryGetValue(NSArray *arguments, NSString *argument) { 57 | NSInteger index = [arguments indexOfObject:argument]; 58 | if (index != NSNotFound) { 59 | return arguments[index+1]; 60 | } 61 | return nil; 62 | } 63 | 64 | BOOL CMSummaryValueExists(NSArray *arguments, NSString *argument) { 65 | NSInteger index = [arguments indexOfObject:argument]; 66 | return index != NSNotFound; 67 | } 68 | -------------------------------------------------------------------------------- /XCSummary/Templates/TemplateGeneratedHeader.h: -------------------------------------------------------------------------------- 1 | #define TestCaseTemplate @"PCEtLSBDb21tb24gVGVzdGNhc2UgLS0+CjxkaXYgc3R5bGU9Im1hcmdpbi1sZWZ0OiAlMi4yZiVAOyBiYWNrZ3JvdW5kLWNvbG9yOiAjQ0JGNEEzOyBwYWRkaW5nOjEwcHg7IHRleHQtYWxpZ246IHJpZ2h0OyI+CiAgICA8dGFibGUgc3R5bGU9IndpZHRoOiAxMDAlIj4KICAgICAgICA8dHI+CiAgICAgICAgICAgIDx0ZCBzdHlsZT0idGV4dC1hbGlnbjogbGVmdDsiPiVAIDxkaXYgc3R5bGU9ImRpc3BsYXk6aW5saW5lOyBjb2xvcjogZ3JleSI+KCUyLjJmIHNlYyk8L2Rpdj4gPC90ZD4KICAgICAgICA8L3RyPgogICAgPC90YWJsZT4KPC9kaXY+CjwhLS0gU2VwYXJhdG9yIC0tPgo8ZGl2IHN0eWxlPSJtYXJnaW46MTBweCI+PC9kaXY+Cg==" 2 | 3 | 4 | #define TestCaseTemplateFailed @"PCEtLSBUZXN0Y2FzZSBGYWlsZWQgLS0+CjxkaXYgc3R5bGU9Im1hcmdpbi1sZWZ0OiAlMi4yZiVAOyBiYWNrZ3JvdW5kLWNvbG9yOiAjRkY2MjdDOyBwYWRkaW5nOjEwcHg7IHRleHQtYWxpZ246IHJpZ2h0OyI+CiAgICA8dGFibGUgc3R5bGU9IndpZHRoOiAxMDAlIj4KICAgICAgICA8dHI+CiAgICAgICAgICAgIDx0ZCBzdHlsZT0idGV4dC1hbGlnbjogbGVmdDsiPiVAIDxkaXYgc3R5bGU9ImRpc3BsYXk6aW5saW5lOyBjb2xvcjogI2QzZDNkMyI+KCUyLjJmIHNlYyk8L2Rpdj48L3RkPgogICAgICAgIDwvdHI+CiAgICA8L3RhYmxlPgo8L2Rpdj4KPCEtLSBTZXBhcmF0b3IgLS0+CjxkaXYgc3R5bGU9Im1hcmdpbjoxMHB4Ij48L2Rpdj4K" 5 | 6 | 7 | #define ActivityTemplateWithoutImage @"PCEtLSBDb21tb24gQWN0aXZpdHkgV2l0aG91dCBJbWFnZSAtLT4KPGRpdiBzdHlsZT0ibWFyZ2luLWxlZnQ6ICUyLjJmJUA7IGJhY2tncm91bmQtY29sb3I6ICNGM0YzRjM7IHBhZGRpbmc6MnB4OyB0ZXh0LWFsaWduOiByaWdodDsgZm9udC1zaXplOjkwJSUiPgogICAgPHRhYmxlIHN0eWxlPSJ3aWR0aDogMTAwJSI+CiAgICAgICAgPHRyPgogICAgICAgICAgICA8dGQgc3R5bGU9InRleHQtYWxpZ246IGxlZnQ7Ij4lQCA8ZGl2IHN0eWxlPSJkaXNwbGF5OmlubGluZTsgY29sb3I6IGdyZXkiPiglMi4yZiBzZWMpPC9kaXY+PC90ZD4KICAgICAgICA8L3RyPgogICAgPC90YWJsZT4KPC9kaXY+CjwhLS0gU2VwYXJhdG9yIC0tPgo8ZGl2IHN0eWxlPSJtYXJnaW46NHB4Ij48L2Rpdj4K" 8 | 9 | 10 | #define ActivityTemplateWithImage @"PCEtLSBDb21tb24gQWN0aXZpdHkgV2l0aCBJbWFnZSAtLT4KPGRpdiBzdHlsZT0ibWFyZ2luLWxlZnQ6ICUyLjJmJUA7IGJhY2tncm91bmQtY29sb3I6ICNGM0YzRjM7IHBhZGRpbmc6MnB4OyB0ZXh0LWFsaWduOiByaWdodDsgZm9udC1zaXplOjkwJSUiPgogICAgPHRhYmxlIHN0eWxlPSJ3aWR0aDogMTAwJSUiPgogICAgICAgIDx0cj4KICAgICAgICAgICAgPHRkIHN0eWxlPSJ0ZXh0LWFsaWduOiBsZWZ0OyI+JUAgPGRpdiBzdHlsZT0iZGlzcGxheTppbmxpbmU7IGNvbG9yOiBncmV5Ij4oJTIuMmYgc2VjKTwvZGl2PiA8L3RkPgogICAgICAgICAgICA8dGQ+PGltZyBzdHlsZT0idGV4dC1hbGlnbjogcmlnaHQ7IHdpZHRoOiA2MDBweDsgaGVpZ2h0OiBhdXRvOyIgc3JjPSIlQCI+PC90ZD4KICAgICAgICA8L3RyPgogICAgPC90YWJsZT4KPC9kaXY+CjwhLS0gU2VwYXJhdG9yIC0tPgo8ZGl2IHN0eWxlPSJtYXJnaW46NHB4Ij48L2Rpdj4K" 11 | 12 | 13 | #define Template @"PCFET0NUWVBFIGh0bWw+CjxodG1sPgogICAgPGhlYWQ+CiAgICAgICAgPG1ldGEgY2hhcnNldD0iVVRGLTgiPgogICAgICAgIDx0aXRsZT48L3RpdGxlPgogICAgPC9oZWFkPgogICAgPGJvZHk+CiAgICAgICAgJUAKICAgIDwvYm9keT4KPC9odG1sPgo=" 14 | 15 | 16 | #define SummaryTemplate @"PCEtLSBUZXN0IFN1bW1hcnkgLS0+CjxkaXYgc3R5bGU9Im1hcmdpbi1sZWZ0OiAxMHB4OyBiYWNrZ3JvdW5kLWNvbG9yOiAjZWVlZWVlOyBwYWRkaW5nOjRweDsgdGV4dC1hbGlnbjogcmlnaHQ7IGZvbnQtc2l6ZToyMHB4Ij4KICAgIDx0YWJsZSBzdHlsZT0id2lkdGg6IDEwMCUiPgogICAgICAgIDx0cj4KICAgICAgICAgICAgPHRkIHN0eWxlPSJ0ZXh0LWFsaWduOiBsZWZ0O3BhZGRpbmc6MTBweDsiPlRlc3QgUmVzdWx0czo8L3RkPgogICAgICAgICAgICA8dGQgc3R5bGU9InRleHQtYWxpZ246IHJpZ2h0O3BhZGRpbmc6MTBweDsiPgogICAgICAgICAgICAgICAgPGRpdiBzdHlsZT0iZGlzcGxheTppbmxpbmU7bWFyZ2luOjEwcHgiPgogICAgICAgICAgICAgICAgICAgICVkIFRlc3RzCiAgICAgICAgICAgICAgICAgICAgPGRpdiBzdHlsZT0iZGlzcGxheTppbmxpbmU7Y29sb3I6Z3JheTtmb250LXNpemU6MTRweCI+CiAgICAgICAgICAgICAgICAgICAgICAgICglQCkKICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgPGRpdiBzdHlsZT0iZGlzcGxheTppbmxpbmU7bWFyZ2luOjEwcHg7Y29sb3I6IzAwQUU0RCI+JWQgc3VjY2Vzc2Z1bDwvZGl2PgogICAgICAgICAgICAgICAgPGRpdiBzdHlsZT0iZGlzcGxheTolQDttYXJnaW46MTBweDtjb2xvcjojRkY2MjdDIj4lZCBmYWlsZWQ8L2Rpdj4KICAgICAgICAgICAgPC90ZD4KICAgICAgICA8L3RyPgogICAgPC90YWJsZT4KPC9kaXY+Cgo8IS0tIFNlcGFyYXRvciAtLT4KPGRpdiBzdHlsZT0ibWFyZ2luOjEwcHgiPjwvZGl2Pgo=" 17 | -------------------------------------------------------------------------------- /XCSummary/Additions/NSArrayAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray+Additions.m 3 | // xcsummary 4 | // 5 | // Created by Anton Mironov on 1/13/15. 6 | // Copyright (c) 2015 MacPaw. All rights reserved. 7 | // 8 | 9 | #import "NSArrayAdditions.h" 10 | 11 | @implementation NSArray (Extensions) 12 | 13 | - (NSMutableArray *)map:(id(^)(id object, NSUInteger index, BOOL *stop))mappingBlock 14 | { 15 | NSMutableArray *result = [NSMutableArray arrayWithCapacity:self.count]; 16 | 17 | [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) 18 | { 19 | id mappedObject = mappingBlock(obj, idx, stop); 20 | if (mappedObject) { 21 | [result addObject:mappedObject]; 22 | } 23 | }]; 24 | 25 | return result; 26 | } 27 | 28 | - (NSMutableArray *)flatMap:(NSArray *(^)(id object, NSUInteger index, BOOL *stop))mappingBlock 29 | { 30 | NSMutableArray *result = [NSMutableArray array]; 31 | 32 | [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) 33 | { 34 | NSArray *mappedArray = mappingBlock(obj, idx, stop); 35 | if (mappedArray.count > 0) { 36 | [result addObjectsFromArray:mappedArray]; 37 | } 38 | }]; 39 | 40 | return result; 41 | } 42 | 43 | - (NSMutableArray *)filter:(BOOL(^)(id object, NSUInteger index, BOOL *stop))filteringBlock 44 | { 45 | return [self map:^id(id object, NSUInteger index, BOOL *stop) 46 | { 47 | return filteringBlock(object, index, stop) ? object : nil; 48 | }]; 49 | } 50 | 51 | - (id)singleObject 52 | { 53 | NSAssert(self.count <= 1, @"This method may not be applied for containers with multiple items"); 54 | return self.lastObject; 55 | } 56 | 57 | - (id)singleObjectOrNil 58 | { 59 | return (1 == self.count) ? self.lastObject : nil; 60 | } 61 | 62 | - (id)safeObjectAtIndex:(NSInteger)index 63 | { 64 | id result = nil; 65 | if (0 <= index && index < self.count) 66 | { 67 | result = [self objectAtIndex:index]; 68 | } 69 | return result; 70 | } 71 | 72 | @end 73 | 74 | 75 | @implementation NSMutableArray (Extensions) 76 | 77 | - (BOOL)safeAddObject:(id)object 78 | { 79 | BOOL didAdd; 80 | 81 | if (object) 82 | { 83 | [self addObject:object]; 84 | didAdd = YES; 85 | } 86 | else 87 | { 88 | NSAssert(NO, @"Attempt to add an empty object"); 89 | didAdd = NO; 90 | } 91 | 92 | return didAdd; 93 | } 94 | 95 | - (void)filterIn:(BOOL(^)(id object, NSUInteger index, BOOL *stop))filteringBlock 96 | { 97 | NSMutableIndexSet *indexesOfObjectsToRemove = [NSMutableIndexSet new]; 98 | [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) 99 | { 100 | if (!filteringBlock(obj, idx, stop)) 101 | { 102 | [indexesOfObjectsToRemove addIndex:idx]; 103 | } 104 | }]; 105 | [self removeObjectsAtIndexes:indexesOfObjectsToRemove]; 106 | } 107 | 108 | - (NSArray *)takeObjectsAtIndexes:(NSIndexSet *)indexes 109 | { 110 | NSArray *result = [self objectsAtIndexes:indexes]; 111 | [self removeObjectsAtIndexes:indexes]; 112 | return result; 113 | } 114 | 115 | - (id)takeObjectAtIndex:(NSUInteger)index 116 | { 117 | id result = [self objectAtIndex:index]; 118 | [self removeObjectAtIndex:index]; 119 | return result; 120 | } 121 | 122 | - (id)takeLastObject 123 | { 124 | NSUInteger count = self.count; 125 | return (count > 0) ? [self takeObjectAtIndex:count - 1] : nil; 126 | } 127 | 128 | - (id)takeFirstObject 129 | { 130 | NSUInteger count = self.count; 131 | return (count > 0) ? [self takeObjectAtIndex:0] : nil; 132 | } 133 | 134 | @end 135 | 136 | @implementation NSArray (CAAdditions) 137 | 138 | - (instancetype)normalizedKeyTimes 139 | { 140 | CGFloat maxValue = [self.firstObject doubleValue]; 141 | for (NSNumber *keyTime in self) 142 | { 143 | maxValue = MAX(maxValue, keyTime.doubleValue); 144 | } 145 | 146 | return [self map:^id(NSNumber *keyTime, NSUInteger index, BOOL *stop) 147 | { 148 | return @(keyTime.doubleValue / maxValue); 149 | }]; 150 | } 151 | 152 | @end 153 | -------------------------------------------------------------------------------- /XCSummary/Templates/CMHTMLReportBuilder.m: -------------------------------------------------------------------------------- 1 | // 2 | // CMHTMLReportBuilder.m 3 | // xcsummary 4 | // 5 | // Created by Kryvoblotskyi Sergii on 12/13/16. 6 | // Copyright © 2016 MacPaw inc. All rights reserved. 7 | // 8 | 9 | #import "CMHTMLReportBuilder.h" 10 | #import "CMTest.h" 11 | #import "CMTestableSummary.h" 12 | #import "CMActivitySummary.h" 13 | #import "TemplateGeneratedHeader.h" 14 | 15 | @interface CMHTMLReportBuilder () 16 | 17 | @property (nonatomic, copy) NSString *path; 18 | @property (nonatomic, copy) NSString *resultsPath; 19 | @property (nonatomic, copy) NSString *htmlResourcePath; 20 | 21 | @property (nonatomic, strong) NSMutableString *resultString; 22 | @property (nonatomic, strong) NSFileManager *fileManager; 23 | @property (nonatomic) BOOL showSuccessTests; 24 | 25 | @property (nonatomic, strong) NSDateComponentsFormatter *timeFormatter; 26 | 27 | @end 28 | 29 | @implementation CMHTMLReportBuilder 30 | 31 | - (instancetype)initWithAttachmentsPath:(NSString *)path 32 | resultsPath:(NSString *)resultsPath 33 | showSuccessTests:(BOOL)showSuccessTests 34 | { 35 | self = [super init]; 36 | if (self) 37 | { 38 | _fileManager = [NSFileManager defaultManager]; 39 | _path = path; 40 | _resultsPath = resultsPath; 41 | _htmlResourcePath = [[resultsPath stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"resources"]; 42 | _resultString = [NSMutableString new]; 43 | _showSuccessTests = showSuccessTests; 44 | [self _prepareResourceFolder]; 45 | } 46 | return self; 47 | } 48 | 49 | - (NSDateComponentsFormatter *)timeFormatter 50 | { 51 | if (!_timeFormatter) 52 | { 53 | NSDateComponentsFormatter *formatter = [NSDateComponentsFormatter new]; 54 | formatter.unitsStyle = NSDateComponentsFormatterUnitsStyleAbbreviated; 55 | formatter.allowedUnits = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond; 56 | _timeFormatter = formatter; 57 | } 58 | return _timeFormatter; 59 | } 60 | 61 | #pragma mark - Public 62 | 63 | - (void)appendSummaries:(NSArray *)summaries 64 | { 65 | NSUInteger successfullTests = [[summaries valueForKeyPath:@"@sum.numberOfSuccessfulTests"] integerValue]; 66 | NSUInteger failedTests = [[summaries valueForKeyPath:@"@sum.numberOfFailedTests"] integerValue]; 67 | 68 | BOOL failuresPresent = failedTests > 0; 69 | NSString *templateFormat = [self _decodeTemplateWithName:SummaryTemplate]; 70 | NSTimeInterval totalTime = [[summaries valueForKeyPath:@"@sum.totalDuration"] doubleValue]; 71 | NSString *timeString = [self.timeFormatter stringFromTimeInterval:totalTime]; 72 | NSString *header = [NSString stringWithFormat:templateFormat, successfullTests + failedTests, timeString, successfullTests, failuresPresent ? @"inline": @"none", failedTests]; 73 | [self.resultString appendString:header]; 74 | } 75 | 76 | - (void)appendTests:(NSArray *)tests indentation:(CGFloat)indentation 77 | { 78 | [tests enumerateObjectsUsingBlock:^(CMTest * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 79 | 80 | [self _appendTestCase:obj indentation:indentation]; 81 | if (obj.subTests.count > 0) 82 | { 83 | [self appendTests:obj.subTests indentation:indentation + 50]; 84 | } 85 | else 86 | { 87 | if (self.showSuccessTests == NO) 88 | { 89 | if (obj.status == CMTestStatusFailure) 90 | { 91 | [self _appendActivities:obj.activities indentation:indentation + 50]; 92 | } 93 | } 94 | else 95 | { 96 | [self _appendActivities:obj.activities indentation:indentation + 50]; 97 | } 98 | } 99 | }]; 100 | } 101 | - (NSString *)build 102 | { 103 | NSString *templateFormat = [self _decodeTemplateWithName:Template]; 104 | return [NSString stringWithFormat:templateFormat, self.resultString.copy]; 105 | } 106 | 107 | #pragma mark - Private 108 | 109 | - (NSString *)_decodeTemplateWithName:(NSString *)fileName 110 | { 111 | NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:fileName options:0]; 112 | NSString *format = [[NSString alloc] initWithData:decodedData encoding:NSUTF8StringEncoding]; 113 | return format; 114 | } 115 | 116 | - (void)_appendTestCase:(CMTest *)testCase indentation:(CGFloat)indentation 117 | { 118 | NSString *templateFormat = testCase.status == CMTestStatusFailure ? 119 | [self _decodeTemplateWithName:TestCaseTemplateFailed] : 120 | [self _decodeTemplateWithName:TestCaseTemplate]; 121 | NSString *composedString = [NSString stringWithFormat:templateFormat, indentation, @"px", testCase.testName, testCase.duration]; 122 | [self.resultString appendString:composedString]; 123 | } 124 | 125 | - (void)_appendActivities:(NSArray *)activities indentation:(CGFloat)indentation 126 | { 127 | [activities enumerateObjectsUsingBlock:^(CMActivitySummary * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 128 | [self _appendActivity:obj indentation:indentation]; 129 | [self _appendActivities:obj.subActivities indentation:indentation + 50]; 130 | }]; 131 | } 132 | 133 | - (void)_appendActivity:(CMActivitySummary *)activity indentation:(CGFloat)indentation 134 | { 135 | NSString *templateFormat = nil; 136 | NSString *composedString = nil; 137 | CMAttachment *attachment = nil; 138 | 139 | attachment = [activity.attachments firstObject]; 140 | 141 | if (activity.hasScreenshotData && attachment) 142 | { 143 | templateFormat = [self _decodeTemplateWithName:ActivityTemplateWithImage]; 144 | NSString *imageName = [NSString stringWithFormat:@"%@", attachment.filename]; 145 | NSString *fullPath = [self.path stringByAppendingPathComponent:imageName]; 146 | 147 | [self.fileManager copyItemAtPath:fullPath toPath:[self.htmlResourcePath stringByAppendingPathComponent:imageName] error:nil]; 148 | 149 | NSString *localImageName = [NSString stringWithFormat:@"resources/%@", imageName]; 150 | composedString = [NSString stringWithFormat:templateFormat, indentation, @"px", activity.title, activity.finishTimeInterval - activity.startTimeInterval, localImageName]; 151 | } 152 | else 153 | { 154 | templateFormat = [self _decodeTemplateWithName:ActivityTemplateWithoutImage]; 155 | composedString = [NSString stringWithFormat:templateFormat, indentation, @"px", activity.title, activity.finishTimeInterval - activity.startTimeInterval]; 156 | } 157 | 158 | [self.resultString appendString:composedString]; 159 | } 160 | 161 | #pragma mark - File Operations 162 | 163 | - (void)_prepareResourceFolder 164 | { 165 | if ([self.fileManager fileExistsAtPath:self.htmlResourcePath] == NO) { 166 | [self.fileManager createDirectoryAtPath:self.htmlResourcePath withIntermediateDirectories:NO attributes:nil error:nil]; 167 | } 168 | } 169 | 170 | @end 171 | -------------------------------------------------------------------------------- /resources/example/result.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 |
Selected tests
(81.60 sec)
15 |
16 | 17 |
18 | 19 |
20 | 21 | 22 | 23 | 24 |
UITests.xctest
(81.60 sec)
25 |
26 | 27 |
28 | 29 |
30 | 31 | 32 | 33 | 34 |
ActionvationRegularActionsUITests
(22.24 sec)
35 |
36 | 37 |
38 | 39 |
40 | 41 | 42 | 43 | 44 |
testLicenseActivation()
(15.16 sec)
45 |
46 | 47 |
48 | 49 |
50 | 51 | 52 | 53 | 54 |
testLicenseDeactivation()
(7.08 sec)
55 |
56 | 57 |
58 | 59 |
60 | 61 | 62 | 63 | 64 |
ActivationUITests
(59.35 sec)
65 |
66 | 67 |
68 | 69 |
70 | 71 | 72 | 73 | 74 |
testActivateButtonDisabled()
(4.01 sec)
75 |
76 | 77 |
78 | 79 |
80 | 81 | 82 | 83 | 84 |
testActivateButtonEnabled()
(4.79 sec)
85 |
86 | 87 |
88 | 89 |
90 | 91 | 92 | 93 | 94 |
testActivationDialogBuyButton()
(2.64 sec)
95 |
96 | 97 |
98 | 99 |
100 | 101 | 102 | 103 | 104 |
Start Test at 2016-12-13 14:42:14.718
(0.00 sec)
105 |
106 | 107 |
108 | 109 |
110 | 111 | 112 | 113 | 114 |
Set Up
(1.86 sec)
115 |
116 | 117 |
118 | 119 |
120 | 121 | 122 | 123 | 124 | 125 |
Launch com.macpaw.CleanMyMac3
(1.85 sec)
126 |
127 | 128 |
129 | 130 |
131 | 132 | 133 | 134 | 135 | 136 |
Terminate
(0.20 sec)
137 |
138 | 139 |
140 | 141 |
142 | 143 | 144 | 145 | 146 | 147 |
Wait for app to idle
(0.22 sec)
148 |
149 | 150 |
151 | 152 |
153 | 154 | 155 | 156 | 157 |
Check predicate `exists == 1` against object `"License Activation" Window`
(0.33 sec)
158 |
159 | 160 |
161 | 162 |
163 | 164 | 165 | 166 | 167 |
Snapshot accessibility hierarchy for com.macpaw.CleanMyMac3
(0.30 sec)
168 |
169 | 170 |
171 | 172 |
173 | 174 | 175 | 176 | 177 |
Find: Descendants matching type Window
(0.00 sec)
178 |
179 | 180 |
181 | 182 |
183 | 184 | 185 | 186 | 187 |
Find: Elements matching predicate '"License Activation" IN identifiers'
(0.00 sec)
188 |
189 | 190 |
191 | 192 |
193 | 194 | 195 | 196 | 197 |
Snapshot accessibility hierarchy for com.macpaw.CleanMyMac3
(0.21 sec)
198 |
199 | 200 |
201 | 202 |
203 | 204 | 205 | 206 | 207 |
Find: Descendants matching type Window
(0.00 sec)
208 |
209 | 210 |
211 | 212 |
213 | 214 | 215 | 216 | 217 |
Find: Elements matching predicate '"License Activation" IN identifiers'
(0.00 sec)
218 |
219 | 220 |
221 | 222 |
223 | 224 | 225 | 226 | 227 |
Find: Descendants matching type StaticText
(0.00 sec)
228 |
229 | 230 |
231 | 232 |
233 | 234 | 235 | 236 | 237 |
Find: Elements matching predicate '"Please enter your Activation Number:" IN identifiers'
(0.00 sec)
238 |
239 | 240 |
241 | 242 |
243 | 244 | 245 | 246 | 247 |
Assertion Failure: ActivationUITests.swift:77: XCTAssertTrue failed -
(0.00 sec)
248 |
249 | 250 |
251 | 252 |
253 | 254 | 255 | 256 | 257 |
Tear Down
(0.00 sec)
258 |
259 | 260 |
261 | 262 |
263 | 264 | 265 | 266 | 267 |
testActivationDialogEnterCodeElements()
(4.39 sec)
268 |
269 | 270 |
271 | 272 |
273 | 274 | 275 | 276 | 277 |
testActivationDialogLaterButton()
(3.91 sec)
278 |
279 | 280 |
281 | 282 |
283 | 284 | 285 | 286 | 287 |
testActivationDialogUIElements()
(2.89 sec)
288 |
289 | 290 |
291 | 292 |
293 | 294 | 295 | 296 | 297 |
testActivationErrorCancel()
(8.51 sec)
298 |
299 | 300 |
301 | 302 |
303 | 304 | 305 | 306 | 307 |
testActivationErrorTryAgain()
(8.44 sec)
308 |
309 | 310 |
311 | 312 |
313 | 314 | 315 | 316 | 317 |
testCancelInEnterCodeDialog()
(4.97 sec)
318 |
319 | 320 |
321 | 322 |
323 | 324 | 325 | 326 | 327 |
testEnterInvalidCode()
(7.35 sec)
328 |
329 | 330 |
331 | 332 |
333 | 334 | 335 | 336 | 337 |
testLostCodeButton()
(5.08 sec)
338 |
339 | 340 |
341 | 342 |
343 | 344 | 345 | 346 | 347 |
testUnlockFullVersionButtonExists()
(2.37 sec)
348 |
349 | 350 |
351 | 352 | 353 | 354 | -------------------------------------------------------------------------------- /XCSummary.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A7BC74361E0052640072AD47 /* NSArrayAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = A7BC741F1E0052640072AD47 /* NSArrayAdditions.m */; }; 11 | A7BC74371E0052640072AD47 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = A7BC74201E0052640072AD47 /* main.m */; }; 12 | A7BC74381E0052640072AD47 /* CMActivitySummary.m in Sources */ = {isa = PBXBuildFile; fileRef = A7BC74231E0052640072AD47 /* CMActivitySummary.m */; }; 13 | A7BC74391E0052640072AD47 /* CMEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = A7BC74251E0052640072AD47 /* CMEntity.m */; }; 14 | A7BC743A1E0052640072AD47 /* CMTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A7BC74271E0052640072AD47 /* CMTest.m */; }; 15 | A7BC743B1E0052640072AD47 /* CMTestableSummary.m in Sources */ = {isa = PBXBuildFile; fileRef = A7BC74291E0052640072AD47 /* CMTestableSummary.m */; }; 16 | A7BC743C1E0052640072AD47 /* CMTestSummaryParser.m in Sources */ = {isa = PBXBuildFile; fileRef = A7BC742C1E0052640072AD47 /* CMTestSummaryParser.m */; }; 17 | A7BC743D1E0052640072AD47 /* CMHTMLReportBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = A7BC74321E0052640072AD47 /* CMHTMLReportBuilder.m */; }; 18 | CB3DCA592165270000DA9D8C /* CMAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = CB3DCA582165270000DA9D8C /* CMAttachment.m */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXCopyFilesBuildPhase section */ 22 | A7B461D11E00171C002B13B0 /* CopyFiles */ = { 23 | isa = PBXCopyFilesBuildPhase; 24 | buildActionMask = 2147483647; 25 | dstPath = /usr/share/man/man1/; 26 | dstSubfolderSpec = 0; 27 | files = ( 28 | ); 29 | runOnlyForDeploymentPostprocessing = 1; 30 | }; 31 | /* End PBXCopyFilesBuildPhase section */ 32 | 33 | /* Begin PBXFileReference section */ 34 | A723D1D61E128315000F0F65 /* SummaryTemplate.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = SummaryTemplate.html; sourceTree = ""; }; 35 | A7B461D31E00171C002B13B0 /* xcsummary */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = xcsummary; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | A7BC741E1E0052640072AD47 /* NSArrayAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSArrayAdditions.h; sourceTree = ""; }; 37 | A7BC741F1E0052640072AD47 /* NSArrayAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSArrayAdditions.m; sourceTree = ""; }; 38 | A7BC74201E0052640072AD47 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 39 | A7BC74221E0052640072AD47 /* CMActivitySummary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CMActivitySummary.h; sourceTree = ""; }; 40 | A7BC74231E0052640072AD47 /* CMActivitySummary.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CMActivitySummary.m; sourceTree = ""; }; 41 | A7BC74241E0052640072AD47 /* CMEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CMEntity.h; sourceTree = ""; }; 42 | A7BC74251E0052640072AD47 /* CMEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CMEntity.m; sourceTree = ""; }; 43 | A7BC74261E0052640072AD47 /* CMTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CMTest.h; sourceTree = ""; }; 44 | A7BC74271E0052640072AD47 /* CMTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CMTest.m; sourceTree = ""; }; 45 | A7BC74281E0052640072AD47 /* CMTestableSummary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CMTestableSummary.h; sourceTree = ""; }; 46 | A7BC74291E0052640072AD47 /* CMTestableSummary.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CMTestableSummary.m; sourceTree = ""; }; 47 | A7BC742B1E0052640072AD47 /* CMTestSummaryParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CMTestSummaryParser.h; sourceTree = ""; }; 48 | A7BC742C1E0052640072AD47 /* CMTestSummaryParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CMTestSummaryParser.m; sourceTree = ""; }; 49 | A7BC742F1E0052640072AD47 /* ActivityTemplateWithImage.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = ActivityTemplateWithImage.html; sourceTree = ""; }; 50 | A7BC74301E0052640072AD47 /* ActivityTemplateWithoutImage.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = ActivityTemplateWithoutImage.html; sourceTree = ""; }; 51 | A7BC74311E0052640072AD47 /* CMHTMLReportBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CMHTMLReportBuilder.h; sourceTree = ""; }; 52 | A7BC74321E0052640072AD47 /* CMHTMLReportBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CMHTMLReportBuilder.m; sourceTree = ""; }; 53 | A7BC74331E0052640072AD47 /* Template.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = Template.html; sourceTree = ""; }; 54 | A7BC74341E0052640072AD47 /* TestCaseTemplate.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = TestCaseTemplate.html; sourceTree = ""; }; 55 | A7BC74351E0052640072AD47 /* TestCaseTemplateFailed.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = TestCaseTemplateFailed.html; sourceTree = ""; }; 56 | CB3DCA57216525E300DA9D8C /* CMAttachment.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CMAttachment.h; sourceTree = ""; }; 57 | CB3DCA582165270000DA9D8C /* CMAttachment.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CMAttachment.m; sourceTree = ""; }; 58 | /* End PBXFileReference section */ 59 | 60 | /* Begin PBXFrameworksBuildPhase section */ 61 | A7B461D01E00171C002B13B0 /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | /* End PBXFrameworksBuildPhase section */ 69 | 70 | /* Begin PBXGroup section */ 71 | A7B461851DFEE9F6002B13B0 = { 72 | isa = PBXGroup; 73 | children = ( 74 | A7BC741C1E0052640072AD47 /* XCSummary */, 75 | A7B4618F1DFEE9F6002B13B0 /* Products */, 76 | ); 77 | sourceTree = ""; 78 | }; 79 | A7B4618F1DFEE9F6002B13B0 /* Products */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | A7B461D31E00171C002B13B0 /* xcsummary */, 83 | ); 84 | name = Products; 85 | sourceTree = ""; 86 | }; 87 | A7BC741C1E0052640072AD47 /* XCSummary */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | A7BC74201E0052640072AD47 /* main.m */, 91 | A7BC741D1E0052640072AD47 /* Additions */, 92 | A7BC74211E0052640072AD47 /* Models */, 93 | A7BC742A1E0052640072AD47 /* Parser */, 94 | A7BC742E1E0052640072AD47 /* Templates */, 95 | ); 96 | path = XCSummary; 97 | sourceTree = ""; 98 | }; 99 | A7BC741D1E0052640072AD47 /* Additions */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | A7BC741E1E0052640072AD47 /* NSArrayAdditions.h */, 103 | A7BC741F1E0052640072AD47 /* NSArrayAdditions.m */, 104 | ); 105 | path = Additions; 106 | sourceTree = ""; 107 | }; 108 | A7BC74211E0052640072AD47 /* Models */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | A7BC74221E0052640072AD47 /* CMActivitySummary.h */, 112 | A7BC74231E0052640072AD47 /* CMActivitySummary.m */, 113 | A7BC74281E0052640072AD47 /* CMTestableSummary.h */, 114 | A7BC74291E0052640072AD47 /* CMTestableSummary.m */, 115 | A7BC74261E0052640072AD47 /* CMTest.h */, 116 | A7BC74271E0052640072AD47 /* CMTest.m */, 117 | A7BC74241E0052640072AD47 /* CMEntity.h */, 118 | A7BC74251E0052640072AD47 /* CMEntity.m */, 119 | CB3DCA57216525E300DA9D8C /* CMAttachment.h */, 120 | CB3DCA582165270000DA9D8C /* CMAttachment.m */, 121 | ); 122 | path = Models; 123 | sourceTree = ""; 124 | }; 125 | A7BC742A1E0052640072AD47 /* Parser */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | A7BC742B1E0052640072AD47 /* CMTestSummaryParser.h */, 129 | A7BC742C1E0052640072AD47 /* CMTestSummaryParser.m */, 130 | ); 131 | path = Parser; 132 | sourceTree = ""; 133 | }; 134 | A7BC742E1E0052640072AD47 /* Templates */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | A7BC74311E0052640072AD47 /* CMHTMLReportBuilder.h */, 138 | A7BC74321E0052640072AD47 /* CMHTMLReportBuilder.m */, 139 | A7BC742F1E0052640072AD47 /* ActivityTemplateWithImage.html */, 140 | A7BC74301E0052640072AD47 /* ActivityTemplateWithoutImage.html */, 141 | A7BC74341E0052640072AD47 /* TestCaseTemplate.html */, 142 | A7BC74351E0052640072AD47 /* TestCaseTemplateFailed.html */, 143 | A7BC74331E0052640072AD47 /* Template.html */, 144 | A723D1D61E128315000F0F65 /* SummaryTemplate.html */, 145 | ); 146 | path = Templates; 147 | sourceTree = ""; 148 | }; 149 | /* End PBXGroup section */ 150 | 151 | /* Begin PBXNativeTarget section */ 152 | A7B461D21E00171C002B13B0 /* xcsummary */ = { 153 | isa = PBXNativeTarget; 154 | buildConfigurationList = A7B461D71E00171C002B13B0 /* Build configuration list for PBXNativeTarget "xcsummary" */; 155 | buildPhases = ( 156 | A7BC743F1E005B6F0072AD47 /* Encode Templates */, 157 | A7B461CF1E00171C002B13B0 /* Sources */, 158 | A7B461D01E00171C002B13B0 /* Frameworks */, 159 | A7B461D11E00171C002B13B0 /* CopyFiles */, 160 | A701334C1E01AFBC00854391 /* chmod */, 161 | ); 162 | buildRules = ( 163 | ); 164 | dependencies = ( 165 | ); 166 | name = xcsummary; 167 | productName = summary; 168 | productReference = A7B461D31E00171C002B13B0 /* xcsummary */; 169 | productType = "com.apple.product-type.tool"; 170 | }; 171 | /* End PBXNativeTarget section */ 172 | 173 | /* Begin PBXProject section */ 174 | A7B461861DFEE9F6002B13B0 /* Project object */ = { 175 | isa = PBXProject; 176 | attributes = { 177 | LastUpgradeCheck = 0810; 178 | ORGANIZATIONNAME = "MacPaw inc"; 179 | TargetAttributes = { 180 | A7B461D21E00171C002B13B0 = { 181 | CreatedOnToolsVersion = 8.1; 182 | DevelopmentTeam = S8EX82NJP6; 183 | ProvisioningStyle = Automatic; 184 | }; 185 | }; 186 | }; 187 | buildConfigurationList = A7B461891DFEE9F6002B13B0 /* Build configuration list for PBXProject "XCSummary" */; 188 | compatibilityVersion = "Xcode 3.2"; 189 | developmentRegion = English; 190 | hasScannedForEncodings = 0; 191 | knownRegions = ( 192 | en, 193 | Base, 194 | ); 195 | mainGroup = A7B461851DFEE9F6002B13B0; 196 | productRefGroup = A7B4618F1DFEE9F6002B13B0 /* Products */; 197 | projectDirPath = ""; 198 | projectRoot = ""; 199 | targets = ( 200 | A7B461D21E00171C002B13B0 /* xcsummary */, 201 | ); 202 | }; 203 | /* End PBXProject section */ 204 | 205 | /* Begin PBXShellScriptBuildPhase section */ 206 | A701334C1E01AFBC00854391 /* chmod */ = { 207 | isa = PBXShellScriptBuildPhase; 208 | buildActionMask = 2147483647; 209 | files = ( 210 | ); 211 | inputPaths = ( 212 | ); 213 | name = chmod; 214 | outputPaths = ( 215 | ); 216 | runOnlyForDeploymentPostprocessing = 0; 217 | shellPath = /bin/sh; 218 | shellScript = "chmod 777 build/xcsummary"; 219 | }; 220 | A7BC743F1E005B6F0072AD47 /* Encode Templates */ = { 221 | isa = PBXShellScriptBuildPhase; 222 | buildActionMask = 2147483647; 223 | files = ( 224 | ); 225 | inputPaths = ( 226 | ); 227 | name = "Encode Templates"; 228 | outputPaths = ( 229 | ); 230 | runOnlyForDeploymentPostprocessing = 0; 231 | shellPath = /bin/sh; 232 | shellScript = "#!/bin/bash\n\ncd XCSummary/Templates\n\n#!/bin/bash\n\nTemplate=$(openssl base64 -in Template.html | tr -d '\\n')\nActivityTemplateWithoutImage=$(openssl base64 -in ActivityTemplateWithoutImage.html | tr -d '\\n')\nActivityTemplateWithImage=$(openssl base64 -in ActivityTemplateWithImage.html | tr -d '\\n')\nTestCaseTemplate=$(openssl base64 -in TestCaseTemplate.html | tr -d '\\n')\nTestCaseTemplateFailed=$(openssl base64 -in TestCaseTemplateFailed.html | tr -d '\\n')\nSummaryTemplate=$(openssl base64 -in SummaryTemplate.html | tr -d '\\n')\n\nsed \"s/XCTemplateXC/$Template/g\" TemplateHeader.h > temp.h\nsed \"s/XCActivityTemplateWithoutImageXC/$ActivityTemplateWithoutImage/g\" temp.h > temp1.h\nsed \"s/XCActivityTemplateWithImageXC/$ActivityTemplateWithImage/g\" temp1.h > temp2.h\nsed \"s/XCTestCaseTemplateXC/$TestCaseTemplate/g\" temp2.h > temp3.h\nsed \"s/XCTestCaseTemplateFailedXC/$TestCaseTemplateFailed/g\" temp3.h > temp4.h\nsed \"s/XCSummaryTemplateXC/$SummaryTemplate/g\" temp4.h > TemplateGeneratedHeader.h\n\nrm -rf temp.h\nrm -rf temp1.h\nrm -rf temp2.h\nrm -rf temp3.h\nrm -rf temp4.h"; 233 | }; 234 | /* End PBXShellScriptBuildPhase section */ 235 | 236 | /* Begin PBXSourcesBuildPhase section */ 237 | A7B461CF1E00171C002B13B0 /* Sources */ = { 238 | isa = PBXSourcesBuildPhase; 239 | buildActionMask = 2147483647; 240 | files = ( 241 | A7BC743D1E0052640072AD47 /* CMHTMLReportBuilder.m in Sources */, 242 | A7BC74371E0052640072AD47 /* main.m in Sources */, 243 | A7BC74381E0052640072AD47 /* CMActivitySummary.m in Sources */, 244 | CB3DCA592165270000DA9D8C /* CMAttachment.m in Sources */, 245 | A7BC743C1E0052640072AD47 /* CMTestSummaryParser.m in Sources */, 246 | A7BC743B1E0052640072AD47 /* CMTestableSummary.m in Sources */, 247 | A7BC74391E0052640072AD47 /* CMEntity.m in Sources */, 248 | A7BC74361E0052640072AD47 /* NSArrayAdditions.m in Sources */, 249 | A7BC743A1E0052640072AD47 /* CMTest.m in Sources */, 250 | ); 251 | runOnlyForDeploymentPostprocessing = 0; 252 | }; 253 | /* End PBXSourcesBuildPhase section */ 254 | 255 | /* Begin XCBuildConfiguration section */ 256 | A7B461A01DFEE9F6002B13B0 /* Debug */ = { 257 | isa = XCBuildConfiguration; 258 | buildSettings = { 259 | ALWAYS_SEARCH_USER_PATHS = NO; 260 | CLANG_ANALYZER_NONNULL = YES; 261 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 262 | CLANG_CXX_LIBRARY = "libc++"; 263 | CLANG_ENABLE_MODULES = YES; 264 | CLANG_ENABLE_OBJC_ARC = YES; 265 | CLANG_WARN_BOOL_CONVERSION = YES; 266 | CLANG_WARN_CONSTANT_CONVERSION = YES; 267 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 268 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 269 | CLANG_WARN_EMPTY_BODY = YES; 270 | CLANG_WARN_ENUM_CONVERSION = YES; 271 | CLANG_WARN_INFINITE_RECURSION = YES; 272 | CLANG_WARN_INT_CONVERSION = YES; 273 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 274 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 275 | CLANG_WARN_UNREACHABLE_CODE = YES; 276 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 277 | CODE_SIGN_IDENTITY = "-"; 278 | COPY_PHASE_STRIP = NO; 279 | DEBUG_INFORMATION_FORMAT = dwarf; 280 | ENABLE_STRICT_OBJC_MSGSEND = YES; 281 | ENABLE_TESTABILITY = YES; 282 | GCC_C_LANGUAGE_STANDARD = gnu99; 283 | GCC_DYNAMIC_NO_PIC = NO; 284 | GCC_NO_COMMON_BLOCKS = YES; 285 | GCC_OPTIMIZATION_LEVEL = 0; 286 | GCC_PREPROCESSOR_DEFINITIONS = ( 287 | "DEBUG=1", 288 | "$(inherited)", 289 | ); 290 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 291 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 292 | GCC_WARN_UNDECLARED_SELECTOR = YES; 293 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 294 | GCC_WARN_UNUSED_FUNCTION = YES; 295 | GCC_WARN_UNUSED_VARIABLE = YES; 296 | MACOSX_DEPLOYMENT_TARGET = 10.12; 297 | MTL_ENABLE_DEBUG_INFO = YES; 298 | ONLY_ACTIVE_ARCH = YES; 299 | SDKROOT = macosx; 300 | }; 301 | name = Debug; 302 | }; 303 | A7B461A11DFEE9F6002B13B0 /* Release */ = { 304 | isa = XCBuildConfiguration; 305 | buildSettings = { 306 | ALWAYS_SEARCH_USER_PATHS = NO; 307 | CLANG_ANALYZER_NONNULL = YES; 308 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 309 | CLANG_CXX_LIBRARY = "libc++"; 310 | CLANG_ENABLE_MODULES = YES; 311 | CLANG_ENABLE_OBJC_ARC = YES; 312 | CLANG_WARN_BOOL_CONVERSION = YES; 313 | CLANG_WARN_CONSTANT_CONVERSION = YES; 314 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 315 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 316 | CLANG_WARN_EMPTY_BODY = YES; 317 | CLANG_WARN_ENUM_CONVERSION = YES; 318 | CLANG_WARN_INFINITE_RECURSION = YES; 319 | CLANG_WARN_INT_CONVERSION = YES; 320 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 321 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 322 | CLANG_WARN_UNREACHABLE_CODE = YES; 323 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 324 | CODE_SIGN_IDENTITY = "-"; 325 | COPY_PHASE_STRIP = NO; 326 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 327 | ENABLE_NS_ASSERTIONS = NO; 328 | ENABLE_STRICT_OBJC_MSGSEND = YES; 329 | GCC_C_LANGUAGE_STANDARD = gnu99; 330 | GCC_NO_COMMON_BLOCKS = YES; 331 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 332 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 333 | GCC_WARN_UNDECLARED_SELECTOR = YES; 334 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 335 | GCC_WARN_UNUSED_FUNCTION = YES; 336 | GCC_WARN_UNUSED_VARIABLE = YES; 337 | MACOSX_DEPLOYMENT_TARGET = 10.12; 338 | MTL_ENABLE_DEBUG_INFO = NO; 339 | SDKROOT = macosx; 340 | }; 341 | name = Release; 342 | }; 343 | A7B461D81E00171C002B13B0 /* Debug */ = { 344 | isa = XCBuildConfiguration; 345 | buildSettings = { 346 | CONFIGURATION_BUILD_DIR = "$(SRCROOT)/build"; 347 | DEVELOPMENT_TEAM = S8EX82NJP6; 348 | HEADER_SEARCH_PATHS = "$(SRCROOT)/XCSummary/Templates"; 349 | OTHER_LDFLAGS = ""; 350 | PRODUCT_NAME = xcsummary; 351 | SYMROOT = "$(SRCROOT)/build"; 352 | }; 353 | name = Debug; 354 | }; 355 | A7B461D91E00171C002B13B0 /* Release */ = { 356 | isa = XCBuildConfiguration; 357 | buildSettings = { 358 | CONFIGURATION_BUILD_DIR = "$(SRCROOT)/build"; 359 | DEVELOPMENT_TEAM = S8EX82NJP6; 360 | HEADER_SEARCH_PATHS = "$(SRCROOT)/XCSummary/Templates"; 361 | OTHER_LDFLAGS = ""; 362 | PRODUCT_NAME = xcsummary; 363 | SYMROOT = "$(SRCROOT)/build"; 364 | }; 365 | name = Release; 366 | }; 367 | /* End XCBuildConfiguration section */ 368 | 369 | /* Begin XCConfigurationList section */ 370 | A7B461891DFEE9F6002B13B0 /* Build configuration list for PBXProject "XCSummary" */ = { 371 | isa = XCConfigurationList; 372 | buildConfigurations = ( 373 | A7B461A01DFEE9F6002B13B0 /* Debug */, 374 | A7B461A11DFEE9F6002B13B0 /* Release */, 375 | ); 376 | defaultConfigurationIsVisible = 0; 377 | defaultConfigurationName = Release; 378 | }; 379 | A7B461D71E00171C002B13B0 /* Build configuration list for PBXNativeTarget "xcsummary" */ = { 380 | isa = XCConfigurationList; 381 | buildConfigurations = ( 382 | A7B461D81E00171C002B13B0 /* Debug */, 383 | A7B461D91E00171C002B13B0 /* Release */, 384 | ); 385 | defaultConfigurationIsVisible = 0; 386 | defaultConfigurationName = Release; 387 | }; 388 | /* End XCConfigurationList section */ 389 | }; 390 | rootObject = A7B461861DFEE9F6002B13B0 /* Project object */; 391 | } 392 | --------------------------------------------------------------------------------