Start of actual Syncbase client library.

Dart has restrictions on where imports for a library package can come
from.  I had to move the generated dart mojom bindings inside the
dart/lib directory to make it happy.

I got rid of the dart/bin scripts, since they don't make sense for this
repo, and were redundant with the tests in dart/test.

Currently we have two libraries: echo_client and syncbase_client.  I did
some refactoring to pull out the common parts of the two.  Once we get
rid of echo_client, things will get simpler again.

The only syncbase method I actually implemented is "appExists", and I
wrote a tests that "appExist(foo) == false".  This test is currently commented out
(see "tests" file) because we don't have syncbase running in a mojo
service quite yet.

Change-Id: I3b5d04325c2b514b9e55140901094000202fa348
diff --git a/.gitignore b/.gitignore
index cacfb4f..82e486e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,5 +4,6 @@
 # Dart dependencies
 /dart/.packages
 /dart/.pub
+/dart/lib/gen
 /dart/pubspec.lock
 /dart/**/packages
diff --git a/Makefile b/Makefile
index f4868f4..bb385e0 100644
--- a/Makefile
+++ b/Makefile
@@ -62,7 +62,7 @@
 	$(MOJOM_BIN) $1 -d . -o $2 -g $3
 endef
 
-all: run-echo-app
+all: test
 
 # Builds the shared library that Mojo services must be linked with.
 $(MOJO_SHARED_LIB):
@@ -76,14 +76,20 @@
 # TODO(nlacasse): The echo_client and echo_server are currently used to test
 # compilation and mojom binding generation.  We should remove them once they
 # are no longer needed.
-gen-mojom: gen/dart-pkg/mojom/lib/mojo/echo.mojom.dart gen/go/src/mojom/echo/echo.mojom.go
-gen-mojom: gen/dart-pkg/mojom/lib/mojo/syncbase.mojom.dart gen/go/src/mojom/syncbase/syncbase.mojom.go
+gen-mojom: dart/lib/gen/dart-pkg/mojom/lib/mojo/echo.mojom.dart gen/go/src/mojom/echo/echo.mojom.go
+gen-mojom: dart/lib/gen/dart-pkg/mojom/lib/mojo/syncbase.mojom.dart gen/go/src/mojom/syncbase/syncbase.mojom.go
 
-gen/dart-pkg/mojom/lib/mojo/echo.mojom.dart: mojom/echo.mojom
-	$(call MOJOM_GEN,$<,gen,dart)
+dart/lib/gen/dart-pkg/mojom/lib/mojo/echo.mojom.dart: mojom/echo.mojom
+	$(call MOJOM_GEN,$<,dart/lib/gen,dart)
+	# TODO(nlacasse): Figure out why mojom_bindings_generator creates these bad
+	# symlinks on dart files.
+	rm -f dart/lib/gen/mojom/echo.mojom.dart
 
-gen/dart-pkg/mojom/lib/mojo/syncbase.mojom.dart: mojom/syncbase.mojom
-	$(call MOJOM_GEN,$<,gen,dart)
+dart/lib/gen/dart-pkg/mojom/lib/mojo/syncbase.mojom.dart: mojom/syncbase.mojom
+	$(call MOJOM_GEN,$<,dart/lib/gen,dart)
+	# TODO(nlacasse): Figure out why mojom_bindings_generator creates these bad
+	# symlinks on dart files.
+	rm -f dart/lib/gen/mojom/syncbase.mojom.dart
 
 gen/go/src/mojom/echo/echo.mojom.go: mojom/echo.mojom
 	$(call MOJOM_GEN,$<,gen,go)
@@ -108,22 +114,25 @@
 # Lint src and test files with dartanalyzer. This takes a few seconds.
 .PHONY: dartanalyzer
 dartanalyzer: dart/packages gen-mojom
-	cd dart && dartanalyzer $(DART_FILES)
+	# TODO(nlacasse): Fix dart mojom binding generator so it does not produce
+	# files that violate dartanalyzer.  For now, we use "grep -v" to hide all
+	# warnings from *.mojom.dart files.
+	cd dart && dartanalyzer bin/*.dart lib/*.dart test/*.dart | grep -v '\.mojom\.dart'
 
 # Installs dart dependencies.
 dart/packages: dart/pubspec.yaml
 	cd dart && pub get
 
-.PHONY: run-echo-app
-run-echo-app: gen/mojo/echo_server.mojo gen-mojom dart/packages
-	$(MOJO_DIR)/src/mojo/devtools/common/mojo_run $(MOJO_FLAGS) -v --enable-multiprocess $(PWD)/dart/bin/echo_client.dart
-
+# TODO(nlacasse): Remove this task and dart/bin/syncbase.dart when the tests
+# are stable enough to reliably test syncbase.
 .PHONY: run-syncbase-app
 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
+	$(MOJO_DIR)/src/mojo/devtools/common/mojo_run $(MOJO_FLAGS) -v --enable-multiprocess $(PWD)/dart/bin/syncbase.dart
 
 .PHONY: test
-test: dartanalyzer gen-mojom gen/mojo/echo_server.mojo
+test: dart/packages gen-mojom gen/mojo/echo_server.mojo
+	# TODO(nlacasse): These tests sometimes hang.  I suspect some connection is
+	# not getting closed properly.  More debugging is necessary.
 	# 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
@@ -134,4 +143,4 @@
 .PHONY: clean
 clean:
 	rm -rf gen
-	rm -rf dart/{packages,.packages,pubspec.lock}
+	rm -rf dart/{lib/gen,packages,.packages,pubspec.lock}
diff --git a/README.md b/README.md
index a92cc72..6cf1514 100644
--- a/README.md
+++ b/README.md
@@ -79,6 +79,11 @@
 This will run all tests listed in the `tests` file in the root directory of
 this repo.
 
+The following command will run a single test file.  This is useful when the
+full test suite hangs with no output.
+
+    $(MOJO_DIR)/src/mojo/devtools/common/mojo_run $(MOJO_FLAGS) -v --shell-path $(MOJO_DIR)/src/out/Debug/mojo_shell dart/test/<filename>
+
 [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
deleted file mode 100644
index bb8aa73..0000000
--- a/dart/bin/echo_client.dart
+++ /dev/null
@@ -1,55 +0,0 @@
-#!mojo mojo:dart_content_handler
-
-import 'dart:async';
-
-// TODO(nlacasse): Use 'show' or 'as' in non-stdlib import statements, per
-// Vanadium Dart convention.
-import 'package:mojo/application.dart';
-import 'package:mojo/core.dart';
-
-import '../../gen/dart-gen/mojom/lib/mojo/echo.mojom.dart' show EchoEchoStringResponseParams, EchoProxy;
-
-class Client extends Application {
-  Client.fromHandle(MojoHandle handle) : super.fromHandle(handle);
-
-  void initialize(List<String> args, 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 serviceUrl = url.replaceFirst('dart/bin/echo_client.dart', 'gen/mojo/echo_server.mojo');
-
-    print('connecting to $serviceUrl');
-
-    EchoProxy p = new EchoProxy.unbound();
-    connectToService(serviceUrl, p);
-
-    print('connected');
-
-    String input = 'foobee';
-
-    print('calling echoString($input)');
-    p.ptr.echoString(input).then((EchoEchoStringResponseParams v) {
-      String output = v.value;
-      print('got echo result: $output');
-
-      assert(input == output);
-      print('SUCCESS');
-
-      closeApplication();
-    });
-
-    print('done calling echoString');
-  }
-
-  Future closeApplication() async {
-    await close();
-    assert(MojoHandle.reportLeakedHandles());
-  }
-}
-
-main(List args) {
-  MojoHandle appHandle = new MojoHandle(args[0]);
-  new Client.fromHandle(appHandle);
-}
diff --git a/dart/bin/syncbase.dart b/dart/bin/syncbase.dart
new file mode 100644
index 0000000..011997e
--- /dev/null
+++ b/dart/bin/syncbase.dart
@@ -0,0 +1,21 @@
+#!mojo mojo:dart_content_handler
+
+// TODO(nlacasse): Remove this file once the tests are sufficiently reliable to
+// test syncbase connectivity.
+
+import 'package:mojo/core.dart' show MojoHandle;
+
+import '../lib/syncbase_client.dart' show SyncbaseClient;
+
+main(List args) async {
+  MojoHandle handle = new MojoHandle(args[0]);
+
+  // TODO(nlacasse): Switch to serving these files over http rather than
+  // directly from the filesystem, so they can be accessed by Android.
+  String url = args[1].replaceFirst('dart/bin/syncbase.dart', 'gen/mojo/syncbase_server.mojo');
+
+  SyncbaseClient c = new SyncbaseClient(handle, url);
+  await c.connect();
+  bool exists = await c.appExists('foo');
+  print('appExists(foo): $exists');
+}
diff --git a/dart/bin/syncbase_client.dart b/dart/bin/syncbase_client.dart
deleted file mode 100644
index 536c6df..0000000
--- a/dart/bin/syncbase_client.dart
+++ /dev/null
@@ -1,42 +0,0 @@
-#!mojo mojo:dart_content_handler
-
-import 'dart:async';
-
-// TODO(nlacasse): Use 'show' or 'as' in non-stdlib import statements, per
-// Vanadium Dart convention.
-import 'package:mojo/application.dart';
-import 'package:mojo/core.dart';
-
-import '../../gen/dart-gen/mojom/lib/mojo/syncbase.mojom.dart' show SyncbaseProxy;
-
-class Client extends Application {
-  Client.fromHandle(MojoHandle handle) : super.fromHandle(handle);
-
-  void initialize(List<String> args, 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 serviceUrl = url.replaceFirst('dart/bin/syncbase_client.dart', 'gen/mojo/syncbase_server.mojo');
-
-    print('connecting to $serviceUrl');
-
-    SyncbaseProxy p = new SyncbaseProxy.unbound();
-    connectToService(serviceUrl, p);
-
-    print('connected');
-
-    closeApplication();
-  }
-
-  Future closeApplication() async {
-    await close();
-    assert(MojoHandle.reportLeakedHandles());
-  }
-}
-
-main(List args) {
-  MojoHandle appHandle = new MojoHandle(args[0]);
-  new Client.fromHandle(appHandle);
-}
diff --git a/dart/lib/echo_client.dart b/dart/lib/echo_client.dart
new file mode 100644
index 0000000..df38894
--- /dev/null
+++ b/dart/lib/echo_client.dart
@@ -0,0 +1,29 @@
+library echo_client;
+
+import 'dart:async';
+
+import 'package:mojo/core.dart' show MojoHandle;
+
+import 'gen/dart-gen/mojom/lib/mojo/echo.mojom.dart' as mojom;
+import 'src/client_base.dart' show ClientBase;
+
+class EchoClient extends ClientBase {
+  final mojom.EchoProxy _proxy;
+
+  EchoClient(MojoHandle handle, String url) :
+    _proxy = new mojom.EchoProxy.unbound(),
+    super(handle, url);
+
+  Future connect() {
+    return connectWithProxy(_proxy);
+  }
+
+  Future<String> echo(String s) async {
+    print('calling echoString($s)');
+    mojom.EchoEchoStringResponseParams v = await _proxy.ptr.echoString(s);
+
+    String output = v.value;
+    print('got echo result: $output');
+    return output;
+  }
+}
diff --git a/dart/lib/src/apptest/apptest.dart b/dart/lib/src/apptest/apptest.dart
deleted file mode 100644
index 83303ea..0000000
--- a/dart/lib/src/apptest/apptest.dart
+++ /dev/null
@@ -1,70 +0,0 @@
-// 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/lib/src/client_base.dart b/dart/lib/src/client_base.dart
new file mode 100644
index 0000000..3c94c40
--- /dev/null
+++ b/dart/lib/src/client_base.dart
@@ -0,0 +1,47 @@
+import 'dart:async';
+
+// TODO(nlacasse): Once echo_client is gone, make this file (and any other
+// shared dependencies) a "part of" syncbase library so we can use private
+// methods and variables.
+
+import 'package:mojo/application.dart' show Application;
+import 'package:mojo/bindings.dart' show ProxyBase;
+import 'package:mojo/core.dart' show MojoHandle;
+
+// InitializedApplication is an Application with a future 'initialized' that is
+// resolved after the 'initialize' method finishes.
+class InitializedApplication extends Application {
+  final _initializeCompleter = new Completer();
+  Future get initialized => _initializeCompleter.future;
+
+  InitializedApplication.fromHandle(MojoHandle handle) :
+    super.fromHandle(handle);
+
+  void initialize(List<String> args, String url) {
+    _initializeCompleter.complete();
+  }
+}
+
+// ClientBase is the base class for a client that needs to connect to a mojo
+// service.
+// TODO(nlacasse): This class is useful for holding the common parts of
+// echo_client and syncbase_client during this time of rapid change.  Once we
+// get rid of echo_client, reconsider if this base class makes sense.
+abstract class ClientBase {
+  final String url;
+  final InitializedApplication _app;
+
+  ClientBase(MojoHandle handle, this.url) :
+    this._app = new InitializedApplication.fromHandle(handle);
+
+  Future connectWithProxy(ProxyBase p) async {
+    await _app.initialized;
+    print('connecting to $url');
+    _app.connectToService(url, p);
+    print('connected');
+  }
+
+  Future close() {
+    return _app.close();
+  }
+}
diff --git a/dart/lib/syncbase_client.dart b/dart/lib/syncbase_client.dart
new file mode 100644
index 0000000..112b708
--- /dev/null
+++ b/dart/lib/syncbase_client.dart
@@ -0,0 +1,31 @@
+library syncbase_client;
+
+import 'dart:async';
+
+import 'package:mojo/core.dart' show MojoHandle;
+
+import 'gen/dart-gen/mojom/lib/mojo/syncbase.mojom.dart' as mojom;
+import 'src/client_base.dart' show ClientBase;
+
+class SyncbaseClient extends ClientBase {
+  final mojom.SyncbaseProxy _proxy;
+
+  SyncbaseClient(MojoHandle handle, String url) :
+    _proxy = new mojom.SyncbaseProxy.unbound(),
+    super(handle, url);
+
+  Future connect() async {
+    return connectWithProxy(_proxy);
+  }
+
+  // TODO(nlacasse): Test this function with working syncbase mojo service.
+  Future<bool> appExists(String name) async {
+    mojom.SyncbaseAppExistsResponseParams v = await _proxy.ptr.appExists(name);
+    if (v.err != null) {
+      throw v.err;
+    }
+    return v.exists;
+  }
+
+  // TODO(nlacasse): Implement more methods here.
+}
diff --git a/dart/test/echo_test.dart b/dart/test/echo_test.dart
index ef6b69b..56be100 100644
--- a/dart/test/echo_test.dart
+++ b/dart/test/echo_test.dart
@@ -2,35 +2,39 @@
 
 import 'dart:async';
 
-import 'package:mojo/application.dart' show Application;
+import 'package:mojo/core.dart' show MojoHandle;
+import 'package:test/test.dart';
 
-// TODO(nlacasse): Get this library from Pub once it is available there.
-import '../lib/src/apptest/apptest.dart';
+import '../lib/echo_client.dart' show EchoClient;
 
-import '../../gen/dart-gen/mojom/lib/mojo/echo.mojom.dart' show EchoEchoStringResponseParams, EchoProxy;
+main(List args) async {
+  // args[0] is the mojo handle.
+  MojoHandle handle = new MojoHandle(args[0]);
 
-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');
+  // TODO(nlacasse): Switch to serving these files over http rather than
+  // directly from the filesystem, so they can be accessed by Android.
+  String serviceUrl = 'file://' + args[1].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);
+  EchoClient c = new EchoClient(handle, serviceUrl);
+  c.connect();
 
+  test('echo string returns correct response', () {
     String input = 'foobee';
-    var v = await ep.ptr.echoString(input);
-    expect(v.value, equals(input));
-
-    await ep.ptr.quit();
-    await ep.close();
+    Future<String> got = c.echo(input);
+    expect(got, completion(equals(input)));
   });
-}
 
-Future<int> main(List args) async {
-  var exitCode = await runAppTests(args[0], [echoTests]);
-  return exitCode;
+  test('echo empty string returns correct response', () {
+    String input = '';
+    Future<String> got = c.echo(input);
+    expect(got, completion(equals(input)));
+  });
+
+  // Append a final test to terminate shell connection.
+  // TODO(nlacasse): Remove this once package 'test' supports a global tearDown
+  // callback.  See https://github.com/dart-lang/test/issues/18.
+  test('terminate shell connection', () async {
+    await c.close();
+    expect(MojoHandle.reportLeakedHandles(), isTrue);
+  });
 }
diff --git a/dart/test/syncbase_test.dart b/dart/test/syncbase_test.dart
new file mode 100755
index 0000000..3958eac
--- /dev/null
+++ b/dart/test/syncbase_test.dart
@@ -0,0 +1,30 @@
+#!mojo mojo:dart_content_handler
+
+import 'package:mojo/core.dart' show MojoHandle;
+import 'package:test/test.dart';
+
+import '../lib/syncbase_client.dart' show SyncbaseClient;
+
+main(List args) async {
+  // args[0] is the mojo handle.
+  MojoHandle handle = new MojoHandle(args[0]);
+
+  // TODO(nlacasse): Switch to serving these files over http rather than
+  // directly from the filesystem, so they can be accessed by Android.
+  String serviceUrl = 'file://' + args[1].replaceFirst('dart/test/syncbase_test.dart', 'gen/mojo/synbase_server.mojo');
+
+  SyncbaseClient c = new SyncbaseClient(handle, serviceUrl);
+  c.connect();
+
+  test('appExists(foo) should be false', () {
+    expect(c.appExists, completion(isFalse));
+  });
+
+  // Append a final test to terminate shell connection.
+  // TODO(nlacasse): Remove this once package 'test' supports a global tearDown
+  // callback.  See https://github.com/dart-lang/test/issues/18.
+  test('terminate shell connection', () async {
+    await c.close();
+    expect(MojoHandle.reportLeakedHandles(), isTrue);
+  });
+}
diff --git a/tests b/tests
index 0f43ea4..5f56e37 100644
--- a/tests
+++ b/tests
@@ -4,5 +4,11 @@
 	{
 		"test": "dart/test/echo_test.dart",
 		"type": "dart"
-	}
+	},
+# TODO(nlacasse,sadovsky): Enable these tests once syncbase runs in a mojo
+# service.
+#	{
+#		"test": "dart/test/syncbase_test.dart",
+#		"type": "dart"
+#	}
 ]