|
1 /* Any copyright is dedicated to the Public Domain. |
|
2 http://creativecommons.org/publicdomain/zero/1.0/ */ |
|
3 |
|
4 "use strict"; |
|
5 const Cc = Components.classes; |
|
6 const Ci = Components.interfaces; |
|
7 const Cu = Components.utils; |
|
8 const Cr = Components.results; |
|
9 |
|
10 const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); |
|
11 const Services = devtools.require("Services"); |
|
12 const { ActorPool, createExtraActors, appendExtraActors } = devtools.require("devtools/server/actors/common"); |
|
13 const DevToolsUtils = devtools.require("devtools/toolkit/DevToolsUtils.js"); |
|
14 const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {}); |
|
15 |
|
16 // Always log packets when running tests. runxpcshelltests.py will throw |
|
17 // the output away anyway, unless you give it the --verbose flag. |
|
18 Services.prefs.setBoolPref("devtools.debugger.log", true); |
|
19 // Enable remote debugging for the relevant tests. |
|
20 Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true); |
|
21 |
|
22 function tryImport(url) { |
|
23 try { |
|
24 Cu.import(url); |
|
25 } catch (e) { |
|
26 dump("Error importing " + url + "\n"); |
|
27 dump(DevToolsUtils.safeErrorString(e) + "\n"); |
|
28 throw e; |
|
29 } |
|
30 } |
|
31 |
|
32 tryImport("resource://gre/modules/devtools/dbg-server.jsm"); |
|
33 tryImport("resource://gre/modules/devtools/dbg-client.jsm"); |
|
34 tryImport("resource://gre/modules/devtools/Loader.jsm"); |
|
35 tryImport("resource://gre/modules/devtools/Console.jsm"); |
|
36 |
|
37 function testExceptionHook(ex) { |
|
38 try { |
|
39 do_report_unexpected_exception(ex); |
|
40 } catch(ex) { |
|
41 return {throw: ex} |
|
42 } |
|
43 return undefined; |
|
44 } |
|
45 |
|
46 // Convert an nsIScriptError 'aFlags' value into an appropriate string. |
|
47 function scriptErrorFlagsToKind(aFlags) { |
|
48 var kind; |
|
49 if (aFlags & Ci.nsIScriptError.warningFlag) |
|
50 kind = "warning"; |
|
51 if (aFlags & Ci.nsIScriptError.exceptionFlag) |
|
52 kind = "exception"; |
|
53 else |
|
54 kind = "error"; |
|
55 |
|
56 if (aFlags & Ci.nsIScriptError.strictFlag) |
|
57 kind = "strict " + kind; |
|
58 |
|
59 return kind; |
|
60 } |
|
61 |
|
62 // Redeclare dbg_assert with a fatal behavior. |
|
63 function dbg_assert(cond, e) { |
|
64 if (!cond) { |
|
65 throw e; |
|
66 } |
|
67 } |
|
68 |
|
69 // Register a console listener, so console messages don't just disappear |
|
70 // into the ether. |
|
71 let errorCount = 0; |
|
72 let listener = { |
|
73 observe: function (aMessage) { |
|
74 errorCount++; |
|
75 try { |
|
76 // If we've been given an nsIScriptError, then we can print out |
|
77 // something nicely formatted, for tools like Emacs to pick up. |
|
78 var scriptError = aMessage.QueryInterface(Ci.nsIScriptError); |
|
79 dump(aMessage.sourceName + ":" + aMessage.lineNumber + ": " + |
|
80 scriptErrorFlagsToKind(aMessage.flags) + ": " + |
|
81 aMessage.errorMessage + "\n"); |
|
82 var string = aMessage.errorMessage; |
|
83 } catch (x) { |
|
84 // Be a little paranoid with message, as the whole goal here is to lose |
|
85 // no information. |
|
86 try { |
|
87 var string = "" + aMessage.message; |
|
88 } catch (x) { |
|
89 var string = "<error converting error message to string>"; |
|
90 } |
|
91 } |
|
92 |
|
93 // Make sure we exit all nested event loops so that the test can finish. |
|
94 while (DebuggerServer.xpcInspector.eventLoopNestLevel > 0) { |
|
95 DebuggerServer.xpcInspector.exitNestedEventLoop(); |
|
96 } |
|
97 do_throw("head_dbg.js got console message: " + string + "\n"); |
|
98 } |
|
99 }; |
|
100 |
|
101 let consoleService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService); |
|
102 consoleService.registerListener(listener); |
|
103 |
|
104 function check_except(func) |
|
105 { |
|
106 try { |
|
107 func(); |
|
108 } catch (e) { |
|
109 do_check_true(true); |
|
110 return; |
|
111 } |
|
112 dump("Should have thrown an exception: " + func.toString()); |
|
113 do_check_true(false); |
|
114 } |
|
115 |
|
116 function testGlobal(aName) { |
|
117 let systemPrincipal = Cc["@mozilla.org/systemprincipal;1"] |
|
118 .createInstance(Ci.nsIPrincipal); |
|
119 |
|
120 let sandbox = Cu.Sandbox(systemPrincipal); |
|
121 sandbox.__name = aName; |
|
122 return sandbox; |
|
123 } |
|
124 |
|
125 function addTestGlobal(aName) |
|
126 { |
|
127 let global = testGlobal(aName); |
|
128 DebuggerServer.addTestGlobal(global); |
|
129 return global; |
|
130 } |
|
131 |
|
132 // List the DebuggerClient |aClient|'s tabs, look for one whose title is |
|
133 // |aTitle|, and apply |aCallback| to the packet's entry for that tab. |
|
134 function getTestTab(aClient, aTitle, aCallback) { |
|
135 aClient.listTabs(function (aResponse) { |
|
136 for (let tab of aResponse.tabs) { |
|
137 if (tab.title === aTitle) { |
|
138 aCallback(tab); |
|
139 return; |
|
140 } |
|
141 } |
|
142 aCallback(null); |
|
143 }); |
|
144 } |
|
145 |
|
146 // Attach to |aClient|'s tab whose title is |aTitle|; pass |aCallback| the |
|
147 // response packet and a TabClient instance referring to that tab. |
|
148 function attachTestTab(aClient, aTitle, aCallback) { |
|
149 getTestTab(aClient, aTitle, function (aTab) { |
|
150 aClient.attachTab(aTab.actor, aCallback); |
|
151 }); |
|
152 } |
|
153 |
|
154 // Attach to |aClient|'s tab whose title is |aTitle|, and then attach to |
|
155 // that tab's thread. Pass |aCallback| the thread attach response packet, a |
|
156 // TabClient referring to the tab, and a ThreadClient referring to the |
|
157 // thread. |
|
158 function attachTestThread(aClient, aTitle, aCallback) { |
|
159 attachTestTab(aClient, aTitle, function (aResponse, aTabClient) { |
|
160 function onAttach(aResponse, aThreadClient) { |
|
161 aCallback(aResponse, aTabClient, aThreadClient); |
|
162 } |
|
163 aTabClient.attachThread({ useSourceMaps: true }, onAttach); |
|
164 }); |
|
165 } |
|
166 |
|
167 // Attach to |aClient|'s tab whose title is |aTitle|, attach to the tab's |
|
168 // thread, and then resume it. Pass |aCallback| the thread's response to |
|
169 // the 'resume' packet, a TabClient for the tab, and a ThreadClient for the |
|
170 // thread. |
|
171 function attachTestTabAndResume(aClient, aTitle, aCallback) { |
|
172 attachTestThread(aClient, aTitle, function(aResponse, aTabClient, aThreadClient) { |
|
173 aThreadClient.resume(function (aResponse) { |
|
174 aCallback(aResponse, aTabClient, aThreadClient); |
|
175 }); |
|
176 }); |
|
177 } |
|
178 |
|
179 /** |
|
180 * Initialize the testing debugger server. |
|
181 */ |
|
182 function initTestDebuggerServer() |
|
183 { |
|
184 DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/root.js"); |
|
185 DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/script.js"); |
|
186 DebuggerServer.addActors("resource://test/testactors.js"); |
|
187 // Allow incoming connections. |
|
188 DebuggerServer.init(function () { return true; }); |
|
189 } |
|
190 |
|
191 function initTestTracerServer() |
|
192 { |
|
193 DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/root.js"); |
|
194 DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/script.js"); |
|
195 DebuggerServer.addActors("resource://test/testactors.js"); |
|
196 DebuggerServer.registerModule("devtools/server/actors/tracer"); |
|
197 // Allow incoming connections. |
|
198 DebuggerServer.init(function () { return true; }); |
|
199 } |
|
200 |
|
201 function finishClient(aClient) |
|
202 { |
|
203 aClient.close(function() { |
|
204 do_test_finished(); |
|
205 }); |
|
206 } |
|
207 |
|
208 /** |
|
209 * Takes a relative file path and returns the absolute file url for it. |
|
210 */ |
|
211 function getFileUrl(aName, aAllowMissing=false) { |
|
212 let file = do_get_file(aName, aAllowMissing); |
|
213 return Services.io.newFileURI(file).spec; |
|
214 } |
|
215 |
|
216 /** |
|
217 * Returns the full path of the file with the specified name in a |
|
218 * platform-independent and URL-like form. |
|
219 */ |
|
220 function getFilePath(aName, aAllowMissing=false) |
|
221 { |
|
222 let file = do_get_file(aName, aAllowMissing); |
|
223 let path = Services.io.newFileURI(file).spec; |
|
224 let filePrePath = "file://"; |
|
225 if ("nsILocalFileWin" in Ci && |
|
226 file instanceof Ci.nsILocalFileWin) { |
|
227 filePrePath += "/"; |
|
228 } |
|
229 return path.slice(filePrePath.length); |
|
230 } |
|
231 |
|
232 Cu.import("resource://gre/modules/NetUtil.jsm"); |
|
233 |
|
234 /** |
|
235 * Returns the full text contents of the given file. |
|
236 */ |
|
237 function readFile(aFileName) { |
|
238 let f = do_get_file(aFileName); |
|
239 let s = Cc["@mozilla.org/network/file-input-stream;1"] |
|
240 .createInstance(Ci.nsIFileInputStream); |
|
241 s.init(f, -1, -1, false); |
|
242 try { |
|
243 return NetUtil.readInputStreamToString(s, s.available()); |
|
244 } finally { |
|
245 s.close(); |
|
246 } |
|
247 } |
|
248 |
|
249 function writeFile(aFileName, aContent) { |
|
250 let file = do_get_file(aFileName, true); |
|
251 let stream = Cc["@mozilla.org/network/file-output-stream;1"] |
|
252 .createInstance(Ci.nsIFileOutputStream); |
|
253 stream.init(file, -1, -1, 0); |
|
254 try { |
|
255 do { |
|
256 let numWritten = stream.write(aContent, aContent.length); |
|
257 aContent = aContent.slice(numWritten); |
|
258 } while (aContent.length > 0); |
|
259 } finally { |
|
260 stream.close(); |
|
261 } |
|
262 } |
|
263 |
|
264 function connectPipeTracing() { |
|
265 return new TracingTransport(DebuggerServer.connectPipe()); |
|
266 } |
|
267 |
|
268 function TracingTransport(childTransport) { |
|
269 this.hooks = null; |
|
270 this.child = childTransport; |
|
271 this.child.hooks = this; |
|
272 |
|
273 this.expectations = []; |
|
274 this.packets = []; |
|
275 this.checkIndex = 0; |
|
276 } |
|
277 |
|
278 function deepEqual(a, b) { |
|
279 if (a === b) |
|
280 return true; |
|
281 if (typeof a != "object" || typeof b != "object") |
|
282 return false; |
|
283 if (a === null || b === null) |
|
284 return false; |
|
285 if (Object.keys(a).length != Object.keys(b).length) |
|
286 return false; |
|
287 for (let k in a) { |
|
288 if (!deepEqual(a[k], b[k])) |
|
289 return false; |
|
290 } |
|
291 return true; |
|
292 } |
|
293 |
|
294 TracingTransport.prototype = { |
|
295 // Remove actor names |
|
296 normalize: function(packet) { |
|
297 return JSON.parse(JSON.stringify(packet, (key, value) => { |
|
298 if (key === "to" || key === "from" || key === "actor") { |
|
299 return "<actorid>"; |
|
300 } |
|
301 return value; |
|
302 })); |
|
303 }, |
|
304 send: function(packet) { |
|
305 this.packets.push({ |
|
306 type: "sent", |
|
307 packet: this.normalize(packet) |
|
308 }); |
|
309 return this.child.send(packet); |
|
310 }, |
|
311 close: function() { |
|
312 return this.child.close(); |
|
313 }, |
|
314 ready: function() { |
|
315 return this.child.ready(); |
|
316 }, |
|
317 onPacket: function(packet) { |
|
318 this.packets.push({ |
|
319 type: "received", |
|
320 packet: this.normalize(packet) |
|
321 }); |
|
322 this.hooks.onPacket(packet); |
|
323 }, |
|
324 onClosed: function() { |
|
325 this.hooks.onClosed(); |
|
326 }, |
|
327 |
|
328 expectSend: function(expected) { |
|
329 let packet = this.packets[this.checkIndex++]; |
|
330 do_check_eq(packet.type, "sent"); |
|
331 do_check_true(deepEqual(packet.packet, this.normalize(expected))); |
|
332 }, |
|
333 |
|
334 expectReceive: function(expected) { |
|
335 let packet = this.packets[this.checkIndex++]; |
|
336 do_check_eq(packet.type, "received"); |
|
337 do_check_true(deepEqual(packet.packet, this.normalize(expected))); |
|
338 }, |
|
339 |
|
340 // Write your tests, call dumpLog at the end, inspect the output, |
|
341 // then sprinkle the calls through the right places in your test. |
|
342 dumpLog: function() { |
|
343 for (let entry of this.packets) { |
|
344 if (entry.type === "sent") { |
|
345 dump("trace.expectSend(" + entry.packet + ");\n"); |
|
346 } else { |
|
347 dump("trace.expectReceive(" + entry.packet + ");\n"); |
|
348 } |
|
349 } |
|
350 } |
|
351 }; |
|
352 |
|
353 function StubTransport() { } |
|
354 StubTransport.prototype.ready = function () {}; |
|
355 StubTransport.prototype.send = function () {}; |
|
356 StubTransport.prototype.close = function () {}; |
|
357 |
|
358 function executeSoon(aFunc) { |
|
359 Services.tm.mainThread.dispatch({ |
|
360 run: DevToolsUtils.makeInfallible(aFunc) |
|
361 }, Ci.nsIThread.DISPATCH_NORMAL); |
|
362 } |