run_loop 0.0.5 → 0.0.6

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