1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// Mock out the support module to avoid depending on the message loop.
6define("mojo/public/js/bindings/support", ["timer"], function(timer) {
7  var waitingCallbacks = [];
8
9  function WaitCookie(id) {
10    this.id = id;
11  }
12
13  function asyncWait(handle, flags, callback) {
14    var id = waitingCallbacks.length;
15    waitingCallbacks.push(callback);
16    return new WaitCookie(id);
17  }
18
19  function cancelWait(cookie) {
20    waitingCallbacks[cookie.id] = null;
21  }
22
23  function numberOfWaitingCallbacks() {
24    var count = 0;
25    for (var i = 0; i < waitingCallbacks.length; ++i) {
26      if (waitingCallbacks[i])
27        ++count;
28    }
29    return count;
30  }
31
32  function pumpOnce(result) {
33    var callbacks = waitingCallbacks;
34    waitingCallbacks = [];
35    for (var i = 0; i < callbacks.length; ++i) {
36      if (callbacks[i])
37        callbacks[i](result);
38    }
39  }
40
41  // Queue up a pumpOnce call to execute after the stack unwinds. Use
42  // this to trigger a pump after all Promises are executed.
43  function queuePump(result) {
44    timer.createOneShot(0, pumpOnce.bind(undefined, result));
45  }
46
47  var exports = {};
48  exports.asyncWait = asyncWait;
49  exports.cancelWait = cancelWait;
50  exports.numberOfWaitingCallbacks = numberOfWaitingCallbacks;
51  exports.pumpOnce = pumpOnce;
52  exports.queuePump = queuePump;
53  return exports;
54});
55
56define([
57    "gin/test/expect",
58    "mojo/public/js/bindings/support",
59    "mojo/public/js/bindings/core",
60    "mojo/public/js/bindings/connection",
61    "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom",
62    "mojo/public/interfaces/bindings/tests/sample_service.mojom",
63    "mojo/apps/js/bindings/threading",
64    "gc",
65], function(expect,
66            mockSupport,
67            core,
68            connection,
69            sample_interfaces,
70            sample_service,
71            threading,
72            gc) {
73  testClientServer();
74  testWriteToClosedPipe();
75  testRequestResponse().then(function() {
76    this.result = "PASS";
77    gc.collectGarbage();  // should not crash
78    threading.quit();
79  }.bind(this)).catch(function(e) {
80    this.result = "FAIL: " + (e.stack || e);
81    threading.quit();
82  }.bind(this));
83
84  function testClientServer() {
85    var receivedFrobinate = false;
86    var receivedDidFrobinate = false;
87
88    // ServiceImpl -------------------------------------------------------------
89
90    function ServiceImpl(peer) {
91      this.peer = peer;
92    }
93
94    ServiceImpl.prototype = Object.create(sample_service.ServiceStub.prototype);
95
96    ServiceImpl.prototype.frobinate = function(foo, baz, port) {
97      receivedFrobinate = true;
98
99      expect(foo.name).toBe("Example name");
100      expect(baz).toBeTruthy();
101      expect(core.close(port)).toBe(core.RESULT_OK);
102
103      this.peer.didFrobinate(42);
104    };
105
106    // ServiceImpl -------------------------------------------------------------
107
108    function ServiceClientImpl(peer) {
109      this.peer = peer;
110    }
111
112    ServiceClientImpl.prototype =
113        Object.create(sample_service.ServiceClientStub.prototype);
114
115    ServiceClientImpl.prototype.didFrobinate = function(result) {
116      receivedDidFrobinate = true;
117
118      expect(result).toBe(42);
119    };
120
121    var pipe = core.createMessagePipe();
122    var anotherPipe = core.createMessagePipe();
123    var sourcePipe = core.createMessagePipe();
124
125    var connection0 = new connection.Connection(
126        pipe.handle0, ServiceImpl, sample_service.ServiceClientProxy);
127
128    var connection1 = new connection.Connection(
129        pipe.handle1, ServiceClientImpl, sample_service.ServiceProxy);
130
131    var foo = new sample_service.Foo();
132    foo.bar = new sample_service.Bar();
133    foo.name = "Example name";
134    foo.source = sourcePipe.handle0;
135    connection1.remote.frobinate(foo, true, anotherPipe.handle0);
136
137    mockSupport.pumpOnce(core.RESULT_OK);
138
139    expect(receivedFrobinate).toBeTruthy();
140    expect(receivedDidFrobinate).toBeTruthy();
141
142    connection0.close();
143    connection1.close();
144
145    expect(mockSupport.numberOfWaitingCallbacks()).toBe(0);
146
147    // sourcePipe.handle0 was closed automatically when sent over IPC.
148    expect(core.close(sourcePipe.handle0)).toBe(core.RESULT_INVALID_ARGUMENT);
149    // sourcePipe.handle1 hasn't been closed yet.
150    expect(core.close(sourcePipe.handle1)).toBe(core.RESULT_OK);
151
152    // anotherPipe.handle0 was closed automatically when sent over IPC.
153    expect(core.close(anotherPipe.handle0)).toBe(core.RESULT_INVALID_ARGUMENT);
154    // anotherPipe.handle1 hasn't been closed yet.
155    expect(core.close(anotherPipe.handle1)).toBe(core.RESULT_OK);
156
157    // The Connection object is responsible for closing these handles.
158    expect(core.close(pipe.handle0)).toBe(core.RESULT_INVALID_ARGUMENT);
159    expect(core.close(pipe.handle1)).toBe(core.RESULT_INVALID_ARGUMENT);
160  }
161
162  function testWriteToClosedPipe() {
163    var pipe = core.createMessagePipe();
164
165    var connection1 = new connection.Connection(
166        pipe.handle1, function() {}, sample_service.ServiceProxy);
167
168    // Close the other end of the pipe.
169    core.close(pipe.handle0);
170
171    // Not observed yet because we haven't pumped events yet.
172    expect(connection1.encounteredError()).toBeFalsy();
173
174    var foo = new sample_service.Foo();
175    foo.bar = new sample_service.Bar();
176    // TODO(darin): crbug.com/357043: pass null in place of |foo| here.
177    connection1.remote.frobinate(foo, true, null);
178
179    // Write failures are not reported.
180    expect(connection1.encounteredError()).toBeFalsy();
181
182    // Pump events, and then we should start observing the closed pipe.
183    mockSupport.pumpOnce(core.RESULT_OK);
184
185    expect(connection1.encounteredError()).toBeTruthy();
186
187    connection1.close();
188  }
189
190  function testRequestResponse() {
191
192    // ProviderImpl ------------------------------------------------------------
193
194    function ProviderImpl(peer) {
195      this.peer = peer;
196    }
197
198    ProviderImpl.prototype =
199        Object.create(sample_interfaces.ProviderStub.prototype);
200
201    ProviderImpl.prototype.echoString = function(a) {
202      mockSupport.queuePump(core.RESULT_OK);
203      return Promise.resolve({a: a});
204    };
205
206    ProviderImpl.prototype.echoStrings = function(a, b) {
207      mockSupport.queuePump(core.RESULT_OK);
208      return Promise.resolve({a: a, b: b});
209    };
210
211    // ProviderClientImpl ------------------------------------------------------
212
213    function ProviderClientImpl(peer) {
214      this.peer = peer;
215    }
216
217    ProviderClientImpl.prototype =
218        Object.create(sample_interfaces.ProviderClientStub.prototype);
219
220    var pipe = core.createMessagePipe();
221
222    var connection0 = new connection.Connection(
223        pipe.handle0, ProviderImpl, sample_interfaces.ProviderClientProxy);
224
225    var connection1 = new connection.Connection(
226        pipe.handle1, ProviderClientImpl, sample_interfaces.ProviderProxy);
227
228    var origReadMessage = core.readMessage;
229    // echoString
230    mockSupport.queuePump(core.RESULT_OK);
231    return connection1.remote.echoString("hello").then(function(response) {
232      expect(response.a).toBe("hello");
233    }).then(function() {
234      // echoStrings
235      mockSupport.queuePump(core.RESULT_OK);
236      return connection1.remote.echoStrings("hello", "world");
237    }).then(function(response) {
238      expect(response.a).toBe("hello");
239      expect(response.b).toBe("world");
240    }).then(function() {
241      // Mock a read failure, expect it to fail.
242      core.readMessage = function() {
243        return { result: core.RESULT_UNKNOWN };
244      };
245      mockSupport.queuePump(core.RESULT_OK);
246      return connection1.remote.echoString("goodbye");
247    }).then(function() {
248      throw Error("Expected echoString to fail.");
249    }, function(error) {
250      expect(error.message).toBe("Connection error: " + core.RESULT_UNKNOWN);
251
252      // Clean up.
253      core.readMessage = origReadMessage;
254    });
255  }
256});
257