From 8fa2bdb099376894beccf0f2d4c561e1572dfeec Mon Sep 17 00:00:00 2001 From: Eugene Susla Date: Thu, 17 Sep 2020 14:57:04 -0700 Subject: Run shell commands reliably Tests sometimes fail due to a shell command failing, bet we don't get the appropriate result from the command run itself, which leads to confusing states and challenging troubleshooting. E.g. jobscheduler reports its failure only via stderr. This introduces a utility that fails with the relevant command output, should something be printed to stderr. Test: atest AutoRevokeTest Bug: 168843991 Change-Id: I5e354c1b88ca3ceeeda2d5500b62c421174fd57c --- core/java/android/app/IUiAutomationConnection.aidl | 2 + core/java/android/app/UiAutomation.java | 48 ++++++++++++++++++++-- core/java/android/app/UiAutomationConnection.java | 27 +++++++++++- 3 files changed, 72 insertions(+), 5 deletions(-) (limited to 'core/java') diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl index 4c9e400681ee..9eeb9f6a95bf 100644 --- a/core/java/android/app/IUiAutomationConnection.aidl +++ b/core/java/android/app/IUiAutomationConnection.aidl @@ -52,4 +52,6 @@ interface IUiAutomationConnection { void dropShellPermissionIdentity(); // Called from the system process. oneway void shutdown(); + void executeShellCommandWithStderr(String command, in ParcelFileDescriptor sink, + in ParcelFileDescriptor source, in ParcelFileDescriptor stderrSink); } diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index ab997e9f7832..4e868fe7370b 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -25,6 +25,7 @@ import android.accessibilityservice.IAccessibilityServiceConnection; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Bitmap; @@ -1245,7 +1246,34 @@ public final class UiAutomation { * @hide */ @TestApi - public ParcelFileDescriptor[] executeShellCommandRw(String command) { + public @NonNull ParcelFileDescriptor[] executeShellCommandRw(@NonNull String command) { + return executeShellCommandInternal(command, false /* includeStderr */); + } + + /** + * Executes a shell command. This method returns three file descriptors, + * one that points to the standard output stream (element at index 0), one that points + * to the standard input stream (element at index 1), and one points to + * standard error stream (element at index 2). The command execution is similar + * to running "adb shell " from a host connected to the device. + *

+ * Note: It is your responsibility to close the returned file + * descriptors once you are done reading/writing. + *

+ * + * @param command The command to execute. + * @return File descriptors (out, in, err) to the standard output/input/error streams. + * + * @hide + */ + @TestApi + @SuppressLint("ArrayReturn") // For consistency with other APIs + public @NonNull ParcelFileDescriptor[] executeShellCommandRwe(@NonNull String command) { + return executeShellCommandInternal(command, true /* includeStderr */); + } + + private ParcelFileDescriptor[] executeShellCommandInternal( + String command, boolean includeStderr) { warnIfBetterCommand(command); ParcelFileDescriptor source_read = null; @@ -1254,6 +1282,9 @@ public final class UiAutomation { ParcelFileDescriptor source_write = null; ParcelFileDescriptor sink_write = null; + ParcelFileDescriptor stderr_source_read = null; + ParcelFileDescriptor stderr_sink_read = null; + try { ParcelFileDescriptor[] pipe_read = ParcelFileDescriptor.createPipe(); source_read = pipe_read[0]; @@ -1263,8 +1294,15 @@ public final class UiAutomation { source_write = pipe_write[0]; sink_write = pipe_write[1]; + if (includeStderr) { + ParcelFileDescriptor[] stderr_read = ParcelFileDescriptor.createPipe(); + stderr_source_read = stderr_read[0]; + stderr_sink_read = stderr_read[1]; + } + // Calling out without a lock held. - mUiAutomationConnection.executeShellCommand(command, sink_read, source_write); + mUiAutomationConnection.executeShellCommandWithStderr( + command, sink_read, source_write, stderr_sink_read); } catch (IOException ioe) { Log.e(LOG_TAG, "Error executing shell command!", ioe); } catch (RemoteException re) { @@ -1272,11 +1310,15 @@ public final class UiAutomation { } finally { IoUtils.closeQuietly(sink_read); IoUtils.closeQuietly(source_write); + IoUtils.closeQuietly(stderr_sink_read); } - ParcelFileDescriptor[] result = new ParcelFileDescriptor[2]; + ParcelFileDescriptor[] result = new ParcelFileDescriptor[includeStderr ? 3 : 2]; result[0] = source_read; result[1] = sink_write; + if (includeStderr) { + result[2] = stderr_source_read; + } return result; } diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java index 70d520176ca1..255b93f79811 100644 --- a/core/java/android/app/UiAutomationConnection.java +++ b/core/java/android/app/UiAutomationConnection.java @@ -372,6 +372,13 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { @Override public void executeShellCommand(final String command, final ParcelFileDescriptor sink, final ParcelFileDescriptor source) throws RemoteException { + executeShellCommandWithStderr(command, sink, source, null /* stderrSink */); + } + + @Override + public void executeShellCommandWithStderr(final String command, final ParcelFileDescriptor sink, + final ParcelFileDescriptor source, final ParcelFileDescriptor stderrSink) + throws RemoteException { synchronized (mLock) { throwIfCalledByNotTrustedUidLocked(); throwIfShutdownLocked(); @@ -409,6 +416,18 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { writeToProcess = null; } + // Read from process stderr and write to pipe + final Thread readStderrFromProcess; + if (stderrSink != null) { + InputStream sink_in = process.getErrorStream(); + OutputStream sink_out = new FileOutputStream(stderrSink.getFileDescriptor()); + + readStderrFromProcess = new Thread(new Repeater(sink_in, sink_out)); + readStderrFromProcess.start(); + } else { + readStderrFromProcess = null; + } + Thread cleanup = new Thread(new Runnable() { @Override public void run() { @@ -419,14 +438,18 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { if (readFromProcess != null) { readFromProcess.join(); } + if (readStderrFromProcess != null) { + readStderrFromProcess.join(); + } } catch (InterruptedException exc) { Log.e(TAG, "At least one of the threads was interrupted"); } IoUtils.closeQuietly(sink); IoUtils.closeQuietly(source); + IoUtils.closeQuietly(stderrSink); process.destroy(); - } - }); + } + }); cleanup.start(); } -- cgit v1.2.3