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 +2 -0
- data/LICENSE +1 -1
- data/README.md +0 -9
- data/lib/run_loop/core.rb +156 -52
- data/lib/run_loop/version.rb +1 -1
- data/run_loop.gemspec +1 -0
- data/scripts/run_loop.js +224 -93
- metadata +19 -4
- data/scripts/run_screenshooter.sh +0 -172
- data/scripts/unix_instruments +0 -92
data/.irbrc
CHANGED
data/LICENSE
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
run-loop
|
2
2
|
|
3
|
-
Copyright (c)
|
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
|
-
|
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
|
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
|
-
|
64
|
+
uidevicefamily=1
|
67
65
|
else
|
68
|
-
|
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
|
-
|
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
|
-
|
92
|
-
|
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
|
-
|
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, "..",
|
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
|
-
|
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
|
-
|
172
|
-
|
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
|
-
|
272
|
+
result
|
177
273
|
end
|
178
274
|
|
179
|
-
def self.stop(
|
180
|
-
results_dir =
|
181
|
-
pid =
|
182
|
-
dest =
|
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(
|
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
|
data/lib/run_loop/version.rb
CHANGED
data/run_loop.gemspec
CHANGED
data/scripts/run_loop.js
CHANGED
@@ -1,57 +1,190 @@
|
|
1
|
-
if(typeof JSON!=='object'){
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
f(
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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 =
|
39
|
-
|
40
|
-
{
|
41
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
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
|
-
|
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
|
-
|
138
|
-
|
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 (
|
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
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
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
|
-
|
305
|
+
_expectedIndex++;
|
175
306
|
continue;
|
176
307
|
}
|
177
308
|
|
178
|
-
|
179
|
-
Log.result("success",
|
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.
|
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-
|
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
|
data/scripts/unix_instruments
DELETED
@@ -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
|
-
|