Fix abstract unix socket test

There was a race between the subprocess listening on the socket and
the test trying to connect.

Also the Android bits of the test harness needed to be taught about
the abstract_socket_test program, similar to how process_test is
handled.

TEST=Fixes tests
Change-Id: I5e1c5d2e23e4bff2a01cd5c491a45e1c58d2578e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/201861
Reviewed-by: Alexander Aprelev <aam@google.com>
Commit-Queue: Zach Anderson <zra@google.com>
diff --git a/pkg/test_runner/lib/src/command.dart b/pkg/test_runner/lib/src/command.dart
index 27ca02d..8ba9f27 100644
--- a/pkg/test_runner/lib/src/command.dart
+++ b/pkg/test_runner/lib/src/command.dart
@@ -609,6 +609,7 @@
 class AdbPrecompilationCommand extends Command implements AdbCommand {
   final String buildPath; // Path to the output directory of the build.
   final String processTestFilename;
+  final String abstractSocketTestFilename;
   final String precompiledTestDirectory;
   final List<String> arguments;
   final bool useElf;
@@ -617,6 +618,7 @@
   AdbPrecompilationCommand(
       this.buildPath,
       this.processTestFilename,
+      this.abstractSocketTestFilename,
       this.precompiledTestDirectory,
       this.arguments,
       this.useElf,
@@ -627,6 +629,7 @@
   AdbPrecompilationCommand indexedCopy(int index) => AdbPrecompilationCommand(
       buildPath,
       processTestFilename,
+      abstractSocketTestFilename,
       precompiledTestDirectory,
       arguments,
       useElf,
@@ -662,17 +665,28 @@
 class AdbDartkCommand extends Command implements AdbCommand {
   final String buildPath;
   final String processTestFilename;
+  final String abstractSocketTestFilename;
   final String kernelFile;
   final List<String> arguments;
   final List<String> extraLibraries;
 
-  AdbDartkCommand(this.buildPath, this.processTestFilename, this.kernelFile,
-      this.arguments, this.extraLibraries,
+  AdbDartkCommand(
+      this.buildPath,
+      this.processTestFilename,
+      this.abstractSocketTestFilename,
+      this.kernelFile,
+      this.arguments,
+      this.extraLibraries,
       {int index = 0})
       : super._("adb_precompilation", index: index);
 
   AdbDartkCommand indexedCopy(int index) => AdbDartkCommand(
-      buildPath, processTestFilename, kernelFile, arguments, extraLibraries,
+      buildPath,
+      processTestFilename,
+      abstractSocketTestFilename,
+      kernelFile,
+      arguments,
+      extraLibraries,
       index: index);
 
   _buildHashCode(HashCodeBuilder builder) {
diff --git a/pkg/test_runner/lib/src/process_queue.dart b/pkg/test_runner/lib/src/process_queue.dart
index ff3e1f1..3c8e5bb 100644
--- a/pkg/test_runner/lib/src/process_queue.dart
+++ b/pkg/test_runner/lib/src/process_queue.dart
@@ -639,6 +639,7 @@
       AdbDevice device, AdbPrecompilationCommand command, int timeout) async {
     var buildPath = command.buildPath;
     var processTest = command.processTestFilename;
+    var abstractSocketTest = command.abstractSocketTestFilename;
     var testdir = command.precompiledTestDirectory;
     var arguments = command.arguments;
     var devicedir = DartPrecompiledAdbRuntimeConfiguration.deviceDir;
@@ -662,10 +663,12 @@
         '$devicedir/dart_precompiled_runtime'));
     steps.add(
         () => device.pushCachedData(processTest, '$devicedir/process_test'));
+    steps.add(() => device.pushCachedData(
+        abstractSocketTest, '$devicedir/abstract_socket_test'));
     steps.add(() => device.runAdbShellCommand([
           'chmod',
           '777',
-          '$devicedir/dart_precompiled_runtime $devicedir/process_test'
+          '$devicedir/dart_precompiled_runtime $devicedir/process_test $devicedir/abstract_socket_test'
         ]));
 
     steps.addAll(_pushLibraries(command, device, devicedir, deviceTestDir));
diff --git a/pkg/test_runner/lib/src/runtime_configuration.dart b/pkg/test_runner/lib/src/runtime_configuration.dart
index e8a4d82..27623e3 100644
--- a/pkg/test_runner/lib/src/runtime_configuration.dart
+++ b/pkg/test_runner/lib/src/runtime_configuration.dart
@@ -125,6 +125,13 @@
     return processTestExecutable;
   }
 
+  String get abstractSocketTestBinaryFileName {
+    var abstractSocketTestExecutable =
+        '$buildDir/abstract_socket_test$executableExtension';
+    TestUtils.ensureExists(abstractSocketTestExecutable, _configuration);
+    return abstractSocketTestExecutable;
+  }
+
   String get d8FileName {
     var d8Dir = Repository.dir.append('third_party/d8');
     var d8Path =
@@ -358,8 +365,10 @@
 
     var buildPath = buildDir;
     var processTest = processTestBinaryFileName;
+    var abstractSocketTest = abstractSocketTestBinaryFileName;
     return [
-      AdbDartkCommand(buildPath, processTest, script, arguments, extraLibs)
+      AdbDartkCommand(buildPath, processTest, abstractSocketTest, script,
+          arguments, extraLibs)
     ];
   }
 }
@@ -385,9 +394,10 @@
     }
 
     var processTest = processTestBinaryFileName;
+    var abstractSocketTest = abstractSocketTestBinaryFileName;
     return [
-      AdbPrecompilationCommand(
-          buildDir, processTest, script, arguments, useElf, extraLibs)
+      AdbPrecompilationCommand(buildDir, processTest, abstractSocketTest,
+          script, arguments, useElf, extraLibs)
     ];
   }
 }
diff --git a/tests/standalone/io/unix_socket_test.dart b/tests/standalone/io/unix_socket_test.dart
index 7c84792..0553e6a 100644
--- a/tests/standalone/io/unix_socket_test.dart
+++ b/tests/standalone/io/unix_socket_test.dart
@@ -175,7 +175,11 @@
   if (!Platform.isLinux && !Platform.isAndroid) {
     return;
   }
+  var retries = 10;
+  var retryDelay = const Duration(seconds: 1);
   Process? process;
+  Future? stdoutFuture;
+  Future? stderrFuture;
   try {
     var socketAddress = '@hidden';
     var abstractSocketServer = getAbstractSocketTestFileName();
@@ -184,10 +188,36 @@
     if (!File(abstractSocketServer).existsSync()) {
       return;
     }
+
+    // Start up a subprocess that listens on '@hidden'.
     process = await Process.start(abstractSocketServer, [socketAddress]);
+    stdoutFuture = process.stdout
+        .transform(const Utf8Decoder(allowMalformed: true))
+        .listen(stdout.write)
+        .asFuture(null);
+    stderrFuture = process.stderr
+        .transform(const Utf8Decoder(allowMalformed: true))
+        .listen(stderr.write)
+        .asFuture(null);
     var serverAddress =
         InternetAddress(socketAddress, type: InternetAddressType.unix);
-    Socket client = await Socket.connect(serverAddress, 0);
+
+    // The subprocess may take some time to start, so retry setting up the
+    // connection a few times.
+    Socket? client;
+    while (true) {
+      try {
+        client = await Socket.connect(serverAddress, 0);
+        break;
+      } catch (e, st) {
+        if (retries <= 0) {
+          rethrow;
+        }
+        retries--;
+      }
+      await Future.delayed(retryDelay);
+    }
+
     List<int> sendData = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
     List<int> data = [];
     var completer = Completer<void>();
@@ -206,6 +236,9 @@
     Expect.fail('Failed with exception:\n$e\n$st');
   } finally {
     process?.kill(ProcessSignal.sigkill);
+    await stdoutFuture;
+    await stderrFuture;
+    await process?.exitCode;
   }
 }
 
diff --git a/tests/standalone_2/io/unix_socket_test.dart b/tests/standalone_2/io/unix_socket_test.dart
index 0549fea..e63b9d8 100644
--- a/tests/standalone_2/io/unix_socket_test.dart
+++ b/tests/standalone_2/io/unix_socket_test.dart
@@ -5,8 +5,8 @@
 // @dart = 2.9
 
 import 'dart:async';
-import 'dart:io';
 import 'dart:convert';
+import 'dart:io';
 
 import 'package:expect/expect.dart';
 
@@ -177,7 +177,11 @@
   if (!Platform.isLinux && !Platform.isAndroid) {
     return;
   }
+  var retries = 10;
+  var retryDelay = const Duration(seconds: 1);
   Process process;
+  var stdoutFuture;
+  var stderrFuture;
   try {
     var socketAddress = '@hidden';
     var abstractSocketServer = getAbstractSocketTestFileName();
@@ -186,10 +190,36 @@
     if (!File(abstractSocketServer).existsSync()) {
       return;
     }
+
+    // Start up a subprocess that listens on '@hidden'.
     process = await Process.start(abstractSocketServer, [socketAddress]);
+    stdoutFuture = process.stdout
+        .transform(const Utf8Decoder(allowMalformed: true))
+        .listen(stdout.write)
+        .asFuture(null);
+    stderrFuture = process.stderr
+        .transform(const Utf8Decoder(allowMalformed: true))
+        .listen(stderr.write)
+        .asFuture(null);
     var serverAddress =
         InternetAddress(socketAddress, type: InternetAddressType.unix);
-    Socket client = await Socket.connect(serverAddress, 0);
+
+    // The subprocess may take some time to start, so retry setting up the
+    // connection a few times.
+    Socket client;
+    while (true) {
+      try {
+        client = await Socket.connect(serverAddress, 0);
+        break;
+      } catch (e, st) {
+        if (retries <= 0) {
+          rethrow;
+        }
+        retries--;
+      }
+      await Future.delayed(retryDelay);
+    }
+
     List<int> sendData = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
     List<int> data = [];
     var completer = Completer<void>();
@@ -208,6 +238,9 @@
     Expect.fail('Failed with exception:\n$e\n$st');
   } finally {
     process?.kill(ProcessSignal.sigkill);
+    await stdoutFuture;
+    await stderrFuture;
+    await process?.exitCode;
   }
 }