run_loop 0.0.5 → 0.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.
data/.irbrc CHANGED
@@ -15,3 +15,5 @@ IRB.conf[:HISTORY_FILE] = ".irb-history"
15
15
  require 'run_loop'
16
16
 
17
17
 
18
+ @app = "/Users/krukow/github/x-platform-example/ios-source/3.3.1/build/Applications/WordPress.app"
19
+
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  run-loop
2
2
 
3
- Copyright (c) Karl Krukow (http://www.lesspainful.com). All rights reserved.
3
+ Copyright (c) LessPainful ApS (https://www.lesspainful.com). All rights reserved.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,15 +1,6 @@
1
1
  run-loop
2
2
  ===================
3
3
 
4
- Credits
5
- =======
6
-
7
- UI Screen Shooter @jonathanpenn for the wrapper around instruments
8
- https://github.com/jonathanpenn/ui-screen-shooter
9
-
10
- UI Screen Shooter is licensed under the MIT license.
11
- See the LICENSE file for more info.
12
-
13
4
 
14
5
  License
15
6
  =======
data/lib/run_loop/core.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'fileutils'
2
2
  require 'tmpdir'
3
3
  require 'timeout'
4
- #require 'open4'
4
+ require 'json'
5
5
 
6
6
  module RunLoop
7
7
 
@@ -32,29 +32,27 @@ module RunLoop
32
32
  device = options[:device] || :iphone
33
33
 
34
34
 
35
- template = automation_template
36
- instruments_path = "instruments"#File.join(scripts_path,"unix_instruments")
37
35
  results_dir = options[:results_dir] || Dir.mktmpdir("run_loop")
38
- results_dir_trace = File.join(results_dir,"trace")
36
+ results_dir_trace = File.join(results_dir, "trace")
39
37
  FileUtils.mkdir_p(results_dir_trace)
40
38
 
41
- script = File.join(results_dir,"_run_loop.js")
39
+ script = File.join(results_dir, "_run_loop.js")
42
40
 
43
41
 
44
42
  code = File.read(options[:script])
45
43
  code = code.gsub(/\$PATH/, results_dir)
44
+ code = code.gsub(/\$MODE/, "FLUSH") unless options[:no_flush]
46
45
 
47
- repl_path = File.join(results_dir,"repl-cmd.txt")
48
- File.open(repl_path, "w") {|file| file.puts "0:UIALogger.logMessage('Listening for run loop commands');"}
46
+ repl_path = File.join(results_dir, "repl-cmd.txt")
47
+ File.open(repl_path, "w") { |file| file.puts "0:UIALogger.logMessage('Listening for run loop commands');" }
49
48
 
50
- File.open(script, "w") {|file| file.puts code}
49
+ File.open(script, "w") { |file| file.puts code }
51
50
 
52
51
 
53
-
54
- bundle_dir_or_bundle_id = options[:app] || ENV['APP_BUNDLE_PATH']
52
+ bundle_dir_or_bundle_id = options[:app] || ENV['BUNDLE_ID']|| ENV['APP_BUNDLE_PATH']
55
53
 
56
54
  unless bundle_dir_or_bundle_id
57
- raise "key :app or environment variable APP_BUNDLE_PATH must be specified as path to app bundle (simulator) or bundle id (device)"
55
+ raise 'key :app or environment variable APP_BUNDLE_PATH or BUNDLE_ID must be specified as path to app bundle (simulator) or bundle id (device)'
58
56
  end
59
57
 
60
58
  if File.exist?(bundle_dir_or_bundle_id)
@@ -63,9 +61,9 @@ module RunLoop
63
61
  plistbuddy="/usr/libexec/PlistBuddy"
64
62
  plistfile="#{bundle_dir_or_bundle_id}/Info.plist"
65
63
  if device == :iphone
66
- uidevicefamily=1
64
+ uidevicefamily=1
67
65
  else
68
- uidevicefamily=2
66
+ uidevicefamily=2
69
67
  end
70
68
  system("#{plistbuddy} -c 'Delete :UIDeviceFamily' '#{plistfile}'")
71
69
  system("#{plistbuddy} -c 'Add :UIDeviceFamily array' '#{plistfile}'")
@@ -74,8 +72,8 @@ module RunLoop
74
72
  udid = options[:udid]
75
73
  unless udid
76
74
  begin
77
- Timeout::timeout(3,TimeoutError) do
78
- udid = `#{File.join(scripts_path,'udidetect')}`.chomp
75
+ Timeout::timeout(3, TimeoutError) do
76
+ udid = `#{File.join(scripts_path, 'udidetect')}`.chomp
79
77
  end
80
78
  rescue TimeoutError => e
81
79
 
@@ -88,21 +86,8 @@ module RunLoop
88
86
  end
89
87
 
90
88
 
91
- if udid
92
- instruments_path = "#{instruments_path} -w #{udid}"
93
- end
94
-
95
-
96
-
97
- cmd = [
98
- instruments_path,
99
- "-D", results_dir_trace,
100
- "-t", template,
101
- "\"#{bundle_dir_or_bundle_id}\"",
102
- "-e", "UIARESULTSPATH", results_dir,
103
- "-e", "UIASCRIPT", script,
104
- *(options[:instruments_args] || [])
105
- ]
89
+ log_file = File.join(results_dir, 'run_loop.out')
90
+ cmd = instruments_command(udid, results_dir_trace, bundle_dir_or_bundle_id, results_dir, script, log_file)
106
91
 
107
92
  pid = fork do
108
93
  log_header("Starting App: #{bundle_dir_or_bundle_id}")
@@ -115,16 +100,130 @@ module RunLoop
115
100
 
116
101
  Process.detach(pid)
117
102
 
118
- File.open(File.join(results_dir,"run_loop.pid"), "w") do |f|
103
+ File.open(File.join(results_dir, "run_loop.pid"), "w") do |f|
119
104
  f.write pid
120
105
  end
121
106
 
122
- return {:pid => pid, :repl_path => repl_path, :results_dir => results_dir}
107
+ run_loop = {:pid => pid, :repl_path => repl_path, :log_file => log_file, :results_dir => results_dir}
108
+
109
+ read_response(run_loop,0)
110
+ begin
111
+ Timeout::timeout(30, TimeoutError) do
112
+ read_response(run_loop, 0)
113
+ end
114
+ rescue TimeoutError => e
115
+ raise "Time out waiting for UIAutomation run-loop to Start. \n #{File.read(log_file)}"
116
+ end
117
+
118
+ run_loop
119
+ end
120
+
121
+ def self.write_request(run_loop, cmd)
122
+ repl_path = run_loop[:repl_path]
123
+
124
+ cur = File.read(repl_path)
125
+
126
+ colon = cur.index(':')
127
+
128
+ if colon.nil?
129
+ raise "Illegal contents of #{repl_path}: #{cur}"
130
+ end
131
+ index = cur[0, colon].to_i + 1
132
+
133
+
134
+ tmp_cmd = File.join(File.dirname(repl_path), '__repl-cmd.txt')
135
+ File.open(tmp_cmd, "w") do |f|
136
+ f.write("#{index}:#{cmd}")
137
+ end
138
+
139
+ FileUtils.mv(tmp_cmd, repl_path)
140
+ index
141
+ end
142
+
143
+ def self.read_response(run_loop, expected_index)
144
+ log_file = run_loop[:log_file]
145
+ offset = 0
146
+ json_token = "OUTPUT_JSON:\n"
147
+ result = nil
148
+ loop do
149
+ unless File.exist?(log_file) && File.size?(log_file)
150
+ sleep(1)
151
+ next
152
+ end
153
+
154
+ size = File.size(log_file)
155
+ output = File.read(log_file, size-offset, offset)
156
+ index_if_found = output.index(json_token)
157
+ if ENV['DEBUG']=='1'
158
+ puts output.gsub("*", '')
159
+ puts "Size #{size}"
160
+ puts "offset #{offset}"
161
+ puts "index_of #{json_token}: #{index_if_found}"
162
+ end
163
+
164
+ if index_if_found
165
+
166
+ offset = offset + index_if_found
167
+ rest = output[index_if_found+json_token.size..output.length]
168
+ index_of_json = rest.index("}\n\n")
169
+
170
+ json = rest[0..index_of_json]
171
+
172
+ if ENV['DEBUG']=='1'
173
+ puts "Index #{index_if_found}, Size: #{size} Offset #{offset}"
174
+
175
+ puts ("parse #{json}")
176
+ end
177
+
178
+ offset = offset + json.size
179
+ parsed_result = JSON.parse(json)
180
+ json_index_if_present = parsed_result['index']
181
+ if json_index_if_present && json_index_if_present == expected_index
182
+ result = parsed_result
183
+ break
184
+ end
185
+ else
186
+
187
+ end
188
+
189
+ sleep(0.5)
190
+ end
191
+ result
192
+
193
+ end
194
+
195
+ def self.pids_for_run_loop(run_loop, &block)
196
+ pids_str = `ps x -o pid,command | grep -v grep | grep "instruments -D #{run_loop[:results_dir]}" | awk '{printf "%s,", $1}'`
197
+ pids = pids_str.split(",").map { |pid| pid.to_i }
198
+ if block_given?
199
+ pids.each do |pid|
200
+ block.call(pid)
201
+ end
202
+ else
203
+ pids
204
+ end
205
+ end
206
+
207
+
208
+ def self.instruments_command(udid, results_dir_trace, bundle_dir_or_bundle_id, results_dir, script, log_file)
209
+ instruments_path = 'instruments'
210
+ if udid
211
+ instruments_path = "#{instruments_path} -w #{udid}"
212
+ end
213
+ [
214
+ instruments_path,
215
+ "-D", results_dir_trace,
216
+ "-t", automation_template,
217
+ "\"#{bundle_dir_or_bundle_id}\"",
218
+ "-e", "UIARESULTSPATH", results_dir,
219
+ "-e", "UIASCRIPT", script,
220
+ "&> #{log_file}"
221
+ ]
123
222
  end
124
223
 
125
224
  def self.automation_template
126
225
  xcode_path = `xcode-select -print-path`.chomp
127
- automation_bundle = File.expand_path(File.join(xcode_path, "..", "Applications", "Instruments.app", "Contents", "PlugIns", "AutomationInstrument.bundle"))
226
+ automation_bundle = File.expand_path(File.join(xcode_path, "..", 'Applications', "Instruments.app", "Contents", "PlugIns", 'AutomationInstrument.bundle'))
128
227
  if not File.exist? automation_bundle
129
228
  automation_bundle= File.expand_path(File.join(xcode_path, "Platforms", "iPhoneOS.platform", "Developer", "Library", "Instruments", "PlugIns", "AutomationInstrument.bundle"))
130
229
  raise "Unable to find AutomationInstrument.bundle" if not File.exist? automation_bundle
@@ -151,45 +250,50 @@ module RunLoop
151
250
  Core.run_with_options(options)
152
251
  end
153
252
 
154
- def self.send_command(run_loop,cmd)
155
- repl_path = run_loop[:repl_path]
253
+ def self.send_command(run_loop, cmd, timeout=15)
156
254
 
157
255
  if not cmd.is_a?(String)
158
256
  raise "Illegal command #{cmd} (must be a string)"
159
257
  end
160
258
 
161
- cur = File.read(repl_path)
162
259
 
163
- colon = cur.index(":")
164
-
165
- if colon.nil?
166
- raise "Illegal contents of #{repl_path}: #{cur}"
167
- end
168
- index = cur[0,colon].to_i + 1
260
+ expected_index = Core.write_request(run_loop, cmd)
261
+ result = nil
169
262
 
263
+ begin
264
+ Timeout::timeout(timeout, TimeoutError) do
265
+ result = Core.read_response(run_loop, expected_index)
266
+ end
170
267
 
171
- tmp_cmd = File.join(File.dirname(repl_path), "__repl-cmd.txt")
172
- File.open(tmp_cmd,"w") do |f|
173
- f.write("#{index}:#{cmd}")
268
+ rescue TimeoutError => e
269
+ raise "Time out waiting for UIAutomation run-loop for command #{cmd}. Waiting for index:#{expected_index}"
174
270
  end
175
271
 
176
- FileUtils.mv(tmp_cmd,repl_path)
272
+ result
177
273
  end
178
274
 
179
- def self.stop(options)
180
- results_dir = options[:results_dir]
181
- pid = options[:pid] || IO.read(File.join(results_dir,"run_loop.pid"))
182
- dest = options[:out] || Dir.pwd
275
+ def self.stop(run_loop, out=Dir.pwd)
276
+ results_dir = run_loop[:results_dir]
277
+ pid = run_loop[:pid] || IO.read(File.join(results_dir, "run_loop.pid"))
278
+ dest = out
183
279
 
184
280
  if pid
185
- Process.kill("HUP",pid.to_i)
281
+ Process.kill('TERM', pid.to_i)
282
+ end
283
+
284
+ Core.pids_for_run_loop(run_loop) do |pid|
285
+ Process.kill('TERM', pid.to_i)
186
286
  end
187
287
 
288
+ sleep(1)
289
+
188
290
  FileUtils.mkdir_p(dest)
189
- pngs = Dir.glob(File.join(results_dir,"Run 1","*.png"))
291
+ pngs = Dir.glob(File.join(results_dir, "Run 1", "*.png"))
190
292
  FileUtils.cp(pngs, dest) if pngs and pngs.length > 0
191
293
  end
192
294
 
295
+
296
+
193
297
  def self.validate_script(options)
194
298
  script = options[:script]
195
299
  if script
@@ -1,3 +1,3 @@
1
1
  module RunLoop
2
- VERSION = "0.0.5"
2
+ VERSION = '0.0.6'
3
3
  end
data/run_loop.gemspec CHANGED
@@ -17,5 +17,6 @@ Gem::Specification.new do |s|
17
17
  s.require_paths = ["lib"]
18
18
 
19
19
  s.add_dependency( "thor" )
20
+ s.add_dependency( "json" )
20
21
  #s.add_dependency( "open4", "~> 1.3.0")
21
22
  end
data/scripts/run_loop.js CHANGED
@@ -1,57 +1,190 @@
1
- if(typeof JSON!=='object'){JSON={};}
2
- (function(){'use strict';function f(n){return n<10?'0'+n:n;}
3
- if(typeof Date.prototype.toJSON!=='function'){Date.prototype.toJSON=function(key){return isFinite(this.valueOf())?this.getUTCFullYear()+'-'+
4
- f(this.getUTCMonth()+1)+'-'+
5
- f(this.getUTCDate())+'T'+
6
- f(this.getUTCHours())+':'+
7
- f(this.getUTCMinutes())+':'+
8
- f(this.getUTCSeconds())+'Z':null;};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf();};}
9
- var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==='string'?c:'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4);})+'"':'"'+string+'"';}
10
- function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==='object'&&typeof value.toJSON==='function'){value=value.toJSON(key);}
11
- if(typeof rep==='function'){value=rep.call(holder,key,value);}
12
- switch(typeof value){case'string':return quote(value);case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null';}
13
- gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==='[object Array]'){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||'null';}
14
- v=partial.length===0?'[]':gap?'[\n'+gap+partial.join(',\n'+gap)+'\n'+mind+']':'['+partial.join(',')+']';gap=mind;return v;}
15
- if(rep&&typeof rep==='object'){length=rep.length;for(i=0;i<length;i+=1){if(typeof rep[i]==='string'){k=rep[i];v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}else{for(k in value){if(Object.prototype.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}
16
- v=partial.length===0?'{}':gap?'{\n'+gap+partial.join(',\n'+gap)+'\n'+mind+'}':'{'+partial.join(',')+'}';gap=mind;return v;}}
17
- if(typeof JSON.stringify!=='function'){JSON.stringify=function(value,replacer,space){var i;gap='';indent='';if(typeof space==='number'){for(i=0;i<space;i+=1){indent+=' ';}}else if(typeof space==='string'){indent=space;}
18
- rep=replacer;if(replacer&&typeof replacer!=='function'&&(typeof replacer!=='object'||typeof replacer.length!=='number')){throw new Error('JSON.stringify');}
19
- return str('',{'':value});};}
20
- if(typeof JSON.parse!=='function'){JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==='object'){for(k in value){if(Object.prototype.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v;}else{delete value[k];}}}}
21
- return reviver.call(holder,key,value);}
22
- text=String(text);cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return'\\u'+
23
- ('0000'+a.charCodeAt(0).toString(16)).slice(-4);});}
24
- if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){j=eval('('+text+')');return typeof reviver==='function'?walk({'':j},''):j;}
25
- throw new SyntaxError('JSON.parse');};}}());
1
+ if (typeof JSON !== 'object') {
2
+ JSON = {};
3
+ }
4
+ (function () {
5
+ 'use strict';
6
+ function f(n) {
7
+ return n < 10 ? '0' + n : n;
8
+ }
9
+
10
+ if (typeof Date.prototype.toJSON !== 'function') {
11
+ Date.prototype.toJSON = function (key) {
12
+ return isFinite(this.valueOf()) ? this.getUTCFullYear() + '-' +
13
+ f(this.getUTCMonth() + 1) + '-' +
14
+ f(this.getUTCDate()) + 'T' +
15
+ f(this.getUTCHours()) + ':' +
16
+ f(this.getUTCMinutes()) + ':' +
17
+ f(this.getUTCSeconds()) + 'Z' : null;
18
+ };
19
+ String.prototype.toJSON = Number.prototype.toJSON = Boolean.prototype.toJSON = function (key) {
20
+ return this.valueOf();
21
+ };
22
+ }
23
+ var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, gap, indent, meta = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"': '\\"', '\\': '\\\\'}, rep;
24
+
25
+ function quote(string) {
26
+ escapable.lastIndex = 0;
27
+ return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
28
+ var c = meta[a];
29
+ return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
30
+ }) + '"' : '"' + string + '"';
31
+ }
32
+
33
+ function str(key, holder) {
34
+ var i, k, v, length, mind = gap, partial, value = holder[key];
35
+ if (value && typeof value === 'object' && typeof value.toJSON === 'function') {
36
+ value = value.toJSON(key);
37
+ }
38
+ if (typeof rep === 'function') {
39
+ value = rep.call(holder, key, value);
40
+ }
41
+ switch (typeof value) {
42
+ case'string':
43
+ return quote(value);
44
+ case'number':
45
+ return isFinite(value) ? String(value) : 'null';
46
+ case'boolean':
47
+ case'null':
48
+ return String(value);
49
+ case'object':
50
+ if (!value) {
51
+ return'null';
52
+ }
53
+ gap += indent;
54
+ partial = [];
55
+ if (Object.prototype.toString.apply(value) === '[object Array]') {
56
+ length = value.length;
57
+ for (i = 0; i < length; i += 1) {
58
+ partial[i] = str(i, value) || 'null';
59
+ }
60
+ v = partial.length === 0 ? '[]' : gap ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : '[' + partial.join(',') + ']';
61
+ gap = mind;
62
+ return v;
63
+ }
64
+ if (rep && typeof rep === 'object') {
65
+ length = rep.length;
66
+ for (i = 0; i < length; i += 1) {
67
+ if (typeof rep[i] === 'string') {
68
+ k = rep[i];
69
+ v = str(k, value);
70
+ if (v) {
71
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
72
+ }
73
+ }
74
+ }
75
+ } else {
76
+ for (k in value) {
77
+ if (Object.prototype.hasOwnProperty.call(value, k)) {
78
+ v = str(k, value);
79
+ if (v) {
80
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
81
+ }
82
+ }
83
+ }
84
+ }
85
+ v = partial.length === 0 ? '{}' : gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : '{' + partial.join(',') + '}';
86
+ gap = mind;
87
+ return v;
88
+ }
89
+ }
90
+
91
+ if (typeof JSON.stringify !== 'function') {
92
+ JSON.stringify = function (value, replacer, space) {
93
+ var i;
94
+ gap = '';
95
+ indent = '';
96
+ if (typeof space === 'number') {
97
+ for (i = 0; i < space; i += 1) {
98
+ indent += ' ';
99
+ }
100
+ } else if (typeof space === 'string') {
101
+ indent = space;
102
+ }
103
+ rep = replacer;
104
+ if (replacer && typeof replacer !== 'function' && (typeof replacer !== 'object' || typeof replacer.length !== 'number')) {
105
+ throw new Error('JSON.stringify');
106
+ }
107
+ return str('', {'': value});
108
+ };
109
+ }
110
+ if (typeof JSON.parse !== 'function') {
111
+ JSON.parse = function (text, reviver) {
112
+ var j;
113
+
114
+ function walk(holder, key) {
115
+ var k, v, value = holder[key];
116
+ if (value && typeof value === 'object') {
117
+ for (k in value) {
118
+ if (Object.prototype.hasOwnProperty.call(value, k)) {
119
+ v = walk(value, k);
120
+ if (v !== undefined) {
121
+ value[k] = v;
122
+ } else {
123
+ delete value[k];
124
+ }
125
+ }
126
+ }
127
+ }
128
+ return reviver.call(holder, key, value);
129
+ }
130
+
131
+ text = String(text);
132
+ cx.lastIndex = 0;
133
+ if (cx.test(text)) {
134
+ text = text.replace(cx, function (a) {
135
+ return'\\u' +
136
+ ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
137
+ });
138
+ }
139
+ if (/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
140
+ j = eval('(' + text + ')');
141
+ return typeof reviver === 'function' ? walk({'': j}, '') : j;
142
+ }
143
+ throw new SyntaxError('JSON.parse');
144
+ };
145
+ }
146
+ }());
26
147
 
27
148
  var commandPath = "$PATH";
28
- if (!/\/$/.test(commandPath))
29
- {
149
+ if (!/\/$/.test(commandPath)) {
30
150
  commandPath += "/";
31
151
  }
32
152
  commandPath += "repl-cmd.txt";
33
153
 
154
+
155
+ var _expectedIndex = 0,//expected index of next command
156
+ _actualIndex,//actual index of next command by reading commandPath
157
+ _index,//index of ':' char in command
158
+ _exp,//expression to be eval'ed
159
+ _result,//result of eval
160
+ _input,//command
161
+ _process;//host command process
162
+
34
163
  var Log = (function () {
35
164
  // According to Appium,
36
165
  //16384 is the buffer size used by instruments
37
166
  var forceFlush = [],
38
- N = 0, i = N;
39
- if ("$MODE" == "FLUSH")
40
- {
41
- N = 16384;
167
+ N = "$MODE" == "FLUSH" ? 16384 : 0,
168
+ i = N;
169
+ while (i--) {
170
+ forceFlush[i] = "*";
42
171
  }
43
- while(i--) { forceFlush[i] = "*"; }
44
172
  forceFlush = forceFlush.join('');
45
173
 
174
+ function log_json(object)
175
+ {
176
+ UIALogger.logMessage("OUTPUT_JSON:\n"+JSON.stringify(object)+"\n");
177
+ }
178
+
46
179
  return {
47
180
  result: function (status, data) {
48
- UIALogger.logMessage(JSON.stringify({"status":status, "value":data}));
181
+ log_json({"status": status, "value": data, "index":_actualIndex})
49
182
  if (forceFlush.length > 0) {
50
183
  UIALogger.logMessage(forceFlush);
51
184
  }
52
185
  },
53
186
  output: function (msg) {
54
- UIALogger.logMessage(JSON.stringify({"output":msg}));
187
+ log_json({"output": msg,"last_index":_actualIndex});
55
188
  if (forceFlush.length > 0) {
56
189
  UIALogger.logMessage(forceFlush);
57
190
  }
@@ -60,26 +193,22 @@ var Log = (function () {
60
193
  })();
61
194
 
62
195
 
63
- function findAlertViewText(alert)
64
- {
65
- if (!alert) {return false;}
196
+ function findAlertViewText(alert) {
197
+ if (!alert) {
198
+ return false;
199
+ }
66
200
  var txt = alert.name(),
67
201
  txts;
68
- if (txt == null)
69
- {
202
+ if (txt == null) {
70
203
  txts = alert.staticTexts();
71
- if (txts != null && txts.length > 0)
72
- {
73
-
204
+ if (txts != null && txts.length > 0) {
74
205
  txt = txts[0].name();
75
206
  }
76
-
77
207
  }
78
208
  return txt;
79
209
  }
80
210
 
81
- function isLocationPrompt(alert)
82
- {
211
+ function isLocationPrompt(alert) {
83
212
  var exps = [
84
213
  ["OK", /vil bruge din aktuelle placering/],
85
214
  ["OK", /Would Like to Use Your Current Location/],
@@ -90,74 +219,77 @@ function isLocationPrompt(alert)
90
219
  txts;
91
220
 
92
221
  txt = findAlertViewText(alert);
93
- for (var i = 0; i < exps.length; i++)
94
- {
222
+ for (var i = 0; i < exps.length; i++) {
95
223
  ans = exps[i][0];
96
224
  exp = exps[i][1];
97
- if (exp.test(txt))
98
- {
225
+ if (exp.test(txt)) {
99
226
  return ans;
100
227
  }
101
228
  }
102
229
  return false;
103
230
  }
104
231
 
105
-
106
- UIATarget.onAlert = function (alert)
107
- {
108
- var answer = isLocationPrompt(alert);
109
- if (answer)
110
- {
111
- alert.buttons()[answer].tap();
232
+ UIATarget.onAlert = function (alert) {
233
+ var target = UIATarget.localTarget();
234
+ target.pushTimeout(10);
235
+ function attemptTouchOKOnLocation(retry_count) {
236
+ retry_count = retry_count || 0;
237
+ if (retry_count >= 5) {
238
+ Log.output("Maxed out retry (5) - unable to dismiss location dialog.");
239
+ return;
240
+ }
241
+ try {
242
+ var answer = isLocationPrompt(alert);
243
+ if (answer) {
244
+ alert.buttons()[answer].tap();
245
+ }
246
+ }
247
+ catch (e) {
248
+ Log.output("Exception while trying to touch alert dialog. Retrying...");
249
+ if (e && typeof e.toString == 'function') {
250
+ Log.output(e.toString());
251
+ }
252
+ target.delay(1);
253
+ attemptTouchOKOnLocation(retry_count + 1);
254
+ }
112
255
  }
256
+
257
+ attemptTouchOKOnLocation(0);
258
+ target.popTimeout();
113
259
  return true;
114
260
  };
115
261
 
116
262
 
117
-
118
-
119
263
  var target = null,
120
264
  host = null;
121
265
 
122
- var expectedIndex = 0,//expected index of next command
123
- actualIndex,//actual index of next command by reading commandPath
124
- index,//index of ':' char in command
125
- exp,//expression to be eval'ed
126
- result,//result of eval
127
- input,//command
128
- process;//host command process
129
266
 
130
- while (true)
131
- {
267
+
268
+ while (true) {
132
269
  target = UIATarget.localTarget();
133
270
  host = target.host();
134
271
  target.delay(0.2);
135
- try
136
- {
137
- process = host.performTaskWithPathArgumentsTimeout("/bin/cat",
138
- [commandPath],
139
- 2);
272
+ try {
273
+ _process = host.performTaskWithPathArgumentsTimeout("/bin/cat",
274
+ [commandPath],
275
+ 2);
140
276
 
141
- } catch (e)
142
- {
277
+ } catch (e) {
143
278
  Log.output("Timeout on cat...");
144
279
  continue;
145
280
  }
146
- if (process.exitCode != 0)
147
- {
148
- Log.output("unable to execute /bin/cat " + commandPath + " exitCode " + process.exitCode + ". Error: " + process.stderr);
281
+ if (_process.exitCode != 0) {
282
+ Log.output("unable to execute /bin/cat " + commandPath + " exitCode " + _process.exitCode + ". Error: " + _process.stderr);
149
283
  }
150
- else
151
- {
152
- input = process.stdout;
153
- try
154
- {
155
- index = input.indexOf(":", 0);
156
- if (index > -1) {
157
- actualIndex = parseInt(input.substring(0,index),10);
158
- if (!isNaN(actualIndex) && actualIndex >= expectedIndex) {
159
- exp = input.substring(index+1, input.length);
160
- result = eval(exp);
284
+ else {
285
+ _input = _process.stdout;
286
+ try {
287
+ _index = _input.indexOf(":", 0);
288
+ if (_index > -1) {
289
+ _actualIndex = parseInt(_input.substring(0, _index), 10);
290
+ if (!isNaN(_actualIndex) && _actualIndex >= _expectedIndex) {
291
+ _exp = _input.substring(_index + 1, _input.length);
292
+ _result = eval(_exp);
161
293
  }
162
294
  else {//likely old command is lingering...
163
295
  continue;
@@ -168,15 +300,14 @@ while (true)
168
300
  }
169
301
 
170
302
  }
171
- catch (err)
172
- {
303
+ catch (err) {
173
304
  Log.result("error", err.toString() + " " + (err.stack ? err.stack.toString() : ""));
174
- expectedIndex++;
305
+ _expectedIndex++;
175
306
  continue;
176
307
  }
177
308
 
178
- expectedIndex++;
179
- Log.result("success",result);
309
+ _expectedIndex++;
310
+ Log.result("success", _result);
180
311
 
181
312
  }
182
313
  }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: run_loop
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-04 00:00:00.000000000 Z
12
+ date: 2013-04-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: thor
@@ -27,6 +27,22 @@ dependencies:
27
27
  - - ! '>='
28
28
  - !ruby/object:Gem::Version
29
29
  version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: json
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
30
46
  description: calabash-cucumber drives tests for native iOS apps. RunLoop provides
31
47
  a number of tools associated with running Calabash tests.
32
48
  email:
@@ -57,8 +73,6 @@ files:
57
73
  - scripts/json2.js
58
74
  - scripts/run_dismiss_location.js
59
75
  - scripts/run_loop.js
60
- - scripts/run_screenshooter.sh
61
- - scripts/unix_instruments
62
76
  homepage: http://calaba.sh
63
77
  licenses: []
64
78
  post_install_message:
@@ -84,3 +98,4 @@ signing_key:
84
98
  specification_version: 3
85
99
  summary: Tools related to running Calabash iOS tests
86
100
  test_files: []
101
+ has_rdoc:
@@ -1,172 +0,0 @@
1
- #!/bin/bash
2
- # Copyright (c) 2012 Jonathan Penn (http://cocoamanifest.net/)
3
-
4
- # Permission is hereby granted, free of charge, to any person obtaining a copy
5
- # of this software and associated documentation files (the "Software"), to deal
6
- # in the Software without restriction, including without limitation the rights
7
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
- # copies of the Software, and to permit persons to whom the Software is
9
- # furnished to do so, subject to the following conditions:
10
-
11
- # The above copyright notice and this permission notice shall be included in
12
- # all copies or substantial portions of the Software.
13
-
14
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
- # THE SOFTWARE.
21
-
22
-
23
- # Tell bash that we want the whole script to fail if any part fails.
24
- set -e
25
-
26
- # We require a parameter for where to put the results
27
- destination="$1"
28
-
29
- main() {
30
- check_destination
31
-
32
- xcode clean build TARGETED_DEVICE_FAMILY=1
33
-
34
- bin/choose_sim_device "iPhone (Retina 3.5-inch)"
35
- shoot en fr ja
36
-
37
- bin/choose_sim_device "iPhone (Retina 4-inch)"
38
- shoot en fr ja
39
-
40
- # We to build again with the iPad device family because otherwise Instruments
41
- # will build and run for iPhone even though the simulator says otherwise.
42
- xcode build TARGETED_DEVICE_FAMILY=2
43
-
44
- bin/choose_sim_device "iPad (Retina)"
45
- shoot en fr ja
46
-
47
- close_sim
48
- }
49
-
50
- # Global variables to keep track of where everything goes
51
- dev_tools_dir=`xcode-select -print-path`
52
- tmp_dir="/tmp"
53
- build_dir="$tmp_dir/screen_shooter"
54
- bundle_dir="$build_dir/app.app"
55
- trace_results_dir="$build_dir/traces"
56
-
57
- check_destination() {
58
- # Abort if the destination directory already exists. Better safe than sorry.
59
-
60
- if [ -z "$destination" ]; then
61
- echo "usage: run_screenshooter.sh destination_directory"
62
- exit 1
63
- elif [ -d "$destination" ]; then
64
- echo "Destination directory \"$destination\" already exists! Aborting."
65
- exit 1
66
- fi
67
- }
68
-
69
- shoot() {
70
- # Takes the sim device type and a language code, runs the screenshot script,
71
- # and then copies over the screenshots to the destination
72
-
73
- for language in $*; do
74
- clean_trace_results_dir
75
- choose_sim_language $language
76
- run_automation "automation/shoot_the_screens.js"
77
- copy_screenshots
78
- done
79
- }
80
-
81
- xcode() {
82
- # A wrapper around `xcodebuild` that tells it to build the app in the temp
83
- # directory. If your app uses workspaces or special schemes, you'll need to
84
- # specify them here.
85
- #
86
- # Use `man xcodebuild` for more information on how to build your project.
87
-
88
- xcodebuild -sdk iphonesimulator \
89
- CONFIGURATION_BUILD_DIR=$build_dir \
90
- PRODUCT_NAME=app \
91
- $*
92
- }
93
-
94
- clean_trace_results_dir() {
95
- # Removes the trace results directory. We need to do this because Instruments
96
- # keeps appending new trace runs and it's simpler for us to always assume
97
- # there's just one run recorded where we look for screenshots.
98
-
99
- rm -rf "$trace_results_dir"
100
- mkdir -p "$trace_results_dir"
101
- }
102
-
103
- choose_sim_language() {
104
- # Alters the global preference file for every simulator type and moves the
105
- # chosen language identifier to the top.
106
-
107
- echo "Localizing for $1"
108
-
109
- pref_files=`ls ~/Library/Application\ Support/iPhone\ Simulator/[0-9]*/Library/Preferences/.GlobalPreferences.plist`
110
-
111
- close_sim
112
-
113
-
114
- # Set the string split delimiter to a newline for the 'for..in'
115
- old=$IFS
116
- IFS="
117
- "
118
-
119
- for file in $pref_files; do
120
- # Disable errors temporarily just in case the prefs don't have the key
121
- # we're trying to delete. This could happen when experimenting and leaving
122
- # the prefs file in an inconsistent state. If anything goes horribly wrong,
123
- # just delete the prefs file altogether and run the simulator to recreate
124
- # it.
125
- set +e
126
- /usr/libexec/PlistBuddy "$file" -c "Delete :AppleLanguages"
127
- set -e
128
-
129
- # Create the language array with just the given language
130
- /usr/libexec/PlistBuddy "$file" \
131
- -c "Add :AppleLanguages array" \
132
- -c "Add :AppleLanguages:0 string '$1'" \
133
- -c "Set :AppleLocale '$1'"
134
- done
135
-
136
- # Put the old string split delimiter back
137
- IFS=$old
138
- }
139
-
140
-
141
- close_sim() {
142
- # Closes the simulator. We need to do this after altering the languages and
143
- # when we want to clean up at the end.
144
-
145
- osascript -e 'tell application "iPhone Simulator" to quit'
146
- }
147
-
148
- run_automation() {
149
- # Runs the UI Automation JavaScript file that actually takes the screenshots.
150
-
151
- tracetemplate="$dev_tools_dir/../Applications/Instruments.app/Contents/PlugIns/AutomationInstrument.bundle/Contents/Resources/Automation.tracetemplate"
152
-
153
- # Check out the `unix_instruments` script to see why we need this wrapper.
154
- bin/unix_instruments \
155
- -D "$trace_results_dir/trace" \
156
- -t "$tracetemplate" \
157
- $bundle_dir \
158
- -e UIARESULTSPATH "$trace_results_dir" \
159
- -e UIASCRIPT "$1" \
160
- $*
161
- }
162
-
163
- copy_screenshots() {
164
- # Since we're always clearing out the trace results before every run, we can
165
- # assume that any screenshots were saved in the "Run 1" directory. Copy them
166
- # to the destination!
167
-
168
- mkdir -p "$destination"
169
- cp $trace_results_dir/Run\ 1/*.png $destination
170
- }
171
-
172
- main
@@ -1,92 +0,0 @@
1
- #!/usr/bin/env bash
2
- #
3
- # Copyright (c) 2012 Jonathan Penn (http://cocoamanifest.net)
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the "Software"), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in all
13
- # copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- # SOFTWARE.
22
- #
23
-
24
-
25
- # unix_instruments
26
- #
27
- # A wrapper around `instruments` that returns a proper unix status code
28
- # depending on whether the run failed or not. Alas, Apple's instruments tool
29
- # doesn't care about unix status codes, so I must grep for the "Fail:" string
30
- # and figure it out myself. As long as the command doesn't output that string
31
- # anywhere else inside it, then it should work.
32
- #
33
- # I use a tee pipe to capture the output and deliver it to stdout
34
- #
35
- # Author: Jonathan Penn (jonathan@cocoamanifest.net)
36
- #
37
-
38
- set -e # Bomb on any script errors
39
-
40
- run_instruments() {
41
- # Because instruments buffers it's output if it determines that it is being
42
- # piped to another process, we have to use ptys to get around that so that we
43
- # can use `tee` to save the output for grepping and print to stdout in real
44
- # time at the same time.
45
- #
46
- # I don't like this because I'm hard coding a tty/pty pair in here. Suggestions
47
- # to make this cleaner?
48
-
49
- output=$(mktemp -t unix-instruments)
50
- instruments $@ &> /dev/ttyvf & pid_instruments=$!
51
-
52
- # Cat the instruments output to tee which outputs to stdout and saves to
53
- # $output at the same time
54
- cat < /dev/ptyvf | tee $output
55
-
56
- # Clear the process id we saved when forking instruments so the cleanup
57
- # function called on exit knows it doesn't have to kill anything
58
- pid_instruments=0
59
-
60
- # Process the instruments output looking for anything that resembles a fail
61
- # message
62
- cat $output | get_error_status
63
- }
64
-
65
- get_error_status() {
66
- # Catch "Instruments Trace Error"
67
- # Catch "Instruments Usage Error"
68
- # Catch "00-00-00 00:00:00 +000 Fail:"
69
- # Catch "00-00-00 00:00:00 +000 Error:"
70
- # Catch "00-00-00 00:00:00 +000 None: Script threw an uncaught JavaScript error"
71
- ruby -e 'exit 1 if STDIN.read =~ /Instruments Usage Error|Instruments Trace Error|^\d+-\d+-\d+ \d+:\d+:\d+ [-+]\d+ (Fail:|Error:|None: Script threw an uncaught JavaScript error)/'
72
- }
73
-
74
- trap cleanup_instruments EXIT INT TERM
75
- function cleanup_instruments() {
76
- # Because we fork instruments in this script, we need to clean up if it's
77
- # still running because of an error or the user pressed Ctrl-C
78
- if [[ $pid_instruments -gt 0 ]]; then
79
- echo "Cleaning up instruments..."
80
- kill $pid_instruments
81
- fi
82
- }
83
-
84
- # Running this file with "----test" will try to parse an error out of whatever
85
- # is handed in to it from stdin. Use this method to double check your work if
86
- # you need a custom "get_error_status" function above.
87
- if [[ $1 == "----test" ]]; then
88
- get_error_status
89
- else
90
- run_instruments $@
91
- fi
92
-