illuminator 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/gem/README.md +37 -0
- data/gem/bin/illuminatorTestRunner.rb +22 -0
- data/gem/lib/illuminator.rb +171 -0
- data/gem/lib/illuminator/argument-parsing.rb +299 -0
- data/gem/lib/illuminator/automation-builder.rb +39 -0
- data/gem/lib/illuminator/automation-runner.rb +589 -0
- data/gem/lib/illuminator/build-artifacts.rb +118 -0
- data/gem/lib/illuminator/device-installer.rb +45 -0
- data/gem/lib/illuminator/host-utils.rb +42 -0
- data/gem/lib/illuminator/instruments-runner.rb +301 -0
- data/gem/lib/illuminator/javascript-runner.rb +98 -0
- data/gem/lib/illuminator/listeners/console-logger.rb +32 -0
- data/gem/lib/illuminator/listeners/full-output.rb +13 -0
- data/gem/lib/illuminator/listeners/instruments-listener.rb +22 -0
- data/gem/lib/illuminator/listeners/intermittent-failure-detector.rb +49 -0
- data/gem/lib/illuminator/listeners/pretty-output.rb +26 -0
- data/gem/lib/illuminator/listeners/saltinel-agent.rb +66 -0
- data/gem/lib/illuminator/listeners/saltinel-listener.rb +26 -0
- data/gem/lib/illuminator/listeners/start-detector.rb +52 -0
- data/gem/lib/illuminator/listeners/stop-detector.rb +46 -0
- data/gem/lib/illuminator/listeners/test-listener.rb +58 -0
- data/gem/lib/illuminator/listeners/trace-error-detector.rb +38 -0
- data/gem/lib/illuminator/options.rb +96 -0
- data/gem/lib/illuminator/resources/IlluminatorGeneratedEnvironment.erb +13 -0
- data/gem/lib/illuminator/resources/IlluminatorGeneratedRunnerForInstruments.erb +19 -0
- data/gem/lib/illuminator/test-definitions.rb +23 -0
- data/gem/lib/illuminator/test-suite.rb +155 -0
- data/gem/lib/illuminator/version.rb +3 -0
- data/gem/lib/illuminator/xcode-builder.rb +144 -0
- data/gem/lib/illuminator/xcode-utils.rb +219 -0
- data/gem/resources/BuildConfiguration.xcconfig +10 -0
- data/gem/resources/js/AppMap.js +767 -0
- data/gem/resources/js/Automator.js +1132 -0
- data/gem/resources/js/Base64.js +142 -0
- data/gem/resources/js/Bridge.js +102 -0
- data/gem/resources/js/Config.js +92 -0
- data/gem/resources/js/Extensions.js +2025 -0
- data/gem/resources/js/Illuminator.js +228 -0
- data/gem/resources/js/Preferences.js +24 -0
- data/gem/resources/scripts/UIAutomationBridge.rb +248 -0
- data/gem/resources/scripts/common.applescript +25 -0
- data/gem/resources/scripts/diff_png.sh +61 -0
- data/gem/resources/scripts/kill_all_sim_processes.sh +17 -0
- data/gem/resources/scripts/plist_to_json.sh +40 -0
- data/gem/resources/scripts/set_hardware_keyboard.applescript +0 -0
- metadata +225 -0
@@ -0,0 +1,228 @@
|
|
1
|
+
/**
|
2
|
+
* Main entry point
|
3
|
+
*
|
4
|
+
* There are no arguments to this function, because it will be controlled by Config arguments
|
5
|
+
* and invoked by testAutomatically.js (a generated file). See the documentation on how to
|
6
|
+
* set up and run Illuminator.
|
7
|
+
*/
|
8
|
+
function IlluminatorIlluminate() {
|
9
|
+
// initial sanity checks
|
10
|
+
assertDesiredSimVersion();
|
11
|
+
|
12
|
+
// send test definitions back to framework
|
13
|
+
if (!host().writeToFile(config.buildArtifacts.automatorScenarioJSON,
|
14
|
+
JSON.stringify(automator.toScenarioObject(false), null, " ")))
|
15
|
+
{
|
16
|
+
throw new IlluminatorSetupException("Could not save necessary build artifact to " +
|
17
|
+
config.buildArtifacts.automatorScenarioJSON);
|
18
|
+
}
|
19
|
+
notifyIlluminatorFramework("Saved scenario definitions to: " + config.buildArtifacts.automatorScenarioJSON);
|
20
|
+
|
21
|
+
// run app-specific callback
|
22
|
+
if (!automator._executeCallback("onInit", {entryPoint: config.entryPoint}, false, false)) return;
|
23
|
+
|
24
|
+
// choose entry point and go
|
25
|
+
switch (config.entryPoint) {
|
26
|
+
|
27
|
+
case "runTestsByName":
|
28
|
+
automator.runNamedScenarios(config.automatorScenarioNames, config.automatorSequenceRandomSeed);
|
29
|
+
break;
|
30
|
+
|
31
|
+
case "runTestsByTag":
|
32
|
+
if (0 == (config.automatorTagsAny.length + config.automatorTagsAll.length + config.automatorTagsNone.length)) {
|
33
|
+
UIALogger.logMessage("No tag sets (any / all / none) were specified, so printing some information about defined scenarios");
|
34
|
+
automator.logInfo();
|
35
|
+
notifyIlluminatorFramework("Successful launch");
|
36
|
+
} else {
|
37
|
+
automator.runTaggedScenarios(config.automatorTagsAny,
|
38
|
+
config.automatorTagsAll,
|
39
|
+
config.automatorTagsNone,
|
40
|
+
config.automatorSequenceRandomSeed);
|
41
|
+
}
|
42
|
+
break;
|
43
|
+
|
44
|
+
case "describe":
|
45
|
+
notifyIlluminatorFramework("Successful launch");
|
46
|
+
var appMapMarkdownPath = config.buildArtifacts.appMapMarkdown;
|
47
|
+
var automatorMarkdownPath = config.buildArtifacts.automatorMarkdown;
|
48
|
+
var automatorJSONPath = config.buildArtifacts.automatorJSON;
|
49
|
+
host().writeToFile(appMapMarkdownPath, appmap.toMarkdown());
|
50
|
+
UIALogger.logMessage("Wrote AppMap definitions to " + appMapMarkdownPath);
|
51
|
+
host().writeToFile(automatorMarkdownPath, automator.toMarkdown());
|
52
|
+
UIALogger.logMessage("Wrote automator definitions to " + automatorMarkdownPath);
|
53
|
+
host().writeToFile(automatorJSONPath, JSON.stringify(automator.toScenarioObject(true), null, " "));
|
54
|
+
UIALogger.logMessage("Wrote automator definition data to " + automatorJSONPath);
|
55
|
+
break;
|
56
|
+
|
57
|
+
default:
|
58
|
+
notifyIlluminatorFramework("Successful launch");
|
59
|
+
throw new IlluminatorSetupException("Unknown Illuminator entry point specified: " + config.entryPoint);
|
60
|
+
}
|
61
|
+
}
|
62
|
+
|
63
|
+
|
64
|
+
/**
|
65
|
+
* Send a message back to the log analyzers (ruby)
|
66
|
+
*
|
67
|
+
* @param message string
|
68
|
+
*/
|
69
|
+
function notifyIlluminatorFramework(message) {
|
70
|
+
UIALogger.logDebug(config.saltinel + " " + message + " " + config.saltinel);
|
71
|
+
}
|
72
|
+
|
73
|
+
function isMatchingVersion(input, prefix, major, minor, rev) {
|
74
|
+
var findStr = prefix + major;
|
75
|
+
|
76
|
+
if (undefined !== minor) {
|
77
|
+
findStr += "." + minor;
|
78
|
+
if (undefined !== rev) {
|
79
|
+
findStr += "." + rev;
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
return input.indexOf(findStr) > -1;
|
84
|
+
}
|
85
|
+
|
86
|
+
function isSimVersion(major, minor, rev) {
|
87
|
+
return isMatchingVersion(target().systemVersion(), "", major, minor, rev);
|
88
|
+
}
|
89
|
+
|
90
|
+
function assertDesiredSimVersion() {
|
91
|
+
var ver = target().systemVersion();
|
92
|
+
if (("iOS " + ver).indexOf(config.automatorDesiredSimVersion) == -1) {
|
93
|
+
throw new IlluminatorSetupException("Simulator version " + ver + " is running, but generated-config.js " +
|
94
|
+
"specifies " + config.automatorDesiredSimVersion);
|
95
|
+
}
|
96
|
+
}
|
97
|
+
|
98
|
+
function actionCompareScreenshotToMaster(parm) {
|
99
|
+
var masterPath = parm.masterPath;
|
100
|
+
var maskPath = parm.maskPath;
|
101
|
+
var captureTitle = parm.captureTitle;
|
102
|
+
var delayCapture = parm.delay === undefined ? 0.4 : parm.delay;
|
103
|
+
|
104
|
+
delay(delayCapture); // wait for any animations to settle
|
105
|
+
|
106
|
+
var diff_pngPath = IlluminatorScriptsDirectory + "/diff_png.sh";
|
107
|
+
UIATarget.localTarget().captureScreenWithName(captureTitle);
|
108
|
+
|
109
|
+
var screenshotFile = captureTitle + ".png";
|
110
|
+
var screenshotPath = config.screenshotDir + "/" + screenshotFile;
|
111
|
+
var compareFileBase = config.screenshotDir + "/compared_" + captureTitle;
|
112
|
+
|
113
|
+
var output = target().host().performTaskWithPathArgumentsTimeout("/bin/sh",
|
114
|
+
[diff_pngPath,
|
115
|
+
masterPath,
|
116
|
+
screenshotPath,
|
117
|
+
maskPath,
|
118
|
+
compareFileBase],
|
119
|
+
20);
|
120
|
+
|
121
|
+
// turn the output into key/value pairs separated by ":"
|
122
|
+
var outputArr = output.stdout.split("\n");
|
123
|
+
var outputObj = {};
|
124
|
+
for (var i = 0; i < outputArr.length; ++i) {
|
125
|
+
var sp = outputArr[i].split(": ", 2)
|
126
|
+
if (sp.length == 2) {
|
127
|
+
outputObj[sp[0]] = sp[1];
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
131
|
+
// sanity check
|
132
|
+
if (!outputObj["pixels changed"]) {
|
133
|
+
throw new IlluminatorRuntimeVerificationException("actionCompareScreenshotToMaster: diff_png.sh failed to produce 'pixels changed' output");
|
134
|
+
}
|
135
|
+
|
136
|
+
// if differences are outside tolerances, throw errors
|
137
|
+
var allPixels = parseInt(outputObj["pixels (total)"]);
|
138
|
+
var wrongPixels = parseInt(outputObj["pixels changed"]);
|
139
|
+
|
140
|
+
var allowedPixels = parm.allowedPixels === undefined ? 0 : parm.allowedPixels;
|
141
|
+
var errmsg = "";
|
142
|
+
if (allowedPixels < wrongPixels) {
|
143
|
+
errmsg = ["Screenshot differed from", masterPath,
|
144
|
+
"by", wrongPixels, "pixels. ",
|
145
|
+
"Comparison image saved to:", compareFileBase + ".png",
|
146
|
+
" and comparison animation saved to:", compareFileBase + ".gif"].join(" ");
|
147
|
+
|
148
|
+
if (parm.deferFailure === true) {
|
149
|
+
automator.deferFailure(errmsg);
|
150
|
+
} else {
|
151
|
+
throw new IlluminatorRuntimeVerificationException(errmsg);
|
152
|
+
}
|
153
|
+
}
|
154
|
+
|
155
|
+
if (parm.allowedPercent !== undefined) {
|
156
|
+
var wrongPct = 100.0 * wrongPixels / allPixels;
|
157
|
+
if (wrongPct > parm.allowedPercent) {
|
158
|
+
errmsg = ["Screenshot differed from", masterPath,
|
159
|
+
"by", wrongPct, "%. ",
|
160
|
+
"Comparison image saved to:", compareFileBase + ".png",
|
161
|
+
" and comparison animation saved to:", compareFileBase + ".gif"].join(" ");
|
162
|
+
|
163
|
+
if (parm.deferFailure === true) {
|
164
|
+
} else {
|
165
|
+
throw new IlluminatorRuntimeVerificationException(errmsg);
|
166
|
+
}
|
167
|
+
}
|
168
|
+
}
|
169
|
+
}
|
170
|
+
|
171
|
+
function actionLogAccessors(parm) {
|
172
|
+
if (parm !== undefined && parm.delay !== undefined) {
|
173
|
+
delay(parm.delay);
|
174
|
+
}
|
175
|
+
var visibleOnly = parm !== undefined && parm.visibleOnly === true;
|
176
|
+
UIALogger.logDebug(target().elementReferenceDump("target", visibleOnly));
|
177
|
+
}
|
178
|
+
|
179
|
+
function actionCaptureElementTree(parm) {
|
180
|
+
target().captureImageTree(parm.imageBaseName);
|
181
|
+
}
|
182
|
+
|
183
|
+
|
184
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////
|
185
|
+
// Appmap additions - common capabilities
|
186
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////
|
187
|
+
appmap.createOrAugmentApp("Illuminator").withScreen("do")
|
188
|
+
.onTarget(config.implementation, function() { return true; })
|
189
|
+
// onTarget(config.implementation...) is a HACK.
|
190
|
+
// Illuminator doesn't define implementations, so we use what we're given.
|
191
|
+
// (i.e. you shouldn't copy/paste this example into your project code)
|
192
|
+
|
193
|
+
.withAction("delay", "Delay a given amount of time")
|
194
|
+
.withParam("seconds", "Number of seconds to delay", true, true)
|
195
|
+
.withImplementation(function(parm) {delay(parm.seconds);})
|
196
|
+
|
197
|
+
.withAction("debug", "Print the results of a debug function")
|
198
|
+
.withParam("debug_fn", "Function returning a string", true)
|
199
|
+
.withImplementation(function(parm) { UIALogger.logMessage(parm.debug_fn()); })
|
200
|
+
|
201
|
+
.withAction("logTree", "Log the UI element tree")
|
202
|
+
.withImplementation(function() { UIATarget.localTarget().logElementTree(); })
|
203
|
+
|
204
|
+
.withAction("logAccessors", "Log the list of valid element accessors")
|
205
|
+
.withParam("visibleOnly", "Whether to log only the visible elements", false, true)
|
206
|
+
.withParam("delay", "Number of seconds to delay before logging", false, true)
|
207
|
+
.withImplementation(actionLogAccessors)
|
208
|
+
|
209
|
+
.withAction("captureElementTree", "Take individual screenshots of all screen elements")
|
210
|
+
.withParam("imageBaseName", "The base name for the image files", true, true)
|
211
|
+
.withImplementation(actionCaptureElementTree)
|
212
|
+
|
213
|
+
.withAction("fail", "Unconditionally fail the current test for debugging purposes")
|
214
|
+
.withImplementation(function() { throw new IlluminatorRuntimeVerificationException("purposely-thrown exception to halt the test scenario"); })
|
215
|
+
|
216
|
+
.withAction("verifyScreenshot", "Validate a screenshot against a png template of the expected view")
|
217
|
+
.withParam("masterPath", "The path to the file that is considered the 'expected' view", true, true)
|
218
|
+
.withParam("maskPath", "The path to the file that masks variable portions of the 'expected' view", true, true)
|
219
|
+
.withParam("captureTitle", "The title of the screenshot to capture", true, true)
|
220
|
+
.withParam("delay", "The amount of time to delay before taking the screenshot", false, true)
|
221
|
+
.withParam("allowedPixels", "The maximum number of pixels that are allowed to differ (default 0)", false, true)
|
222
|
+
.withParam("allowedPercent", "The maximum percentage of pixels that are allowed to differ (default 0)", false, true)
|
223
|
+
.withParam("deferFailure", "Whether to defer a failure until the end of the test", false, true)
|
224
|
+
.withImplementation(actionCompareScreenshotToMaster)
|
225
|
+
|
226
|
+
.withAction("testAsAction", "Execute an entire test as one action")
|
227
|
+
.withParam("test", "The function that performs the entire test")
|
228
|
+
.withImplementation(function (parm) { parm.test(); });
|
@@ -0,0 +1,24 @@
|
|
1
|
+
// Preferences.js
|
2
|
+
//
|
3
|
+
// Provides the set of known user preferences for Illuminator
|
4
|
+
|
5
|
+
|
6
|
+
(function() {
|
7
|
+
|
8
|
+
var root = this,
|
9
|
+
preferences = null;
|
10
|
+
|
11
|
+
// put preferences in namespace of importing code
|
12
|
+
if (typeof exports !== 'undefined') {
|
13
|
+
preferences = exports;
|
14
|
+
} else {
|
15
|
+
preferences = root.preferences = {};
|
16
|
+
}
|
17
|
+
|
18
|
+
preferences.extensions = {};
|
19
|
+
preferences.extensions.reduceTimeout = 10; // seconds
|
20
|
+
|
21
|
+
preferences.automator = {};
|
22
|
+
preferences.automator.onError = {};
|
23
|
+
|
24
|
+
}).call(this);
|
@@ -0,0 +1,248 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'json'
|
4
|
+
require 'base64'
|
5
|
+
require 'dnssd'
|
6
|
+
require 'socket'
|
7
|
+
require 'timeout'
|
8
|
+
require 'optparse'
|
9
|
+
|
10
|
+
# all parsing code goes here
|
11
|
+
def parse_arguments(args)
|
12
|
+
ret = {}
|
13
|
+
ret["timeout"] = 15
|
14
|
+
op = OptionParser.new do |opts|
|
15
|
+
opts.banner = "Usage: #{__FILE__} [options]"
|
16
|
+
opts.separator ""
|
17
|
+
opts.separator "Specific options:"
|
18
|
+
|
19
|
+
opts.on("-a", "--argument=[JSON]",
|
20
|
+
"Pass the supplied JSON data over the bridge") do |v|
|
21
|
+
ret["argument"] = v
|
22
|
+
end
|
23
|
+
|
24
|
+
opts.on("-b", "--b64argument=[B64-JSON]",
|
25
|
+
"Pass the base64-encoded JSON data over the bridge") do |v|
|
26
|
+
ret["b64argument"] = v
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.on("-s", "--selector=SELECTOR",
|
30
|
+
"Call the given function (selector) via the bridge") do |v|
|
31
|
+
ret["selector"] = v
|
32
|
+
end
|
33
|
+
|
34
|
+
opts.on("-c", "--callUID=UID",
|
35
|
+
"Use the given UID to properly identify the return value of this call") do |v|
|
36
|
+
ret["callUID"] = v
|
37
|
+
end
|
38
|
+
|
39
|
+
opts.on("-r", "--hardwareID=[HARDWAREID]",
|
40
|
+
"If provided, connect to the physical iOS device with this hardware ID instead of a simulator") do |v|
|
41
|
+
ret["hardwareID"] = v
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.on("-t", "--timeout=[TIMEOUT]",
|
45
|
+
"The timeout in seconds for reading a response from the bridge (default 15)") do |v|
|
46
|
+
ret["timeout"] = v.to_i
|
47
|
+
end
|
48
|
+
|
49
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
50
|
+
puts opts
|
51
|
+
exit
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
op.parse!(args)
|
56
|
+
return ret
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
# communicate the result to the console
|
61
|
+
# success: boolean whether it all went well
|
62
|
+
# failmsg: what to say went wrong
|
63
|
+
# checkpoints: some stuff to print out, debug info
|
64
|
+
# output_data: the actual results
|
65
|
+
def print_result_and_exit(success, failmsg, checkpoints, response=nil)
|
66
|
+
output_data = {}
|
67
|
+
output_data["checkpoints"] = checkpoints
|
68
|
+
output_data["response"] = response unless response.nil?
|
69
|
+
output_data["success"] = success
|
70
|
+
output_data["message"] = failmsg
|
71
|
+
puts JSON.pretty_generate(output_data)
|
72
|
+
exit(success ? 0 : 1)
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
def get_host_port_of_hardware_id(hardware_id, timeout_seconds)
|
77
|
+
service = DNSSD::Service.new
|
78
|
+
host, port = nil, nil
|
79
|
+
begin
|
80
|
+
Timeout::timeout(timeout_seconds) do
|
81
|
+
service.browse '_bridge._tcp' do |result|
|
82
|
+
if result.name.eql? "UIAutomationBridge_#{hardware_id}"
|
83
|
+
resolver = DNSSD::Service.new
|
84
|
+
resolver.resolve result do |r|
|
85
|
+
host = r.target
|
86
|
+
port = r.port
|
87
|
+
break unless r.flags.more_coming?
|
88
|
+
end
|
89
|
+
break
|
90
|
+
end
|
91
|
+
end
|
92
|
+
return host, port
|
93
|
+
end
|
94
|
+
rescue Timeout::Error
|
95
|
+
end
|
96
|
+
return host, port
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
def connect(host, port, timeout=5)
|
101
|
+
addr = Socket.getaddrinfo(host, nil)
|
102
|
+
sock = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
|
103
|
+
|
104
|
+
if timeout
|
105
|
+
secs = Integer(timeout)
|
106
|
+
usecs = Integer((timeout - secs) * 1_000_000)
|
107
|
+
optval = [secs, usecs].pack('l_2')
|
108
|
+
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
|
109
|
+
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
|
110
|
+
end
|
111
|
+
begin
|
112
|
+
sock.connect(Socket.pack_sockaddr_in(port, addr[0][3]))
|
113
|
+
return sock, ""
|
114
|
+
rescue Exception => e
|
115
|
+
return nil, "Failed to connect to #{host}:#{port} - #{e.class.name} - #{e.message}"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
# these describe what we expect to have happen, and we will check them off as they do
|
121
|
+
checkpoints = {}
|
122
|
+
checkpoints["ruby"] = true
|
123
|
+
checkpoints["argument"] = nil
|
124
|
+
checkpoints["hardwareID"] = nil
|
125
|
+
checkpoints["connection"] = nil
|
126
|
+
checkpoints["request"] = nil
|
127
|
+
checkpoints["response"] = nil
|
128
|
+
checkpoints["callUIDMatch"] = nil
|
129
|
+
|
130
|
+
|
131
|
+
# process the input
|
132
|
+
options = parse_arguments(ARGV)
|
133
|
+
|
134
|
+
# verify that the input selector was provided
|
135
|
+
if options["selector"].nil?
|
136
|
+
print_result_and_exit(false, "selector not provided", checkpoints)
|
137
|
+
end
|
138
|
+
|
139
|
+
# decode b64 argument if provided
|
140
|
+
unless options["b64argument"].nil?
|
141
|
+
begin
|
142
|
+
decoded_arg = Base64.strict_decode64(options["b64argument"])
|
143
|
+
options['argument'] = decoded_arg # the next if block will find & process this
|
144
|
+
JSON.parse(decoded_arg)
|
145
|
+
rescue ArgumentError => e
|
146
|
+
print_result_and_exit(false, "Error decoding b64argument: #{e.message}", checkpoints)
|
147
|
+
rescue JSON::ParserError => e
|
148
|
+
print_result_and_exit(false, "Decoded b64argument does not appear to contain (valid) JSON", checkpoints)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# parse JSON in argument if provided (or b64 provided)
|
153
|
+
unless options["argument"].nil?
|
154
|
+
checkpoints["argument"] = false
|
155
|
+
begin
|
156
|
+
parsed_arg = JSON.parse(options["argument"])
|
157
|
+
options["jsonArgument"] = parsed_arg
|
158
|
+
checkpoints["argument"] = true
|
159
|
+
rescue JSON::ParserError => e
|
160
|
+
print_result_and_exit(false, "Error parsing JSON argument: #{e.message}", checkpoints)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# build the request that will go the server
|
165
|
+
request_hash = {}
|
166
|
+
request_hash['argument'] = options["jsonArgument"] unless options["jsonArgument"].nil?
|
167
|
+
request_hash["selector"] = options["selector"]
|
168
|
+
request_hash["callUID"] = options["callUID"]
|
169
|
+
request = request_hash.to_json
|
170
|
+
checkpoints["actual_request"] = request_hash
|
171
|
+
|
172
|
+
# get the host/port according to whether we are using hardware
|
173
|
+
host, port = '127.0.0.1', 4200
|
174
|
+
unless options["hardwareID"].nil?
|
175
|
+
checkpoints["hardwareID"] = false
|
176
|
+
host, port = get_host_port_of_hardware_id(options["hardwareID"], 3)
|
177
|
+
if host.nil? or port.nil?
|
178
|
+
print_result_and_exit(false, "Failed to get host/port for hardware ID", checkpoints)
|
179
|
+
end
|
180
|
+
checkpoints["hardwareID"] = true
|
181
|
+
end
|
182
|
+
|
183
|
+
# connect
|
184
|
+
checkpoints["host"] = host
|
185
|
+
checkpoints["port"] = port
|
186
|
+
checkpoints["connection"] = false
|
187
|
+
socket_stream, err_message = connect host, port
|
188
|
+
if socket_stream.nil?
|
189
|
+
print_result_and_exit(false, err_message, checkpoints)
|
190
|
+
end
|
191
|
+
checkpoints["connection"] = true
|
192
|
+
|
193
|
+
begin
|
194
|
+
# send request
|
195
|
+
checkpoints["request"] = false
|
196
|
+
socket_stream.write(request)
|
197
|
+
|
198
|
+
checkpoints["request"] = true
|
199
|
+
|
200
|
+
# read response
|
201
|
+
checkpoints["response"] = false
|
202
|
+
response = ''
|
203
|
+
|
204
|
+
timeout(options["timeout"]) do
|
205
|
+
while true
|
206
|
+
new_data = nil
|
207
|
+
begin
|
208
|
+
timeout(0.1) do
|
209
|
+
new_data = socket_stream.gets("}") # read up to closing brace at a time
|
210
|
+
end
|
211
|
+
rescue Timeout::Error
|
212
|
+
# nothing
|
213
|
+
end
|
214
|
+
|
215
|
+
unless new_data.nil?
|
216
|
+
response = response + new_data
|
217
|
+
end
|
218
|
+
|
219
|
+
begin
|
220
|
+
# successfully parse, or go back and try again
|
221
|
+
JSON.parse(response)
|
222
|
+
break
|
223
|
+
rescue
|
224
|
+
# nothing
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
checkpoints["response"] = true
|
229
|
+
checkpoints["response_length"] = response.length
|
230
|
+
|
231
|
+
rescue Timeout::Error
|
232
|
+
print_result_and_exit(false, "Timed out waiting for response", checkpoints)
|
233
|
+
rescue Exception => e
|
234
|
+
print_result_and_exit(false, "Error while waiting for response: #{e.inspect()}", checkpoints)
|
235
|
+
ensure
|
236
|
+
socket_stream.close
|
237
|
+
end
|
238
|
+
|
239
|
+
resp = JSON.parse(response)
|
240
|
+
|
241
|
+
# check callUID
|
242
|
+
checkpoints["callUIDMatch"] = false
|
243
|
+
if options["callUID"] != resp["callUID"]
|
244
|
+
print_result_and_exit(false, "Expected callUID=#{options["callUID"]} but got callUID=#{resp["callUID"]}", checkpoints, resp)
|
245
|
+
end
|
246
|
+
checkpoints["callUIDMatch"] = true
|
247
|
+
|
248
|
+
print_result_and_exit(true, "all bridge options completed successfully", checkpoints, resp)
|