xcknife 0.6.3 → 0.6.4

Sign up to get free protection for your applications and to get access to all the features.
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