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 +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
|
-
|