Add dartfix integration test

... and update changelog and tweak SDK constraints

Change-Id: Ifb3e733b84c3aecad0ff8fe805f77ef497248f3b
Reviewed-on: https://dart-review.googlesource.com/c/86600
Commit-Queue: Dan Rubel <danrubel@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/dartfix/CHANGELOG.md b/pkg/dartfix/CHANGELOG.md
index 07a68b9..33d9bff 100644
--- a/pkg/dartfix/CHANGELOG.md
+++ b/pkg/dartfix/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 0.1.2
+ * update SDK constraints
+ * add example.dart showing what can be "fixed"
+
 # 0.1.1
  * Remove reading dartfix version from pubspec
 
diff --git a/pkg/dartfix/example/example-fixed.dart b/pkg/dartfix/example/example-fixed.dart
new file mode 100644
index 0000000..4f4a314
--- /dev/null
+++ b/pkg/dartfix/example/example-fixed.dart
@@ -0,0 +1,18 @@
+// This file contains code that has been modified by running dartfix.
+// See example.dart for the original unmodified code.
+
+// Dart will automatically convert int literals to doubles.
+// Running dartfix converts this double literal to an int.
+const double myDouble = 4;
+
+// This class is used as a mixin but does not use the new mixin syntax.
+// Running dartfix converts this class to use the new syntax.
+mixin MyMixin {
+  final someValue = myDouble;
+}
+
+class MyClass with MyMixin {}
+
+main() {
+  print('myDouble = ${MyClass().someValue}');
+}
diff --git a/pkg/dartfix/example/example.dart b/pkg/dartfix/example/example.dart
new file mode 100644
index 0000000..53d5292
--- /dev/null
+++ b/pkg/dartfix/example/example.dart
@@ -0,0 +1,18 @@
+// This file contains code that is modified by running dartfix.
+// After running dartfix, the content of this file matches example-fixed.dart.
+
+// Dart will automatically convert int literals to doubles.
+// Running dartfix converts this double literal to an int.
+const double myDouble = 4.0;
+
+// This class is used as a mixin but does not use the new mixin syntax.
+// Running dartfix converts this class to use the new syntax.
+class MyMixin {
+  final someValue = myDouble;
+}
+
+class MyClass with MyMixin {}
+
+main() {
+  print('myDouble = ${MyClass().someValue}');
+}
diff --git a/pkg/dartfix/lib/src/driver.dart b/pkg/dartfix/lib/src/driver.dart
index e115930..1ccc556 100644
--- a/pkg/dartfix/lib/src/driver.dart
+++ b/pkg/dartfix/lib/src/driver.dart
@@ -29,17 +29,19 @@
   bool force;
   bool overwrite;
   List<String> targets;
+  EditDartfixResult result;
 
   Ansi get ansi => logger.ansi;
 
-  Future start(List<String> args) async {
+  Future start(List<String> args,
+      {Context testContext, Logger testLogger}) async {
     final Options options = Options.parse(args);
 
     force = options.force;
     overwrite = options.overwrite;
     targets = options.targets;
-    context = options.context;
-    logger = options.logger;
+    context = testContext ?? options.context;
+    logger = testLogger ?? options.logger;
     server = new Server(listener: new _Listener(logger));
     handler = new _Handler(this);
 
@@ -47,7 +49,6 @@
       context.exit(15);
     }
 
-    EditDartfixResult result;
     try {
       final progress = await setupAnalysis(options);
       result = await requestFixes(options, progress);
@@ -55,7 +56,7 @@
       await server.stop();
     }
     if (result != null) {
-      applyFixes(result);
+      applyFixes();
     }
   }
 
@@ -112,7 +113,7 @@
     return EditDartfixResult.fromJson(decoder, 'result', json);
   }
 
-  Future applyFixes(EditDartfixResult result) async {
+  Future applyFixes() async {
     showDescriptions('Recommended changes', result.suggestions);
     showDescriptions('Recommended changes that cannot be automatically applied',
         result.otherSuggestions);
diff --git a/pkg/dartfix/pubspec.yaml b/pkg/dartfix/pubspec.yaml
index b3d6515..4f69787 100644
--- a/pkg/dartfix/pubspec.yaml
+++ b/pkg/dartfix/pubspec.yaml
@@ -1,5 +1,5 @@
 name: dartfix
-version: 0.1.1
+version: 0.1.2
 author: Dart Team <misc@dartlang.org>
 description:
   A tool for migrating Dart source to newer versions of the Dart SDK,
@@ -10,7 +10,7 @@
 environment:
   # pin to a narrow SDK range because there will be future versions of dartfix
   # which are more appropriate for those future versions of the SDK
-  sdk: '>=2.1.0-dev.9.2 <2.3.0'
+  sdk: '>=2.1.0 <2.3.0'
 dependencies:
   # pin to an exact version of analysis_server_client because the edit.dartfix protocol
   # is experimental and will continue to evolve
diff --git a/pkg/dartfix/test/src/driver_test.dart b/pkg/dartfix/test/src/driver_test.dart
index 97cf270..1aa5493 100644
--- a/pkg/dartfix/test/src/driver_test.dart
+++ b/pkg/dartfix/test/src/driver_test.dart
@@ -9,7 +9,22 @@
 import 'package:pub_semver/pub_semver.dart';
 import 'package:test/test.dart';
 
+import 'test_context.dart';
+
+const _debug = true;
+const _updateExample = false;
+
 main() {
+  File exampleFile;
+  File exampleFixedFile;
+  Directory exampleDir;
+
+  setUp(() {
+    exampleFile = findFile('pkg/dartfix/example/example.dart');
+    exampleFixedFile = findFile('pkg/dartfix/example/example-fixed.dart');
+    exampleDir = exampleFile.parent;
+  });
+
   test('protocol version', () {
     // The edit.dartfix protocol is experimental and will continue to evolve
     // an so dartfix will only work with this specific version of the protocol.
@@ -26,6 +41,70 @@
     // analysis_server_client and dartfix packages must be published.
     expect(clientVersion, clientVersionInDartfixPubspec);
   });
+
+  test('fix example', () async {
+    final driver = new Driver();
+    final testContext = new TestContext();
+    final testLogger = new TestLogger();
+    String exampleSource = await exampleFile.readAsString();
+
+    await driver.start([exampleDir.path],
+        testContext: testContext, testLogger: testLogger);
+    if (_debug) {
+      print(testLogger.stderrBuffer.toString());
+      print(testLogger.stdoutBuffer.toString());
+      print('--- original example');
+      print(exampleSource);
+    }
+
+    final suggestions = driver.result.suggestions;
+    expect(suggestions, hasLength(2));
+    expectHasSuggestion(suggestions, 'Convert MyMixin to a mixin');
+    expectHasSuggestion(suggestions, 'Replace a double literal');
+
+    expect(driver.result.edits, hasLength(1));
+    for (SourceEdit edit in driver.result.edits[0].edits) {
+      exampleSource = edit.apply(exampleSource);
+    }
+    if (_debug) {
+      print('--- fixed example');
+      print(exampleSource);
+    }
+
+    exampleSource = replaceLeadingComment(exampleSource);
+    if (_updateExample) {
+      await exampleFixedFile.writeAsString(exampleSource);
+    } else {
+      final expectedSource = await exampleFixedFile.readAsString();
+      expect(exampleSource, expectedSource);
+    }
+  });
+
+  test('run example', () async {
+    if (_debug) print('--- launching original example');
+    final futureResult1 =
+        Process.run(Platform.resolvedExecutable, [exampleFile.path]);
+
+    if (_debug) print('--- launching fixed example');
+    final futureResult2 =
+        Process.run(Platform.resolvedExecutable, [exampleFixedFile.path]);
+
+    if (_debug) print('--- waiting for original example');
+    final result1 = await futureResult1;
+
+    if (_debug) print('--- waiting for fixed example');
+    final result2 = await futureResult2;
+
+    final stdout1 = result1.stdout;
+    final stdout2 = result2.stdout;
+    if (_debug) {
+      print('--- original example output');
+      print(stdout1);
+      print('--- fixed example output');
+      print(stdout2);
+    }
+    expect(stdout1, stdout2);
+  });
 }
 
 String get clientVersion =>
@@ -58,3 +137,24 @@
   }
   fail('Failed to find $key in ${pubspec.path}');
 }
+
+void expectHasSuggestion(
+    List<DartFixSuggestion> suggestions, String expectedText) {
+  for (DartFixSuggestion suggestion in suggestions) {
+    if (suggestion.description.contains(expectedText)) {
+      return;
+    }
+  }
+  fail('Failed to find suggestion containing: $expectedText');
+}
+
+String replaceLeadingComment(String source) {
+  final out = new StringBuffer('''
+// This file contains code that has been modified by running dartfix.
+// See example.dart for the original unmodified code.
+  '''
+      .trim());
+  final pattern = 'the content of this file matches example-fixed.dart.';
+  out.write(source.substring(source.indexOf(pattern) + pattern.length));
+  return out.toString();
+}