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.
@@ -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