run_loop 2.0.5 → 2.0.6

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: e74f558ad6dac0b1679f8d2cdd4566810aa922fd
4
- data.tar.gz: a81469d91af4d38631e344de86e8176f85b66b06
3
+ metadata.gz: e713b8074f4a98cc789fe39327c63c675d8483cb
4
+ data.tar.gz: 98d467c958129c5a33efa0fe654088f38b0b7b0f
5
5
  SHA512:
6
- metadata.gz: 512cf1830f6ee0a79e00879ad72c38c2f20563867128995652c58dc2d6f34d5200a289f5d08c148c8b16bc8f293409be0e3b3cae965601855caa481e33cd7eaa
7
- data.tar.gz: 110fc79bc875f364d9cf3a1b020467c0649a3d39e7c4f2c701bfb0c812d352d399bc3ce3561df0a83000b941044d7bdb0e8762f9c71f2123189c40245456ae75
6
+ metadata.gz: 810a32b9c38f9b720129ca06dc8a80cb6d92c4243d8bd7dc4dd62cee55600e19d68f9eb38d7c28643f9ed023d3437e1c9b35beb26a1e9f95a64b8de3a9308a23
7
+ data.tar.gz: 0963ce5adff45b9aae3b20cc2635000ea69ef166deef85956461c2808abca49a371140d8a42d911a622a4e88547fbd6e90c713ff63befeaf49087b921ecfb56a
@@ -20,6 +20,8 @@ require 'run_loop/sim_control'
20
20
  require 'run_loop/device'
21
21
  require 'run_loop/instruments'
22
22
  require 'run_loop/lipo'
23
+ require "run_loop/otool"
24
+ require "run_loop/strings"
23
25
  require 'run_loop/cache/cache'
24
26
  require 'run_loop/host_cache'
25
27
  require 'run_loop/patches/awesome_print'
@@ -14,6 +14,20 @@ module RunLoop
14
14
  # @return [RunLoop::App] A instance of App with a path.
15
15
  def initialize(app_bundle_path)
16
16
  @path = File.expand_path(app_bundle_path)
17
+
18
+ if !App.valid?(app_bundle_path)
19
+ raise ArgumentError,
20
+ %Q{App does not exist at path or is not an app bundle.
21
+
22
+ #{app_bundle_path}
23
+
24
+ Bundle must:
25
+
26
+ 1. be a directory that exists,
27
+ 2. have a .app extension,
28
+ 3. and contain an Info.plist.
29
+ }
30
+ end
17
31
  end
18
32
 
19
33
  # @!visibility private
@@ -28,19 +42,23 @@ module RunLoop
28
42
 
29
43
  # Is this a valid app?
30
44
  def valid?
31
- [File.exist?(path),
32
- File.directory?(path),
33
- File.extname(path) == '.app'].all?
45
+ App.valid?(path)
46
+ end
47
+
48
+ # @!visibility private
49
+ def self.valid?(app_bundle_path)
50
+ return false if app_bundle_path.nil?
51
+
52
+ File.exist?(app_bundle_path) &&
53
+ File.directory?(app_bundle_path) &&
54
+ File.extname(app_bundle_path) == '.app' &&
55
+ File.exist?(File.join(app_bundle_path, "Info.plist"))
34
56
  end
35
57
 
36
58
  # Returns the Info.plist path.
37
59
  # @raise [RuntimeError] If there is no Info.plist.
38
60
  def info_plist_path
39
- info_plist = File.join(path, 'Info.plist')
40
- unless File.exist?(info_plist)
41
- raise "Expected an Info.plist at '#{path}'"
42
- end
43
- info_plist
61
+ @info_plist_path ||= File.join(path, 'Info.plist')
44
62
  end
45
63
 
46
64
  # Inspects the app's Info.plist for the bundle identifier.
@@ -69,20 +87,26 @@ module RunLoop
69
87
 
70
88
  # Inspects the app's file for the server version
71
89
  def calabash_server_version
72
- if valid?
73
- path_to_bin = File.join(path, executable_name)
74
- xcrun ||= RunLoop::Xcrun.new
75
- hash = xcrun.exec(["strings", path_to_bin])
76
- unless hash.nil?
77
- version_str = hash[:out][/CALABASH VERSION: \d+\.\d+\.\d+/, 0]
78
- unless version_str.nil? || version_str == ""
79
- server_ver = version_str.split(":")[1].delete(' ')
80
- RunLoop::Version.new(server_ver)
81
- end
90
+ version = nil
91
+ executables.each do |executable|
92
+ version = strings(executable).server_version
93
+ break if version
94
+ end
95
+ version
96
+ end
97
+
98
+ # @!visibility private
99
+ # Collects the paths to executables in the bundle.
100
+ def executables
101
+ executables = []
102
+ Dir.glob("#{path}/**/*") do |file|
103
+ next if File.directory?(file)
104
+ next if skip_executable_check?(file)
105
+ if otool(file).executable?
106
+ executables << file
82
107
  end
83
- else
84
- raise 'Path is not valid'
85
108
  end
109
+ executables
86
110
  end
87
111
 
88
112
  # Returns the sha1 of the application.
@@ -92,9 +116,89 @@ module RunLoop
92
116
 
93
117
  private
94
118
 
119
+ # @!visibility private
95
120
  def plist_buddy
96
121
  @plist_buddy ||= RunLoop::PlistBuddy.new
97
122
  end
98
123
 
124
+ # @!visibility private
125
+ # An otool factory.
126
+ def otool(file)
127
+ RunLoop::Otool.new(file)
128
+ end
129
+
130
+ # @!visibility private
131
+ # A strings factory
132
+ def strings(file)
133
+ RunLoop::Strings.new(file)
134
+ end
135
+
136
+ # @!visibility private
137
+ def skip_executable_check?(file)
138
+ image?(file) ||
139
+ text?(file) ||
140
+ plist?(file) ||
141
+ lproj_asset?(file) ||
142
+ code_signing_asset?(file) ||
143
+ core_data_asset?(file)
144
+ end
145
+
146
+ # @!visibility private
147
+ def text?(file)
148
+ extension = File.extname(file)
149
+
150
+ extension == ".txt" ||
151
+ extension == ".md" ||
152
+ extension == ".html" ||
153
+ extension == ".xml" ||
154
+ extension == ".json" ||
155
+ extension == ".yaml" ||
156
+ extension == ".yml" ||
157
+ extension == ".rtf" ||
158
+ file[/NOTICE|LICENSE|README|ABOUT/, 0]
159
+ end
160
+
161
+ # @!visibility private
162
+ def image?(file)
163
+ file[/jpeg|jpg|gif|png|tiff|svg|pdf|car|iTunesArtwork/, 0]
164
+ end
165
+
166
+ # @!visibility private
167
+ def plist?(file)
168
+ File.extname(file) == ".plist"
169
+ end
170
+
171
+ # @!visibility private
172
+ def lproj_asset?(file)
173
+ extension = File.extname(file)
174
+
175
+ file[/lproj/, 0] ||
176
+ file[/storyboard/, 0] ||
177
+ extension == ".strings" ||
178
+ extension == ".xib" ||
179
+ extension == ".nib"
180
+ end
181
+
182
+ # @!visibility private
183
+ def code_signing_asset?(file)
184
+ name = File.basename(file)
185
+ extension = File.extname(file)
186
+
187
+ name == "PkgInfo" ||
188
+ name == "embedded" ||
189
+ extension == ".mobileprovision" ||
190
+ extension == ".xcent" ||
191
+ file[/_CodeSignature/, 0]
192
+ end
193
+
194
+ # @!visibility private
195
+ def core_data_asset?(file)
196
+ extension = File.extname(file)
197
+
198
+ file[/momd/, 0] ||
199
+ extension == ".mom" ||
200
+ extension == ".db"
201
+ end
99
202
  end
100
203
  end
204
+
@@ -2,18 +2,11 @@ module RunLoop
2
2
  # A model of the an .ipa - a application binary for iOS devices.
3
3
  class Ipa
4
4
 
5
-
6
5
  # The path to this .ipa.
7
6
  # @!attribute [r] path
8
7
  # @return [String] A path to this .ipa.
9
8
  attr_reader :path
10
9
 
11
- # The bundle identifier of this ipa.
12
- # @!attribute [r] bundle_identifier
13
- # @return [String] The bundle identifier of this ipa; obtained by inspecting
14
- # the app's Info.plist.
15
- attr_reader :bundle_identifier
16
-
17
10
  # Create a new ipa instance.
18
11
  # @param [String] path_to_ipa The path the .ipa file.
19
12
  # @return [Calabash::Ipa] A new ipa instance.
@@ -42,96 +35,59 @@ module RunLoop
42
35
 
43
36
  # The bundle identifier of this ipa.
44
37
  # @return [String] A string representation of this ipa's CFBundleIdentifier
45
- # @raise [RuntimeError] If ipa does not expand into a Payload/<app name>.app
46
- # directory.
47
- # @raise [RuntimeError] If an Info.plist does exist in the .app.
48
38
  def bundle_identifier
49
- if bundle_dir.nil? || !File.exist?(bundle_dir)
50
- raise "Expected a '#{File.basename(path).split('.').first}.app'\nat path '#{payload_dir}'"
51
- end
52
-
53
- @bundle_identifier ||= lambda {
54
- info_plist_path = File.join(bundle_dir, 'Info.plist')
55
- unless File.exist? info_plist_path
56
- raise "Expected an 'Info.plist' at '#{bundle_dir}'"
57
- end
58
- identifier = plist_buddy.plist_read('CFBundleIdentifier', info_plist_path)
59
-
60
- unless identifier
61
- raise "Expected key 'CFBundleIdentifier' in '#{info_plist_path}'"
62
- end
63
- identifier
64
- }.call
39
+ app.bundle_identifier
65
40
  end
66
41
 
67
42
  # Inspects the app's Info.plist for the executable name.
68
43
  # @return [String] The value of CFBundleExecutable.
69
- # @raise [RuntimeError] If the plist cannot be read or the
70
- # CFBundleExecutable is empty or does not exist.
71
44
  def executable_name
72
- if bundle_dir.nil? || !File.exist?(bundle_dir)
73
- raise "Expected a '#{File.basename(path).split('.').first}.app'\nat path '#{payload_dir}'"
74
- end
75
-
76
- @executable_name ||= lambda {
77
- info_plist_path = File.join(bundle_dir, 'Info.plist')
78
- unless File.exist? info_plist_path
79
- raise "Expected an 'Info.plist' at '#{bundle_dir}'"
80
- end
81
- name = plist_buddy.plist_read('CFBundleExecutable', info_plist_path)
82
-
83
- unless name
84
- raise "Expected key 'CFBundleExecutable' in '#{info_plist_path}'"
85
- end
86
- name
87
- }.call
45
+ app.executable_name
88
46
  end
89
47
 
90
- # Inspects the app's file for the server version
48
+ # Inspects the app's executables for the server version
49
+ # @return[RunLoop::Version] a version instance
91
50
  def calabash_server_version
92
- if bundle_dir.nil? || !File.exist?(bundle_dir)
93
- raise "Expected a '#{File.basename(path).split('.').first}.app'\nat path '#{payload_dir}'"
94
- else
95
- if !executable_name.nil? && executable_name != ''
96
- path_to_bin = File.join(bundle_dir, executable_name)
97
- xcrun ||= RunLoop::Xcrun.new
98
- hash = xcrun.exec(["strings", path_to_bin])
99
- unless hash.nil?
100
- version_str = hash[:out][/CALABASH VERSION: \d+\.\d+\.\d+/, 0]
101
- unless version_str.nil? || version_str == ""
102
- server_ver = version_str.split(":")[1].delete(' ')
103
- RunLoop::Version.new(server_ver)
104
- end
105
- end
106
- end
107
- end
51
+ app.calabash_server_version
108
52
  end
109
53
 
110
54
  private
111
55
 
56
+ # @!visibility private
112
57
  def tmpdir
113
58
  @tmpdir ||= Dir.mktmpdir
114
59
  end
115
60
 
61
+ # @!visibility private
116
62
  def payload_dir
117
- @payload_dir ||= lambda {
63
+ @payload_dir ||= lambda do
118
64
  FileUtils.cp(path, tmpdir)
119
65
  zip_path = File.join(tmpdir, File.basename(path))
120
66
  Dir.chdir(tmpdir) do
121
67
  system('unzip', *['-q', zip_path])
122
68
  end
123
69
  File.join(tmpdir, 'Payload')
124
- }.call
70
+ end.call
125
71
  end
126
72
 
73
+ # @!visibility private
127
74
  def bundle_dir
128
- @bundle_dir ||= lambda {
129
- Dir.glob(File.join(payload_dir, '*')).detect {|f| File.directory?(f) && f.end_with?('.app')}
130
- }.call
75
+ @bundle_dir ||= lambda do
76
+ Dir.glob(File.join(payload_dir, '*')).detect do |f|
77
+ File.directory?(f) && f.end_with?('.app')
78
+ end
79
+ end.call
80
+ end
81
+
82
+ # @!visibility private
83
+ def app
84
+ @app ||= RunLoop::App.new(bundle_dir)
131
85
  end
132
86
 
87
+ # @!visibility private
133
88
  def plist_buddy
134
89
  @plist_buddy ||= RunLoop::PlistBuddy.new
135
90
  end
136
91
  end
137
92
  end
93
+
@@ -0,0 +1,76 @@
1
+ module RunLoop
2
+ # @!visibility private
3
+ #
4
+ # A class for interacting with otool
5
+ class Otool
6
+
7
+ # @!visibility private
8
+ attr_reader :path
9
+
10
+ # @!visibility private
11
+ def initialize(path)
12
+ @path = path
13
+
14
+ if !Otool.valid_path?(path)
15
+ raise ArgumentError,
16
+ %Q{File:
17
+
18
+ #{path}
19
+
20
+ must exist and not be a directory.
21
+ }
22
+ end
23
+ end
24
+
25
+ # @!visibility private
26
+ def to_s
27
+ "#<OTOOL: #{path}>"
28
+ end
29
+
30
+ # @!visibility private
31
+ def inspect
32
+ to_s
33
+ end
34
+
35
+ # @!visibility private
36
+ def executable?
37
+ !arch_info[/is not an object file/, 0]
38
+ end
39
+
40
+ private
41
+
42
+ # @!visibility private
43
+ def arch_info
44
+ args = ["otool", "-hv", "-arch", "all", path]
45
+ opts = { :log_cmd => true }
46
+
47
+ hash = xcrun.exec(args, opts)
48
+
49
+ if hash[:exit_status] != 0
50
+ raise RuntimeError,
51
+ %Q{Could not get arch info from file:
52
+
53
+ #{path}
54
+
55
+ #{args.join(" ")}
56
+
57
+ exited #{hash[:exit_status]} with the following output:
58
+
59
+ #{hash[:out]}
60
+ }
61
+ end
62
+
63
+ @arch_info = hash[:out]
64
+ end
65
+
66
+ # @!visibility private
67
+ def self.valid_path?(path)
68
+ File.exist?(path) && !File.directory?(path)
69
+ end
70
+
71
+ # @!visibility private
72
+ def xcrun
73
+ RunLoop::Xcrun.new
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,88 @@
1
+ module RunLoop
2
+ # @!visibility private
3
+ #
4
+ # A class for interacting with the strings tool
5
+ class Strings
6
+
7
+ # @!visibility private
8
+ attr_reader :path
9
+
10
+ # @!visibility private
11
+ def initialize(path)
12
+ @path = path
13
+
14
+ if !Strings.valid_path?(path)
15
+ raise ArgumentError,
16
+ %Q{File:
17
+
18
+ #{path}
19
+
20
+ must exist and not be a directory.
21
+ }
22
+ end
23
+ end
24
+
25
+ # @!visibility private
26
+ def to_s
27
+ "#<STRINGS: #{path}>"
28
+ end
29
+
30
+ # @!visibility private
31
+ def inspect
32
+ to_s
33
+ end
34
+
35
+ # @!visibility private
36
+ #
37
+ # @return [RunLoop::Version] A version instance or nil if the file
38
+ # at path does not contain server version information.
39
+ def server_version
40
+ regex = /CALABASH VERSION: (\d+\.\d+\.\d+(\.pre\d+)?)/
41
+ match = dump[regex, 0]
42
+
43
+ if match
44
+ str = match.split(":")[1]
45
+ RunLoop::Version.new(str)
46
+ else
47
+ nil
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ # @!visibility private
54
+ def dump
55
+ args = ["strings", path]
56
+ opts = { :log_cmd => true }
57
+
58
+ hash = xcrun.exec(args, opts)
59
+
60
+ if hash[:exit_status] != 0
61
+ raise RuntimeError,
62
+ %Q{Could not get strings info from file:
63
+
64
+ #{path}
65
+
66
+ #{args.join(" ")}
67
+
68
+ exited #{hash[:exit_status]} with the following output:
69
+
70
+ #{hash[:out]}
71
+ }
72
+ end
73
+
74
+ @dump = hash[:out]
75
+ end
76
+
77
+ # @!visibility private
78
+ def self.valid_path?(path)
79
+ File.exist?(path) && !File.directory?(path)
80
+ end
81
+
82
+ # @!visibility private
83
+ def xcrun
84
+ RunLoop::Xcrun.new
85
+ end
86
+ end
87
+ end
88
+
@@ -1,5 +1,5 @@
1
1
  module RunLoop
2
- VERSION = "2.0.5"
2
+ VERSION = "2.0.6"
3
3
 
4
4
  # A model of a software release version that can be used to compare two versions.
5
5
  #
@@ -21,6 +21,8 @@ module RunLoop
21
21
  # Raised when Xcrun fails.
22
22
  class Error < RuntimeError; end
23
23
 
24
+ # Raised when the output of the command cannot be coerced to UTF8
25
+ class UTF8Error < RuntimeError; end
24
26
 
25
27
  # Raised when Xcrun times out.
26
28
  class TimeoutError < RuntimeError; end
@@ -58,12 +60,7 @@ IO.popen requires all arguments to be Strings.
58
60
  start_time = Time.now
59
61
  command_output = CommandRunner.run(['xcrun'] + args, timeout: timeout)
60
62
 
61
- if command_output[:out]
62
- out = command_output[:out].force_encoding('utf-8').chomp
63
- else
64
- out = ''
65
- end
66
-
63
+ out = encode_utf8_or_raise(command_output[:out], cmd)
67
64
  process_status = command_output[:status]
68
65
 
69
66
  hash =
@@ -74,6 +71,8 @@ IO.popen requires all arguments to be Strings.
74
71
  :exit_status => process_status.exitstatus
75
72
  }
76
73
 
74
+ rescue UTF8Error => e
75
+ raise e
77
76
  rescue => e
78
77
  elapsed = "%0.2f" % (Time.now - start_time)
79
78
  raise Error,
@@ -100,5 +99,35 @@ with a timeout of #{timeout}
100
99
 
101
100
  hash
102
101
  end
102
+
103
+ private
104
+
105
+ # @!visibility private
106
+ def encode_utf8_or_raise(string, command)
107
+ return '' if !string
108
+
109
+ utf8 = string.force_encoding("UTF-8").chomp
110
+
111
+ return utf8 if utf8.valid_encoding?
112
+
113
+ encoded = utf8.encode('UTF-8', 'UTF-8', invalid: :replace, undef: :replace, replace: '')
114
+
115
+ return encoded if encoded.valid_encoding?
116
+
117
+ raise UTF8Error, %Q{
118
+ Could not force UTF-8 encoding on this string:
119
+
120
+ #{string}
121
+
122
+ which is the output of this command:
123
+
124
+ #{command}
125
+
126
+ Please file an issue with a stacktrace and the text of this error.
127
+
128
+ https://github.com/calabash/run_loop/issues
129
+ }
130
+ end
103
131
  end
104
132
  end
133
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: run_loop
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.5
4
+ version: 2.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Karl Krukow
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-29 00:00:00.000000000 Z
11
+ date: 2016-02-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -295,6 +295,7 @@ files:
295
295
  - lib/run_loop/lldb.rb
296
296
  - lib/run_loop/locale.rb
297
297
  - lib/run_loop/logging.rb
298
+ - lib/run_loop/otool.rb
298
299
  - lib/run_loop/patches/awesome_print.rb
299
300
  - lib/run_loop/plist_buddy.rb
300
301
  - lib/run_loop/process_terminator.rb
@@ -302,6 +303,7 @@ files:
302
303
  - lib/run_loop/regex.rb
303
304
  - lib/run_loop/sim_control.rb
304
305
  - lib/run_loop/simctl/plists.rb
306
+ - lib/run_loop/strings.rb
305
307
  - lib/run_loop/template.rb
306
308
  - lib/run_loop/version.rb
307
309
  - lib/run_loop/xcode.rb