[frontend-server] Report error count on compile and recompile.

This allows frontend-server users(flutter) to do quick check whether frontend compilation reported errors as it produced output kernel file.

Change-Id: I6e4da8fac33104968eb7720200a1b7153ec8f3de
Reviewed-on: https://dart-review.googlesource.com/51000
Reviewed-by: Siva Annamalai <asiva@google.com>
Commit-Queue: Alexander Aprelev <aam@google.com>
diff --git a/pkg/vm/lib/frontend_server.dart b/pkg/vm/lib/frontend_server.dart
index 33b37b5..fe7e529 100644
--- a/pkg/vm/lib/frontend_server.dart
+++ b/pkg/vm/lib/frontend_server.dart
@@ -286,7 +286,8 @@
       final BinaryPrinter printer = printerFactory.newBinaryPrinter(sink);
       printer.writeComponentFile(component);
       await sink.close();
-      _outputStream.writeln('$boundaryKey $_kernelBinaryFilename');
+      _outputStream
+          .writeln('$boundaryKey $_kernelBinaryFilename ${errors.length}');
       final String depfile = options['depfile'];
       if (depfile != null) {
         await _writeDepfile(component, _kernelBinaryFilename, depfile);
@@ -348,6 +349,7 @@
     if (filename != null) {
       setMainSourceFilename(filename);
     }
+    errors.clear();
     final Component deltaProgram =
         await _generator.compile(entryPoint: _mainSource);
 
@@ -359,7 +361,8 @@
     final BinaryPrinter printer = printerFactory.newBinaryPrinter(sink);
     printer.writeComponentFile(deltaProgram);
     await sink.close();
-    _outputStream.writeln('$boundaryKey $_kernelBinaryFilename');
+    _outputStream
+        .writeln('$boundaryKey $_kernelBinaryFilename ${errors.length}');
     _kernelBinaryFilename = _kernelBinaryFilenameIncremental;
     return null;
   }
diff --git a/pkg/vm/test/frontend_server_test.dart b/pkg/vm/test/frontend_server_test.dart
index 2377066..bac3e69 100644
--- a/pkg/vm/test/frontend_server_test.dart
+++ b/pkg/vm/test/frontend_server_test.dart
@@ -512,11 +512,17 @@
       streamController.add('compile ${file.path}\n'.codeUnits);
       int count = 0;
       Completer<bool> allDone = new Completer<bool>();
-      receivedResults.stream.listen((String outputFilename) {
+      receivedResults.stream.listen((String outputFilenameAndErrorCount) {
+        int delim = outputFilenameAndErrorCount.lastIndexOf(' ');
+        expect(delim > 0, equals(true));
+        String outputFilename = outputFilenameAndErrorCount.substring(0, delim);
+        int errorsCount =
+            int.parse(outputFilenameAndErrorCount.substring(delim + 1).trim());
         if (count == 0) {
           // First request is to 'compile', which results in full kernel file.
           expect(dillFile.existsSync(), equals(true));
           expect(outputFilename, dillFile.path);
+          expect(errorsCount, 0);
           count += 1;
           streamController.add('accept\n'.codeUnits);
           var file2 = new File('${tempDir.path}/bar.dart')..createSync();
@@ -531,6 +537,7 @@
           // kernel file.
           var dillIncFile = new File('${dillFile.path}.incremental.dill');
           expect(outputFilename, dillIncFile.path);
+          expect(errorsCount, 0);
           expect(dillIncFile.existsSync(), equals(true));
           allDone.complete(true);
         }
@@ -538,6 +545,93 @@
       expect(await allDone.future, true);
     });
 
+    test('compile and recompile report non-zero error count', () async {
+      var file = new File('${tempDir.path}/foo.dart')..createSync();
+      file.writeAsStringSync("main() { foo(); bar(); }\n");
+      var dillFile = new File('${tempDir.path}/app.dill');
+      expect(dillFile.existsSync(), equals(false));
+      final List<String> args = <String>[
+        '--sdk-root=${sdkRoot.toFilePath()}',
+        '--strong',
+        '--incremental',
+        '--platform=${platformKernel.path}',
+        '--output-dill=${dillFile.path}'
+      ];
+
+      final StreamController<List<int>> streamController =
+          new StreamController<List<int>>();
+      final StreamController<List<int>> stdoutStreamController =
+          new StreamController<List<int>>();
+      final IOSink ioSink = new IOSink(stdoutStreamController.sink);
+      StreamController<String> receivedResults = new StreamController<String>();
+
+      String boundaryKey;
+      stdoutStreamController.stream
+          .transform(utf8.decoder)
+          .transform(const LineSplitter())
+          .listen((String s) {
+        const String RESULT_OUTPUT_SPACE = 'result ';
+        if (boundaryKey == null) {
+          if (s.startsWith(RESULT_OUTPUT_SPACE)) {
+            boundaryKey = s.substring(RESULT_OUTPUT_SPACE.length);
+          }
+        } else {
+          if (s.startsWith(boundaryKey)) {
+            receivedResults.add(s.substring(boundaryKey.length + 1));
+            boundaryKey = null;
+          }
+        }
+      });
+      int exitcode =
+          await starter(args, input: streamController.stream, output: ioSink);
+      expect(exitcode, equals(0));
+      streamController.add('compile ${file.path}\n'.codeUnits);
+      int count = 0;
+      Completer<bool> allDone = new Completer<bool>();
+      receivedResults.stream.listen((String outputFilenameAndErrorCount) {
+        int delim = outputFilenameAndErrorCount.lastIndexOf(' ');
+        expect(delim > 0, equals(true));
+        String outputFilename = outputFilenameAndErrorCount.substring(0, delim);
+        int errorsCount =
+            int.parse(outputFilenameAndErrorCount.substring(delim + 1).trim());
+        switch (count) {
+          case 0:
+            expect(dillFile.existsSync(), equals(true));
+            expect(outputFilename, dillFile.path);
+            expect(errorsCount, 2);
+            count += 1;
+            streamController.add('accept\n'.codeUnits);
+            var file2 = new File('${tempDir.path}/bar.dart')..createSync();
+            file2.writeAsStringSync("main() { baz(); }\n");
+            streamController.add('recompile ${file2.path} abc\n'
+                '${file2.path}\n'
+                'abc\n'
+                .codeUnits);
+            break;
+          case 1:
+            var dillIncFile = new File('${dillFile.path}.incremental.dill');
+            expect(outputFilename, dillIncFile.path);
+            expect(errorsCount, 1);
+            count += 1;
+            streamController.add('accept\n'.codeUnits);
+            var file2 = new File('${tempDir.path}/bar.dart')..createSync();
+            file2.writeAsStringSync("main() { }\n");
+            streamController.add('recompile ${file2.path} abc\n'
+                '${file2.path}\n'
+                'abc\n'
+                .codeUnits);
+            break;
+          case 2:
+            var dillIncFile = new File('${dillFile.path}.incremental.dill');
+            expect(outputFilename, dillIncFile.path);
+            expect(errorsCount, 0);
+            expect(dillIncFile.existsSync(), equals(true));
+            allDone.complete(true);
+        }
+      });
+      expect(await allDone.future, true);
+    });
+
     test('compile and recompile with MultiRootFileSystem', () async {
       var file = new File('${tempDir.path}/foo.dart')..createSync();
       file.writeAsStringSync("main() {}\n");