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#include "mojo/public/cpp/bindings/error_handler.h"
6#include "mojo/public/cpp/environment/environment.h"
7#include "mojo/public/cpp/utility/run_loop.h"
8#include "mojo/public/interfaces/bindings/tests/math_calculator.mojom.h"
9#include "mojo/public/interfaces/bindings/tests/sample_service.mojom.h"
10#include "testing/gtest/include/gtest/gtest.h"
11
12namespace mojo {
13namespace test {
14namespace {
15
16class ErrorObserver : public ErrorHandler {
17 public:
18  ErrorObserver() : encountered_error_(false) {
19  }
20
21  bool encountered_error() const { return encountered_error_; }
22
23  virtual void OnConnectionError() MOJO_OVERRIDE {
24    encountered_error_ = true;
25  }
26
27 private:
28  bool encountered_error_;
29};
30
31class MathCalculatorImpl : public InterfaceImpl<math::Calculator> {
32 public:
33  virtual ~MathCalculatorImpl() {}
34
35  MathCalculatorImpl()
36      : total_(0.0),
37        got_connection_(false) {
38  }
39
40  virtual void OnConnectionEstablished() MOJO_OVERRIDE {
41    got_connection_ = true;
42  }
43
44  virtual void Clear() MOJO_OVERRIDE {
45    client()->Output(total_);
46  }
47
48  virtual void Add(double value) MOJO_OVERRIDE {
49    total_ += value;
50    client()->Output(total_);
51  }
52
53  virtual void Multiply(double value) MOJO_OVERRIDE {
54    total_ *= value;
55    client()->Output(total_);
56  }
57
58  bool got_connection() const {
59    return got_connection_;
60  }
61
62 private:
63  double total_;
64  bool got_connection_;
65};
66
67class MathCalculatorUIImpl : public math::CalculatorUI {
68 public:
69  explicit MathCalculatorUIImpl(math::CalculatorPtr calculator)
70      : calculator_(calculator.Pass()),
71        output_(0.0) {
72    calculator_.set_client(this);
73  }
74
75  bool WaitForIncomingMethodCall() {
76    return calculator_.WaitForIncomingMethodCall();
77  }
78
79  bool encountered_error() const {
80    return calculator_.encountered_error();
81  }
82
83  void Add(double value) {
84    calculator_->Add(value);
85  }
86
87  void Subtract(double value) {
88    calculator_->Add(-value);
89  }
90
91  void Multiply(double value) {
92    calculator_->Multiply(value);
93  }
94
95  void Divide(double value) {
96    calculator_->Multiply(1.0 / value);
97  }
98
99  double GetOutput() const {
100    return output_;
101  }
102
103 private:
104  // math::CalculatorUI implementation:
105  virtual void Output(double value) MOJO_OVERRIDE {
106    output_ = value;
107  }
108
109  math::CalculatorPtr calculator_;
110  double output_;
111};
112
113class SelfDestructingMathCalculatorUIImpl : public math::CalculatorUI {
114 public:
115  explicit SelfDestructingMathCalculatorUIImpl(math::CalculatorPtr calculator)
116      : calculator_(calculator.Pass()),
117        nesting_level_(0) {
118    ++num_instances_;
119    calculator_.set_client(this);
120  }
121
122  void BeginTest(bool nested) {
123    nesting_level_ = nested ? 2 : 1;
124    calculator_->Add(1.0);
125  }
126
127  static int num_instances() { return num_instances_; }
128
129 private:
130  virtual ~SelfDestructingMathCalculatorUIImpl() {
131    --num_instances_;
132  }
133
134  virtual void Output(double value) MOJO_OVERRIDE {
135    if (--nesting_level_ > 0) {
136      // Add some more and wait for re-entrant call to Output!
137      calculator_->Add(1.0);
138      RunLoop::current()->RunUntilIdle();
139    } else {
140      delete this;
141    }
142  }
143
144  math::CalculatorPtr calculator_;
145  int nesting_level_;
146  static int num_instances_;
147};
148
149// static
150int SelfDestructingMathCalculatorUIImpl::num_instances_ = 0;
151
152class ReentrantServiceImpl : public InterfaceImpl<sample::Service> {
153 public:
154  virtual ~ReentrantServiceImpl() {}
155
156  ReentrantServiceImpl()
157      : got_connection_(false), call_depth_(0), max_call_depth_(0) {}
158
159  virtual void OnConnectionEstablished() MOJO_OVERRIDE {
160    got_connection_ = true;
161  }
162
163  bool got_connection() const {
164    return got_connection_;
165  }
166
167  int max_call_depth() { return max_call_depth_; }
168
169  virtual void Frobinate(sample::FooPtr foo,
170                         sample::Service::BazOptions baz,
171                         sample::PortPtr port) MOJO_OVERRIDE {
172    max_call_depth_ = std::max(++call_depth_, max_call_depth_);
173    if (call_depth_ == 1) {
174      EXPECT_TRUE(WaitForIncomingMethodCall());
175    }
176    call_depth_--;
177  }
178
179  virtual void GetPort(mojo::InterfaceRequest<sample::Port> port)
180      MOJO_OVERRIDE {}
181
182 private:
183  bool got_connection_;
184  int call_depth_;
185  int max_call_depth_;
186};
187
188class InterfacePtrTest : public testing::Test {
189 public:
190  virtual ~InterfacePtrTest() {
191    loop_.RunUntilIdle();
192  }
193
194  void PumpMessages() {
195    loop_.RunUntilIdle();
196  }
197
198 private:
199  Environment env_;
200  RunLoop loop_;
201};
202
203TEST_F(InterfacePtrTest, EndToEnd) {
204  math::CalculatorPtr calc;
205  MathCalculatorImpl* impl = BindToProxy(new MathCalculatorImpl(), &calc);
206  EXPECT_TRUE(impl->got_connection());
207
208  // Suppose this is instantiated in a process that has pipe1_.
209  MathCalculatorUIImpl calculator_ui(calc.Pass());
210
211  calculator_ui.Add(2.0);
212  calculator_ui.Multiply(5.0);
213
214  PumpMessages();
215
216  EXPECT_EQ(10.0, calculator_ui.GetOutput());
217}
218
219TEST_F(InterfacePtrTest, EndToEnd_Synchronous) {
220  math::CalculatorPtr calc;
221  MathCalculatorImpl* impl = BindToProxy(new MathCalculatorImpl(), &calc);
222  EXPECT_TRUE(impl->got_connection());
223
224  // Suppose this is instantiated in a process that has pipe1_.
225  MathCalculatorUIImpl calculator_ui(calc.Pass());
226
227  EXPECT_EQ(0.0, calculator_ui.GetOutput());
228
229  calculator_ui.Add(2.0);
230  EXPECT_EQ(0.0, calculator_ui.GetOutput());
231  impl->WaitForIncomingMethodCall();
232  calculator_ui.WaitForIncomingMethodCall();
233  EXPECT_EQ(2.0, calculator_ui.GetOutput());
234
235  calculator_ui.Multiply(5.0);
236  EXPECT_EQ(2.0, calculator_ui.GetOutput());
237  impl->WaitForIncomingMethodCall();
238  calculator_ui.WaitForIncomingMethodCall();
239  EXPECT_EQ(10.0, calculator_ui.GetOutput());
240}
241
242TEST_F(InterfacePtrTest, Movable) {
243  math::CalculatorPtr a;
244  math::CalculatorPtr b;
245  BindToProxy(new MathCalculatorImpl(), &b);
246
247  EXPECT_TRUE(!a);
248  EXPECT_FALSE(!b);
249
250  a = b.Pass();
251
252  EXPECT_FALSE(!a);
253  EXPECT_TRUE(!b);
254}
255
256TEST_F(InterfacePtrTest, Resettable) {
257  math::CalculatorPtr a;
258
259  EXPECT_TRUE(!a);
260
261  MessagePipe pipe;
262
263  // Save this so we can test it later.
264  Handle handle = pipe.handle0.get();
265
266  a = MakeProxy<math::Calculator>(pipe.handle0.Pass());
267
268  EXPECT_FALSE(!a);
269
270  a.reset();
271
272  EXPECT_TRUE(!a);
273  EXPECT_FALSE(a.internal_state()->router_for_testing());
274
275  // Test that handle was closed.
276  EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, CloseRaw(handle));
277}
278
279TEST_F(InterfacePtrTest, EncounteredError) {
280  math::CalculatorPtr proxy;
281  MathCalculatorImpl* server = BindToProxy(new MathCalculatorImpl(), &proxy);
282
283  MathCalculatorUIImpl calculator_ui(proxy.Pass());
284
285  calculator_ui.Add(2.0);
286  PumpMessages();
287  EXPECT_EQ(2.0, calculator_ui.GetOutput());
288  EXPECT_FALSE(calculator_ui.encountered_error());
289
290  calculator_ui.Multiply(5.0);
291  EXPECT_FALSE(calculator_ui.encountered_error());
292
293  // Close the server.
294  server->internal_state()->router()->CloseMessagePipe();
295
296  // The state change isn't picked up locally yet.
297  EXPECT_FALSE(calculator_ui.encountered_error());
298
299  PumpMessages();
300
301  // OK, now we see the error.
302  EXPECT_TRUE(calculator_ui.encountered_error());
303}
304
305TEST_F(InterfacePtrTest, EncounteredErrorCallback) {
306  math::CalculatorPtr proxy;
307  MathCalculatorImpl* server = BindToProxy(new MathCalculatorImpl(), &proxy);
308
309  ErrorObserver error_observer;
310  proxy.set_error_handler(&error_observer);
311
312  MathCalculatorUIImpl calculator_ui(proxy.Pass());
313
314  calculator_ui.Add(2.0);
315  PumpMessages();
316  EXPECT_EQ(2.0, calculator_ui.GetOutput());
317  EXPECT_FALSE(calculator_ui.encountered_error());
318
319  calculator_ui.Multiply(5.0);
320  EXPECT_FALSE(calculator_ui.encountered_error());
321
322  // Close the server.
323  server->internal_state()->router()->CloseMessagePipe();
324
325  // The state change isn't picked up locally yet.
326  EXPECT_FALSE(calculator_ui.encountered_error());
327
328  PumpMessages();
329
330  // OK, now we see the error.
331  EXPECT_TRUE(calculator_ui.encountered_error());
332
333  // We should have also been able to observe the error through the
334  // ErrorHandler interface.
335  EXPECT_TRUE(error_observer.encountered_error());
336}
337
338TEST_F(InterfacePtrTest, NoClientAttribute) {
339  // This is a test to ensure the following compiles. The sample::Port interface
340  // does not have an explicit Client attribute.
341  sample::PortPtr port;
342  MessagePipe pipe;
343  port.Bind(pipe.handle0.Pass());
344}
345
346TEST_F(InterfacePtrTest, DestroyInterfacePtrOnClientMethod) {
347  math::CalculatorPtr proxy;
348  BindToProxy(new MathCalculatorImpl(), &proxy);
349
350  EXPECT_EQ(0, SelfDestructingMathCalculatorUIImpl::num_instances());
351
352  SelfDestructingMathCalculatorUIImpl* impl =
353      new SelfDestructingMathCalculatorUIImpl(proxy.Pass());
354  impl->BeginTest(false);
355
356  PumpMessages();
357
358  EXPECT_EQ(0, SelfDestructingMathCalculatorUIImpl::num_instances());
359}
360
361TEST_F(InterfacePtrTest, NestedDestroyInterfacePtrOnClientMethod) {
362  math::CalculatorPtr proxy;
363  BindToProxy(new MathCalculatorImpl(), &proxy);
364
365  EXPECT_EQ(0, SelfDestructingMathCalculatorUIImpl::num_instances());
366
367  SelfDestructingMathCalculatorUIImpl* impl =
368      new SelfDestructingMathCalculatorUIImpl(proxy.Pass());
369  impl->BeginTest(true);
370
371  PumpMessages();
372
373  EXPECT_EQ(0, SelfDestructingMathCalculatorUIImpl::num_instances());
374}
375
376TEST_F(InterfacePtrTest, ReentrantWaitForIncomingMethodCall) {
377  sample::ServicePtr proxy;
378  ReentrantServiceImpl* impl = BindToProxy(new ReentrantServiceImpl(), &proxy);
379  EXPECT_TRUE(impl->got_connection());
380
381  proxy->Frobinate(sample::FooPtr(),
382                   sample::Service::BAZ_OPTIONS_REGULAR,
383                   sample::PortPtr());
384  proxy->Frobinate(sample::FooPtr(),
385                   sample::Service::BAZ_OPTIONS_REGULAR,
386                   sample::PortPtr());
387
388  PumpMessages();
389
390  EXPECT_EQ(2, impl->max_call_depth());
391}
392
393}  // namespace
394}  // namespace test
395}  // namespace mojo
396