Test harness that tests Dart client <=> Go server communication.

Change-Id: I18bac956eeba1c44067d2c4aeb5b54609ef28f7a
diff --git a/.gitignore b/.gitignore
index e648e97..cacfb4f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,5 +3,6 @@
 
 # Dart dependencies
 /dart/.packages
+/dart/.pub
 /dart/pubspec.lock
 /dart/**/packages
diff --git a/Makefile b/Makefile
index 8534ec7..f4868f4 100644
--- a/Makefile
+++ b/Makefile
@@ -122,9 +122,14 @@
 run-syncbase-app: gen/mojo/syncbase_server.mojo gen-mojom dart/packages
 	$(MOJO_DIR)/src/mojo/devtools/common/mojo_run $(MOJO_FLAGS) -v --enable-multiprocess $(PWD)/dart/bin/syncbase_client.dart
 
-# TODO(nlacasse): This should run real tests once we have them.
 .PHONY: test
-test: dartanalyzer
+test: dartanalyzer gen-mojom gen/mojo/echo_server.mojo
+	# TODO(nlacasse): We should be passing "--enable-multiprocess" here, since
+	# that is usually needed for Go Mojo services.  However, using that flag
+	# causes the test runner to crash on exit with "Connection error to the
+	# shell".  The tests somehow run and pass without that flag, so maybe it's
+	# not necessary?
+	$(MOJO_DIR)/src/mojo/devtools/common/mojo_test $(MOJO_FLAGS) -v --shell-path $(MOJO_DIR)/src/out/Debug/mojo_shell tests
 
 .PHONY: clean
 clean:
diff --git a/README.md b/README.md
index b9353e3..a92cc72 100644
--- a/README.md
+++ b/README.md
@@ -70,6 +70,15 @@
 Googlers: http://go/install-dart
 External: https://www.dartlang.org/downloads/
 
+## Testing
+
+Run the tests:
+
+    make test
+
+This will run all tests listed in the `tests` file in the root directory of
+this repo.
+
 [architecture proposal]: https://docs.google.com/document/d/1TyxPYIhj9VBCtY7eAXu_MEV9y0dtRx7n7UY4jm76Qq4/edit
 [depot tools]: http://www.chromium.org/developers/how-tos/install-depot-tools
 [goma]: https://sites.google.com/a/google.com/goma/how-to-use-goma/how-to-use-goma-for-chrome-team
diff --git a/dart/bin/echo_client.dart b/dart/bin/echo_client.dart
index b919391..bb8aa73 100644
--- a/dart/bin/echo_client.dart
+++ b/dart/bin/echo_client.dart
@@ -30,7 +30,7 @@
     String input = 'foobee';
 
     print('calling echoString($input)');
-    ep.ptr.echoString(input).then((EchoEchoStringResponseParams v) {
+    p.ptr.echoString(input).then((EchoEchoStringResponseParams v) {
       String output = v.value;
       print('got echo result: $output');
 
diff --git a/dart/lib/src/apptest/apptest.dart b/dart/lib/src/apptest/apptest.dart
new file mode 100644
index 0000000..83303ea
--- /dev/null
+++ b/dart/lib/src/apptest/apptest.dart
@@ -0,0 +1,70 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// TODO(nlacasse): This file was copied from
+// $MOJO_DIR/src/mojo/dart/apptest/lib/apptest.dart.
+// The Mojo team has plans to make this library available in Pub.  Once they
+// do, we should delete this file, and use the one in Pub.
+
+library apptest;
+
+import 'package:mojo/application.dart';
+import 'package:mojo/core.dart';
+
+// Import and reexport the test package. We are a *.dartzip file designed to
+// be linked into your_apptest.mojo file and are your main entrypoint.
+import 'package:test/test.dart';
+export 'package:test/test.dart';
+
+typedef AppTestFunction(Application app, String url);
+
+// This class is an application that does nothing but tears down the connections
+// between each test.
+class _ConnectionToShellApplication extends Application {
+  final List<AppTestFunction> _testFunctions;
+
+  _ConnectionToShellApplication.fromHandle(
+      MojoHandle handle, this._testFunctions)
+      : super.fromHandle(handle);
+
+  // Only run the test suite passed in once we have received an initialize()
+  // call from the shell. We need to first have a valid connection to the shell
+  // so that apptests can connect to other applications.
+  void initialize(List<String> args, String url) {
+    group('dart_apptests', () {
+      setUp(testSetUp);
+      tearDown(testTearDown);
+      for (var testFunction in _testFunctions) {
+        testFunction(this, url);
+      }
+    });
+    // Append a final test to terminate shell connection.
+    // TODO(johnmccutchan): Remove this once package 'test' supports a global
+    // tearDown callback.
+    test('TERMINATE SHELL CONNECTION', () async {
+      await close();
+      assert(MojoHandle.reportLeakedHandles());
+    });
+  }
+
+  void testSetUp() {
+  }
+
+  void testTearDown() {
+    // Reset any connections between tests.
+    resetConnections();
+  }
+}
+
+/// The public interface to apptests.
+///
+/// In a dart mojo application, [incomingHandle] is `args[0]`. [testFunctions]
+/// is list of [AppTestFunction]. Each function will be passed the application
+/// and url.
+runAppTests(var incomingHandle, List<AppTestFunction> testFunctions) {
+  var appHandle = new MojoHandle(incomingHandle);
+  var application =
+      new _ConnectionToShellApplication.fromHandle(appHandle, testFunctions);
+  /// [Application]'s [initialize] will be called.
+}
diff --git a/dart/pubspec.yaml b/dart/pubspec.yaml
index 9f863bd..fe4af35 100644
--- a/dart/pubspec.yaml
+++ b/dart/pubspec.yaml
@@ -1,3 +1,4 @@
 name: Ether
 dependencies:
     mojo: any
+    test: any
diff --git a/dart/test/echo_test.dart b/dart/test/echo_test.dart
new file mode 100644
index 0000000..ef6b69b
--- /dev/null
+++ b/dart/test/echo_test.dart
@@ -0,0 +1,36 @@
+#!mojo mojo:dart_content_handler
+
+import 'dart:async';
+
+import 'package:mojo/application.dart' show Application;
+
+// TODO(nlacasse): Get this library from Pub once it is available there.
+import '../lib/src/apptest/apptest.dart';
+
+import '../../gen/dart-gen/mojom/lib/mojo/echo.mojom.dart' show EchoEchoStringResponseParams, EchoProxy;
+
+echoTests(Application app, String url) {
+  // TODO(nlacasse): This is pretty gross, but there's no good way to get the
+  // current directory from within a Mojo app.  Dart's Directory.current() is
+  // either broken or unsupported.  In any case, this will never work on
+  // Android, so we should switch to serving these files over http rather
+  // than directly from the filesystem.
+  String echoServerUrl = url.replaceFirst('dart/test/echo_test.dart', 'gen/mojo/echo_server.mojo');
+
+  test('echo returns correct response', () async {
+    EchoProxy ep = new EchoProxy.unbound();
+    app.connectToService(echoServerUrl, ep);
+
+    String input = 'foobee';
+    var v = await ep.ptr.echoString(input);
+    expect(v.value, equals(input));
+
+    await ep.ptr.quit();
+    await ep.close();
+  });
+}
+
+Future<int> main(List args) async {
+  var exitCode = await runAppTests(args[0], [echoTests]);
+  return exitCode;
+}
diff --git a/go/src/echo_server.go b/go/src/echo_server.go
index 2a8ccd8..b0c1a14 100644
--- a/go/src/echo_server.go
+++ b/go/src/echo_server.go
@@ -22,13 +22,20 @@
 //#include "mojo/public/c/system/types.h"
 import "C"
 
-type echoImpl struct{}
+type echoImpl struct {
+	stub *bindings.Stub
+}
 
 func (e *echoImpl) EchoString(in *string) (out *string, err error) {
 	log.Printf("server: %s\n", *in)
 	return in, nil
 }
 
+func (echo *echoImpl) Quit() error {
+	echo.stub.Close()
+	return nil
+}
+
 type delegate struct {
 	stubs []*bindings.Stub
 }
@@ -38,6 +45,7 @@
 func (d *delegate) Create(req echo.Echo_Request) {
 	impl := &echoImpl{}
 	stub := echo.NewEchoStub(req, impl, bindings.GetAsyncWaiter())
+	impl.stub = stub
 	d.stubs = append(d.stubs, stub)
 	go func() {
 		for {
diff --git a/mojom/echo.mojom b/mojom/echo.mojom
index cd341d2..62d7ccf 100644
--- a/mojom/echo.mojom
+++ b/mojom/echo.mojom
@@ -2,9 +2,25 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-[JavaPackage="org.chromium.mojom.echo"]
 module mojo;
 
 interface Echo {
   EchoString(string? value) => (string? value);
+
+  // TODO(nlacasse): The test runner currently needs this Quit method to tell
+  // the echo service to quit, otherwise the tests hang because the echo
+  // service never stops running and listening for connections.
+  //
+  // The Mojo dart tests that this is based on do essentially the same thing.
+  // See $MOJO_DIR/src/services/dart/dart_apptests/echo_apptests.dart
+  //
+  // The "Quitting Mojo Apps" document
+  // (https://drive.google.com/a/google.com/folderview?id=0B-WCZkfLIXQTajI4WWZhdGhjNnM)
+  // says that Mojo applications should gracefully quit when Mojo shell
+  // terminates (see Condition C in that doc), but that does not appear to be
+  // implemented yet.
+  //
+  // Once the Mojo app can gracefully terminate on shell exit, we should get
+  // rid of this method.
+  Quit();
 };
diff --git a/tests b/tests
new file mode 100644
index 0000000..c3ed059
--- /dev/null
+++ b/tests
@@ -0,0 +1,9 @@
+// See $MOJO_DIR/src/mojo/devtools/common/mojo_test for the format of this
+// file.
+
+tests = [
+	{
+		"test": "dart/test/echo_test.dart",
+		"type": "dart"
+	}
+]