[dart2wasm] Align test outcomes of dart2wasm across configurations

Aligns exit codes of

  * `dart compile wasm` and
  * `pkg/dart2wasm/tool/compile_benchmark`

Also make them use exit codes recognized by the test runner to
distinguish CFE crashes, CFE compile-time-errors and other failures.

Also update status file entries to from D8 specific entries
to JS commandline shell entries

=> Step towards aligning D8 & JSC test results

Change-Id: I1acb8803f5db7c732ad546d5989b1c555583e7c5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/383660
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
diff --git a/pkg/dart2wasm/lib/compile.dart b/pkg/dart2wasm/lib/compile.dart
index 1418700..5bbf4fd 100644
--- a/pkg/dart2wasm/lib/compile.dart
+++ b/pkg/dart2wasm/lib/compile.dart
@@ -45,11 +45,38 @@
 import 'target.dart' hide Mode;
 import 'translator.dart';
 
-class CompilerOutput {
+sealed class CompilationResult {}
+
+class CompilationSuccess extends CompilationResult {
   final Map<String, ({Uint8List moduleBytes, String? sourceMap})> wasmModules;
   final String jsRuntime;
 
-  CompilerOutput(this.wasmModules, this.jsRuntime);
+  CompilationSuccess(this.wasmModules, this.jsRuntime);
+}
+
+class CompilationError extends CompilationResult {}
+
+/// The CFE has crashed with an exception.
+///
+/// This is a CFE bug and should be reported by users.
+class CFECrashError extends CompilationError {
+  final Object error;
+  final StackTrace stackTrace;
+
+  CFECrashError(this.error, this.stackTrace);
+}
+
+/// Compiling the Dart program resulted in compile-time errors.
+///
+/// This is a bug in the dart program (e.g. syntax errors, static type errors,
+/// ...) that's being compiled.  Users have to address those errors in their
+/// code for it to compile successfully.
+///
+/// The errors are already printed via the `handleDiagnosticMessage` callback.
+/// (We print them as soon as they are reported by CFE. i.e. we stream errors
+/// instead of accumulating/batching all of them and reporting at the end.)
+class CFECompileTimeErrors extends CompilationError {
+  CFECompileTimeErrors();
 }
 
 /// Compile a Dart file into a Wasm module.
@@ -63,14 +90,14 @@
 /// This value will be added to the Wasm module in `sourceMappingURL` section.
 /// When this argument is null the code generator does not generate source
 /// mappings.
-Future<CompilerOutput?> compileToModule(
+Future<CompilationResult> compileToModule(
     compiler.WasmCompilerOptions options,
     Uri Function(String moduleName)? sourceMapUrlGenerator,
     void Function(DiagnosticMessage) handleDiagnosticMessage) async {
-  var succeeded = true;
+  var hadCompileTimeError = false;
   void diagnosticMessageHandler(DiagnosticMessage message) {
     if (message.severity == Severity.error) {
-      succeeded = false;
+      hadCompileTimeError = true;
     }
     handleDiagnosticMessage(message);
   }
@@ -118,12 +145,16 @@
     compilerOptions.compileSdk = true;
   }
 
-  CompilerResult? compilerResult =
-      await kernelForProgram(options.mainUri, compilerOptions);
-  if (compilerResult == null || !succeeded) {
-    return null;
+  CompilerResult? compilerResult;
+  try {
+    compilerResult = await kernelForProgram(options.mainUri, compilerOptions);
+  } catch (e, s) {
+    return CFECrashError(e, s);
   }
-  Component component = compilerResult.component!;
+  if (hadCompileTimeError) return CFECompileTimeErrors();
+  assert(compilerResult != null);
+
+  Component component = compilerResult!.component!;
   CoreTypes coreTypes = compilerResult.coreTypes!;
   ClassHierarchy classHierarchy = compilerResult.classHierarchy!;
   LibraryIndex libraryIndex = LibraryIndex(component, [
@@ -231,5 +262,5 @@
       translator.internalizedStringsForJSRuntime,
       mode);
 
-  return CompilerOutput(wasmModules, jsRuntime);
+  return CompilationSuccess(wasmModules, jsRuntime);
 }
diff --git a/pkg/dart2wasm/lib/generate_wasm.dart b/pkg/dart2wasm/lib/generate_wasm.dart
index 05f4311..292988f 100644
--- a/pkg/dart2wasm/lib/generate_wasm.dart
+++ b/pkg/dart2wasm/lib/generate_wasm.dart
@@ -47,17 +47,31 @@
           ? moduleNameToRelativeSourceMapUri
           : null;
 
-  CompilerOutput? output = await compileToModule(
-      options,
-      relativeSourceMapUrlMapper,
-      (message) => printDiagnosticMessage(message, errorPrinter));
+  CompilationResult result =
+      await compileToModule(options, relativeSourceMapUrlMapper, (message) {
+    printDiagnosticMessage(message, errorPrinter);
+  });
 
-  if (output == null) {
-    return 1;
+  // If the compilation to wasm failed we use appropriate exit codes recognized
+  // by our test infrastructure. We use the same exit codes as the VM does. See:
+  //    runtime/bin/error_exit.h:kDartFrontendErrorExitCode
+  //    runtime/bin/error_exit.h:kCompilationErrorExitCode
+  //    runtime/bin/error_exit.h:kErrorExitCode
+  if (result is! CompilationSuccess) {
+    if (result is CFECrashError) {
+      print('The compiler crashed with: ${result.error}');
+      print(result.stackTrace);
+      return 252;
+    }
+    if (result is CFECompileTimeErrors) {
+      return 254;
+    }
+
+    return 255;
   }
 
   final writeFutures = <Future>[];
-  output.wasmModules.forEach((moduleName, moduleInfo) {
+  result.wasmModules.forEach((moduleName, moduleInfo) {
     final (:moduleBytes, :sourceMap) = moduleInfo;
     final File outFile = File(moduleNameToWasmOutputFile(moduleName));
     outFile.parent.createSync(recursive: true);
@@ -72,7 +86,7 @@
 
   final jsFile = options.outputJSRuntimeFile ??
       path.setExtension(options.outputFile, '.mjs');
-  await File(jsFile).writeAsString(output.jsRuntime);
+  await File(jsFile).writeAsString(result.jsRuntime);
 
   return 0;
 }
diff --git a/pkg/dartdev/lib/src/commands/compile.dart b/pkg/dartdev/lib/src/commands/compile.dart
index 4862d36..c8cc3f2 100644
--- a/pkg/dartdev/lib/src/commands/compile.dart
+++ b/pkg/dartdev/lib/src/commands/compile.dart
@@ -20,7 +20,7 @@
 import '../utils.dart';
 
 const int genericErrorExitCode = 255;
-const int compileErrorExitCode = 64;
+const int compileErrorExitCode = 254;
 
 class Option {
   final String flag;
@@ -847,7 +847,7 @@
     ];
     try {
       final exitCode = await runProcess(dart2wasmCommand);
-      if (exitCode != 0) return compileErrorExitCode;
+      if (exitCode != 0) return exitCode;
     } catch (e, st) {
       log.stderr('Error: Wasm compilation failed');
       log.stderr(e.toString());
diff --git a/pkg/dartdev/test/commands/compile_test.dart b/pkg/dartdev/test/commands/compile_test.dart
index 4fc8148..6a4a33f 100644
--- a/pkg/dartdev/test/commands/compile_test.dart
+++ b/pkg/dartdev/test/commands/compile_test.dart
@@ -12,7 +12,7 @@
 
 import '../utils.dart';
 
-const int compileErrorExitCode = 64;
+const int compileErrorExitCode = 254;
 
 void main() {
   ensureRunFromSdkBinDart();
@@ -179,7 +179,7 @@
       ],
     );
     expect(result.stderr, contains('Compile Dart'));
-    expect(result.exitCode, compileErrorExitCode);
+    expect(result.exitCode, 64);
   });
 
   test('--help', () async {
@@ -1310,7 +1310,7 @@
 
     expect(result.stderr, isNot(contains(soundNullSafetyMessage)));
     expect(result.stderr, contains('must be assigned before it can be used'));
-    expect(result.exitCode, 64);
+    expect(result.exitCode, compileErrorExitCode);
   });
 
   test('Compile JIT snapshot with default (sound null safety)', () async {
diff --git a/pkg/test_runner/lib/src/command_output.dart b/pkg/test_runner/lib/src/command_output.dart
index 86d618c..7a100aa 100644
--- a/pkg/test_runner/lib/src/command_output.dart
+++ b/pkg/test_runner/lib/src/command_output.dart
@@ -919,6 +919,28 @@
       super.stdout, super.stderr, super.time, super.compilationSkipped);
 
   @override
+  Expectation result(TestCase testCase) {
+    if (hasCrashed) return Expectation.crash;
+    if (hasTimedOut) return Expectation.timeout;
+    if (hasNonUtf8) return Expectation.nonUtf8Error;
+    if (truncatedOutput) return Expectation.truncatedOutput;
+
+    switch (exitCode) {
+      case VMCommandOutput._dfeErrorExitCode:
+        return Expectation.dartkCrash;
+      case VMCommandOutput._compileErrorExitCode:
+        if (testCase.testFile.isStaticErrorTest) {
+          return _validateExpectedErrors(testCase);
+        }
+        return Expectation.compileTimeError;
+      case VMCommandOutput._uncaughtExceptionExitCode:
+        return Expectation.crash;
+      default:
+        return exitCode != 0 ? Expectation.fail : Expectation.pass;
+    }
+  }
+
+  @override
   void _parseErrors() {
     var errors = <StaticError>[];
     // We expect errors to be printed to `stderr` for dart2wasm.
diff --git a/tests/lib/lib.status b/tests/lib/lib.status
index 06259c5..7b35f9f 100644
--- a/tests/lib/lib.status
+++ b/tests/lib/lib.status
@@ -29,13 +29,6 @@
 [ $runtime != chrome ]
 js/static_interop_test/import/import_trustedscripturl_test: SkipByDesign # Trusted Types are only supported in Chrome currently.
 
-[ $runtime == d8 ]
-js/export/static_interop_mock/proto_test: SkipByDesign # Uses dart:html.
-js/static_interop_test/constants_test: SkipByDesign # Uses dart:html.
-js/static_interop_test/futurevaluetype_test: SkipByDesign # Uses dart:html.
-js/static_interop_test/import/import_test: SkipByDesign # TODO(srujzs): This test uses the file system to load a module. Since the test runner doesn't start an HTTP server for d8, I don't think this is supported.
-js/static_interop_test/supertype_transform_test: SkipByDesign # Uses dart:html.
-
 [ $runtime == dart_precompiled ]
 isolate/package_config_getter_test: SkipByDesign # AOT mode doesn't preserve package structure.
 
@@ -100,6 +93,13 @@
 js/trust_types_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
 js/type_parameter_lowering_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
 
+[ $jscl ]
+js/export/static_interop_mock/proto_test: SkipByDesign # Uses dart:html.
+js/static_interop_test/constants_test: SkipByDesign # Uses dart:html.
+js/static_interop_test/futurevaluetype_test: SkipByDesign # Uses dart:html.
+js/static_interop_test/import/import_test: SkipByDesign # TODO(srujzs): This test uses the file system to load a module. Since the test runner doesn't start an HTTP server for commandline JS shells, I don't think this is supported.
+js/static_interop_test/supertype_transform_test: SkipByDesign # Uses dart:html.
+
 [ $simulator ]
 convert/utf85_test: Skip # Pass, Slow Issue 20111.