1
0
mirror of https://github.com/ARM-software/workload-automation.git synced 2025-01-18 20:11:20 +00:00

Fix applaunch workload

This patch fixes following issues:

* Starting from Android 11 it is not possible to use Runtime.exec to
execute commands that require shell permissions like `am start`.

* Engineering Android versions has su that doesn't support -c argument.
This commit is contained in:
Konstantin Baladurin 2024-05-08 12:06:10 +01:00
parent e0bf7668b8
commit 3f5707e63c
7 changed files with 144 additions and 44 deletions

View File

@ -14,7 +14,7 @@
#
# pylint: disable=attribute-defined-outside-init
from wa import ApkUiautoWorkload, Parameter
from wa import ApkUiautoWorkload, Parameter, TargetError
from wa.framework import pluginloader
@ -89,6 +89,54 @@ class Applaunch(ApkUiautoWorkload):
"""),
]
def __init__(self, target, **kwargs):
super(Applaunch, self).__init__(target, **kwargs)
# Android doesn't allow to writable dex files starting 14 version and
# default location (/sdcard/devlib-target) doesn't support readonly files
# so we use /data/local/tmp as asset directory for this workload.
self.asset_directory = '/data/local/tmp'
self._su_has_command_option = None
def workload_apk(self):
return self.deployed_assets[-1]
def initialize(self, context):
super(Applaunch, self).initialize(context)
worload_apk = self.workload_apk()
self.gui.uiauto_params['workload_apk'] = worload_apk
# Make workload apk readonly to comply with Android >= 14.
if self.target.get_sdk_version() >= 34:
self.target.execute(f'chmod -w {worload_apk}')
# Check installed su version and return whether it supports -c argument.
#
# Targets can have different versions of su
# - Targets with engineering Android version have su with following usage:
# su [WHO [COMMAND...]]
# - Targets with rooted user Android version have su that supports passing
# command via -c argument.
def su_has_command_option(self):
if self._su_has_command_option is None:
try:
self.target.execute('su -c id')
self._su_has_command_option = True
except TargetError:
self._su_has_command_option = False
if self._su_has_command_option is False:
try:
self.target.execute('su root id')
except TargetError:
raise WorkloadError(
'su must be installed and support passing command '
'via -c argument (su -c <command>) or as positional '
'argument after user (su <user> <command>)'
)
return self._su_has_command_option
def init_resources(self, context):
super(Applaunch, self).init_resources(context)
self.workload_params['markers_enabled'] = True
@ -112,6 +160,7 @@ class Applaunch(ApkUiautoWorkload):
self.gui.uiauto_params['launch_activity'] = "None"
self.gui.uiauto_params['applaunch_type'] = self.applaunch_type
self.gui.uiauto_params['applaunch_iterations'] = self.applaunch_iterations
self.gui.uiauto_params['su_has_command_option'] = self.su_has_command_option()
def setup(self, context):
self.workload.gui.uiauto_params['package_name'] = self.workload.apk.apk_info.package

View File

@ -3,12 +3,12 @@ apply plugin: 'com.android.application'
def packageName = "com.arm.wa.uiauto.applaunch"
android {
compileSdkVersion 28
buildToolsVersion "28.0.3"
namespace "${packageName}"
compileSdkVersion 34
defaultConfig {
applicationId "${packageName}"
minSdkVersion 18
targetSdkVersion 28
targetSdkVersion 34
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
@ -25,11 +25,5 @@ dependencies {
implementation 'com.android.support.test:runner:0.5'
implementation 'com.android.support.test:rules:0.5'
implementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
implementation(name: 'uiauto', ext:'aar')
}
repositories {
flatDir {
dirs 'libs'
}
implementation files('libs/uiauto.aar')
}

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.arm.wa.uiauto.applaunch"
android:versionCode="1"
android:versionName="1.0"
android:allowBackup="false">

View File

@ -15,7 +15,9 @@
package com.arm.wa.uiauto.applaunch;
import android.os.Build;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.uiautomator.UiObject;
import android.util.Log;
@ -30,7 +32,10 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.File;
import java.io.InputStreamReader;
import dalvik.system.DexClassLoader;
@ -52,18 +57,19 @@ public class UiAutomation extends BaseUiAutomation {
protected Bundle parameters;
protected String packageName;
protected String packageID;
protected boolean suHasCommandOption;
@Before
public void initialize() throws Exception {
parameters = getParams();
packageID = getPackageID(parameters);
suHasCommandOption = parameters.getBoolean("su_has_command_option");
// Get workload apk file parameters
String packageName = parameters.getString("package_name");
packageName = parameters.getString("package_name");
String workload = parameters.getString("workload");
String workloadAPKPath = parameters.getString("workdir");
String workloadName = String.format("com.arm.wa.uiauto.%1s.apk", workload);
String workloadAPKFile = String.format("%1s/%2s", workloadAPKPath, workloadName);
String workloadAPKFile = parameters.getString("workload_apk");
// Load the apk file
File apkFile = new File(workloadAPKFile);
@ -153,7 +159,6 @@ public class UiAutomation extends BaseUiAutomation {
private String testTag;
private String launchCommand;
private ActionLogger logger;
Process launch_p;
public AppLaunch(String testTag, String launchCommand) {
this.testTag = testTag;
@ -169,24 +174,13 @@ public class UiAutomation extends BaseUiAutomation {
// Launches the application.
public void launchMain() throws Exception{
launch_p = Runtime.getRuntime().exec(launchCommand);
launchValidate(launch_p);
}
// Called by launchMain() to check if app launch is successful
public void launchValidate(Process launch_p) throws Exception {
launch_p.waitFor();
Integer exit_val = launch_p.exitValue();
if (exit_val != 0) {
throw new Exception("Application could not be launched");
}
executeShellCommand(launchCommand);
}
// Marks the end of application launch of the workload.
public void endLaunch() throws Exception{
waitObject(launchEndObject, launch_timeout);
logger.stop();
launch_p.destroy();
}
}
@ -203,15 +197,14 @@ public class UiAutomation extends BaseUiAutomation {
// Kills the application process
public void killApplication() throws Exception{
Process kill_p;
String command = String.format("am force-stop %s", packageName);
kill_p = Runtime.getRuntime().exec(new String[] { "su", "-c", command});
kill_p.waitFor();
kill_p.destroy();
executeShellCommand(command);
}
// Kills the background processes
public void killBackground() throws Exception{
// Workload has KILL_BACKGROUND_PROCESSES permission so it can execute following
// command and it is not necessary to execute it with shell permissions.
Process kill_p;
kill_p = Runtime.getRuntime().exec("am kill-all");
kill_p.waitFor();
@ -220,15 +213,80 @@ public class UiAutomation extends BaseUiAutomation {
// Drop the caches
public void dropCaches() throws Exception{
Process sync;
sync = Runtime.getRuntime().exec(new String[] { "su", "-c", "sync"});
sync.waitFor();
sync.destroy();
executeShellCommand("sync", /*asRoot=*/true);
executeShellCommand("echo 3 > /proc/sys/vm/drop_caches", /*asRoot=*/true);
}
Process drop_cache;
String command = "echo 3 > /proc/sys/vm/drop_caches";
drop_cache = Runtime.getRuntime().exec(new String[] { "su", "-c", command});
drop_cache.waitFor();
drop_cache.destroy();
private String getSuCommand(String command) {
if (suHasCommandOption) {
return String.format("su -c '%s'", command);
}
// If su doesn't support -c argument we assume that it has following usage:
// su [WHO [COMMAND]]
// that corresponds to su from engineering Android version.
return String.format("su root %s", command);
}
private void executeShellCommand(String command) throws Exception {
executeShellCommand(command, /*asRoot=*/false);
}
private static ParcelFileDescriptor[] executeShellCommand(android.app.UiAutomation uiAutomation,
String command) throws IOException {
ParcelFileDescriptor[] result = new ParcelFileDescriptor[2];
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
ParcelFileDescriptor[] fds = uiAutomation.executeShellCommandRwe(command);
fds[1].close(); // close stdin
result[0] = fds[0]; // stdout
result[1] = fds[2]; // stderr
return result;
}
result[0] = uiAutomation.executeShellCommand(command);
return result;
}
private void executeShellCommand(String command, boolean asRoot) throws Exception {
android.app.UiAutomation uiAutomation = mInstrumentation.getUiAutomation();
String shellCommand = command;
if (asRoot) {
shellCommand = getSuCommand(command);
}
Log.d("Shell command: ", shellCommand);
ParcelFileDescriptor[] fds = UiAutomation.executeShellCommand(uiAutomation, command);
ParcelFileDescriptor fdOut = fds[0];
ParcelFileDescriptor fdErr = fds[1];
String out = readStreamAndClose(fdOut);
Log.d("Shell out: ", out);
String err = readStreamAndClose(fdErr);
if (!err.isEmpty()) {
Log.e("Shell err: ", err);
String msg = String.format("Shell command '%s' failed with error: '%s'", command, err);
throw new Exception(msg);
}
}
private static String readStreamAndClose(ParcelFileDescriptor fd) throws IOException {
if (fd == null) {
return "";
}
try (BufferedReader in = new BufferedReader(new InputStreamReader(
new ParcelFileDescriptor.AutoCloseInputStream(fd)))) {
StringBuilder sb = new StringBuilder();
while (true) {
String line = in.readLine();
if (line == null) {
break;
}
sb.append(line);
sb.append('\n');
}
return sb.toString();
}
}
}

View File

@ -6,7 +6,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.2.1'
classpath 'com.android.tools.build:gradle:8.4.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip