xcknife 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,7 @@
1
+ #import <UIKit/UIKit.h>
2
+
3
+ //! Project version number for TestDumper.
4
+ FOUNDATION_EXPORT double TestDumperVersionNumber;
5
+
6
+ //! Project version string for TestDumper.
7
+ FOUNDATION_EXPORT const unsigned char TestDumperVersionString[];
@@ -0,0 +1,6 @@
1
+ #!/bin/bash
2
+ xcodebuild -project TestDumper.xcodeproj \
3
+ -configuration Debug \
4
+ -derivedDataPath testdumperbuild \
5
+ -scheme TestDumper \
6
+ -sdk iphonesimulator build
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: xcknife_clean [input] [output]"
11
+ return puts "Usage: xcknife-min [input] [output]"
12
12
  end
13
13
  lines = IO.readlines(input_file_name)
14
14
  lines_written = 0
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'xcknife'
3
+
4
+ XCKnife::TestDumper.invoke
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
@@ -2,8 +2,9 @@ require 'xcknife/events_analyzer'
2
2
  require 'xcknife/stream_parser'
3
3
  require 'xcknife/xctool_cmd_helper'
4
4
  require 'xcknife/runner'
5
+ require 'xcknife/test_dumper'
5
6
  require 'xcknife/exceptions'
6
7
 
7
8
  module XCKnife
8
- VERSION = '0.5.0'
9
+ VERSION = '0.6.0'
9
10
  end
@@ -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| xctool_only_arguments_for_a_partition_set(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: xctool_only_arguments(partition),
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