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
This commit is contained in:
@@ -537,7 +537,8 @@ package android.app {
|
||||
|
||||
public final class UiAutomation {
|
||||
method public void destroy();
|
||||
method public android.os.ParcelFileDescriptor[] executeShellCommandRw(String);
|
||||
method @NonNull public android.os.ParcelFileDescriptor[] executeShellCommandRw(@NonNull String);
|
||||
method @NonNull public android.os.ParcelFileDescriptor[] executeShellCommandRwe(@NonNull String);
|
||||
method @Deprecated public boolean grantRuntimePermission(String, String, android.os.UserHandle);
|
||||
method @Deprecated public boolean revokeRuntimePermission(String, String, android.os.UserHandle);
|
||||
method public void syncInputTransactions();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 <command>" from a host connected to the device.
|
||||
* <p>
|
||||
* <strong>Note:</strong> It is your responsibility to close the returned file
|
||||
* descriptors once you are done reading/writing.
|
||||
* </p>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user