xcknife 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|