xcknife 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +5 -0
- data/.gitmodules +3 -0
- data/.travis.yml +9 -0
- data/README.md +5 -1
- data/Rakefile +11 -0
- data/TestDumper/README.md +11 -0
- data/TestDumper/TestDumper.xcodeproj/project.pbxproj +525 -0
- data/TestDumper/TestDumper.xcodeproj/xcshareddata/xcschemes/TestDumper.xcscheme +109 -0
- data/TestDumper/TestDumper/Info.plist +26 -0
- data/TestDumper/TestDumper/Initialize.m +193 -0
- data/TestDumper/TestDumper/TestDumper.h +7 -0
- data/TestDumper/build.sh +6 -0
- data/bin/xcknife +0 -0
- data/bin/xcknife-min +1 -1
- data/bin/xcknife-test-dumper +4 -0
- data/example/README.md +1 -3
- data/lib/xcknife.rb +2 -1
- data/lib/xcknife/runner.rb +10 -2
- data/lib/xcknife/test_dumper.rb +196 -0
- data/lib/xcknife/xctool_cmd_helper.rb +26 -0
- data/xcknife.gemspec +2 -2
- metadata +16 -9
- data/spec/spec_helper.rb +0 -4
- data/spec/xcknife_spec.rb +0 -344
- data/spec/xctool_cmd_helper_spec.rb +0 -13
@@ -0,0 +1,109 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<Scheme
|
3
|
+
LastUpgradeVersion = "0800"
|
4
|
+
version = "1.3">
|
5
|
+
<BuildAction
|
6
|
+
parallelizeBuildables = "YES"
|
7
|
+
buildImplicitDependencies = "YES">
|
8
|
+
<BuildActionEntries>
|
9
|
+
<BuildActionEntry
|
10
|
+
buildForTesting = "YES"
|
11
|
+
buildForRunning = "YES"
|
12
|
+
buildForProfiling = "YES"
|
13
|
+
buildForArchiving = "YES"
|
14
|
+
buildForAnalyzing = "YES">
|
15
|
+
<BuildableReference
|
16
|
+
BuildableIdentifier = "primary"
|
17
|
+
BlueprintIdentifier = "F60C68B91B8D038300CC8521"
|
18
|
+
BuildableName = "TestDumper.framework"
|
19
|
+
BlueprintName = "TestDumper"
|
20
|
+
ReferencedContainer = "container:TestDumper.xcodeproj">
|
21
|
+
</BuildableReference>
|
22
|
+
</BuildActionEntry>
|
23
|
+
</BuildActionEntries>
|
24
|
+
</BuildAction>
|
25
|
+
<TestAction
|
26
|
+
buildConfiguration = "Debug"
|
27
|
+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
28
|
+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
29
|
+
shouldUseLaunchSchemeArgsEnv = "YES">
|
30
|
+
<Testables>
|
31
|
+
<TestableReference
|
32
|
+
skipped = "NO">
|
33
|
+
<BuildableReference
|
34
|
+
BuildableIdentifier = "primary"
|
35
|
+
BlueprintIdentifier = "F60C68C41B8D038300CC8521"
|
36
|
+
BuildableName = "TestDumperTests.xctest"
|
37
|
+
BlueprintName = "TestDumperTests"
|
38
|
+
ReferencedContainer = "container:TestDumper.xcodeproj">
|
39
|
+
</BuildableReference>
|
40
|
+
</TestableReference>
|
41
|
+
<TestableReference
|
42
|
+
skipped = "NO">
|
43
|
+
<BuildableReference
|
44
|
+
BuildableIdentifier = "primary"
|
45
|
+
BlueprintIdentifier = "F67087161B8D09D7000466B2"
|
46
|
+
BuildableName = "TestTests.xctest"
|
47
|
+
BlueprintName = "TestTests"
|
48
|
+
ReferencedContainer = "container:TestDumper.xcodeproj">
|
49
|
+
</BuildableReference>
|
50
|
+
</TestableReference>
|
51
|
+
</Testables>
|
52
|
+
<MacroExpansion>
|
53
|
+
<BuildableReference
|
54
|
+
BuildableIdentifier = "primary"
|
55
|
+
BlueprintIdentifier = "F60C68B91B8D038300CC8521"
|
56
|
+
BuildableName = "TestDumper.framework"
|
57
|
+
BlueprintName = "TestDumper"
|
58
|
+
ReferencedContainer = "container:TestDumper.xcodeproj">
|
59
|
+
</BuildableReference>
|
60
|
+
</MacroExpansion>
|
61
|
+
<AdditionalOptions>
|
62
|
+
</AdditionalOptions>
|
63
|
+
</TestAction>
|
64
|
+
<LaunchAction
|
65
|
+
buildConfiguration = "Debug"
|
66
|
+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
67
|
+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
68
|
+
launchStyle = "0"
|
69
|
+
useCustomWorkingDirectory = "NO"
|
70
|
+
ignoresPersistentStateOnLaunch = "NO"
|
71
|
+
debugDocumentVersioning = "YES"
|
72
|
+
debugServiceExtension = "internal"
|
73
|
+
allowLocationSimulation = "YES">
|
74
|
+
<MacroExpansion>
|
75
|
+
<BuildableReference
|
76
|
+
BuildableIdentifier = "primary"
|
77
|
+
BlueprintIdentifier = "F60C68B91B8D038300CC8521"
|
78
|
+
BuildableName = "TestDumper.framework"
|
79
|
+
BlueprintName = "TestDumper"
|
80
|
+
ReferencedContainer = "container:TestDumper.xcodeproj">
|
81
|
+
</BuildableReference>
|
82
|
+
</MacroExpansion>
|
83
|
+
<AdditionalOptions>
|
84
|
+
</AdditionalOptions>
|
85
|
+
</LaunchAction>
|
86
|
+
<ProfileAction
|
87
|
+
buildConfiguration = "Release"
|
88
|
+
shouldUseLaunchSchemeArgsEnv = "YES"
|
89
|
+
savedToolIdentifier = ""
|
90
|
+
useCustomWorkingDirectory = "NO"
|
91
|
+
debugDocumentVersioning = "YES">
|
92
|
+
<MacroExpansion>
|
93
|
+
<BuildableReference
|
94
|
+
BuildableIdentifier = "primary"
|
95
|
+
BlueprintIdentifier = "F60C68B91B8D038300CC8521"
|
96
|
+
BuildableName = "TestDumper.framework"
|
97
|
+
BlueprintName = "TestDumper"
|
98
|
+
ReferencedContainer = "container:TestDumper.xcodeproj">
|
99
|
+
</BuildableReference>
|
100
|
+
</MacroExpansion>
|
101
|
+
</ProfileAction>
|
102
|
+
<AnalyzeAction
|
103
|
+
buildConfiguration = "Debug">
|
104
|
+
</AnalyzeAction>
|
105
|
+
<ArchiveAction
|
106
|
+
buildConfiguration = "Release"
|
107
|
+
revealArchiveInOrganizer = "YES">
|
108
|
+
</ArchiveAction>
|
109
|
+
</Scheme>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
3
|
+
<plist version="1.0">
|
4
|
+
<dict>
|
5
|
+
<key>CFBundleDevelopmentRegion</key>
|
6
|
+
<string>en</string>
|
7
|
+
<key>CFBundleExecutable</key>
|
8
|
+
<string>$(EXECUTABLE_NAME)</string>
|
9
|
+
<key>CFBundleIdentifier</key>
|
10
|
+
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
11
|
+
<key>CFBundleInfoDictionaryVersion</key>
|
12
|
+
<string>6.0</string>
|
13
|
+
<key>CFBundleName</key>
|
14
|
+
<string>$(PRODUCT_NAME)</string>
|
15
|
+
<key>CFBundlePackageType</key>
|
16
|
+
<string>FMWK</string>
|
17
|
+
<key>CFBundleShortVersionString</key>
|
18
|
+
<string>1.0</string>
|
19
|
+
<key>CFBundleSignature</key>
|
20
|
+
<string>????</string>
|
21
|
+
<key>CFBundleVersion</key>
|
22
|
+
<string>$(CURRENT_PROJECT_VERSION)</string>
|
23
|
+
<key>NSPrincipalClass</key>
|
24
|
+
<string></string>
|
25
|
+
</dict>
|
26
|
+
</plist>
|
@@ -0,0 +1,193 @@
|
|
1
|
+
//
|
2
|
+
// Initialize.m
|
3
|
+
// TestDumper
|
4
|
+
//
|
5
|
+
// Created by Mike Lewis on 8/25/15.
|
6
|
+
// Copyright (c) 2015 Square, Inc. All rights reserved.
|
7
|
+
//
|
8
|
+
|
9
|
+
@import Dispatch;
|
10
|
+
@import XCTest;
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
@class NSSet, NSString, NSURL, NSUUID;
|
16
|
+
|
17
|
+
@interface XCTestConfiguration : NSObject <NSSecureCoding>
|
18
|
+
{
|
19
|
+
NSURL *_testBundleURL;
|
20
|
+
NSSet *_testsToSkip;
|
21
|
+
NSSet *_testsToRun;
|
22
|
+
BOOL _reportResultsToIDE;
|
23
|
+
NSUUID *_sessionIdentifier;
|
24
|
+
NSString *_pathToXcodeReportingSocket;
|
25
|
+
BOOL _disablePerformanceMetrics;
|
26
|
+
BOOL _treatMissingBaselinesAsFailures;
|
27
|
+
NSURL *_baselineFileURL;
|
28
|
+
NSString *_targetApplicationPath;
|
29
|
+
NSString *_targetApplicationBundleID;
|
30
|
+
NSString *_productModuleName;
|
31
|
+
BOOL _reportActivities;
|
32
|
+
BOOL _testsMustRunOnMainThread;
|
33
|
+
}
|
34
|
+
|
35
|
+
+ (id)configurationWithContentsOfFile:(id)arg1;
|
36
|
+
+ (id)activeTestConfiguration;
|
37
|
+
+ (void)setActiveTestConfiguration:(id)arg1;
|
38
|
+
+ (BOOL)supportsSecureCoding;
|
39
|
+
@property BOOL testsMustRunOnMainThread; // @synthesize testsMustRunOnMainThread=_testsMustRunOnMainThread;
|
40
|
+
@property BOOL reportActivities; // @synthesize reportActivities=_reportActivities;
|
41
|
+
@property(copy) NSString *productModuleName; // @synthesize productModuleName=_productModuleName;
|
42
|
+
@property(copy) NSString *targetApplicationBundleID; // @synthesize targetApplicationBundleID=_targetApplicationBundleID;
|
43
|
+
@property(copy) NSString *targetApplicationPath; // @synthesize targetApplicationPath=_targetApplicationPath;
|
44
|
+
@property BOOL treatMissingBaselinesAsFailures; // @synthesize treatMissingBaselinesAsFailures=_treatMissingBaselinesAsFailures;
|
45
|
+
@property BOOL disablePerformanceMetrics; // @synthesize disablePerformanceMetrics=_disablePerformanceMetrics;
|
46
|
+
@property BOOL reportResultsToIDE; // @synthesize reportResultsToIDE=_reportResultsToIDE;
|
47
|
+
@property(copy) NSURL *baselineFileURL; // @synthesize baselineFileURL=_baselineFileURL;
|
48
|
+
@property(copy) NSString *pathToXcodeReportingSocket; // @synthesize pathToXcodeReportingSocket=_pathToXcodeReportingSocket;
|
49
|
+
@property(copy) NSUUID *sessionIdentifier; // @synthesize sessionIdentifier=_sessionIdentifier;
|
50
|
+
@property(copy) NSSet *testsToSkip; // @synthesize testsToSkip=_testsToSkip;
|
51
|
+
@property(copy) NSSet *testsToRun; // @synthesize testsToRun=_testsToRun;
|
52
|
+
@property(copy) NSURL *testBundleURL; // @synthesize testBundleURL=_testBundleURL;
|
53
|
+
- (BOOL)isEqual:(id)arg1;
|
54
|
+
- (unsigned long long)hash;
|
55
|
+
- (id)description;
|
56
|
+
- (BOOL)writeToFile:(id)arg1;
|
57
|
+
- (void)encodeWithCoder:(id)arg1;
|
58
|
+
- (id)initWithCoder:(id)arg1;
|
59
|
+
- (id)init;
|
60
|
+
- (void)dealloc;
|
61
|
+
|
62
|
+
@end
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
@interface XCTestSuite (DumpAdditions)
|
67
|
+
|
68
|
+
- (void)printTestsWithLevel:(NSInteger)level withTarget:(NSString*) target withParent:(NSString*) parent outputFile:(FILE *)outputFile;
|
69
|
+
|
70
|
+
@end
|
71
|
+
|
72
|
+
#include <dlfcn.h>
|
73
|
+
|
74
|
+
|
75
|
+
// Used for a structured log, just like Xctool's.
|
76
|
+
// Example: https://github.com/square/xcknife/blob/master/example/xcknife-exemplar.json-stream
|
77
|
+
static void PrintJSON(FILE *outFile, id JSONObject)
|
78
|
+
{
|
79
|
+
NSError *error = nil;
|
80
|
+
NSData *data = [NSJSONSerialization dataWithJSONObject:JSONObject options:0 error:&error];
|
81
|
+
|
82
|
+
if (error) {
|
83
|
+
fprintf(outFile, "{ \"message\" : \"Error while serializing to JSON. Check out simulator logs for details\" }");
|
84
|
+
NSLog(@"ERROR: Error generating JSON for object: %s: %s\n",
|
85
|
+
[[JSONObject description] UTF8String],
|
86
|
+
[[error localizedFailureReason] UTF8String]);
|
87
|
+
exit(1);
|
88
|
+
}
|
89
|
+
|
90
|
+
fwrite([data bytes], 1, [data length], outFile);
|
91
|
+
fprintf(outFile, "\n");
|
92
|
+
}
|
93
|
+
|
94
|
+
static void PrintDumpStart(FILE *outFile, NSString *testType) {
|
95
|
+
PrintJSON(outFile, @{@"message" : @"Starting Test Dumper",
|
96
|
+
@"testType" : testType,
|
97
|
+
@"event": @"begin-test-suite"});
|
98
|
+
}
|
99
|
+
|
100
|
+
static void PrintDumpEnd(FILE *outFile, NSString *testType) {
|
101
|
+
PrintJSON(outFile, @{@"message" : @"Completed Test Dumper",
|
102
|
+
@"testType" : testType,
|
103
|
+
@"event": @"end-action"});
|
104
|
+
}
|
105
|
+
|
106
|
+
static void PrintTestTarget(FILE *outFile, NSString *targetName, NSString *bundleName) {
|
107
|
+
PrintJSON(outFile, @{@"event" : @"begin-ocunit", @"bundleName" : bundleName, @"targetName" : targetName});
|
108
|
+
}
|
109
|
+
|
110
|
+
static void PrintTestClass(FILE *outFile, NSString *testClass) {
|
111
|
+
PrintJSON(outFile, @{@"className" : testClass,
|
112
|
+
@"test" : @"1",
|
113
|
+
@"event" : @"end-test",
|
114
|
+
@"totalDuration" : @"0"});
|
115
|
+
}
|
116
|
+
|
117
|
+
const int TEST_TARGET_LEVEL = 0;
|
118
|
+
const int TEST_CLASS_LEVEL = 1;
|
119
|
+
const int TEST_METHOD_LEVEL = 2;
|
120
|
+
|
121
|
+
__attribute__((constructor))
|
122
|
+
void initialize() {
|
123
|
+
XCTestConfiguration *config = [[XCTestConfiguration alloc] init];
|
124
|
+
NSString *testType = [NSString stringWithUTF8String: getenv("XCTEST_TYPE")];
|
125
|
+
NSString *testTarget = [NSString stringWithUTF8String: getenv("XCTEST_TARGET")];
|
126
|
+
|
127
|
+
|
128
|
+
if ([testType isEqualToString: @"APPTEST"]) {
|
129
|
+
config.testBundleURL = [NSURL fileURLWithPath:NSProcessInfo.processInfo.environment[@"XCInjectBundle"]];
|
130
|
+
config.targetApplicationPath = NSProcessInfo.processInfo.environment[@"XCInjectBundleInto"];
|
131
|
+
|
132
|
+
NSString *configPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"%ul.xctestconfiguration", arc4random()]];
|
133
|
+
|
134
|
+
NSLog(@"Writing config to %@", configPath);
|
135
|
+
|
136
|
+
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:config];
|
137
|
+
|
138
|
+
[data writeToFile:configPath atomically:true];
|
139
|
+
|
140
|
+
setenv("XCTestConfigurationFilePath", configPath.UTF8String, YES);
|
141
|
+
|
142
|
+
dlopen(getenv("IDE_INJECTION_PATH"), RTLD_GLOBAL);
|
143
|
+
}
|
144
|
+
NSString *testBundle = [[[NSProcessInfo processInfo] arguments] lastObject];
|
145
|
+
[[NSBundle bundleWithPath:testBundle] load];
|
146
|
+
|
147
|
+
FILE *outFile;
|
148
|
+
NSString *testDumperOutputPath = NSProcessInfo.processInfo.environment[@"TestDumperOutputPath"];
|
149
|
+
|
150
|
+
if (testDumperOutputPath == nil) {
|
151
|
+
outFile = stdout;
|
152
|
+
} else {
|
153
|
+
outFile = fopen(testDumperOutputPath.UTF8String, "w+");
|
154
|
+
}
|
155
|
+
|
156
|
+
NSLog(@"Opened %@ with fd %p", testDumperOutputPath, outFile);
|
157
|
+
|
158
|
+
PrintDumpStart(outFile, testType);
|
159
|
+
[[XCTestSuite defaultTestSuite] printTestsWithLevel:0 withTarget: testTarget withParent: nil outputFile:outFile];
|
160
|
+
PrintDumpEnd(outFile, testType);
|
161
|
+
fclose(outFile);
|
162
|
+
exit(0);
|
163
|
+
}
|
164
|
+
|
165
|
+
// This test enumerates the Xctest classes and targets, in the json-stream format. We only enumerate the first test method,
|
166
|
+
// since xcknife does use test method level information (ref: https://github.com/square/xcknife)
|
167
|
+
@implementation XCTestSuite (DumpAdditions)
|
168
|
+
|
169
|
+
- (void)printTestsWithLevel:(NSInteger)level withTarget:(NSString*) target withParent:(NSString*) parent outputFile:(FILE *)outputFile;
|
170
|
+
{
|
171
|
+
|
172
|
+
for (XCTest *t in self.tests) {
|
173
|
+
switch (level) {
|
174
|
+
case TEST_TARGET_LEVEL :
|
175
|
+
PrintTestTarget(outputFile, target, t.name);
|
176
|
+
break;
|
177
|
+
case TEST_METHOD_LEVEL:
|
178
|
+
PrintTestClass(outputFile, parent);
|
179
|
+
break;
|
180
|
+
default:
|
181
|
+
NSLog(@"Uknown level %ld", level);
|
182
|
+
|
183
|
+
}
|
184
|
+
if (level == TEST_METHOD_LEVEL) {
|
185
|
+
break;
|
186
|
+
}
|
187
|
+
if ([t isKindOfClass:[XCTestSuite class]]) {
|
188
|
+
[(XCTestSuite *)t printTestsWithLevel: (level + 1) withTarget: target withParent: t.name outputFile:outputFile];
|
189
|
+
}
|
190
|
+
}
|
191
|
+
}
|
192
|
+
|
193
|
+
@end
|
data/TestDumper/build.sh
ADDED
data/bin/xcknife
CHANGED
File without changes
|
data/bin/xcknife-min
CHANGED
@@ -8,7 +8,7 @@ require 'json' unless defined?(::JSON)
|
|
8
8
|
INTERESTING_EVENTS = %w[begin-ocunit end-test].to_set
|
9
9
|
def cleanup(input_file_name, output_file_name)
|
10
10
|
if input_file_name.nil? or output_file_name.nil?
|
11
|
-
return puts "Usage:
|
11
|
+
return puts "Usage: xcknife-min [input] [output]"
|
12
12
|
end
|
13
13
|
lines = IO.readlines(input_file_name)
|
14
14
|
lines_written = 0
|
data/example/README.md
CHANGED
@@ -10,6 +10,4 @@ $ ruby run_example.rb
|
|
10
10
|
|
11
11
|
This is equivalent to the example of:
|
12
12
|
|
13
|
-
|
14
|
-
$ xcknife -p CommonTestTarget -p CommonTestTarget,iPadTestTarget 6 example/xcknife-exemplar-historical-data.json-stream example/xcknife-exemplar.json-stream
|
15
|
-
```
|
13
|
+
`$ xcknife -p CommonTestTarget -p CommonTestTarget,iPadTestTarget 6 example/xcknife-exemplar-historical-data.json-stream example/xcknife-exemplar.json-stream`
|
data/lib/xcknife.rb
CHANGED
data/lib/xcknife/runner.rb
CHANGED
@@ -12,6 +12,7 @@ module XCKnife
|
|
12
12
|
|
13
13
|
def initialize(args)
|
14
14
|
@abbreviated_output = false
|
15
|
+
@xcodebuild_output = false
|
15
16
|
@partitions = []
|
16
17
|
@partition_names = []
|
17
18
|
@worker_count = nil
|
@@ -30,9 +31,15 @@ module XCKnife
|
|
30
31
|
warn "Error: #{e}"
|
31
32
|
exit 1
|
32
33
|
end
|
34
|
+
|
33
35
|
private
|
34
36
|
def gen_abbreviated_output(result)
|
35
|
-
result.test_maps.map { |partition_set|
|
37
|
+
result.test_maps.map { |partition_set| only_arguments_for_a_partition_set(partition_set, output_type) }
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
def output_type
|
42
|
+
@xcodebuild_output ? :xcodebuild : :xctool
|
36
43
|
end
|
37
44
|
|
38
45
|
def gen_full_output(result)
|
@@ -66,7 +73,7 @@ module XCKnife
|
|
66
73
|
def partition_data(result, shard_number, partition, partition_set_i, partition_j)
|
67
74
|
{
|
68
75
|
shard_number: shard_number,
|
69
|
-
cli_arguments:
|
76
|
+
cli_arguments: only_arguments(output_type, partition),
|
70
77
|
partition_imbalance_ratio: result.test_time_imbalances.partitions[partition_set_i][partition_j]
|
71
78
|
}
|
72
79
|
end
|
@@ -109,6 +116,7 @@ module XCKnife
|
|
109
116
|
end
|
110
117
|
opts.on("-o", "--output FILENAME", "Output file. Defaults to STDOUT") { |v| @output_file_name = v }
|
111
118
|
opts.on("-a", "--abbrev", "Results are abbreviated") { |v| @abbreviated_output = v }
|
119
|
+
opts.on("-x", "--xcodebuild-output", "Output is formatted for xcodebuild") { |v| @xcodebuild_output = v }
|
112
120
|
|
113
121
|
opts.on_tail("-h", "--help", "Show this message") do
|
114
122
|
puts opts
|
@@ -0,0 +1,196 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'pp'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'tmpdir'
|
5
|
+
require 'ostruct'
|
6
|
+
require 'set'
|
7
|
+
|
8
|
+
module XCKnife
|
9
|
+
class TestDumper
|
10
|
+
def self.invoke
|
11
|
+
new(ARGV).run
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(args)
|
15
|
+
@derived_data_folder, @output_file, @device_id = args
|
16
|
+
@device_id ||= "booted"
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
if @derived_data_folder.nil? or @output_file.nil?
|
21
|
+
return puts "Usage: xcknife-test-dumper [derived_data_folder] [output_file] [<device_id>]"
|
22
|
+
end
|
23
|
+
helper = TestDumperHelper.new(@device_id)
|
24
|
+
output_fd = File.open(@output_file, "w")
|
25
|
+
Dir.mktmpdir("xctestdumper_") do |outfolder|
|
26
|
+
helper.call(@derived_data_folder, outfolder).each do |test_specification|
|
27
|
+
concat_to_file(test_specification, output_fd)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
output_fd.close
|
31
|
+
puts "Done listing test methods"
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def concat_to_file(test_specification, output_fd)
|
36
|
+
file = test_specification.json_stream_file
|
37
|
+
wait_test_dumper_completion(file)
|
38
|
+
IO.readlines(file).each do |line|
|
39
|
+
event = OpenStruct.new(JSON.load(line))
|
40
|
+
output_fd.write(line) unless should_test_event_be_ignored?(test_specification, event)
|
41
|
+
end
|
42
|
+
output_fd.flush
|
43
|
+
end
|
44
|
+
|
45
|
+
# Current limitation: this only supports class level skipping
|
46
|
+
def should_test_event_be_ignored?(test_specification, event)
|
47
|
+
return false unless event["test"] == "1"
|
48
|
+
test_specification.skip_test_identifiers.include?(event["className"])
|
49
|
+
end
|
50
|
+
|
51
|
+
def wait_test_dumper_completion(file)
|
52
|
+
retries_count = 0
|
53
|
+
until has_test_dumper_terminated?(file) do
|
54
|
+
retries_count += 1
|
55
|
+
assert_has_not_timed_out(retries_count, file)
|
56
|
+
sleep 0.1
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def assert_has_not_timed_out(retries_count, file)
|
61
|
+
if retries_count == 100
|
62
|
+
puts "Timeout error on: #{file}"
|
63
|
+
exit 1
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def has_test_dumper_terminated?(file)
|
68
|
+
return false unless File.exists?(file)
|
69
|
+
last_line = `tail -n 1 "#{file}"`
|
70
|
+
return /Completed Test Dumper/.match(last_line)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
class TestDumperHelper
|
76
|
+
TestSpecification = Struct.new :json_stream_file, :skip_test_identifiers
|
77
|
+
|
78
|
+
def initialize(device_id)
|
79
|
+
@xcode_path = `xcode-select -p`.strip
|
80
|
+
@simctl_path = `xcrun -f simctl`.strip
|
81
|
+
@platforms_path = "#{@xcode_path}/Platforms/"
|
82
|
+
@platform_path = "#{@platforms_path}/iPhoneSimulator.platform"
|
83
|
+
@sdk_path = "#{@platform_path}/Developer/SDKs/iPhoneSimulator.sdk"
|
84
|
+
@testroot = nil
|
85
|
+
@device_id = device_id
|
86
|
+
end
|
87
|
+
|
88
|
+
def call(derived_data_folder, list_folder)
|
89
|
+
@testroot = "#{derived_data_folder}/Build/Products/"
|
90
|
+
xctestrun_file = Dir["#{@testroot}/*.xctestrun"].first
|
91
|
+
if xctestrun_file.nil?
|
92
|
+
puts "No xctestrun on #{@testroot}"
|
93
|
+
exit 1
|
94
|
+
end
|
95
|
+
xctestrun_as_json = `plutil -convert json -o - "#{xctestrun_file}"`
|
96
|
+
FileUtils.mkdir_p(list_folder)
|
97
|
+
JSON.load(xctestrun_as_json).map do |test_bundle_name, test_bundle|
|
98
|
+
list_tests_wiht_simctl(list_folder, test_bundle, test_bundle_name)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def list_tests_wiht_simctl(list_folder, test_bundle, test_bundle_name)
|
103
|
+
env_variables = test_bundle["EnvironmentVariables"]
|
104
|
+
testing_env_variables = test_bundle["TestingEnvironmentVariables"]
|
105
|
+
outpath = "#{list_folder}/#{test_bundle_name}"
|
106
|
+
test_host = replace_vars(test_bundle["TestHostPath"])
|
107
|
+
test_bundle_path = replace_vars(test_bundle["TestBundlePath"], test_host)
|
108
|
+
test_dumper_path = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'TestDumper', 'TestDumper.dylib'))
|
109
|
+
unless File.exist?(test_dumper_path)
|
110
|
+
warn "Could not find TestDumpber.dylib on #{test_dumper_path}"
|
111
|
+
exit 1
|
112
|
+
end
|
113
|
+
|
114
|
+
is_logic_test = test_bundle["TestHostBundleIdentifier"].nil?
|
115
|
+
env = simctl_child_attrs(
|
116
|
+
"XCTEST_TYPE" => is_logic_test ? "LOGICTEST" : "APPTEST",
|
117
|
+
"XCTEST_TARGET" => test_bundle_name,
|
118
|
+
"TestDumperOutputPath" => outpath,
|
119
|
+
"IDE_INJECTION_PATH" => testing_env_variables["DYLD_INSERT_LIBRARIES"],
|
120
|
+
"XCInjectBundleInto" => testing_env_variables["XCInjectBundleInto"],
|
121
|
+
"XCInjectBundle" => test_bundle_path,
|
122
|
+
"TestBundleLocation" => test_bundle_path,
|
123
|
+
"OS_ACTIVITY_MODE" => "disable",
|
124
|
+
"DYLD_PRINT_LIBRARIES" => "YES",
|
125
|
+
"DYLD_PRINT_ENV" => "YES",
|
126
|
+
"DYLD_ROOT_PATH" => @sdk_path,
|
127
|
+
"DYLD_LIBRARY_PATH" => env_variables["DYLD_LIBRARY_PATH"],
|
128
|
+
"DYLD_FRAMEWORK_PATH" => env_variables["DYLD_FRAMEWORK_PATH"],
|
129
|
+
"DYLD_FALLBACK_LIBRARY_PATH" => "#{@sdk_path}/usr/lib",
|
130
|
+
"DYLD_FALLBACK_FRAMEWORK_PATH" => "#{@platform_path}/Developer/Library/Frameworks",
|
131
|
+
"DYLD_INSERT_LIBRARIES" => test_dumper_path,
|
132
|
+
)
|
133
|
+
inject_vars(env, test_host)
|
134
|
+
if is_logic_test
|
135
|
+
run_logic_test(env, test_host, test_bundle_path)
|
136
|
+
else
|
137
|
+
install_app(test_host)
|
138
|
+
test_host_bundle_identifier = replace_vars(test_bundle["TestHostBundleIdentifier"], test_host)
|
139
|
+
run_apptest(env, test_host_bundle_identifier, test_bundle_path)
|
140
|
+
end
|
141
|
+
return TestSpecification.new outpath, discover_tests_to_skip(test_bundle)
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def discover_tests_to_skip(test_bundle)
|
147
|
+
identifier_for_test_method = "/"
|
148
|
+
skip_test_identifiers = test_bundle["SkipTestIdentifiers"] || []
|
149
|
+
skip_test_identifiers.reject { |i| i.include?(identifier_for_test_method) }.to_set
|
150
|
+
end
|
151
|
+
|
152
|
+
def simctl
|
153
|
+
@simctl_path
|
154
|
+
end
|
155
|
+
|
156
|
+
def replace_vars(str, testhost = "<UNKNOWN>")
|
157
|
+
str.gsub("__PLATFORMS__", @platforms_path).
|
158
|
+
gsub("__TESTHOST__", testhost).
|
159
|
+
gsub("__TESTROOT__", @testroot)
|
160
|
+
end
|
161
|
+
|
162
|
+
def inject_vars(env, test_host)
|
163
|
+
env.each do |k, v|
|
164
|
+
env[k] = replace_vars(v || "", test_host)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def simctl_child_attrs(attrs = {})
|
169
|
+
env = {}
|
170
|
+
attrs.each { |k, v| env["SIMCTL_CHILD_#{k}"] = v }
|
171
|
+
env
|
172
|
+
end
|
173
|
+
|
174
|
+
def install_app(test_host_path)
|
175
|
+
until system("#{simctl} install #{@device_id} '#{test_host_path}'")
|
176
|
+
sleep 0.1
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def run_apptest(env, test_host_bundle_identifier, test_bundle_path)
|
181
|
+
call_simctl env, "launch #{@device_id} '#{test_host_bundle_identifier}' -XCTest All '#{test_bundle_path}'"
|
182
|
+
end
|
183
|
+
|
184
|
+
def run_logic_test(env, test_host, test_bundle_path)
|
185
|
+
call_simctl env, "spawn #{@device_id} '#{test_host}' -XCTest All '#{test_bundle_path}' 2> /dev/null"
|
186
|
+
end
|
187
|
+
|
188
|
+
def call_simctl(env, string_args)
|
189
|
+
cmd = "#{simctl} #{string_args}"
|
190
|
+
puts "Running:\n$ #{cmd}"
|
191
|
+
unless system(env, cmd)
|
192
|
+
puts "Simctl errored with the following env:\n #{env.pretty_print_inspect}"
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|