xcknife 0.6.3 → 0.6.4
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/.travis.yml +16 -7
- data/README.md +1 -1
- data/Rakefile +1 -1
- data/TestDumper/README.md +14 -1
- data/TestDumper/TestDumper/Initialize.m +15 -1
- data/TestDumper/build.sh +3 -2
- data/lib/xcknife.rb +2 -1
- data/lib/xcknife/test_dumper.rb +121 -22
- data/lib/xcknife/xcscheme_analyzer.rb +29 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1e7dcc40d67413a5b35f7eba1289d75724965a63
|
4
|
+
data.tar.gz: 7ea917dd613f5fcdd8e6a41471538c01bf9df4d2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b786211a86a81b9483a8721de939de4bcb7eba683698e334d6f299e706153ee607a3b8de6621599465cecbf236b42805618f2e71812a664c9c9cf8abd20c8ae3
|
7
|
+
data.tar.gz: 1295e3a8a1e7040d0acadf14d42e2c36838f9fdde95c8d57c83b4bcaa4986165b689239a27cdecbdb512233110b3d54c40d1175c649f7c8b7c267642495c47bf
|
data/.travis.yml
CHANGED
@@ -1,9 +1,18 @@
|
|
1
|
-
language:
|
1
|
+
language: objective-c
|
2
2
|
sudo: false
|
3
|
-
|
4
|
-
|
3
|
+
matrix:
|
4
|
+
include:
|
5
|
+
- os: osx
|
6
|
+
osx_image: xcode8
|
7
|
+
- os: osx
|
8
|
+
osx_image: xcode8.1
|
9
|
+
- os: osx
|
10
|
+
osx_image: xcode8.2
|
5
11
|
|
6
|
-
|
7
|
-
-
|
8
|
-
|
9
|
-
|
12
|
+
before_script:
|
13
|
+
- export LANG=en_US.UTF-8
|
14
|
+
install: bundle
|
15
|
+
script:
|
16
|
+
- csrutil status
|
17
|
+
- git submodule update --init --recursive
|
18
|
+
- bundle exec rake build_test_dumper spec
|
data/README.md
CHANGED
@@ -18,7 +18,7 @@ More information on XCKnife, go [here](https://corner.squareup.com/2016/06/xckni
|
|
18
18
|
## Using as command line tool
|
19
19
|
|
20
20
|
```
|
21
|
-
$ xcknife
|
21
|
+
$ xcknife --help
|
22
22
|
Usage: xcknife [options] worker-count historical-timings-json-stream-file [current-tests-json-stream-file]
|
23
23
|
-p, --partition TARGETS Comma separated list of targets. Can be used multiple times
|
24
24
|
-o, --output FILENAME Output file. Defaults to STDOUT
|
data/Rakefile
CHANGED
@@ -11,7 +11,7 @@ task :build_test_dumper do
|
|
11
11
|
target_dir = File.join(File.dirname(__FILE__), "TestDumper")
|
12
12
|
Dir.chdir(target_dir) do
|
13
13
|
system "./build.sh"
|
14
|
-
FileUtils.copy_file("./testdumperbuild/Build/Products/
|
14
|
+
FileUtils.copy_file("./testdumperbuild/Build/Products/Debug-iphonesimulator/TestDumper.framework/TestDumper", "./TestDumper.dylib")
|
15
15
|
puts "TestDumper.dylib was created successfully"
|
16
16
|
end
|
17
17
|
end
|
data/TestDumper/README.md
CHANGED
@@ -8,4 +8,17 @@ Run
|
|
8
8
|
|
9
9
|
```
|
10
10
|
$ ./build.sh
|
11
|
-
````
|
11
|
+
````
|
12
|
+
|
13
|
+
## Using as command line tool
|
14
|
+
|
15
|
+
```
|
16
|
+
$ xcknife-test-dumper --help
|
17
|
+
Usage: xcknife-test-dumper [options] derived_data_folder output_file [device_id]
|
18
|
+
-d, --debug Debug mode enabled
|
19
|
+
-r, --retry-count COUNT Max retry count for simulator output
|
20
|
+
-t OUTPUT_FOLDER, Sets temporary Output folder
|
21
|
+
--temporary-output
|
22
|
+
-s, --scheme XCSCHEME_FILE Reads environments variables from the xcscheme file
|
23
|
+
-h, --help Show this message
|
24
|
+
```
|
@@ -120,9 +120,16 @@ const int TEST_TARGET_LEVEL = 0;
|
|
120
120
|
const int TEST_CLASS_LEVEL = 1;
|
121
121
|
const int TEST_METHOD_LEVEL = 2;
|
122
122
|
|
123
|
-
|
124
123
|
__attribute__((constructor))
|
125
124
|
void initialize() {
|
125
|
+
NSLog(@"Starting TestDumper");
|
126
|
+
NSString *testDumperOutputPath = NSProcessInfo.processInfo.environment[@"TestDumperOutputPath"];
|
127
|
+
NSFileManager *fileManager = [NSFileManager defaultManager];
|
128
|
+
|
129
|
+
if ([fileManager fileExistsAtPath:testDumperOutputPath]) {
|
130
|
+
NSLog(@"File already exists %@. Stopping", testDumperOutputPath);
|
131
|
+
exit(0);
|
132
|
+
}
|
126
133
|
NSString *testType = [NSString stringWithUTF8String: getenv("XCTEST_TYPE")];
|
127
134
|
|
128
135
|
if ([testType isEqualToString: @"APPTEST"]) {
|
@@ -169,6 +176,10 @@ void enumerateTests() {
|
|
169
176
|
}
|
170
177
|
|
171
178
|
NSLog(@"Opened %@ with fd %p", testDumperOutputPath, outFile);
|
179
|
+
if (outFile == NULL) {
|
180
|
+
NSLog(@"File already exists %@. Stopping", testDumperOutputPath);
|
181
|
+
exit(0);
|
182
|
+
}
|
172
183
|
|
173
184
|
PrintDumpStart(outFile, testType);
|
174
185
|
[[XCTestSuite defaultTestSuite] printTestsWithLevel:0 withTarget: testTarget withParent: nil outputFile:outFile];
|
@@ -193,6 +204,9 @@ void enumerateTests() {
|
|
193
204
|
case TEST_METHOD_LEVEL:
|
194
205
|
PrintTestClass(outputFile, parent);
|
195
206
|
break;
|
207
|
+
case TEST_CLASS_LEVEL:
|
208
|
+
// nothing to do here
|
209
|
+
break;
|
196
210
|
default:
|
197
211
|
NSLog(@"Uknown level %ld", level);
|
198
212
|
|
data/TestDumper/build.sh
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
#!/bin/bash
|
2
|
+
rm -rf TestDumper.dylib testdumperbuild
|
2
3
|
xcodebuild -project TestDumper.xcodeproj \
|
3
|
-
-configuration
|
4
|
+
-configuration Debug \
|
4
5
|
-derivedDataPath testdumperbuild \
|
5
6
|
-scheme TestDumper \
|
6
|
-
-sdk iphonesimulator build
|
7
|
+
-sdk iphonesimulator build ONLY_ACTIVE_ARCH=NO
|
data/lib/xcknife.rb
CHANGED
data/lib/xcknife/test_dumper.rb
CHANGED
@@ -4,6 +4,7 @@ require 'fileutils'
|
|
4
4
|
require 'tmpdir'
|
5
5
|
require 'ostruct'
|
6
6
|
require 'set'
|
7
|
+
require 'logger'
|
7
8
|
|
8
9
|
module XCKnife
|
9
10
|
class TestDumper
|
@@ -11,32 +12,120 @@ module XCKnife
|
|
11
12
|
new(ARGV).run
|
12
13
|
end
|
13
14
|
|
15
|
+
attr_reader :logger
|
16
|
+
|
14
17
|
def initialize(args)
|
15
|
-
@
|
18
|
+
@debug = false
|
19
|
+
@max_retry_count = 150
|
20
|
+
@temporary_output_folder = nil
|
21
|
+
@xcscheme_file = nil
|
22
|
+
@parser = build_parser
|
23
|
+
parse_arguments(args)
|
16
24
|
@device_id ||= "booted"
|
25
|
+
@logger = Logger.new($stdout)
|
26
|
+
@logger.level = @debug ? Logger::DEBUG : Logger::FATAL
|
27
|
+
@parser = nil
|
17
28
|
end
|
18
29
|
|
19
30
|
def run
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
helper = TestDumperHelper.new(@device_id)
|
31
|
+
helper = TestDumperHelper.new(@device_id, @max_retry_count, @debug, @logger)
|
32
|
+
extra_environment_variables = parse_scheme_file
|
33
|
+
logger.info { "Environment variables from xcscheme: #{extra_environment_variables.pretty_inspect}" }
|
24
34
|
output_fd = File.open(@output_file, "w")
|
25
|
-
|
26
|
-
|
27
|
-
|
35
|
+
if @temporary_output_folder.nil?
|
36
|
+
Dir.mktmpdir("xctestdumper_") do |outfolder|
|
37
|
+
list_tests(extra_environment_variables, helper, outfolder, output_fd)
|
38
|
+
end
|
39
|
+
else
|
40
|
+
unless File.directory?(@temporary_output_folder)
|
41
|
+
puts "Error no such directory: #{@temporary_output_folder}"
|
42
|
+
exit 1
|
43
|
+
end
|
44
|
+
|
45
|
+
if Dir.entries(@temporary_output_folder).any? { |f| File.file?(File.join(@temporary_output_folder,f)) }
|
46
|
+
puts "Warning: #{@temporary_output_folder} is not empty! Files can be overwritten."
|
28
47
|
end
|
48
|
+
list_tests(extra_environment_variables, helper, File.absolute_path(@temporary_output_folder), output_fd)
|
29
49
|
end
|
30
50
|
output_fd.close
|
31
51
|
puts "Done listing test methods"
|
32
52
|
end
|
33
53
|
|
34
54
|
private
|
55
|
+
def list_tests(extra_environment_variables, helper, outfolder, output_fd)
|
56
|
+
helper.call(@derived_data_folder, outfolder, extra_environment_variables).each do |test_specification|
|
57
|
+
concat_to_file(test_specification, output_fd)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
def parse_scheme_file
|
63
|
+
return {} unless @xcscheme_file
|
64
|
+
unless File.exists?(@xcscheme_file)
|
65
|
+
puts "Error: no such xcscheme file: #{@xcscheme_file}"
|
66
|
+
exit 1
|
67
|
+
end
|
68
|
+
XCKnife::XcschemeAnalyzer.extract_environment_variables(IO.read(@xcscheme_file))
|
69
|
+
end
|
70
|
+
|
71
|
+
def parse_arguments(args)
|
72
|
+
positional_arguments = parse_options(args)
|
73
|
+
if positional_arguments.size < required_arguments.size
|
74
|
+
warn_and_exit("You must specify *all* required arguments: #{required_arguments.join(", ")}")
|
75
|
+
end
|
76
|
+
@derived_data_folder, @output_file, @device_id = positional_arguments
|
77
|
+
end
|
78
|
+
|
79
|
+
def parse_options(args)
|
80
|
+
begin
|
81
|
+
return @parser.parse(args)
|
82
|
+
rescue OptionParser::ParseError => error
|
83
|
+
warn_and_exit(error)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def build_parser
|
88
|
+
OptionParser.new do |opts|
|
89
|
+
opts.banner += " #{arguments_banner}"
|
90
|
+
opts.on("-d", "--debug", "Debug mode enabled") { |v| @debug = v }
|
91
|
+
opts.on("-r", "--retry-count COUNT", "Max retry count for simulator output", Integer) { |v| @max_retry_count = v }
|
92
|
+
opts.on("-t", "--temporary-output OUTPUT_FOLDER", "Sets temporary Output folder") { |v| @temporary_output_folder = v }
|
93
|
+
opts.on("-s", "--scheme XCSCHEME_FILE", "Reads environments variables from the xcscheme file") { |v| @xcscheme_file = v }
|
94
|
+
|
95
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
96
|
+
puts opts
|
97
|
+
exit
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def required_arguments
|
103
|
+
%w[derived_data_folder output_file]
|
104
|
+
end
|
105
|
+
|
106
|
+
def optional_arguments
|
107
|
+
%w[device_id]
|
108
|
+
end
|
109
|
+
|
110
|
+
def arguments_banner
|
111
|
+
optional_args = optional_arguments.map { |a| "[#{a}]" }
|
112
|
+
(required_arguments + optional_args).join(" ")
|
113
|
+
end
|
114
|
+
|
115
|
+
def warn_and_exit(msg)
|
116
|
+
warn "#{msg.to_s.capitalize} \n\n#{@parser}"
|
117
|
+
exit 1
|
118
|
+
end
|
119
|
+
|
35
120
|
def concat_to_file(test_specification, output_fd)
|
36
121
|
file = test_specification.json_stream_file
|
37
122
|
IO.readlines(file).each do |line|
|
38
123
|
event = OpenStruct.new(JSON.load(line))
|
39
|
-
|
124
|
+
if should_test_event_be_ignored?(test_specification, event)
|
125
|
+
logger.info "Skipped test dumper line #{line}"
|
126
|
+
else
|
127
|
+
output_fd.write(line)
|
128
|
+
end
|
40
129
|
output_fd.flush
|
41
130
|
end
|
42
131
|
output_fd.flush
|
@@ -52,7 +141,9 @@ module XCKnife
|
|
52
141
|
class TestDumperHelper
|
53
142
|
TestSpecification = Struct.new :json_stream_file, :skip_test_identifiers
|
54
143
|
|
55
|
-
|
144
|
+
attr_reader :logger
|
145
|
+
|
146
|
+
def initialize(device_id, max_retry_count, debug, logger)
|
56
147
|
@xcode_path = `xcode-select -p`.strip
|
57
148
|
@simctl_path = `xcrun -f simctl`.strip
|
58
149
|
@platforms_path = "#{@xcode_path}/Platforms/"
|
@@ -60,9 +151,12 @@ module XCKnife
|
|
60
151
|
@sdk_path = "#{@platform_path}/Developer/SDKs/iPhoneSimulator.sdk"
|
61
152
|
@testroot = nil
|
62
153
|
@device_id = device_id
|
154
|
+
@max_retry_count = max_retry_count
|
155
|
+
@logger = logger
|
156
|
+
@debug = debug
|
63
157
|
end
|
64
158
|
|
65
|
-
def call(derived_data_folder, list_folder)
|
159
|
+
def call(derived_data_folder, list_folder, extra_environment_variables = {})
|
66
160
|
@testroot = "#{derived_data_folder}/Build/Products/"
|
67
161
|
xctestrun_file = Dir["#{@testroot}/*.xctestrun"].first
|
68
162
|
if xctestrun_file.nil?
|
@@ -72,14 +166,14 @@ module XCKnife
|
|
72
166
|
xctestrun_as_json = `plutil -convert json -o - "#{xctestrun_file}"`
|
73
167
|
FileUtils.mkdir_p(list_folder)
|
74
168
|
JSON.load(xctestrun_as_json).map do |test_bundle_name, test_bundle|
|
75
|
-
test_specification = list_tests_wiht_simctl(list_folder, test_bundle, test_bundle_name)
|
169
|
+
test_specification = list_tests_wiht_simctl(list_folder, test_bundle, test_bundle_name, extra_environment_variables)
|
76
170
|
wait_test_dumper_completion(test_specification.json_stream_file)
|
77
171
|
test_specification
|
78
172
|
end
|
79
173
|
end
|
80
174
|
|
81
175
|
private
|
82
|
-
def list_tests_wiht_simctl(list_folder, test_bundle, test_bundle_name)
|
176
|
+
def list_tests_wiht_simctl(list_folder, test_bundle, test_bundle_name, extra_environment_variables)
|
83
177
|
env_variables = test_bundle["EnvironmentVariables"]
|
84
178
|
testing_env_variables = test_bundle["TestingEnvironmentVariables"]
|
85
179
|
outpath = "#{list_folder}/#{test_bundle_name}"
|
@@ -110,7 +204,10 @@ module XCKnife
|
|
110
204
|
"DYLD_FALLBACK_FRAMEWORK_PATH" => "#{@platform_path}/Developer/Library/Frameworks",
|
111
205
|
"DYLD_INSERT_LIBRARIES" => test_dumper_path,
|
112
206
|
)
|
207
|
+
env.merge!(simctl_child_attrs(extra_environment_variables))
|
113
208
|
inject_vars(env, test_host)
|
209
|
+
FileUtils.remove(outpath) if File.exists?(outpath)
|
210
|
+
logger.info { "Temporary TestDumper file for #{test_bundle_name} is #{outpath}" }
|
114
211
|
if is_logic_test
|
115
212
|
run_logic_test(env, test_host, test_bundle_path)
|
116
213
|
else
|
@@ -159,18 +256,14 @@ module XCKnife
|
|
159
256
|
retries_count = 0
|
160
257
|
until has_test_dumper_terminated?(file) do
|
161
258
|
retries_count += 1
|
162
|
-
|
259
|
+
if retries_count == @max_retry_count
|
260
|
+
puts "Timeout error on: #{file}"
|
261
|
+
exit 1
|
262
|
+
end
|
163
263
|
sleep 0.1
|
164
264
|
end
|
165
265
|
end
|
166
266
|
|
167
|
-
def assert_has_not_timed_out(retries_count, file)
|
168
|
-
if retries_count == 100
|
169
|
-
puts "Timeout error on: #{file}"
|
170
|
-
exit 1
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
267
|
def has_test_dumper_terminated?(file)
|
175
268
|
return false unless File.exists?(file)
|
176
269
|
last_line = `tail -n 1 "#{file}"`
|
@@ -182,12 +275,18 @@ module XCKnife
|
|
182
275
|
end
|
183
276
|
|
184
277
|
def run_logic_test(env, test_host, test_bundle_path)
|
185
|
-
call_simctl env, "spawn #{@device_id} '#{test_host}' -XCTest All '#{test_bundle_path}'
|
278
|
+
call_simctl env, "spawn #{@device_id} '#{test_host}' -XCTest All '#{test_bundle_path}'#{redirect_output}"
|
279
|
+
end
|
280
|
+
|
281
|
+
def redirect_output
|
282
|
+
return '' unless @debug
|
283
|
+
' 2> /dev/null'
|
186
284
|
end
|
187
285
|
|
188
286
|
def call_simctl(env, string_args)
|
189
287
|
cmd = "#{simctl} #{string_args}"
|
190
288
|
puts "Running:\n$ #{cmd}"
|
289
|
+
logger.info { "Environment variables:\n #{env.pretty_print_inspect}" }
|
191
290
|
unless system(env, cmd)
|
192
291
|
puts "Simctl errored with the following env:\n #{env.pretty_print_inspect}"
|
193
292
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
|
3
|
+
module XCKnife
|
4
|
+
module XcschemeAnalyzer
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def extract_environment_variables(xscheme_data)
|
8
|
+
ret = {}
|
9
|
+
xml_root = REXML::Document.new(xscheme_data).root
|
10
|
+
|
11
|
+
|
12
|
+
action = xml_root.elements["//TestAction"]
|
13
|
+
return ret if action.nil?
|
14
|
+
if action.attributes['shouldUseLaunchSchemeArgsEnv'] == "YES"
|
15
|
+
action = xml_root.elements["//LaunchAction"]
|
16
|
+
end
|
17
|
+
return ret if action.nil?
|
18
|
+
env_elements = action.elements[".//EnvironmentVariables"]
|
19
|
+
return ret if env_elements.nil?
|
20
|
+
env_elements.elements.each do |e|
|
21
|
+
attrs = e.attributes
|
22
|
+
if attrs["isEnabled"] == "YES"
|
23
|
+
ret[attrs["key"]] = attrs["value"]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
ret
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xcknife
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Ribeiro
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-02-
|
11
|
+
date: 2017-02-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -69,6 +69,7 @@ files:
|
|
69
69
|
- lib/xcknife/runner.rb
|
70
70
|
- lib/xcknife/stream_parser.rb
|
71
71
|
- lib/xcknife/test_dumper.rb
|
72
|
+
- lib/xcknife/xcscheme_analyzer.rb
|
72
73
|
- lib/xcknife/xctool_cmd_helper.rb
|
73
74
|
- xcknife.gemspec
|
74
75
|
homepage: https://github.com/square/xcknife
|