pkg/modular_test: follow up cleanup

* resultKind -> resultId (missed this when I renamed dataKind to dataId)
* refactor test/pipeline_common.dart: made the test strategy use generic steps so the concept of
concat/lowercase/replace-join now is encapsulated in pipeline_common, while memory_pipeline_test
and io_pipeline_test know how to create steps that apply an opaque function.

Change-Id: I9e425845931db8722a51c1044f6b74b0c8c26ae4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/100940
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Sigmund Cherem <sigmund@google.com>
diff --git a/pkg/modular_test/lib/src/io_pipeline.dart b/pkg/modular_test/lib/src/io_pipeline.dart
index a92c6c7..acc6144 100644
--- a/pkg/modular_test/lib/src/io_pipeline.dart
+++ b/pkg/modular_test/lib/src/io_pipeline.dart
@@ -53,10 +53,10 @@
   @override
   Future<void> runStep(IOModularStep step, Module module,
       Map<Module, Set<DataId>> visibleData) async {
-    var folder = await Directory.systemTemp
-        .createTemp('modular_test_${step.resultKind}-');
-    _tmpFolders[step.resultKind] ??= (await Directory.systemTemp
-            .createTemp('modular_test_${step.resultKind}_res-'))
+    var folder =
+        await Directory.systemTemp.createTemp('modular_test_${step.resultId}-');
+    _tmpFolders[step.resultId] ??= (await Directory.systemTemp
+            .createTemp('modular_test_${step.resultId}_res-'))
         .uri;
     for (var module in visibleData.keys) {
       for (var dataId in visibleData[module]) {
@@ -78,13 +78,13 @@
         (Module m, DataId id) => Uri.parse("${m.name}.${id.name}"));
 
     var outputFile = File.fromUri(
-        folder.uri.resolve("${module.name}.${step.resultKind.name}"));
+        folder.uri.resolve("${module.name}.${step.resultId.name}"));
     if (!await outputFile.exists()) {
       throw StateError(
           "Step '${step.runtimeType}' didn't produce an output file");
     }
-    await outputFile.copy(_tmpFolders[step.resultKind]
-        .resolve("${module.name}.${step.resultKind.name}")
+    await outputFile.copy(_tmpFolders[step.resultId]
+        .resolve("${module.name}.${step.resultId.name}")
         .toFilePath());
     await folder.delete(recursive: true);
   }
diff --git a/pkg/modular_test/lib/src/memory_pipeline.dart b/pkg/modular_test/lib/src/memory_pipeline.dart
index 4254bbc..4f1740a 100644
--- a/pkg/modular_test/lib/src/memory_pipeline.dart
+++ b/pkg/modular_test/lib/src/memory_pipeline.dart
@@ -57,6 +57,6 @@
     }
     Object result = await step.execute(module, (Uri uri) => inputSources[uri],
         (Module m, DataId id) => inputData[m][id]);
-    (_results[module] ??= {})[step.resultKind] = result;
+    (_results[module] ??= {})[step.resultId] = result;
   }
 }
diff --git a/pkg/modular_test/lib/src/pipeline.dart b/pkg/modular_test/lib/src/pipeline.dart
index 1323459..4bb3e89 100644
--- a/pkg/modular_test/lib/src/pipeline.dart
+++ b/pkg/modular_test/lib/src/pipeline.dart
@@ -25,7 +25,7 @@
   /// This can be data produced on a previous stage of the pipeline
   /// or produced by this same step when it was run on a dependency.
   ///
-  /// If this list includes [resultKind], then the modular-step has to be run on
+  /// If this list includes [resultId], then the modular-step has to be run on
   /// dependencies before it is run on a module. Otherwise, it could be run in
   /// parallel.
   final List<DataId> dependencyDataNeeded;
@@ -33,17 +33,17 @@
   /// Data that this step needs to read about the module itself.
   ///
   /// This is meant to be data produced in earlier stages of the modular
-  /// pipeline. It is an error to include [resultKind] in this list.
+  /// pipeline. It is an error to include [resultId] in this list.
   final List<DataId> moduleDataNeeded;
 
   /// Data that this step produces.
-  final DataId resultKind;
+  final DataId resultId;
 
   ModularStep(
       {this.needsSources: true,
       this.dependencyDataNeeded: const [],
       this.moduleDataNeeded: const [],
-      this.resultKind});
+      this.resultId});
 }
 
 /// An object to uniquely identify modular data produced by a modular step.
@@ -70,7 +70,7 @@
     // or by the same step on a dependency.
     Map<DataId, S> previousKinds = {};
     for (var step in steps) {
-      var resultKind = step.resultKind;
+      var resultKind = step.resultId;
       if (previousKinds.containsKey(resultKind)) {
         _validationError("Cannot produce the same data on two modular steps."
             " '$resultKind' was previously produced by "
@@ -139,7 +139,7 @@
       }
     }
     await runStep(step, module, visibleData);
-    (computedData[module] ??= {}).add(step.resultKind);
+    (computedData[module] ??= {}).add(step.resultId);
   }
 
   Future<void> runStep(
diff --git a/pkg/modular_test/test/io_pipeline_test.dart b/pkg/modular_test/test/io_pipeline_test.dart
index a30207c..bc80082 100644
--- a/pkg/modular_test/test/io_pipeline_test.dart
+++ b/pkg/modular_test/test/io_pipeline_test.dart
@@ -40,22 +40,28 @@
   }
 
   @override
-  IOModularStep createConcatStep({bool requestSources: true}) =>
-      ConcatStep(requestSources);
+  IOModularStep createSourceOnlyStep(
+          {String Function(Map<Uri, String>) action,
+          DataId resultId,
+          bool requestSources: true}) =>
+      SourceOnlyStep(action, resultId, requestSources);
 
   @override
-  IOModularStep createLowerCaseStep({bool requestModuleData: true}) =>
-      LowerCaseStep(requestModuleData);
+  IOModularStep createModuleDataStep(
+          {String Function(String) action,
+          DataId inputId,
+          DataId resultId,
+          bool requestModuleData: true}) =>
+      ModuleDataStep(action, inputId, resultId, requestModuleData);
 
   @override
-  IOModularStep createReplaceAndJoinStep(
-          {bool requestDependenciesData: true}) =>
-      ReplaceAndJoinStep(requestDependenciesData);
-
-  @override
-  IOModularStep createReplaceAndJoinStep2(
-          {bool requestDependenciesData: true}) =>
-      ReplaceAndJoinStep2(requestDependenciesData);
+  IOModularStep createLinkStep(
+          {String Function(String, List<String>) action,
+          DataId inputId,
+          DataId depId,
+          DataId resultId,
+          bool requestDependenciesData: true}) =>
+      LinkStep(action, inputId, depId, resultId, requestDependenciesData);
 
   @override
   String getResult(covariant IOPipeline pipeline, Module m, DataId dataId) {
@@ -74,25 +80,76 @@
   }
 }
 
-class ConcatStep implements IOModularStep {
+class SourceOnlyStep implements IOModularStep {
+  final String Function(Map<Uri, String>) action;
+  final DataId resultId;
   final bool needsSources;
   List<DataId> get dependencyDataNeeded => const [];
   List<DataId> get moduleDataNeeded => const [];
-  DataId get resultKind => const DataId("concat");
 
-  ConcatStep(this.needsSources);
+  SourceOnlyStep(this.action, this.resultId, this.needsSources);
 
   @override
   Future<void> execute(
       Module module, Uri root, ModuleDataToRelativeUri toUri) async {
-    var buffer = new StringBuffer();
+    Map<Uri, String> sources = {};
+
     for (var uri in module.sources) {
       var file = File.fromUri(root.resolveUri(uri));
       String data = await file.exists() ? await file.readAsString() : null;
-      buffer.write("$uri: ${data}\n");
+      sources[uri] = data;
     }
-    await File.fromUri(root.resolveUri(toUri(module, resultKind)))
-        .writeAsString('$buffer');
+    await File.fromUri(root.resolveUri(toUri(module, resultId)))
+        .writeAsString(action(sources));
+  }
+}
+
+class ModuleDataStep implements IOModularStep {
+  final String Function(String) action;
+  bool get needsSources => false;
+  List<DataId> get dependencyDataNeeded => const [];
+  final List<DataId> moduleDataNeeded;
+  final DataId resultId;
+  final DataId inputId;
+
+  ModuleDataStep(this.action, this.inputId, this.resultId, bool requestInput)
+      : moduleDataNeeded = requestInput ? [inputId] : [];
+
+  @override
+  Future<void> execute(
+      Module module, Uri root, ModuleDataToRelativeUri toUri) async {
+    var inputData = await _readHelper(module, root, inputId, toUri);
+    var result =
+        inputData == null ? "data for $module was null" : action(inputData);
+    await File.fromUri(root.resolveUri(toUri(module, resultId)))
+        .writeAsString(result);
+  }
+}
+
+class LinkStep implements IOModularStep {
+  bool get needsSources => false;
+  final List<DataId> dependencyDataNeeded;
+  List<DataId> get moduleDataNeeded => [inputId];
+  final String Function(String, List<String>) action;
+  final DataId inputId;
+  final DataId depId;
+  final DataId resultId;
+
+  LinkStep(this.action, this.inputId, this.depId, this.resultId,
+      bool requestDependencies)
+      : dependencyDataNeeded = requestDependencies ? [depId] : [];
+
+  @override
+  Future<void> execute(
+      Module module, Uri root, ModuleDataToRelativeUri toUri) async {
+    List<String> depsData = [];
+    for (var dependency in module.dependencies) {
+      var depData = await _readHelper(dependency, root, depId, toUri);
+      depsData.add(depData);
+    }
+    var inputData = await _readHelper(module, root, inputId, toUri);
+    await File.fromUri(root.resolveUri(toUri(module, resultId)))
+        .writeAsString(action(inputData, depsData));
   }
 }
 
@@ -104,77 +161,3 @@
   }
   return null;
 }
-
-class LowerCaseStep implements IOModularStep {
-  bool get needsSources => false;
-  List<DataId> get dependencyDataNeeded => const [];
-  final List<DataId> moduleDataNeeded;
-  DataId get resultKind => const DataId("lowercase");
-
-  LowerCaseStep(bool requestConcat)
-      : moduleDataNeeded = requestConcat ? const [DataId("concat")] : const [];
-
-  @override
-  Future<void> execute(
-      Module module, Uri root, ModuleDataToRelativeUri toUri) async {
-    var concatData =
-        await _readHelper(module, root, const DataId("concat"), toUri);
-    if (concatData == null) concatData = "data for $module was null";
-    await File.fromUri(root.resolveUri(toUri(module, resultKind)))
-        .writeAsString(concatData.toLowerCase());
-  }
-}
-
-class ReplaceAndJoinStep implements IOModularStep {
-  bool get needsSources => false;
-  final List<DataId> dependencyDataNeeded;
-  List<DataId> get moduleDataNeeded => const [DataId("lowercase")];
-  DataId get resultKind => const DataId("join");
-
-  ReplaceAndJoinStep(bool requestDependencies)
-      : dependencyDataNeeded =
-            requestDependencies ? const [DataId("join")] : [];
-
-  @override
-  Future<void> execute(
-      Module module, Uri root, ModuleDataToRelativeUri toUri) async {
-    var buffer = new StringBuffer();
-    for (var dependency in module.dependencies) {
-      var depData =
-          await _readHelper(dependency, root, const DataId("join"), toUri);
-      buffer.write("$depData\n");
-    }
-    var moduleData =
-        await _readHelper(module, root, const DataId("lowercase"), toUri);
-    buffer.write(moduleData.replaceAll(".dart:", ""));
-    await File.fromUri(root.resolveUri(toUri(module, resultKind)))
-        .writeAsString('$buffer');
-  }
-}
-
-class ReplaceAndJoinStep2 implements IOModularStep {
-  bool get needsSources => false;
-  final List<DataId> dependencyDataNeeded;
-  List<DataId> get moduleDataNeeded => const [DataId("lowercase")];
-  DataId get resultKind => const DataId("join");
-
-  ReplaceAndJoinStep2(bool requestDependencies)
-      : dependencyDataNeeded =
-            requestDependencies ? const [DataId("lowercase")] : [];
-
-  @override
-  Future<void> execute(
-      Module module, Uri root, ModuleDataToRelativeUri toUri) async {
-    var buffer = new StringBuffer();
-    for (var dependency in module.dependencies) {
-      var depData =
-          await _readHelper(dependency, root, const DataId("lowercase"), toUri);
-      buffer.write("$depData\n");
-    }
-    var moduleData =
-        await _readHelper(module, root, const DataId("lowercase"), toUri);
-    buffer.write(moduleData.replaceAll(".dart:", ""));
-    await File.fromUri(root.resolveUri(toUri(module, resultKind)))
-        .writeAsString('$buffer');
-  }
-}
diff --git a/pkg/modular_test/test/memory_pipeline_test.dart b/pkg/modular_test/test/memory_pipeline_test.dart
index 29c407f..2e35a940 100644
--- a/pkg/modular_test/test/memory_pipeline_test.dart
+++ b/pkg/modular_test/test/memory_pipeline_test.dart
@@ -27,22 +27,28 @@
   }
 
   @override
-  MemoryModularStep createConcatStep({bool requestSources: true}) =>
-      ConcatStep(requestSources);
+  MemoryModularStep createSourceOnlyStep(
+          {String Function(Map<Uri, String>) action,
+          DataId resultId,
+          bool requestSources: true}) =>
+      SourceOnlyStep(action, resultId, requestSources);
 
   @override
-  MemoryModularStep createLowerCaseStep({bool requestModuleData: true}) =>
-      LowerCaseStep(requestModuleData);
+  MemoryModularStep createModuleDataStep(
+          {String Function(String) action,
+          DataId inputId,
+          DataId resultId,
+          bool requestModuleData: true}) =>
+      ModuleDataStep(action, inputId, resultId, requestModuleData);
 
   @override
-  MemoryModularStep createReplaceAndJoinStep(
-          {bool requestDependenciesData: true}) =>
-      ReplaceAndJoinStep(requestDependenciesData);
-
-  @override
-  MemoryModularStep createReplaceAndJoinStep2(
-          {bool requestDependenciesData: true}) =>
-      ReplaceAndJoinStep2(requestDependenciesData);
+  MemoryModularStep createLinkStep(
+          {String Function(String, List<String>) action,
+          DataId inputId,
+          DataId depId,
+          DataId resultId,
+          bool requestDependenciesData: true}) =>
+      LinkStep(action, inputId, depId, resultId, requestDependenciesData);
 
   @override
   String getResult(covariant MemoryPipeline pipeline, Module m, DataId dataId) {
@@ -52,81 +58,63 @@
   FutureOr<void> cleanup(Pipeline<MemoryModularStep> pipeline) => null;
 }
 
-class ConcatStep implements MemoryModularStep {
+class SourceOnlyStep implements MemoryModularStep {
+  final String Function(Map<Uri, String>) action;
+  final DataId resultId;
   final bool needsSources;
   List<DataId> get dependencyDataNeeded => const [];
   List<DataId> get moduleDataNeeded => const [];
-  DataId get resultKind => const DataId("concat");
 
-  ConcatStep(this.needsSources);
+  SourceOnlyStep(this.action, this.resultId, this.needsSources);
 
   Future<Object> execute(Module module, SourceProvider sourceProvider,
       ModuleDataProvider dataProvider) {
-    var buffer = new StringBuffer();
+    Map<Uri, String> sources = {};
     for (var uri in module.sources) {
-      buffer.write("$uri: ${sourceProvider(module.rootUri.resolveUri(uri))}\n");
+      sources[uri] = sourceProvider(module.rootUri.resolveUri(uri));
     }
-    return Future.value('$buffer');
+    return Future.value(action(sources));
   }
 }
 
-class LowerCaseStep implements MemoryModularStep {
+class ModuleDataStep implements MemoryModularStep {
+  final String Function(String) action;
   bool get needsSources => false;
   List<DataId> get dependencyDataNeeded => const [];
   final List<DataId> moduleDataNeeded;
-  DataId get resultKind => const DataId("lowercase");
+  final DataId resultId;
+  final DataId inputId;
 
-  LowerCaseStep(bool requestConcat)
-      : moduleDataNeeded = requestConcat ? const [DataId("concat")] : const [];
+  ModuleDataStep(this.action, this.inputId, this.resultId, bool requestInput)
+      : moduleDataNeeded = requestInput ? [inputId] : [];
 
   Future<Object> execute(Module module, SourceProvider sourceProvider,
       ModuleDataProvider dataProvider) {
-    var concatData = dataProvider(module, const DataId("concat")) as String;
-    if (concatData == null) return Future.value("data for $module was null");
-    return Future.value(concatData.toLowerCase());
+    var inputData = dataProvider(module, inputId) as String;
+    if (inputData == null) return Future.value("data for $module was null");
+    return Future.value(action(inputData));
   }
 }
 
-class ReplaceAndJoinStep implements MemoryModularStep {
+class LinkStep implements MemoryModularStep {
   bool get needsSources => false;
   final List<DataId> dependencyDataNeeded;
-  List<DataId> get moduleDataNeeded => const [DataId("lowercase")];
-  DataId get resultKind => const DataId("join");
+  List<DataId> get moduleDataNeeded => [inputId];
+  final String Function(String, List<String>) action;
+  final DataId inputId;
+  final DataId depId;
+  final DataId resultId;
 
-  ReplaceAndJoinStep(bool requestDependencies)
-      : dependencyDataNeeded =
-            requestDependencies ? const [DataId("join")] : [];
+  LinkStep(this.action, this.inputId, this.depId, this.resultId,
+      bool requestDependencies)
+      : dependencyDataNeeded = requestDependencies ? [depId] : [];
 
   Future<Object> execute(Module module, SourceProvider sourceProvider,
       ModuleDataProvider dataProvider) {
-    var buffer = new StringBuffer();
-    for (var dependency in module.dependencies) {
-      buffer.write("${dataProvider(dependency, const DataId("join"))}\n");
-    }
-    var moduleData = dataProvider(module, const DataId("lowercase")) as String;
-    buffer.write(moduleData.replaceAll(".dart:", ""));
-    return Future.value('$buffer');
-  }
-}
-
-class ReplaceAndJoinStep2 implements MemoryModularStep {
-  bool get needsSources => false;
-  final List<DataId> dependencyDataNeeded;
-  List<DataId> get moduleDataNeeded => const [DataId("lowercase")];
-  DataId get resultKind => const DataId("join");
-
-  ReplaceAndJoinStep2(bool requestDependencies)
-      : dependencyDataNeeded =
-            requestDependencies ? const [DataId("lowercase")] : [];
-
-  Future<Object> execute(Module module, SourceProvider sourceProvider,
-      ModuleDataProvider dataProvider) {
-    var buffer = new StringBuffer();
-    for (var dependency in module.dependencies) {
-      buffer.write("${dataProvider(dependency, const DataId("lowercase"))}\n");
-    }
-    var moduleData = dataProvider(module, const DataId("lowercase")) as String;
-    buffer.write(moduleData.replaceAll(".dart:", ""));
-    return Future.value('$buffer');
+    List<String> depsData = module.dependencies
+        .map((d) => dataProvider(d, depId) as String)
+        .toList();
+    var inputData = dataProvider(module, inputId) as String;
+    return Future.value(action(inputData, depsData));
   }
 }
diff --git a/pkg/modular_test/test/pipeline_common.dart b/pkg/modular_test/test/pipeline_common.dart
index 159e464..a01da5c 100644
--- a/pkg/modular_test/test/pipeline_common.dart
+++ b/pkg/modular_test/test/pipeline_common.dart
@@ -30,24 +30,32 @@
   /// the pipeline created here.
   FutureOr<Pipeline<S>> createPipeline(Map<Uri, String> sources, List<S> steps);
 
-  /// Create a step that concatenates all contents of the sources in a module.
-  S createConcatStep({bool requestSources: true});
+  /// Create a step that applies [action] on all input files of the module, and
+  /// emits a result with the given [id]
+  S createSourceOnlyStep(
+      {String Function(Map<Uri, String>) action,
+      DataId resultId,
+      bool requestSources: true});
 
-  /// Create a step that consumes the concat step result and converts the
-  /// contents to lower-case.
-  S createLowerCaseStep({bool requestModuleData: true});
+  /// Create a step that applies [action] on the module [inputId] data, and
+  /// emits a result with the given [resultId].
+  S createModuleDataStep(
+      {String Function(String) action,
+      DataId inputId,
+      DataId resultId,
+      bool requestModuleData: true});
 
-  /// Create a step that consumes the concat and lower-case steps and does a
-  /// replace and join operation as expected in the tests below.
+  /// Create a step that applies [action] on the module [inputId] data and the
+  /// the [depId] data of dependencies and finally emits a result with the given
+  /// [resultId].
   ///
-  /// This step consumes it's own data from dependencies.
-  S createReplaceAndJoinStep({bool requestDependenciesData: true});
-
-  /// Create a step that consumes the concat and lower-case steps and does a
-  /// replace and join operation as expected in the tests below.
-  ///
-  /// This step consumes the lower-case step data from dependencies.
-  S createReplaceAndJoinStep2({bool requestDependenciesData: true});
+  /// [depId] may be the same as [resultId] or [inputId].
+  S createLinkStep(
+      {String Function(String, List<String>) action,
+      DataId inputId,
+      DataId depId,
+      DataId resultId,
+      bool requestDependenciesData: true});
 
   /// Return the result data produced by a modular step.
   String getResult(Pipeline<S> pipeline, Module m, DataId dataId);
@@ -75,118 +83,172 @@
   var multipleModulesInput = ModularTest([m1, m2], m2);
 
   test('can read source data if requested', () async {
-    var concatStep = testStrategy.createConcatStep();
+    var concatStep =
+        testStrategy.createSourceOnlyStep(action: _concat, resultId: _concatId);
     var pipeline = await testStrategy.createPipeline(sources, <S>[concatStep]);
     await pipeline.run(singleModuleInput);
-    expect(testStrategy.getResult(pipeline, m1, concatStep.resultKind),
+    expect(testStrategy.getResult(pipeline, m1, concatStep.resultId),
         "a1.dart: A1\na2.dart: A2\n");
     await testStrategy.cleanup(pipeline);
   });
 
   test('cannot read source data if not requested', () async {
-    var concatStep = testStrategy.createConcatStep(requestSources: false);
+    var concatStep = testStrategy.createSourceOnlyStep(
+        action: _concat, resultId: _concatId, requestSources: false);
     var pipeline = await testStrategy.createPipeline(sources, <S>[concatStep]);
     await pipeline.run(singleModuleInput);
-    expect(testStrategy.getResult(pipeline, m1, concatStep.resultKind),
+    expect(testStrategy.getResult(pipeline, m1, concatStep.resultId),
         "a1.dart: null\na2.dart: null\n");
     await testStrategy.cleanup(pipeline);
   });
 
   test('step is applied to all modules', () async {
-    var concatStep = testStrategy.createConcatStep();
+    var concatStep =
+        testStrategy.createSourceOnlyStep(action: _concat, resultId: _concatId);
     var pipeline = await testStrategy.createPipeline(sources, <S>[concatStep]);
     await pipeline.run(multipleModulesInput);
-    expect(testStrategy.getResult(pipeline, m1, concatStep.resultKind),
+    expect(testStrategy.getResult(pipeline, m1, concatStep.resultId),
         "a1.dart: A1\na2.dart: A2\n");
-    expect(testStrategy.getResult(pipeline, m2, concatStep.resultKind),
+    expect(testStrategy.getResult(pipeline, m2, concatStep.resultId),
         "b1.dart: B1\nb2.dart: B2\n");
     await testStrategy.cleanup(pipeline);
   });
 
   test('can read previous step results if requested', () async {
-    var concatStep = testStrategy.createConcatStep();
-    var lowercaseStep = testStrategy.createLowerCaseStep();
+    var concatStep =
+        testStrategy.createSourceOnlyStep(action: _concat, resultId: _concatId);
+    var lowercaseStep = testStrategy.createModuleDataStep(
+        action: _lowercase, inputId: _concatId, resultId: _lowercaseId);
     var pipeline = await testStrategy
         .createPipeline(sources, <S>[concatStep, lowercaseStep]);
     await pipeline.run(multipleModulesInput);
-    expect(testStrategy.getResult(pipeline, m1, lowercaseStep.resultKind),
+    expect(testStrategy.getResult(pipeline, m1, lowercaseStep.resultId),
         "a1.dart: a1\na2.dart: a2\n");
-    expect(testStrategy.getResult(pipeline, m2, lowercaseStep.resultKind),
+    expect(testStrategy.getResult(pipeline, m2, lowercaseStep.resultId),
         "b1.dart: b1\nb2.dart: b2\n");
     await testStrategy.cleanup(pipeline);
   });
 
   test('cannot read previous step results if not requested', () async {
-    var concatStep = testStrategy.createConcatStep();
-    var lowercaseStep =
-        testStrategy.createLowerCaseStep(requestModuleData: false);
+    var concatStep =
+        testStrategy.createSourceOnlyStep(action: _concat, resultId: _concatId);
+    var lowercaseStep = testStrategy.createModuleDataStep(
+        action: _lowercase,
+        inputId: _concatId,
+        resultId: _lowercaseId,
+        requestModuleData: false);
     var pipeline = await testStrategy
         .createPipeline(sources, <S>[concatStep, lowercaseStep]);
     await pipeline.run(multipleModulesInput);
-    expect(testStrategy.getResult(pipeline, m1, lowercaseStep.resultKind),
+    expect(testStrategy.getResult(pipeline, m1, lowercaseStep.resultId),
         "data for [module a] was null");
-    expect(testStrategy.getResult(pipeline, m2, lowercaseStep.resultKind),
+    expect(testStrategy.getResult(pipeline, m2, lowercaseStep.resultId),
         "data for [module b] was null");
     await testStrategy.cleanup(pipeline);
   });
 
   test('can read same-step results of dependencies if requested', () async {
-    var concatStep = testStrategy.createConcatStep();
-    var lowercaseStep = testStrategy.createLowerCaseStep();
-    var replaceJoinStep = testStrategy.createReplaceAndJoinStep();
+    var concatStep =
+        testStrategy.createSourceOnlyStep(action: _concat, resultId: _concatId);
+    var lowercaseStep = testStrategy.createModuleDataStep(
+        action: _lowercase, inputId: _concatId, resultId: _lowercaseId);
+    var replaceJoinStep = testStrategy.createLinkStep(
+        action: _replaceAndJoin,
+        inputId: _lowercaseId,
+        depId: _joinId,
+        resultId: _joinId);
     var pipeline = await testStrategy.createPipeline(
         sources, <S>[concatStep, lowercaseStep, replaceJoinStep]);
     await pipeline.run(multipleModulesInput);
-    expect(testStrategy.getResult(pipeline, m1, replaceJoinStep.resultKind),
+    expect(testStrategy.getResult(pipeline, m1, replaceJoinStep.resultId),
         "a1 a1\na2 a2\n");
-    expect(testStrategy.getResult(pipeline, m2, replaceJoinStep.resultKind),
+    expect(testStrategy.getResult(pipeline, m2, replaceJoinStep.resultId),
         "a1 a1\na2 a2\n\nb1 b1\nb2 b2\n");
     await testStrategy.cleanup(pipeline);
   });
 
   test('cannot read same-step results of dependencies if not requested',
       () async {
-    var concatStep = testStrategy.createConcatStep();
-    var lowercaseStep = testStrategy.createLowerCaseStep();
-    var replaceJoinStep =
-        testStrategy.createReplaceAndJoinStep(requestDependenciesData: false);
+    var concatStep =
+        testStrategy.createSourceOnlyStep(action: _concat, resultId: _concatId);
+    var lowercaseStep = testStrategy.createModuleDataStep(
+        action: _lowercase, inputId: _concatId, resultId: _lowercaseId);
+    var replaceJoinStep = testStrategy.createLinkStep(
+        action: _replaceAndJoin,
+        inputId: _lowercaseId,
+        depId: _joinId,
+        resultId: _joinId,
+        requestDependenciesData: false);
     var pipeline = await testStrategy.createPipeline(
         sources, <S>[concatStep, lowercaseStep, replaceJoinStep]);
     await pipeline.run(multipleModulesInput);
-    expect(testStrategy.getResult(pipeline, m1, replaceJoinStep.resultKind),
+    expect(testStrategy.getResult(pipeline, m1, replaceJoinStep.resultId),
         "a1 a1\na2 a2\n");
-    expect(testStrategy.getResult(pipeline, m2, replaceJoinStep.resultKind),
+    expect(testStrategy.getResult(pipeline, m2, replaceJoinStep.resultId),
         "null\nb1 b1\nb2 b2\n");
     await testStrategy.cleanup(pipeline);
   });
 
   test('can read prior step results of dependencies if requested', () async {
-    var concatStep = testStrategy.createConcatStep();
-    var lowercaseStep = testStrategy.createLowerCaseStep();
-    var replaceJoinStep = testStrategy.createReplaceAndJoinStep2();
+    var concatStep =
+        testStrategy.createSourceOnlyStep(action: _concat, resultId: _concatId);
+    var lowercaseStep = testStrategy.createModuleDataStep(
+        action: _lowercase, inputId: _concatId, resultId: _lowercaseId);
+    var replaceJoinStep = testStrategy.createLinkStep(
+        action: _replaceAndJoin,
+        inputId: _lowercaseId,
+        depId: _lowercaseId,
+        resultId: _joinId);
     var pipeline = await testStrategy.createPipeline(
         sources, <S>[concatStep, lowercaseStep, replaceJoinStep]);
     await pipeline.run(multipleModulesInput);
-    expect(testStrategy.getResult(pipeline, m1, replaceJoinStep.resultKind),
+    expect(testStrategy.getResult(pipeline, m1, replaceJoinStep.resultId),
         "a1 a1\na2 a2\n");
-    expect(testStrategy.getResult(pipeline, m2, replaceJoinStep.resultKind),
+    expect(testStrategy.getResult(pipeline, m2, replaceJoinStep.resultId),
         "a1.dart: a1\na2.dart: a2\n\nb1 b1\nb2 b2\n");
     await testStrategy.cleanup(pipeline);
   });
 
   test('cannot read prior step results of dependencies if not requested',
       () async {
-    var concatStep = testStrategy.createConcatStep();
-    var lowercaseStep = testStrategy.createLowerCaseStep();
-    var replaceJoinStep =
-        testStrategy.createReplaceAndJoinStep2(requestDependenciesData: false);
+    var concatStep =
+        testStrategy.createSourceOnlyStep(action: _concat, resultId: _concatId);
+    var lowercaseStep = testStrategy.createModuleDataStep(
+        action: _lowercase, inputId: _concatId, resultId: _lowercaseId);
+    var replaceJoinStep = testStrategy.createLinkStep(
+        action: _replaceAndJoin,
+        inputId: _lowercaseId,
+        depId: _lowercaseId,
+        resultId: _joinId,
+        requestDependenciesData: false);
     var pipeline = await testStrategy.createPipeline(
         sources, <S>[concatStep, lowercaseStep, replaceJoinStep]);
     await pipeline.run(multipleModulesInput);
-    expect(testStrategy.getResult(pipeline, m1, replaceJoinStep.resultKind),
+    expect(testStrategy.getResult(pipeline, m1, replaceJoinStep.resultId),
         "a1 a1\na2 a2\n");
-    expect(testStrategy.getResult(pipeline, m2, replaceJoinStep.resultKind),
+    expect(testStrategy.getResult(pipeline, m2, replaceJoinStep.resultId),
         "null\nb1 b1\nb2 b2\n");
     await testStrategy.cleanup(pipeline);
   });
 }
+
+DataId _concatId = const DataId("concat");
+DataId _lowercaseId = const DataId("lowercase");
+DataId _joinId = const DataId("join");
+
+String _concat(Map<Uri, String> sources) {
+  var buffer = new StringBuffer();
+  sources.forEach((uri, contents) {
+    buffer.write("$uri: $contents\n");
+  });
+  return '$buffer';
+}
+
+String _lowercase(String contents) => contents.toLowerCase();
+
+String _replaceAndJoin(String moduleData, List<String> depContents) {
+  var buffer = new StringBuffer();
+  depContents.forEach(buffer.writeln);
+  buffer.write(moduleData.replaceAll(".dart:", ""));
+  return '$buffer';
+}
diff --git a/pkg/modular_test/test/validate_test.dart b/pkg/modular_test/test/validate_test.dart
index 6858544..152271a 100644
--- a/pkg/modular_test/test/validate_test.dart
+++ b/pkg/modular_test/test/validate_test.dart
@@ -18,12 +18,12 @@
     var id3 = DataId("data_c");
     validateSteps([
       ModularStep(
-          needsSources: true, dependencyDataNeeded: [id1], resultKind: id1),
-      ModularStep(moduleDataNeeded: [id1], resultKind: id2),
+          needsSources: true, dependencyDataNeeded: [id1], resultId: id1),
+      ModularStep(moduleDataNeeded: [id1], resultId: id2),
       ModularStep(
           moduleDataNeeded: [id2],
           dependencyDataNeeded: [id1, id3],
-          resultKind: id3),
+          resultId: id3),
     ]);
   });
 
@@ -31,7 +31,7 @@
     var id1 = DataId("data_a");
     expect(
         () => validateSteps([
-              ModularStep(moduleDataNeeded: [id1], resultKind: id1),
+              ModularStep(moduleDataNeeded: [id1], resultId: id1),
             ]),
         throwsA(TypeMatcher<InvalidPipelineError>()));
   });
@@ -40,15 +40,14 @@
     var id1 = DataId("data_a");
     var id2 = DataId("data_b");
     validateSteps([
-      ModularStep(
-          resultKind: id1), // id1 must be produced before it is consumed.
-      ModularStep(dependencyDataNeeded: [id1], resultKind: id2),
+      ModularStep(resultId: id1), // id1 must be produced before it is consumed.
+      ModularStep(dependencyDataNeeded: [id1], resultId: id2),
     ]);
 
     expect(
         () => validateSteps([
-              ModularStep(dependencyDataNeeded: [id1], resultKind: id2),
-              ModularStep(resultKind: id1),
+              ModularStep(dependencyDataNeeded: [id1], resultId: id2),
+              ModularStep(resultId: id1),
             ]),
         throwsA(TypeMatcher<InvalidPipelineError>()));
   });
@@ -57,8 +56,8 @@
     var id1 = DataId("data_a");
     expect(
         () => validateSteps([
-              ModularStep(resultKind: id1),
-              ModularStep(resultKind: id1),
+              ModularStep(resultId: id1),
+              ModularStep(resultId: id1),
             ]),
         throwsA(TypeMatcher<InvalidPipelineError>()));
   });