[sledge] Refactor integration test

This CL adds `LedgerTestInstanceProvider` which is used to return
multiple connections to the same Ledger, and the `newSledgeForTesting`
helper.

This CL also tests that connecting to two different Sledge backed by the same
Ledger works.

TEST=fx run-test sledge_integration_tests
Change-Id: Ic05ccb85c23fc7ced874565904ff321231b03f9f
diff --git a/public/dart/sledge/README.md b/public/dart/sledge/README.md
index 003121c..b637a01 100644
--- a/public/dart/sledge/README.md
+++ b/public/dart/sledge/README.md
@@ -124,7 +124,7 @@
 ```
 fx set x64 --product ermine --monolith topaz/packages/all --monolith topaz/packages/tests/all
 fx build
-fx shell "run fuchsia-pkg://fuchsia.com/sledge_integration_tests#meta/sledge_integration_tests.cmx"
+fx run-test sledge_integration_tests
 ```
 
 The results of the tests are visible in the logs:
diff --git a/public/dart/sledge/lib/src/sledge.dart b/public/dart/sledge/lib/src/sledge.dart
index 0d188af..4c76f51 100644
--- a/public/dart/sledge/lib/src/sledge.dart
+++ b/public/dart/sledge/lib/src/sledge.dart
@@ -126,11 +126,9 @@
   }
 
   /// Convenience constructor for integration tests.
-  factory Sledge.fromLedgerHandle(
-      fidl.InterfaceHandle<ledger.Ledger> ledgerHandle,
-      [SledgePageId pageId]) {
-    return new Sledge._(ledgerHandle, pageId);
-  }
+  Sledge.fromLedgerHandle(fidl.InterfaceHandle<ledger.Ledger> ledgerHandle,
+      [SledgePageId pageId])
+      : this._(ledgerHandle, pageId);
 
   /// Closes connection to ledger.
   void close() {
diff --git a/public/dart/sledge/test/integration_tests/helpers.dart b/public/dart/sledge/test/integration_tests/helpers.dart
new file mode 100644
index 0000000..507c7dc
--- /dev/null
+++ b/public/dart/sledge/test/integration_tests/helpers.dart
@@ -0,0 +1,65 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:fidl/fidl.dart' as fidl;
+import 'package:fidl_fuchsia_ledger/fidl.dart' as ledger;
+import 'package:fidl_fuchsia_sys/fidl_async.dart'
+    show LaunchInfo, ComponentControllerProxy;
+import 'package:lib.app.dart/app_async.dart' show Services, StartupContext;
+import 'package:sledge/sledge.dart';
+
+/// References a service that provides channels to a unique Ledger instance.
+class LedgerTestInstanceProvider {
+  /// Default constructor.
+  LedgerTestInstanceProvider(this.services, this._controller);
+
+  /// The service providing channels to Ledger.
+  Services services;
+  // Prevents the controller from being GCed, which would result in the service
+  // being closed.
+  // ignore: unused_field
+  ComponentControllerProxy _controller;
+}
+
+/// Returns a new LedgerTestInstanceProvider that creates connections to a
+/// in-memory Ledger.
+Future<LedgerTestInstanceProvider> newLedgerTestInstanceProvider() async {
+  String server =
+      'fuchsia-pkg://fuchsia.com/ledger_test_instance_provider#meta/ledger_test_instance_provider.cmx';
+  final Services services = new Services();
+  final LaunchInfo launchInfo =
+      new LaunchInfo(url: server, directoryRequest: services.request());
+  final context = new StartupContext.fromStartupInfo();
+  final ComponentControllerProxy controller = new ComponentControllerProxy();
+  await context.launcher.createComponent(launchInfo, controller.ctrl.request());
+  return new LedgerTestInstanceProvider(services, controller);
+}
+
+/// Sledge subclass that makes sure the ComponentControllerProxy does not get GCed.
+class _SledgeForTesting extends Sledge {
+  _SledgeForTesting(fidl.InterfaceHandle<ledger.Ledger> ledgerHandle,
+      SledgePageId pageId, this._controller)
+      : super.fromLedgerHandle(ledgerHandle, pageId);
+  // Prevents the connection to Ledger from being closed.
+  // ignore: unused_field
+  ComponentControllerProxy _controller;
+}
+
+/// Creates a new test Sledge instance backed by an in-memory Ledger provided
+/// by [ledgerInstanceProvider].
+/// If no [ledgerInstanceProvider] is provided, a new provider is created.
+Future<Sledge> newSledgeForTesting(
+    {SledgePageId pageId,
+    LedgerTestInstanceProvider ledgerInstanceProvider}) async {
+  pageId ??= new SledgePageId('');
+  ledgerInstanceProvider ??= await newLedgerTestInstanceProvider();
+  fidl.InterfaceHandle<ledger.Ledger> ledgerHandle =
+      await ledgerInstanceProvider.services
+          .connectToServiceByName<ledger.Ledger>(ledger.Ledger.$serviceName);
+  final sledge = new _SledgeForTesting(
+      ledgerHandle, pageId, ledgerInstanceProvider._controller);
+  return sledge;
+}
diff --git a/public/dart/sledge/test/integration_tests/sledge_integration_test.dart b/public/dart/sledge/test/integration_tests/sledge_integration_test.dart
index 87e97b0..955cfd5 100644
--- a/public/dart/sledge/test/integration_tests/sledge_integration_test.dart
+++ b/public/dart/sledge/test/integration_tests/sledge_integration_test.dart
@@ -2,57 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:async';
-
-import 'package:fidl/fidl.dart';
-import 'package:fidl_fuchsia_ledger/fidl.dart' as ledger;
-import 'package:fidl_fuchsia_sys/fidl_async.dart' show LaunchInfo, ComponentControllerProxy;
-import 'package:lib.app.dart/app_async.dart' show Services, StartupContext;
 import 'package:sledge/sledge.dart';
 import 'package:test/test.dart';
 
-class _LedgerTestInstance {
-  _LedgerTestInstance(this._componentControllerProxy);
-  InterfaceHandle<ledger.Ledger> ledgerHandle;
-  // ignore: unused_field
-  ComponentControllerProxy _componentControllerProxy;
-}
-
-/// Returns a new in-memory Ledger handle.
-Future<_LedgerTestInstance> getInMemoryLedgerTestInstance() async {
-  String server =
-      'fuchsia-pkg://fuchsia.com/ledger_test_instance_provider#meta/ledger_test_instance_provider.cmx';
-  final Services services = new Services();
-  final LaunchInfo launchInfo =
-      new LaunchInfo(url: server, directoryRequest: services.request());
-  final context = new StartupContext.fromStartupInfo();
-  final ComponentControllerProxy controller = new ComponentControllerProxy();
-  await context.launcher.createComponent(launchInfo, controller.ctrl.request());
-
-  final ledgerTestInstance = new _LedgerTestInstance(controller)
-  ..ledgerHandle = await services.connectToServiceByName<ledger.Ledger>(ledger.Ledger.$serviceName);
-  return ledgerTestInstance;
-}
-
-Completer<_LedgerTestInstance> _ledgerTestInstance;
-
-/// Creates a new test Sledge instance backed by an in-memory Ledger.
-Future<Sledge> getTestSledge() async {
-  if (_ledgerTestInstance == null) {
-    // If the completer has never been created, create it and start the process
-    // of obtaining a LedgerTestInstance.
-    _ledgerTestInstance = new Completer();
-    final futureLedgerTestInstance = getInMemoryLedgerTestInstance();
-    _ledgerTestInstance.complete(await futureLedgerTestInstance);
-  }
-  final ledgerTestInstance = await _ledgerTestInstance.future;
-  final sledge = new Sledge.fromLedgerHandle(ledgerTestInstance.ledgerHandle);
-  return sledge;
-}
+import 'helpers.dart';
 
 void main() {
   test('Save a document', () async {
-    final sledge = await getTestSledge();
+    final pageId = new SledgePageId('some page');
+    final ledgerInstanceProvider = await newLedgerTestInstanceProvider();
+    final activeSledge = await newSledgeForTesting(
+        ledgerInstanceProvider: ledgerInstanceProvider, pageId: pageId);
     Map<String, BaseType> schemaDescription = <String, BaseType>{
       'someInteger': new Integer()
     };
@@ -60,16 +20,58 @@
     DocumentId id = new DocumentId(schema);
 
     // Store a document in Sledge.
-    await sledge.runInTransaction(() async {
+    await activeSledge.runInTransaction(() async {
       final List<Document> documents =
-          await sledge.getDocuments(new Query(schema));
+          await activeSledge.getDocuments(new Query(schema));
       assert(documents.isEmpty);
-      assert(await sledge.documentExists(id) == false);
+      assert(await activeSledge.documentExists(id) == false);
 
-      Document doc = await sledge.getDocument(id);
+      Document doc = await activeSledge.getDocument(id);
       assert(doc['someInteger'].value == 0);
+
       doc['someInteger'].value = 42;
       assert(doc['someInteger'].value == 42);
     });
+
+    // Verify that the document is present when reading using a separate
+    // Sledge instance.
+    final passiveSledge = await newSledgeForTesting(
+        ledgerInstanceProvider: ledgerInstanceProvider, pageId: pageId);
+    await passiveSledge.runInTransaction(() async {
+      final List<Document> documents =
+          await passiveSledge.getDocuments(new Query(schema));
+      assert(documents.isNotEmpty);
+      assert(await passiveSledge.documentExists(id) == true);
+      Document doc = await passiveSledge.getDocument(id);
+      assert(doc['someInteger'].value == 42);
+    });
+
+    // Verify that the document is not present in a Sledge instance
+    // created with a different page.
+    final unrelatedPageId = new SledgePageId('some other page');
+    final unrelatedSledge = await newSledgeForTesting(
+        ledgerInstanceProvider: ledgerInstanceProvider,
+        pageId: unrelatedPageId);
+    await unrelatedSledge.runInTransaction(() async {
+      final List<Document> documents =
+          await unrelatedSledge.getDocuments(new Query(schema));
+      assert(documents.isEmpty);
+    });
+
+    // Change a document in [activeSledge] and wait until [passiveSledge] gets
+    // the updates.
+    // This tests that Ledger changes are properly propagated by Sledge.
+    await activeSledge.runInTransaction(() async {
+      Document doc = await activeSledge.getDocument(id);
+      doc['someInteger'].value = 43;
+    });
+
+    int someInteger;
+    while (someInteger != 43) {
+      await passiveSledge.runInTransaction(() async {
+        Document doc = await passiveSledge.getDocument(id);
+        someInteger = doc['someInteger'].value;
+      });
+    }
   });
 }