|
1 /** |
|
2 * Test TCPSocket.js by creating an XPCOM-style server socket, then sending |
|
3 * data in both directions and making sure each side receives their data |
|
4 * correctly and with the proper events. |
|
5 * |
|
6 * This test is derived from netwerk/test/unit/test_socks.js, except we don't |
|
7 * involve a subprocess. |
|
8 * |
|
9 * Future work: |
|
10 * - SSL. see https://bugzilla.mozilla.org/show_bug.cgi?id=466524 |
|
11 * https://bugzilla.mozilla.org/show_bug.cgi?id=662180 |
|
12 * Alternatively, mochitests could be used. |
|
13 * - Testing overflow logic. |
|
14 * |
|
15 **/ |
|
16 |
|
17 const Cc = Components.classes; |
|
18 const Ci = Components.interfaces; |
|
19 const Cr = Components.results; |
|
20 const Cu = Components.utils; |
|
21 const CC = Components.Constructor; |
|
22 |
|
23 /** |
|
24 * |
|
25 * Constants |
|
26 * |
|
27 */ |
|
28 |
|
29 // Test parameter. |
|
30 const PORT = 8085; |
|
31 const BACKLOG = -1; |
|
32 |
|
33 // Some binary data to send. |
|
34 const DATA_ARRAY = [0, 255, 254, 0, 1, 2, 3, 0, 255, 255, 254, 0], |
|
35 DATA_ARRAY_BUFFER = new ArrayBuffer(DATA_ARRAY.length), |
|
36 TYPED_DATA_ARRAY = new Uint8Array(DATA_ARRAY_BUFFER), |
|
37 HELLO_WORLD = "hlo wrld. ", |
|
38 BIG_ARRAY = new Array(65539); |
|
39 |
|
40 TYPED_DATA_ARRAY.set(DATA_ARRAY, 0); |
|
41 |
|
42 for (var i_big = 0; i_big < BIG_ARRAY.length; i_big++) { |
|
43 BIG_ARRAY[i_big] = Math.floor(Math.random() * 256); |
|
44 } |
|
45 |
|
46 const BIG_ARRAY_BUFFER = new ArrayBuffer(BIG_ARRAY.length); |
|
47 const BIG_TYPED_ARRAY = new Uint8Array(BIG_ARRAY_BUFFER); |
|
48 BIG_TYPED_ARRAY.set(BIG_ARRAY); |
|
49 |
|
50 const TCPSocket = new (CC("@mozilla.org/tcp-socket;1", |
|
51 "nsIDOMTCPSocket"))(); |
|
52 |
|
53 const gInChild = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime) |
|
54 .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; |
|
55 |
|
56 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
57 Cu.import("resource://gre/modules/Services.jsm"); |
|
58 |
|
59 /** |
|
60 * |
|
61 * Helper functions |
|
62 * |
|
63 */ |
|
64 |
|
65 |
|
66 function makeSuccessCase(name) { |
|
67 return function() { |
|
68 do_print('got expected: ' + name); |
|
69 run_next_test(); |
|
70 }; |
|
71 } |
|
72 |
|
73 function makeJointSuccess(names) { |
|
74 let funcs = {}, successCount = 0; |
|
75 names.forEach(function(name) { |
|
76 funcs[name] = function() { |
|
77 do_print('got expected: ' + name); |
|
78 if (++successCount === names.length) |
|
79 run_next_test(); |
|
80 }; |
|
81 }); |
|
82 return funcs; |
|
83 } |
|
84 |
|
85 function makeFailureCase(name) { |
|
86 return function() { |
|
87 let argstr; |
|
88 if (arguments.length) { |
|
89 argstr = '(args: ' + |
|
90 Array.map(arguments, function(x) { return x.data + ""; }).join(" ") + ')'; |
|
91 } |
|
92 else { |
|
93 argstr = '(no arguments)'; |
|
94 } |
|
95 do_throw('got unexpected: ' + name + ' ' + argstr); |
|
96 }; |
|
97 } |
|
98 |
|
99 function makeExpectData(name, expectedData, fromEvent, callback) { |
|
100 let dataBuffer = fromEvent ? null : [], done = false; |
|
101 let dataBufferView = null; |
|
102 return function(receivedData) { |
|
103 if (receivedData.data) { |
|
104 receivedData = receivedData.data; |
|
105 } |
|
106 let recvLength = receivedData.byteLength !== undefined ? |
|
107 receivedData.byteLength : receivedData.length; |
|
108 |
|
109 if (fromEvent) { |
|
110 if (dataBuffer) { |
|
111 let newBuffer = new ArrayBuffer(dataBuffer.byteLength + recvLength); |
|
112 let newBufferView = new Uint8Array(newBuffer); |
|
113 newBufferView.set(dataBufferView, 0); |
|
114 newBufferView.set(receivedData, dataBuffer.byteLength); |
|
115 dataBuffer = newBuffer; |
|
116 dataBufferView = newBufferView; |
|
117 } |
|
118 else { |
|
119 dataBuffer = receivedData; |
|
120 dataBufferView = new Uint8Array(dataBuffer); |
|
121 } |
|
122 } |
|
123 else { |
|
124 dataBuffer = dataBuffer.concat(receivedData); |
|
125 } |
|
126 do_print(name + ' received ' + recvLength + ' bytes'); |
|
127 |
|
128 if (done) |
|
129 do_throw(name + ' Received data event when already done!'); |
|
130 |
|
131 let dataView = dataBuffer.byteLength !== undefined ? new Uint8Array(dataBuffer) : dataBuffer; |
|
132 if (dataView.length >= expectedData.length) { |
|
133 // check the bytes are equivalent |
|
134 for (let i = 0; i < expectedData.length; i++) { |
|
135 if (dataView[i] !== expectedData[i]) { |
|
136 do_throw(name + ' Received mismatched character at position ' + i); |
|
137 } |
|
138 } |
|
139 if (dataView.length > expectedData.length) |
|
140 do_throw(name + ' Received ' + dataView.length + ' bytes but only expected ' + |
|
141 expectedData.length + ' bytes.'); |
|
142 |
|
143 done = true; |
|
144 if (callback) { |
|
145 callback(); |
|
146 } else { |
|
147 run_next_test(); |
|
148 } |
|
149 } |
|
150 }; |
|
151 } |
|
152 |
|
153 var server = null, sock = null, connectedsock = null, failure_drain = null; |
|
154 var count = 0; |
|
155 /** |
|
156 * |
|
157 * Test functions |
|
158 * |
|
159 */ |
|
160 |
|
161 /** |
|
162 * Connect the socket to the server. This test is added as the first |
|
163 * test, and is also added after every test which results in the socket |
|
164 * being closed. |
|
165 */ |
|
166 |
|
167 function connectSock() { |
|
168 if (server) { |
|
169 server.close(); |
|
170 } |
|
171 |
|
172 var yayFuncs = makeJointSuccess(['serveropen', 'clientopen']); |
|
173 var options = { binaryType: 'arraybuffer' }; |
|
174 |
|
175 server = TCPSocket.listen(PORT, options, BACKLOG); |
|
176 server.onconnect = function(socket) { |
|
177 connectedsock = socket; |
|
178 connectedsock.ondata = makeFailureCase('serverdata'); |
|
179 connectedsock.onerror = makeFailureCase('servererror'); |
|
180 connectedsock.onclose = makeFailureCase('serverclose'); |
|
181 yayFuncs.serveropen(); |
|
182 }; |
|
183 server.onerror = makeFailureCase('error'); |
|
184 sock = TCPSocket.open( |
|
185 '127.0.0.1', PORT, options); |
|
186 sock.onopen = yayFuncs.clientopen; |
|
187 sock.ondrain = null; |
|
188 sock.ondata = makeFailureCase('data'); |
|
189 sock.onerror = makeFailureCase('error'); |
|
190 sock.onclose = makeFailureCase('close'); |
|
191 } |
|
192 |
|
193 /** |
|
194 * Connect the socket to the server after the server was closed. |
|
195 * This test is added after test to close the server was conducted. |
|
196 */ |
|
197 function openSockInClosingServer() { |
|
198 var success = makeSuccessCase('clientnotopen'); |
|
199 var options = { binaryType: 'arraybuffer' }; |
|
200 |
|
201 sock = TCPSocket.open( |
|
202 '127.0.0.1', PORT, options); |
|
203 |
|
204 sock.onopen = makeFailureCase('open'); |
|
205 sock.onerror = success; |
|
206 } |
|
207 |
|
208 /** |
|
209 * Test that sending a small amount of data works, and that buffering |
|
210 * does not take place for this small amount of data. |
|
211 */ |
|
212 |
|
213 function sendDataToServer() { |
|
214 connectedsock.ondata = makeExpectData('serverdata', DATA_ARRAY, true); |
|
215 if (!sock.send(DATA_ARRAY_BUFFER)) { |
|
216 do_throw("send should not have buffered such a small amount of data"); |
|
217 } |
|
218 } |
|
219 |
|
220 /** |
|
221 * Test that data sent from the server correctly fires the ondata |
|
222 * callback on the client side. |
|
223 */ |
|
224 |
|
225 function receiveDataFromServer() { |
|
226 connectedsock.ondata = makeFailureCase('serverdata'); |
|
227 sock.ondata = makeExpectData('data', DATA_ARRAY, true); |
|
228 |
|
229 connectedsock.send(DATA_ARRAY_BUFFER); |
|
230 } |
|
231 |
|
232 /** |
|
233 * Test that when the server closes the connection, the onclose callback |
|
234 * is fired on the client side. |
|
235 */ |
|
236 |
|
237 function serverCloses() { |
|
238 // we don't really care about the server's close event, but we do want to |
|
239 // make sure it happened for sequencing purposes. |
|
240 sock.ondata = makeFailureCase('data'); |
|
241 sock.onclose = makeFailureCase('close1'); |
|
242 connectedsock.onclose = makeFailureCase('close2'); |
|
243 |
|
244 server.close(); |
|
245 run_next_test(); |
|
246 } |
|
247 |
|
248 /** |
|
249 * Test that when the client closes the connection, the onclose callback |
|
250 * is fired on the server side. |
|
251 */ |
|
252 |
|
253 function cleanup() { |
|
254 do_print("Cleaning up"); |
|
255 sock.onclose = null; |
|
256 connectedsock.onclose = null; |
|
257 |
|
258 server.close(); |
|
259 sock.close(); |
|
260 if (count == 1){ |
|
261 if (!gInChild) |
|
262 Services.prefs.clearUserPref('dom.mozTCPSocket.enabled'); |
|
263 } |
|
264 count++; |
|
265 run_next_test(); |
|
266 } |
|
267 // - connect, data and events work both ways |
|
268 add_test(connectSock); |
|
269 |
|
270 add_test(sendDataToServer); |
|
271 |
|
272 add_test(receiveDataFromServer); |
|
273 // - server closes on us |
|
274 add_test(serverCloses); |
|
275 |
|
276 // - send and receive after closing server |
|
277 add_test(sendDataToServer); |
|
278 add_test(receiveDataFromServer); |
|
279 // - check a connection refused from client to server after closing server |
|
280 add_test(serverCloses); |
|
281 |
|
282 add_test(openSockInClosingServer); |
|
283 |
|
284 // - clean up |
|
285 add_test(cleanup); |
|
286 |
|
287 // - send and receive in reverse order for client and server |
|
288 add_test(connectSock); |
|
289 |
|
290 add_test(receiveDataFromServer); |
|
291 |
|
292 add_test(sendDataToServer); |
|
293 |
|
294 // - clean up |
|
295 |
|
296 add_test(cleanup); |
|
297 |
|
298 function run_test() { |
|
299 if (!gInChild) |
|
300 Services.prefs.setBoolPref('dom.mozTCPSocket.enabled', true); |
|
301 |
|
302 run_next_test(); |
|
303 } |