end_to_end_async_unittest.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
1// Copyright (c) 2012 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 <algorithm>
6#include <string>
7#include <vector>
8
9#include "base/bind.h"
10#include "base/memory/scoped_ptr.h"
11#include "base/message_loop.h"
12#include "base/stl_util.h"
13#include "base/test/test_timeouts.h"
14#include "base/threading/thread.h"
15#include "base/threading/thread_restrictions.h"
16#include "dbus/bus.h"
17#include "dbus/message.h"
18#include "dbus/object_path.h"
19#include "dbus/object_proxy.h"
20#include "dbus/test_service.h"
21#include "testing/gtest/include/gtest/gtest.h"
22
23namespace {
24
25// See comments in ObjectProxy::RunResponseCallback() for why the number was
26// chosen.
27const int kHugePayloadSize = 64 << 20;  // 64 MB
28
29}  // namespace
30
31// The end-to-end test exercises the asynchronous APIs in ObjectProxy and
32// ExportedObject.
33class EndToEndAsyncTest : public testing::Test {
34 public:
35  EndToEndAsyncTest() {
36  }
37
38  virtual void SetUp() {
39    // Make the main thread not to allow IO.
40    base::ThreadRestrictions::SetIOAllowed(false);
41
42    // Start the D-Bus thread.
43    dbus_thread_.reset(new base::Thread("D-Bus Thread"));
44    base::Thread::Options thread_options;
45    thread_options.message_loop_type = MessageLoop::TYPE_IO;
46    ASSERT_TRUE(dbus_thread_->StartWithOptions(thread_options));
47
48    // Start the test service, using the D-Bus thread.
49    dbus::TestService::Options options;
50    options.dbus_thread_message_loop_proxy = dbus_thread_->message_loop_proxy();
51    test_service_.reset(new dbus::TestService(options));
52    ASSERT_TRUE(test_service_->StartService());
53    ASSERT_TRUE(test_service_->WaitUntilServiceIsStarted());
54    ASSERT_TRUE(test_service_->HasDBusThread());
55
56    // Create the client, using the D-Bus thread.
57    dbus::Bus::Options bus_options;
58    bus_options.bus_type = dbus::Bus::SESSION;
59    bus_options.connection_type = dbus::Bus::PRIVATE;
60    bus_options.dbus_thread_message_loop_proxy =
61        dbus_thread_->message_loop_proxy();
62    bus_ = new dbus::Bus(bus_options);
63    object_proxy_ = bus_->GetObjectProxy(
64        "org.chromium.TestService",
65        dbus::ObjectPath("/org/chromium/TestObject"));
66    ASSERT_TRUE(bus_->HasDBusThread());
67
68    // Connect to the "Test" signal of "org.chromium.TestInterface" from
69    // the remote object.
70    object_proxy_->ConnectToSignal(
71        "org.chromium.TestInterface",
72        "Test",
73        base::Bind(&EndToEndAsyncTest::OnTestSignal,
74                   base::Unretained(this)),
75        base::Bind(&EndToEndAsyncTest::OnConnected,
76                   base::Unretained(this)));
77    // Wait until the object proxy is connected to the signal.
78    message_loop_.Run();
79
80    // Connect to the "Test2" signal of "org.chromium.TestInterface" from
81    // the remote object. There was a bug where we were emitting error
82    // messages like "Requested to remove an unknown match rule: ..." at
83    // the shutdown of Bus when an object proxy is connected to more than
84    // one signal of the same interface. See crosbug.com/23382 for details.
85    object_proxy_->ConnectToSignal(
86        "org.chromium.TestInterface",
87        "Test2",
88        base::Bind(&EndToEndAsyncTest::OnTest2Signal,
89                   base::Unretained(this)),
90        base::Bind(&EndToEndAsyncTest::OnConnected,
91                   base::Unretained(this)));
92    // Wait until the object proxy is connected to the signal.
93    message_loop_.Run();
94
95    // Create a second object proxy for the root object.
96    root_object_proxy_ = bus_->GetObjectProxy(
97        "org.chromium.TestService",
98        dbus::ObjectPath("/"));
99    ASSERT_TRUE(bus_->HasDBusThread());
100
101    // Connect to the "Test" signal of "org.chromium.TestInterface" from
102    // the root remote object too.
103    root_object_proxy_->ConnectToSignal(
104        "org.chromium.TestInterface",
105        "Test",
106        base::Bind(&EndToEndAsyncTest::OnRootTestSignal,
107                   base::Unretained(this)),
108        base::Bind(&EndToEndAsyncTest::OnConnected,
109                   base::Unretained(this)));
110    // Wait until the root object proxy is connected to the signal.
111    message_loop_.Run();
112  }
113
114  virtual void TearDown() {
115    bus_->ShutdownOnDBusThreadAndBlock();
116
117    // Shut down the service.
118    test_service_->ShutdownAndBlock();
119
120    // Reset to the default.
121    base::ThreadRestrictions::SetIOAllowed(true);
122
123    // Stopping a thread is considered an IO operation, so do this after
124    // allowing IO.
125    test_service_->Stop();
126  }
127
128 protected:
129  // Replaces the bus with a broken one.
130  void SetUpBrokenBus() {
131    // Shut down the existing bus.
132    bus_->ShutdownOnDBusThreadAndBlock();
133
134    // Create new bus with invalid address.
135    const char kInvalidAddress[] = "";
136    dbus::Bus::Options bus_options;
137    bus_options.bus_type = dbus::Bus::CUSTOM_ADDRESS;
138    bus_options.address = kInvalidAddress;
139    bus_options.connection_type = dbus::Bus::PRIVATE;
140    bus_options.dbus_thread_message_loop_proxy =
141        dbus_thread_->message_loop_proxy();
142    bus_ = new dbus::Bus(bus_options);
143    ASSERT_TRUE(bus_->HasDBusThread());
144
145    // Create new object proxy.
146    object_proxy_ = bus_->GetObjectProxy(
147        "org.chromium.TestService",
148        dbus::ObjectPath("/org/chromium/TestObject"));
149  }
150
151  // Calls the method asynchronously. OnResponse() will be called once the
152  // response is received.
153  void CallMethod(dbus::MethodCall* method_call,
154                  int timeout_ms) {
155    object_proxy_->CallMethod(method_call,
156                              timeout_ms,
157                              base::Bind(&EndToEndAsyncTest::OnResponse,
158                                         base::Unretained(this)));
159  }
160
161  // Calls the method asynchronously. OnResponse() will be called once the
162  // response is received without error, otherwise OnError() will be called.
163  void CallMethodWithErrorCallback(dbus::MethodCall* method_call,
164                                   int timeout_ms) {
165    object_proxy_->CallMethodWithErrorCallback(
166        method_call,
167        timeout_ms,
168        base::Bind(&EndToEndAsyncTest::OnResponse, base::Unretained(this)),
169        base::Bind(&EndToEndAsyncTest::OnError, base::Unretained(this)));
170  }
171
172  // Wait for the give number of responses.
173  void WaitForResponses(size_t num_responses) {
174    while (response_strings_.size() < num_responses) {
175      message_loop_.Run();
176    }
177  }
178
179  // Called when the response is received.
180  void OnResponse(dbus::Response* response) {
181    // |response| will be deleted on exit of the function. Copy the
182    // payload to |response_strings_|.
183    if (response) {
184      dbus::MessageReader reader(response);
185      std::string response_string;
186      ASSERT_TRUE(reader.PopString(&response_string));
187      response_strings_.push_back(response_string);
188    } else {
189      response_strings_.push_back("");
190    }
191    message_loop_.Quit();
192  };
193
194  // Wait for the given number of errors.
195  void WaitForErrors(size_t num_errors) {
196    while (error_names_.size() < num_errors) {
197      message_loop_.Run();
198    }
199  }
200
201  // Called when an error is received.
202  void OnError(dbus::ErrorResponse* error) {
203    // |error| will be deleted on exit of the function. Copy the payload to
204    // |error_names_|.
205    if (error) {
206      ASSERT_NE("", error->GetErrorName());
207      error_names_.push_back(error->GetErrorName());
208    } else {
209      error_names_.push_back("");
210    }
211    message_loop_.Quit();
212  }
213
214  // Called when the "Test" signal is received, in the main thread.
215  // Copy the string payload to |test_signal_string_|.
216  void OnTestSignal(dbus::Signal* signal) {
217    dbus::MessageReader reader(signal);
218    ASSERT_TRUE(reader.PopString(&test_signal_string_));
219    message_loop_.Quit();
220  }
221
222  // Called when the "Test" signal is received, in the main thread, by
223  // the root object proxy. Copy the string payload to
224  // |root_test_signal_string_|.
225  void OnRootTestSignal(dbus::Signal* signal) {
226    dbus::MessageReader reader(signal);
227    ASSERT_TRUE(reader.PopString(&root_test_signal_string_));
228    message_loop_.Quit();
229  }
230
231  // Called when the "Test2" signal is received, in the main thread.
232  void OnTest2Signal(dbus::Signal* signal) {
233    dbus::MessageReader reader(signal);
234    message_loop_.Quit();
235  }
236
237  // Called when connected to the signal.
238  void OnConnected(const std::string& interface_name,
239                   const std::string& signal_name,
240                   bool success) {
241    ASSERT_TRUE(success);
242    message_loop_.Quit();
243  }
244
245  // Wait for the hey signal to be received.
246  void WaitForTestSignal() {
247    // OnTestSignal() will quit the message loop.
248    message_loop_.Run();
249  }
250
251  MessageLoop message_loop_;
252  std::vector<std::string> response_strings_;
253  std::vector<std::string> error_names_;
254  scoped_ptr<base::Thread> dbus_thread_;
255  scoped_refptr<dbus::Bus> bus_;
256  dbus::ObjectProxy* object_proxy_;
257  dbus::ObjectProxy* root_object_proxy_;
258  scoped_ptr<dbus::TestService> test_service_;
259  // Text message from "Test" signal.
260  std::string test_signal_string_;
261  // Text message from "Test" signal delivered to root.
262  std::string root_test_signal_string_;
263};
264
265TEST_F(EndToEndAsyncTest, Echo) {
266  const char* kHello = "hello";
267
268  // Create the method call.
269  dbus::MethodCall method_call("org.chromium.TestInterface", "Echo");
270  dbus::MessageWriter writer(&method_call);
271  writer.AppendString(kHello);
272
273  // Call the method.
274  const int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT;
275  CallMethod(&method_call, timeout_ms);
276
277  // Check the response.
278  WaitForResponses(1);
279  EXPECT_EQ(kHello, response_strings_[0]);
280}
281
282TEST_F(EndToEndAsyncTest, EchoWithErrorCallback) {
283  const char* kHello = "hello";
284
285  // Create the method call.
286  dbus::MethodCall method_call("org.chromium.TestInterface", "Echo");
287  dbus::MessageWriter writer(&method_call);
288  writer.AppendString(kHello);
289
290  // Call the method.
291  const int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT;
292  CallMethodWithErrorCallback(&method_call, timeout_ms);
293
294  // Check the response.
295  WaitForResponses(1);
296  EXPECT_EQ(kHello, response_strings_[0]);
297  EXPECT_TRUE(error_names_.empty());
298}
299
300// Call Echo method three times.
301TEST_F(EndToEndAsyncTest, EchoThreeTimes) {
302  const char* kMessages[] = { "foo", "bar", "baz" };
303
304  for (size_t i = 0; i < arraysize(kMessages); ++i) {
305    // Create the method call.
306    dbus::MethodCall method_call("org.chromium.TestInterface", "Echo");
307    dbus::MessageWriter writer(&method_call);
308    writer.AppendString(kMessages[i]);
309
310    // Call the method.
311    const int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT;
312    CallMethod(&method_call, timeout_ms);
313  }
314
315  // Check the responses.
316  WaitForResponses(3);
317  // Sort as the order of the returned messages is not deterministic.
318  std::sort(response_strings_.begin(), response_strings_.end());
319  EXPECT_EQ("bar", response_strings_[0]);
320  EXPECT_EQ("baz", response_strings_[1]);
321  EXPECT_EQ("foo", response_strings_[2]);
322}
323
324TEST_F(EndToEndAsyncTest, Echo_HugePayload) {
325  const std::string kHugePayload(kHugePayloadSize, 'o');
326
327  // Create the method call with a huge payload.
328  dbus::MethodCall method_call("org.chromium.TestInterface", "Echo");
329  dbus::MessageWriter writer(&method_call);
330  writer.AppendString(kHugePayload);
331
332  // Call the method.
333  const int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT;
334  CallMethod(&method_call, timeout_ms);
335
336  // This caused a DCHECK failure before. Ensure that the issue is fixed.
337  WaitForResponses(1);
338  EXPECT_EQ(kHugePayload, response_strings_[0]);
339}
340
341TEST_F(EndToEndAsyncTest, BrokenBus) {
342  const char* kHello = "hello";
343
344  // Set up a broken bus.
345  SetUpBrokenBus();
346
347  // Create the method call.
348  dbus::MethodCall method_call("org.chromium.TestInterface", "Echo");
349  dbus::MessageWriter writer(&method_call);
350  writer.AppendString(kHello);
351
352  // Call the method.
353  const int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT;
354  CallMethod(&method_call, timeout_ms);
355  WaitForResponses(1);
356
357  // Should fail because of the broken bus.
358  ASSERT_EQ("", response_strings_[0]);
359}
360
361TEST_F(EndToEndAsyncTest, BrokenBusWithErrorCallback) {
362  const char* kHello = "hello";
363
364  // Set up a broken bus.
365  SetUpBrokenBus();
366
367  // Create the method call.
368  dbus::MethodCall method_call("org.chromium.TestInterface", "Echo");
369  dbus::MessageWriter writer(&method_call);
370  writer.AppendString(kHello);
371
372  // Call the method.
373  const int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT;
374  CallMethodWithErrorCallback(&method_call, timeout_ms);
375  WaitForErrors(1);
376
377  // Should fail because of the broken bus.
378  ASSERT_TRUE(response_strings_.empty());
379  ASSERT_EQ("", error_names_[0]);
380}
381
382TEST_F(EndToEndAsyncTest, Timeout) {
383  const char* kHello = "hello";
384
385  // Create the method call.
386  dbus::MethodCall method_call("org.chromium.TestInterface", "SlowEcho");
387  dbus::MessageWriter writer(&method_call);
388  writer.AppendString(kHello);
389
390  // Call the method with timeout of 0ms.
391  const int timeout_ms = 0;
392  CallMethod(&method_call, timeout_ms);
393  WaitForResponses(1);
394
395  // Should fail because of timeout.
396  ASSERT_EQ("", response_strings_[0]);
397}
398
399TEST_F(EndToEndAsyncTest, TimeoutWithErrorCallback) {
400  const char* kHello = "hello";
401
402  // Create the method call.
403  dbus::MethodCall method_call("org.chromium.TestInterface", "SlowEcho");
404  dbus::MessageWriter writer(&method_call);
405  writer.AppendString(kHello);
406
407  // Call the method with timeout of 0ms.
408  const int timeout_ms = 0;
409  CallMethodWithErrorCallback(&method_call, timeout_ms);
410  WaitForErrors(1);
411
412  // Should fail because of timeout.
413  ASSERT_TRUE(response_strings_.empty());
414  ASSERT_EQ(DBUS_ERROR_NO_REPLY, error_names_[0]);
415}
416
417// Tests calling a method that sends its reply asynchronously.
418TEST_F(EndToEndAsyncTest, AsyncEcho) {
419  const char* kHello = "hello";
420
421  // Create the method call.
422  dbus::MethodCall method_call("org.chromium.TestInterface", "AsyncEcho");
423  dbus::MessageWriter writer(&method_call);
424  writer.AppendString(kHello);
425
426  // Call the method.
427  const int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT;
428  CallMethod(&method_call, timeout_ms);
429
430  // Check the response.
431  WaitForResponses(1);
432  EXPECT_EQ(kHello, response_strings_[0]);
433}
434
435TEST_F(EndToEndAsyncTest, NonexistentMethod) {
436  dbus::MethodCall method_call("org.chromium.TestInterface", "Nonexistent");
437
438  const int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT;
439  CallMethod(&method_call, timeout_ms);
440  WaitForResponses(1);
441
442  // Should fail because the method is nonexistent.
443  ASSERT_EQ("", response_strings_[0]);
444}
445
446TEST_F(EndToEndAsyncTest, NonexistentMethodWithErrorCallback) {
447  dbus::MethodCall method_call("org.chromium.TestInterface", "Nonexistent");
448
449  const int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT;
450  CallMethodWithErrorCallback(&method_call, timeout_ms);
451  WaitForErrors(1);
452
453  // Should fail because the method is nonexistent.
454  ASSERT_TRUE(response_strings_.empty());
455  ASSERT_EQ(DBUS_ERROR_UNKNOWN_METHOD, error_names_[0]);
456}
457
458TEST_F(EndToEndAsyncTest, BrokenMethod) {
459  dbus::MethodCall method_call("org.chromium.TestInterface", "BrokenMethod");
460
461  const int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT;
462  CallMethod(&method_call, timeout_ms);
463  WaitForResponses(1);
464
465  // Should fail because the method is broken.
466  ASSERT_EQ("", response_strings_[0]);
467}
468
469TEST_F(EndToEndAsyncTest, BrokenMethodWithErrorCallback) {
470  dbus::MethodCall method_call("org.chromium.TestInterface", "BrokenMethod");
471
472  const int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT;
473  CallMethodWithErrorCallback(&method_call, timeout_ms);
474  WaitForErrors(1);
475
476  // Should fail because the method is broken.
477  ASSERT_TRUE(response_strings_.empty());
478  ASSERT_EQ(DBUS_ERROR_FAILED, error_names_[0]);
479}
480
481TEST_F(EndToEndAsyncTest, InvalidObjectPath) {
482  // Trailing '/' is only allowed for the root path.
483  const dbus::ObjectPath invalid_object_path("/org/chromium/TestObject/");
484
485  // Replace object proxy with new one.
486  object_proxy_ = bus_->GetObjectProxy("org.chromium.TestService",
487                                       invalid_object_path);
488
489  dbus::MethodCall method_call("org.chromium.TestInterface", "Echo");
490
491  const int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT;
492  CallMethodWithErrorCallback(&method_call, timeout_ms);
493  WaitForErrors(1);
494
495  // Should fail because of the invalid path.
496  ASSERT_TRUE(response_strings_.empty());
497  ASSERT_EQ("", error_names_[0]);
498}
499
500TEST_F(EndToEndAsyncTest, InvalidServiceName) {
501  // Bus name cannot contain '/'.
502  const std::string invalid_service_name = ":1/2";
503
504  // Replace object proxy with new one.
505  object_proxy_ = bus_->GetObjectProxy(
506      invalid_service_name, dbus::ObjectPath("org.chromium.TestObject"));
507
508  dbus::MethodCall method_call("org.chromium.TestInterface", "Echo");
509
510  const int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT;
511  CallMethodWithErrorCallback(&method_call, timeout_ms);
512  WaitForErrors(1);
513
514  // Should fail because of the invalid bus name.
515  ASSERT_TRUE(response_strings_.empty());
516  ASSERT_EQ("", error_names_[0]);
517}
518
519TEST_F(EndToEndAsyncTest, EmptyResponseCallback) {
520  const char* kHello = "hello";
521
522  // Create the method call.
523  dbus::MethodCall method_call("org.chromium.TestInterface", "Echo");
524  dbus::MessageWriter writer(&method_call);
525  writer.AppendString(kHello);
526
527  // Call the method with an empty callback.
528  const int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT;
529  object_proxy_->CallMethod(&method_call,
530                            timeout_ms,
531                            dbus::ObjectProxy::EmptyResponseCallback());
532  // Post a delayed task to quit the message loop.
533  message_loop_.PostDelayedTask(FROM_HERE,
534                                MessageLoop::QuitClosure(),
535                                TestTimeouts::tiny_timeout());
536  message_loop_.Run();
537  // We cannot tell if the empty callback is called, but at least we can
538  // check if the test does not crash.
539}
540
541TEST_F(EndToEndAsyncTest, TestSignal) {
542  const char kMessage[] = "hello, world";
543  // Send the test signal from the exported object.
544  test_service_->SendTestSignal(kMessage);
545  // Receive the signal with the object proxy. The signal is handled in
546  // EndToEndAsyncTest::OnTestSignal() in the main thread.
547  WaitForTestSignal();
548  ASSERT_EQ(kMessage, test_signal_string_);
549}
550
551TEST_F(EndToEndAsyncTest, TestSignalFromRoot) {
552  const char kMessage[] = "hello, world";
553  // Object proxies are tied to a particular object path, if a signal
554  // arrives from a different object path like "/" the first object proxy
555  // |object_proxy_| should not handle it, and should leave it for the root
556  // object proxy |root_object_proxy_|.
557  test_service_->SendTestSignalFromRoot(kMessage);
558  WaitForTestSignal();
559  // Verify the signal was not received by the specific proxy.
560  ASSERT_TRUE(test_signal_string_.empty());
561  // Verify the string WAS received by the root proxy.
562  ASSERT_EQ(kMessage, root_test_signal_string_);
563}
564
565TEST_F(EndToEndAsyncTest, TestHugeSignal) {
566  const std::string kHugeMessage(kHugePayloadSize, 'o');
567
568  // Send the huge signal from the exported object.
569  test_service_->SendTestSignal(kHugeMessage);
570  // This caused a DCHECK failure before. Ensure that the issue is fixed.
571  WaitForTestSignal();
572  ASSERT_EQ(kHugeMessage, test_signal_string_);
573}
574
575class SignalReplacementTest : public EndToEndAsyncTest {
576 public:
577  SignalReplacementTest() {
578  }
579
580  virtual void SetUp() {
581    // Set up base class.
582    EndToEndAsyncTest::SetUp();
583
584    // Reconnect the root object proxy's signal handler to a new handler
585    // so that we can verify that a second call to ConnectSignal() delivers
586    // to our new handler and not the old.
587    object_proxy_->ConnectToSignal(
588        "org.chromium.TestInterface",
589        "Test",
590        base::Bind(&SignalReplacementTest::OnReplacementTestSignal,
591                   base::Unretained(this)),
592        base::Bind(&SignalReplacementTest::OnReplacementConnected,
593                   base::Unretained(this)));
594    // Wait until the object proxy is connected to the signal.
595    message_loop_.Run();
596  }
597
598 protected:
599  // Called when the "Test" signal is received, in the main thread.
600  // Copy the string payload to |replacement_test_signal_string_|.
601  void OnReplacementTestSignal(dbus::Signal* signal) {
602    dbus::MessageReader reader(signal);
603    ASSERT_TRUE(reader.PopString(&replacement_test_signal_string_));
604    message_loop_.Quit();
605  }
606
607  // Called when connected to the signal.
608  void OnReplacementConnected(const std::string& interface_name,
609                              const std::string& signal_name,
610                              bool success) {
611    ASSERT_TRUE(success);
612    message_loop_.Quit();
613  }
614
615  // Text message from "Test" signal delivered to replacement handler.
616  std::string replacement_test_signal_string_;
617};
618
619TEST_F(SignalReplacementTest, TestSignalReplacement) {
620  const char kMessage[] = "hello, world";
621  // Send the test signal from the exported object.
622  test_service_->SendTestSignal(kMessage);
623  // Receive the signal with the object proxy.
624  WaitForTestSignal();
625  // Verify the string WAS NOT received by the original handler.
626  ASSERT_TRUE(test_signal_string_.empty());
627  // Verify the signal WAS received by the replacement handler.
628  ASSERT_EQ(kMessage, replacement_test_signal_string_);
629}
630