mojo.syncbase: Mojo+Syncbase: make list* methods return strings, fix exec string quoting bug
MultiPart: 2/2
Change-Id: I7bf04d424636fd93034f623d43695c0682af536b
diff --git a/lib/src/app.dart b/lib/src/app.dart
index b3f3876..d1052f0 100644
--- a/lib/src/app.dart
+++ b/lib/src/app.dart
@@ -4,7 +4,6 @@
part of syncbase_client;
-// TODO(sadovsky): Add listDatabases method.
class SyncbaseApp extends NamedResource {
// NOTE(sadovsky): For the Mojo Syncbase service, we only store names from app
// down - i.e. there is no service name.
@@ -41,13 +40,10 @@
return v.perms;
}
- // TODO(aghassemi): Maybe add an abstract class for SyncbaseDatabase so we can
- // return either NoSqlDatabase or SqlDatabase (when both exist).
- Future<List<SyncbaseNoSqlDatabase>> listDatabases() async {
+ Future<List<String>> listDatabases() async {
var v = await _ctx.syncbase.appListDatabases(fullName);
if (isError(v.err)) throw v.err;
-
- return v.databases.map((dbName) => this.noSqlDatabase(dbName)).toList();
+ return v.databases;
}
Future setPermissions(mojom.Perms perms, String version) async {
diff --git a/lib/src/client_context.dart b/lib/src/client_context.dart
index 571a9d3..2c61fdb 100644
--- a/lib/src/client_context.dart
+++ b/lib/src/client_context.dart
@@ -4,17 +4,16 @@
part of syncbase_client;
-// ClientContext holds references to objects that other layers need from the
-// SyncbaseClient, e.g. the Mojo proxy and stub manager.
+// ClientContext holds references to objects that other layers need from
+// SyncbaseClient.
class ClientContext {
// Handle to the Syncbase Mojo proxy.
mojom.SyncbaseProxy proxy;
- // Just a convenience getter for the Mojo proxy's Syncbase pointer.
+ // Convenience getter for the Mojo proxy's Syncbase pointer.
mojom.Syncbase get syncbase => proxy.ptr;
- // Handle to the unclosed stubs manager. Used by layers that need to track
- // Mojo stubs that need to be closed when the Syncbase client is closed.
+ // Used to track Mojo stubs to close when the Syncbase client is closed.
UnclosedStubsManager unclosedStubsManager;
ClientContext._internal(this.proxy, this.unclosedStubsManager);
diff --git a/lib/src/nosql/database.dart b/lib/src/nosql/database.dart
index b4e4581..82dc010 100644
--- a/lib/src/nosql/database.dart
+++ b/lib/src/nosql/database.dart
@@ -69,7 +69,8 @@
_ctx.unclosedStubsManager.register(stub);
- // TODO(aghassemi): Implement naming utilities such as Join in Dart and use them instead.
+ // TODO(aghassemi): Implement naming utilities such as Join in Dart and use
+ // them instead.
var pattern = naming.join(tableName, prefix) + '*';
var req = new mojom.GlobRequest();
req.pattern = pattern;
@@ -93,8 +94,7 @@
Future<List<SyncbaseTable>> listTables() async {
var v = await _ctx.syncbase.dbListTables(fullName);
if (isError(v.err)) throw v.err;
-
- return v.tables.map((tableName) => this.table(tableName)).toList();
+ return v.tables;
}
Future<String> beginBatch(mojom.BatchOptions opts) async {
@@ -142,7 +142,7 @@
// NOTE(aghassemi): We need to make newAck optional to match mojo's
// define class, but newAck is always provided by mojo when called.
if (newAck == null) {
- throw new ArgumentError('newAck can not be null');
+ throw new ArgumentError('newAck must not be null');
}
_sc.add(result);
@@ -176,7 +176,7 @@
// NOTE(aghassemi): We need to make newAck optional to match mojo's
// define class, but newAck is always provided by mojo when called.
if (newAck == null) {
- throw new ArgumentError('newAck can not be null');
+ throw new ArgumentError('newAck must not be null');
}
// Testing instrumentation for testing flow control.
if (testing.isTesting) {
diff --git a/lib/src/nosql/table.dart b/lib/src/nosql/table.dart
index 95fe265..2a4cdf5 100644
--- a/lib/src/nosql/table.dart
+++ b/lib/src/nosql/table.dart
@@ -115,7 +115,7 @@
// NOTE(aghassemi): We need to make newAck optional to match mojo's
// define class, but newAck is always provided by mojo when called.
if (newAck == null) {
- throw new ArgumentError('newAck can not be null');
+ throw new ArgumentError('newAck must not be null');
}
_sc.add(keyValue);
diff --git a/lib/src/stream_flow_control.dart b/lib/src/stream_flow_control.dart
index 342c2fb..761b4f0 100644
--- a/lib/src/stream_flow_control.dart
+++ b/lib/src/stream_flow_control.dart
@@ -5,19 +5,20 @@
part of syncbase_client;
// StreamFlowControl is a mixin that exposes methods initFlowControl() and
-// onNextUnlock() to allow mixers add flow control to their stream controllers.
+// onNextUnlock(), enabling mixers to add flow control to their stream
+// controllers.
class StreamFlowControl {
// We are in "locked" state if and only if _mutex is not null.
- // We are locked by default until we gain the first subscriber.
+ // We start out locked, until we gain our first subscriber.
Completer _mutex = new Completer();
- // Setup flow control by adding listeners to the stream controller.
- // Mixin classes can't have constructors, so this is just a method that should
- // be called from mixer's constructor.
+ // Initializes flow control by adding listeners to the stream controller.
+ // The mixer's constructor should call this method. (Mixins can't have
+ // constructors.)
initFlowControl(StreamController sc) {
- // Unlock when gaining the first subscriber.
+ // Unlock when gaining our first subscriber.
sc.onListen = _unlock;
- // Lock when losing the last subscriber.
+ // Lock when losing our last subscriber.
sc.onCancel = _lock;
// Lock when paused.
sc.onPause = _lock;
@@ -25,31 +26,30 @@
sc.onResume = _unlock;
}
- // Returns a future that either completes immediately if we are not locked or
- // if we are locked, it gets completed as soon as we get unlocked.
- // Mixers can use this method to decide when to ack, telling the server to
- // continue sending events.
+ // Returns a future that completes immediately if we are unlocked, or as soon
+ // as we become unlocked otherwise.
+ // Mixers can use this method to decide when to ack, i.e. when to tell the
+ // server to continue sending events.
Future onNextUnlock() {
if (_mutex == null) {
- // We are not locked, return a completed future.
+ // We are not locked; return a completed future.
return new Future.value();
}
return _mutex.future;
}
// Locks the stream controller.
- // When locked, server does not sent us change events anymore until we unlock.
- // This happens because we don't send back an ack to the server when locked.
+ // When locked, the server cannot send us any more change events.
+ // This happens because we don't ack the server's change events when locked.
_lock() {
if (_mutex == null) {
_mutex = new Completer();
}
}
- // Unlcoks the stream controller.
- // When unlocked, server can send up more change events.
- // This happens because we send back an ack to the server when unlocked after
- // every change we receive instructing the server to send more.
+ // Unlocks the stream controller.
+ // When unlocked, the server can send us more change events.
+ // This happens because we ack the server's change events when unlocked.
_unlock() {
if (_mutex != null) {
_mutex.complete();
diff --git a/lib/src/unclosed_stubs_manager.dart b/lib/src/unclosed_stubs_manager.dart
index b154b87..e3ae604 100644
--- a/lib/src/unclosed_stubs_manager.dart
+++ b/lib/src/unclosed_stubs_manager.dart
@@ -4,8 +4,8 @@
part of syncbase_client;
-// UnclosedStubsManager allows different layers to keep track of the Mojo stubs
-// that need to be closed when the Syncbase client is closed.
+// UnclosedStubsManager is used to track Mojo stubs to close when the Syncbase
+// client is closed.
class UnclosedStubsManager {
List _stubs = [];
@@ -19,7 +19,7 @@
bool exists = _stubs.remove(stub);
if (!exists) {
throw new ArgumentError.value(stub, 'stub',
- 'Does not exist. Please ensure it is registered before calling close.');
+ 'Stub has not been registered, or has already been closed.');
}
stub.close();
}
diff --git a/lib/syncbase_client.dart b/lib/syncbase_client.dart
index 5dd4ebb..7875d82 100644
--- a/lib/syncbase_client.dart
+++ b/lib/syncbase_client.dart
@@ -44,7 +44,6 @@
return err != null && err.id != '';
}
-// TODO(sadovsky): Add listApps method.
class SyncbaseClient {
final ClientContext _ctx;
@@ -78,8 +77,7 @@
Future<List<SyncbaseApp>> listApps() async {
var v = await _ctx.syncbase.serviceListApps();
if (isError(v.err)) throw v.err;
-
- return v.apps.map((appName) => this.app(appName)).toList();
+ return v.apps;
}
Future setPermissions(mojom.Perms perms, String version) async {
diff --git a/test/integration/syncbase_app_test.dart b/test/integration/syncbase_app_test.dart
index 6777ec5..b6fc73e 100644
--- a/test/integration/syncbase_app_test.dart
+++ b/test/integration/syncbase_app_test.dart
@@ -35,12 +35,10 @@
var apps = await c.listApps();
- // NOTE(aghassemi): Since the Syncbase instance is shared between all tests,
- // we will get a lot more than just 1 app, so we simply verify that our
- // appName is in the returned list.
- expect(apps.length, greaterThan(0));
- var ourApp = apps.firstWhere((e) => e.name == appName);
- expect(ourApp.name, equals(appName));
+ // Note: The Syncbase instance is shared among all tests, so listApps() will
+ // return lots of apps; here, we simply verify that our appName is in the
+ // returned list.
+ expect(apps.contains(appName), equals(true));
});
test('listing databases', () async {
@@ -48,18 +46,18 @@
var app = c.app(appName);
await app.create(utils.emptyPerms());
- var dbNames = [utils.uniqueName('db1'), utils.uniqueName('db2')];
- dbNames.sort();
+ var want = [utils.uniqueName('db1'), utils.uniqueName('db2')];
+ want.sort();
- for (var dbName in dbNames) {
+ for (var dbName in want) {
await app.noSqlDatabase(dbName).create(utils.emptyPerms());
}
- var dbs = await app.listDatabases();
- dbs.sort((d1, d2) => d1.name.compareTo(d2.name));
- expect(dbs.length, equals(dbNames.length));
- for (var i = 0; i < dbNames.length; i++) {
- expect(dbs[i].name, equals(dbNames[i]));
+ var got = await app.listDatabases();
+ got.sort((d1, d2) => d1.compareTo(d2));
+ expect(got.length, equals(want.length));
+ for (var i = 0; i < got.length; i++) {
+ expect(got[i], equals(want[i]));
}
});
diff --git a/test/integration/syncbase_database_test.dart b/test/integration/syncbase_database_test.dart
index 850766f..ba963b9 100644
--- a/test/integration/syncbase_database_test.dart
+++ b/test/integration/syncbase_database_test.dart
@@ -15,6 +15,10 @@
import './utils.dart' as utils;
+Iterable<String> decodeStrs(List<List<int>> bufList) {
+ return bufList.map((x) => UTF8.decode(x));
+}
+
runDatabaseTests(SyncbaseClient c) {
test('getting a handle to a database', () {
var app = c.app(utils.uniqueName('app'));
@@ -43,18 +47,18 @@
var db = app.noSqlDatabase(utils.uniqueName('db'));
await db.create(utils.emptyPerms());
- var tableNames = [utils.uniqueName('table1'), utils.uniqueName('table2')];
- tableNames.sort();
+ var want = [utils.uniqueName('table1'), utils.uniqueName('table2')];
+ want.sort();
- for (var tableName in tableNames) {
+ for (var tableName in want) {
await db.table(tableName).create(utils.emptyPerms());
}
- var tables = await db.listTables();
- tables.sort((t1, t2) => t1.name.compareTo(t2.name));
- expect(tables.length, equals(tableNames.length));
- for (var i = 0; i < tableNames.length; i++) {
- expect(tables[i].name, equals(tableNames[i]));
+ var got = await db.listTables();
+ got.sort((t1, t2) => t1.compareTo(t2));
+ expect(got.length, equals(want.length));
+ for (var i = 0; i < got.length; i++) {
+ expect(got[i], equals(want[i]));
}
});
@@ -94,13 +98,14 @@
// Ensure we see all the expected changes in order in the watch stream.
var changeNum = 0;
await for (var change in watchStream) {
- // Classes generated by mojom Dart compiler do not override == and hashCode
- // but they do override toString to print all properties. So we use toString
- // to assert equality.
+ // Classes generated by mojom Dart compiler do not override == and
+ // hashCode but they do override toString to print all properties. So we
+ // use toString to assert equality.
expect(change.toString(), equals(expectedChanges[changeNum].toString()));
changeNum++;
- // We need to break out of awaiting for watch stream values when we get everything we expected.
- // because watch stream does not end until canceled by design and we don't have canceling mechanism yet.
+ // We need to break out of awaiting for watch stream values when we get
+ // everything we expected. because watch stream does not end until
+ // canceled by design and we don't have canceling mechanism yet.
if (changeNum == expectedChanges.length) {
break;
}
@@ -180,20 +185,15 @@
var resultStream = db.exec(query);
var results = await resultStream.toList();
-
- // Expect first entry to be column headers.
- var headers = results[0].values;
- expect(headers, equals([UTF8.encode('"code"'), UTF8.encode('"cityname"')]));
-
- // Expect the two entries
- var entry1 = results[1].values;
- expect(entry1, equals([UTF8.encode('"aӲ읔"'), UTF8.encode('ᚸӲ읔+קAل')]));
-
- var entry2 = results[2].values;
- expect(entry2, equals([UTF8.encode('"yyz"'), UTF8.encode('Toronto')]));
-
- // Expect no more entries than two data rows and one column header.
+ // Expect column headers plus two rows.
expect(results.length, 3);
+
+ // Check column headers.
+ expect(decodeStrs(results[0].values), equals(['code', 'cityname']));
+
+ // Check rows.
+ expect(decodeStrs(results[1].values), equals(['aӲ읔', 'ᚸӲ읔+קAل']));
+ expect(decodeStrs(results[2].values), equals(['yyz', 'Toronto']));
});
// TODO(nlacasse): Test database.get/setPermissions.
diff --git a/tool/sync_repos.sh b/tool/sync_repos.sh
index f4244db..b504337 100755
--- a/tool/sync_repos.sh
+++ b/tool/sync_repos.sh
@@ -13,6 +13,9 @@
cd $MOJO_DIR/src
git pull
+# If needed, git checkout at a specific commit as follows.
+# git fetch
+# git checkout -f f871196158df873c7ff8498225bd011e0148174e
gclient sync
if [[ "${DESKTOP}" = "1" ]]; then
./mojo/tools/mojob.py gn