mirror of
https://github.com/ARM-software/workload-automation.git
synced 2025-01-31 02:01:16 +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:
parent
e0bf7668b8
commit
3f5707e63c
@ -14,7 +14,7 @@
|
|||||||
#
|
#
|
||||||
# pylint: disable=attribute-defined-outside-init
|
# pylint: disable=attribute-defined-outside-init
|
||||||
|
|
||||||
from wa import ApkUiautoWorkload, Parameter
|
from wa import ApkUiautoWorkload, Parameter, TargetError
|
||||||
from wa.framework import pluginloader
|
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):
|
def init_resources(self, context):
|
||||||
super(Applaunch, self).init_resources(context)
|
super(Applaunch, self).init_resources(context)
|
||||||
self.workload_params['markers_enabled'] = True
|
self.workload_params['markers_enabled'] = True
|
||||||
@ -112,6 +160,7 @@ class Applaunch(ApkUiautoWorkload):
|
|||||||
self.gui.uiauto_params['launch_activity'] = "None"
|
self.gui.uiauto_params['launch_activity'] = "None"
|
||||||
self.gui.uiauto_params['applaunch_type'] = self.applaunch_type
|
self.gui.uiauto_params['applaunch_type'] = self.applaunch_type
|
||||||
self.gui.uiauto_params['applaunch_iterations'] = self.applaunch_iterations
|
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):
|
def setup(self, context):
|
||||||
self.workload.gui.uiauto_params['package_name'] = self.workload.apk.apk_info.package
|
self.workload.gui.uiauto_params['package_name'] = self.workload.apk.apk_info.package
|
||||||
|
Binary file not shown.
@ -3,12 +3,12 @@ apply plugin: 'com.android.application'
|
|||||||
def packageName = "com.arm.wa.uiauto.applaunch"
|
def packageName = "com.arm.wa.uiauto.applaunch"
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 28
|
namespace "${packageName}"
|
||||||
buildToolsVersion "28.0.3"
|
compileSdkVersion 34
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "${packageName}"
|
applicationId "${packageName}"
|
||||||
minSdkVersion 18
|
minSdkVersion 18
|
||||||
targetSdkVersion 28
|
targetSdkVersion 34
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@ -25,11 +25,5 @@ dependencies {
|
|||||||
implementation 'com.android.support.test:runner:0.5'
|
implementation 'com.android.support.test:runner:0.5'
|
||||||
implementation 'com.android.support.test:rules:0.5'
|
implementation 'com.android.support.test:rules:0.5'
|
||||||
implementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
|
implementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
|
||||||
implementation(name: 'uiauto', ext:'aar')
|
implementation files('libs/uiauto.aar')
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
flatDir {
|
|
||||||
dirs 'libs'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.arm.wa.uiauto.applaunch"
|
|
||||||
android:versionCode="1"
|
android:versionCode="1"
|
||||||
android:versionName="1.0"
|
android:versionName="1.0"
|
||||||
android:allowBackup="false">
|
android:allowBackup="false">
|
||||||
|
@ -15,7 +15,9 @@
|
|||||||
|
|
||||||
package com.arm.wa.uiauto.applaunch;
|
package com.arm.wa.uiauto.applaunch;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
import android.support.test.runner.AndroidJUnit4;
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
import android.support.test.uiautomator.UiObject;
|
import android.support.test.uiautomator.UiObject;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@ -30,7 +32,10 @@ import org.junit.Before;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
import dalvik.system.DexClassLoader;
|
import dalvik.system.DexClassLoader;
|
||||||
|
|
||||||
@ -52,18 +57,19 @@ public class UiAutomation extends BaseUiAutomation {
|
|||||||
protected Bundle parameters;
|
protected Bundle parameters;
|
||||||
protected String packageName;
|
protected String packageName;
|
||||||
protected String packageID;
|
protected String packageID;
|
||||||
|
protected boolean suHasCommandOption;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void initialize() throws Exception {
|
public void initialize() throws Exception {
|
||||||
parameters = getParams();
|
parameters = getParams();
|
||||||
packageID = getPackageID(parameters);
|
packageID = getPackageID(parameters);
|
||||||
|
|
||||||
|
suHasCommandOption = parameters.getBoolean("su_has_command_option");
|
||||||
|
|
||||||
// Get workload apk file parameters
|
// Get workload apk file parameters
|
||||||
String packageName = parameters.getString("package_name");
|
packageName = parameters.getString("package_name");
|
||||||
String workload = parameters.getString("workload");
|
String workload = parameters.getString("workload");
|
||||||
String workloadAPKPath = parameters.getString("workdir");
|
String workloadAPKFile = parameters.getString("workload_apk");
|
||||||
String workloadName = String.format("com.arm.wa.uiauto.%1s.apk", workload);
|
|
||||||
String workloadAPKFile = String.format("%1s/%2s", workloadAPKPath, workloadName);
|
|
||||||
|
|
||||||
// Load the apk file
|
// Load the apk file
|
||||||
File apkFile = new File(workloadAPKFile);
|
File apkFile = new File(workloadAPKFile);
|
||||||
@ -153,7 +159,6 @@ public class UiAutomation extends BaseUiAutomation {
|
|||||||
private String testTag;
|
private String testTag;
|
||||||
private String launchCommand;
|
private String launchCommand;
|
||||||
private ActionLogger logger;
|
private ActionLogger logger;
|
||||||
Process launch_p;
|
|
||||||
|
|
||||||
public AppLaunch(String testTag, String launchCommand) {
|
public AppLaunch(String testTag, String launchCommand) {
|
||||||
this.testTag = testTag;
|
this.testTag = testTag;
|
||||||
@ -169,24 +174,13 @@ public class UiAutomation extends BaseUiAutomation {
|
|||||||
|
|
||||||
// Launches the application.
|
// Launches the application.
|
||||||
public void launchMain() throws Exception{
|
public void launchMain() throws Exception{
|
||||||
launch_p = Runtime.getRuntime().exec(launchCommand);
|
executeShellCommand(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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marks the end of application launch of the workload.
|
// Marks the end of application launch of the workload.
|
||||||
public void endLaunch() throws Exception{
|
public void endLaunch() throws Exception{
|
||||||
waitObject(launchEndObject, launch_timeout);
|
waitObject(launchEndObject, launch_timeout);
|
||||||
logger.stop();
|
logger.stop();
|
||||||
launch_p.destroy();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,15 +197,14 @@ public class UiAutomation extends BaseUiAutomation {
|
|||||||
|
|
||||||
// Kills the application process
|
// Kills the application process
|
||||||
public void killApplication() throws Exception{
|
public void killApplication() throws Exception{
|
||||||
Process kill_p;
|
|
||||||
String command = String.format("am force-stop %s", packageName);
|
String command = String.format("am force-stop %s", packageName);
|
||||||
kill_p = Runtime.getRuntime().exec(new String[] { "su", "-c", command});
|
executeShellCommand(command);
|
||||||
kill_p.waitFor();
|
|
||||||
kill_p.destroy();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kills the background processes
|
// Kills the background processes
|
||||||
public void killBackground() throws Exception{
|
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;
|
Process kill_p;
|
||||||
kill_p = Runtime.getRuntime().exec("am kill-all");
|
kill_p = Runtime.getRuntime().exec("am kill-all");
|
||||||
kill_p.waitFor();
|
kill_p.waitFor();
|
||||||
@ -220,15 +213,80 @@ public class UiAutomation extends BaseUiAutomation {
|
|||||||
|
|
||||||
// Drop the caches
|
// Drop the caches
|
||||||
public void dropCaches() throws Exception{
|
public void dropCaches() throws Exception{
|
||||||
Process sync;
|
executeShellCommand("sync", /*asRoot=*/true);
|
||||||
sync = Runtime.getRuntime().exec(new String[] { "su", "-c", "sync"});
|
executeShellCommand("echo 3 > /proc/sys/vm/drop_caches", /*asRoot=*/true);
|
||||||
sync.waitFor();
|
}
|
||||||
sync.destroy();
|
|
||||||
|
|
||||||
Process drop_cache;
|
private String getSuCommand(String command) {
|
||||||
String command = "echo 3 > /proc/sys/vm/drop_caches";
|
if (suHasCommandOption) {
|
||||||
drop_cache = Runtime.getRuntime().exec(new String[] { "su", "-c", command});
|
return String.format("su -c '%s'", command);
|
||||||
drop_cache.waitFor();
|
}
|
||||||
drop_cache.destroy();
|
// 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ buildscript {
|
|||||||
google()
|
google()
|
||||||
}
|
}
|
||||||
dependencies {
|
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
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
|
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user