[fuchsia_modular_test] add methods to test harness spec builder.

TEST=
- fx run-test fuchsia_modular_test_package_integration_tests

Change-Id: I1a350166d3cb44a1a616457320fa54ce364e85cc
diff --git a/public/dart/fuchsia_modular_test/lib/src/test_harness_spec_builder.dart b/public/dart/fuchsia_modular_test/lib/src/test_harness_spec_builder.dart
index 9cc02aa..8a1e434 100644
--- a/public/dart/fuchsia_modular_test/lib/src/test_harness_spec_builder.dart
+++ b/public/dart/fuchsia_modular_test/lib/src/test_harness_spec_builder.dart
@@ -19,6 +19,8 @@
 /// ```
 class TestHarnessSpecBuilder {
   final _componentsToIntercept = <InterceptSpec>[];
+  final _envServicesToInherit = <String>['fuchsia.logger.LogSink'];
+  final _envServicesToInject = <InjectedService>[];
 
   /// Registers the component url to be intercepted.
   ///
@@ -31,6 +33,10 @@
   void addComponentToIntercept(String componentUrl, {List<String> services}) {
     ArgumentError.checkNotNull(componentUrl, 'componentUrl');
 
+    if (componentUrl.isEmpty) {
+      throw ArgumentError('componentUrl must not be an empty string');
+    }
+
     // verify that we have unique component urls
     for (final spec in _componentsToIntercept) {
       if (spec.componentUrl == componentUrl) {
@@ -48,9 +54,59 @@
         extraCmxContents: _createCmxSandBox(extraContents)));
   }
 
+  /// Adds [service] to the list of services which should be inherited by the
+  /// parent environment.
+  ///
+  /// The following environemnt services will be added by default. If you wish
+  /// to not inherit these services you can remove them after building.
+  ///  - fuchsia.logger.LogSink
+  void addEnvironmentServiceToInherit(String service) {
+    ArgumentError.checkNotNull(service, 'service');
+
+    if (service.isEmpty) {
+      throw ArgumentError('service must not be an empty string');
+    }
+
+    if (_envServicesToInherit.contains(service)) {
+      throw Exception(
+          'Attempting to add [$service] twice. Services must be unique');
+    }
+
+    _envServicesToInherit.add(service);
+  }
+
+  /// Adds the service with the given [name] and [componentUrl] to the list
+  /// of services to inject.
+  ///
+  /// The [TestHarness] will automatically create these services and add
+  /// expose them to the environment under test.
+  void addEnvironmentServiceToInject(String name, String componentUrl) {
+    ArgumentError.checkNotNull(name, 'name');
+    ArgumentError.checkNotNull(componentUrl, 'componentUrl');
+
+    if (name.isEmpty || componentUrl.isEmpty) {
+      throw ArgumentError('name and componentUrl must not be an empty string');
+    }
+
+    final service = InjectedService(name: name, url: componentUrl);
+    if (_envServicesToInject.contains(service)) {
+      throw Exception(
+          'Attempting to add [$service] twice. Services must be unique');
+    }
+
+    _envServicesToInject.add(service);
+  }
+
   /// Returns the [TestHarnessSpec] object which can be passed to the [TestHarnessProxy]
+  ///
+  /// After building the [TestHarnessSpec] you may update its values directly if the builder
+  /// does not offer a method which satisfies your specific needs.
   TestHarnessSpec build() {
-    return TestHarnessSpec(componentsToIntercept: _componentsToIntercept);
+    return TestHarnessSpec(
+      componentsToIntercept: _componentsToIntercept,
+      envServicesToInherit: _envServicesToInherit,
+      envServicesToInject: _envServicesToInject,
+    );
   }
 
   fuchsia_mem.Buffer _createCmxSandBox(Map<String, dynamic> contents) {
diff --git a/public/dart/fuchsia_modular_test/test/test_harness_spec_builder_test.dart b/public/dart/fuchsia_modular_test/test/test_harness_spec_builder_test.dart
index b7b2291..62cd99a 100644
--- a/public/dart/fuchsia_modular_test/test/test_harness_spec_builder_test.dart
+++ b/public/dart/fuchsia_modular_test/test/test_harness_spec_builder_test.dart
@@ -5,10 +5,11 @@
 import 'dart:convert';
 
 // ignore_for_file: implementation_imports
-import 'package:test/test.dart';
+import 'package:fidl_fuchsia_modular_testing/fidl_async.dart';
 import 'package:fuchsia_logger/logger.dart';
-import 'package:fuchsia_modular_test/src/test_harness_spec_builder.dart';
 import 'package:fuchsia_modular_test/src/test_harness_fixtures.dart';
+import 'package:fuchsia_modular_test/src/test_harness_spec_builder.dart';
+import 'package:test/test.dart';
 import 'package:zircon/zircon.dart';
 
 void main() {
@@ -21,51 +22,144 @@
       builder = TestHarnessSpecBuilder();
     });
 
-    test('addComponentToIntercept fails on null component url', () {
-      expect(() => builder.addComponentToIntercept(null), throwsArgumentError);
+    group('adding components', () {
+      test('addComponentToIntercept fails on null component url', () {
+        expect(
+            () => builder.addComponentToIntercept(null), throwsArgumentError);
+      });
+
+      test('addComponentToIntercept fails on empty component url', () {
+        expect(() => builder.addComponentToIntercept(''), throwsArgumentError);
+      });
+
+      test('addComponentToIntercept adds to the spec', () {
+        final url = generateComponentUrl();
+        builder.addComponentToIntercept(url);
+
+        final spec = builder.build();
+        expect(spec.componentsToIntercept,
+            contains(predicate((ispec) => ispec.componentUrl == url)));
+      });
+
+      test('able to add many components', () {
+        builder
+          ..addComponentToIntercept(generateComponentUrl())
+          ..addComponentToIntercept(generateComponentUrl());
+
+        expect(builder.build().componentsToIntercept.length, 2);
+      });
+
+      test('componentUrl can only be added once', () {
+        final url = generateComponentUrl();
+        builder.addComponentToIntercept(url);
+        expect(() => builder.addComponentToIntercept(url), throwsException);
+      });
+
+      test('can add services to the components cmx file', () {
+        const service = 'fuchsia.sys.Launcher';
+        builder.addComponentToIntercept(generateComponentUrl(),
+            services: [service]);
+
+        final spec = builder.build();
+        final interceptSpec = spec.componentsToIntercept.first;
+        final contents = interceptSpec.extraCmxContents;
+        final vmo = SizedVmo(contents.vmo.handle, contents.size);
+        final bytes = vmo.read(contents.size).bytesAsUint8List();
+
+        final expectedSandbox = {
+          'sandbox': {
+            'services': [service]
+          }
+        };
+
+        expect(expectedSandbox, json.decode(utf8.decode(bytes)));
+      });
+
+      test('can add additional components_to_intercept after build', () {
+        expect(() => builder.build().componentsToIntercept.add(InterceptSpec()),
+            returnsNormally);
+      });
     });
 
-    test('addComponentToIntercept adds to the spec', () {
-      final url = generateComponentUrl();
-      builder.addComponentToIntercept(url);
+    group('adding environment services to inherit', () {
+      test('can add additional env_services_to_inherit after build', () {
+        expect(() => builder.build().envServicesToInherit.add('foo'),
+            returnsNormally);
+      });
 
-      final spec = builder.build();
-      expect(spec.componentsToIntercept,
-          contains(predicate((ispec) => ispec.componentUrl == url)));
+      test('automatically inherits logger service', () {
+        expect(builder.build().envServicesToInherit,
+            contains('fuchsia.logger.LogSink'));
+      });
+
+      test('throws argument error for null service', () {
+        expect(() => builder.addEnvironmentServiceToInherit(null),
+            throwsArgumentError);
+      });
+
+      test('throws argument error for empty service', () {
+        expect(() => builder.addEnvironmentServiceToInherit(''),
+            throwsArgumentError);
+      });
+
+      test('throws exception for duplicate service', () {
+        builder.addEnvironmentServiceToInherit('service');
+        expect(() => builder.addEnvironmentServiceToInherit('service'),
+            throwsException);
+      });
+
+      test('addEnvServiceToInherit adds the service to the inherited services',
+          () {
+        builder.addEnvironmentServiceToInherit('fuchsia.my.Service');
+        expect(builder.build().envServicesToInherit,
+            contains('fuchsia.my.Service'));
+      });
     });
 
-    test('able to add many components', () {
-      builder
-        ..addComponentToIntercept(generateComponentUrl())
-        ..addComponentToIntercept(generateComponentUrl());
+    group('adding environment services to inject', () {
+      test('can add additional env_services_to_inject after build', () {
+        expect(
+            () => builder
+                .build()
+                .envServicesToInject
+                .add(InjectedService(name: 'foo', url: generateComponentUrl())),
+            returnsNormally);
+      });
 
-      expect(builder.build().componentsToIntercept.length, 2);
-    });
+      test('throws argument error for null input values', () {
+        expect(
+            () => builder.addEnvironmentServiceToInject(
+                null, generateComponentUrl()),
+            throwsArgumentError);
+        expect(() => builder.addEnvironmentServiceToInject('name', null),
+            throwsArgumentError);
+      });
 
-    test('componentUrl can only be added once', () {
-      final url = generateComponentUrl();
-      builder.addComponentToIntercept(url);
-      expect(() => builder.addComponentToIntercept(url), throwsException);
-    });
+      test('throws argument error for empty name and component url', () {
+        expect(
+            () => builder.addEnvironmentServiceToInject(
+                '', generateComponentUrl()),
+            throwsArgumentError);
+        expect(() => builder.addEnvironmentServiceToInject('name', ''),
+            throwsArgumentError);
+      });
 
-    test('can add services to the components cmx file', () {
-      const service = 'fuchsia.sys.Launcher';
-      builder
-          .addComponentToIntercept(generateComponentUrl(), services: [service]);
+      test('throws exception for duplicate service', () {
+        const name = 'myService';
+        final url = generateComponentUrl();
 
-      final spec = builder.build();
-      final interceptSpec = spec.componentsToIntercept.first;
-      final contents = interceptSpec.extraCmxContents;
-      final vmo = SizedVmo(contents.vmo.handle, contents.size);
-      final bytes = vmo.read(contents.size).bytesAsUint8List();
+        builder.addEnvironmentServiceToInject(name, url);
+        expect(() => builder.addEnvironmentServiceToInject(name, url),
+            throwsException);
+      });
 
-      final expectedSandbox = {
-        'sandbox': {
-          'services': [service]
-        }
-      };
-
-      expect(expectedSandbox, json.decode(utf8.decode(bytes)));
+      test('addEnvServiceToInject adds the service to the injected services',
+          () {
+        final url = generateComponentUrl();
+        builder.addEnvironmentServiceToInject('fuchsia.my.Service', url);
+        expect(builder.build().envServicesToInject,
+            contains(InjectedService(name: 'fuchsia.my.Service', url: url)));
+      });
     });
   });
 }