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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a16ad712fe21666c9ec13b16c1cc3007120bc9eb
4
- data.tar.gz: fc3109c894275ca6c8e5fed344fd35a3a11d0457
3
+ metadata.gz: 1e7dcc40d67413a5b35f7eba1289d75724965a63
4
+ data.tar.gz: 7ea917dd613f5fcdd8e6a41471538c01bf9df4d2
5
5
  SHA512:
6
- metadata.gz: f364876c260015cc6b0729035c5835b295e1772b27671b50448064659841ab21e2384476b4e5860387e0f397db41d80f4fe48730b6e36879776295e18d89056d
7
- data.tar.gz: 456da664e95295f2ae4a20982b245e7ade3a616047cf89dd654e0de0d5d053f2d1a11da6b9bc88b8f1099fd917111f7ed963a112b9c3c2cde6c50056a406a00d
6
+ metadata.gz: b786211a86a81b9483a8721de939de4bcb7eba683698e334d6f299e706153ee607a3b8de6621599465cecbf236b42805618f2e71812a664c9c9cf8abd20c8ae3
7
+ data.tar.gz: 1295e3a8a1e7040d0acadf14d42e2c36838f9fdde95c8d57c83b4bcaa4986165b689239a27cdecbdb512233110b3d54c40d1175c649f7c8b7c267642495c47bf
@@ -1,9 +1,18 @@
1
- language: ruby
1
+ language: objective-c
2
2
  sudo: false
3
- before_install: gem install bundler
4
- bundler_args: --without development
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
- rvm:
7
- - 2.2.3
8
- - 2.3.0
9
- - 2.3.1
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 -h
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/Release-iphonesimulator/TestDumper.framework/TestDumper", "./TestDumper.dylib")
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
@@ -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
 
@@ -1,6 +1,7 @@
1
1
  #!/bin/bash
2
+ rm -rf TestDumper.dylib testdumperbuild
2
3
  xcodebuild -project TestDumper.xcodeproj \
3
- -configuration Release \
4
+ -configuration Debug \
4
5
  -derivedDataPath testdumperbuild \
5
6
  -scheme TestDumper \
6
- -sdk iphonesimulator build
7
+ -sdk iphonesimulator build ONLY_ACTIVE_ARCH=NO
@@ -4,7 +4,8 @@ require 'xcknife/xctool_cmd_helper'
4
4
  require 'xcknife/runner'
5
5
  require 'xcknife/test_dumper'
6
6
  require 'xcknife/exceptions'
7
+ require 'xcknife/xcscheme_analyzer'
7
8
 
8
9
  module XCKnife
9
- VERSION = '0.6.3'
10
+ VERSION = '0.6.4'
10
11
  end
@@ -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
- @derived_data_folder, @output_file, @device_id = args
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
- 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)
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
- Dir.mktmpdir("xctestdumper_") do |outfolder|
26
- helper.call(@derived_data_folder, outfolder).each do |test_specification|
27
- concat_to_file(test_specification, output_fd)
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
- output_fd.write(line) unless should_test_event_be_ignored?(test_specification, event)
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
- def initialize(device_id)
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
- assert_has_not_timed_out(retries_count, file)
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}' 2> /dev/null"
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.3
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-13 00:00:00.000000000 Z
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