Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix applaunch workload #1261

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 50 additions & 1 deletion wa/workloads/applaunch/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We tend to try and avoid hardcoding paths to support different configurations if possible.
Currently we have the default self.executables_directory set to /data/local/tmp/bin. I'm wondering if these directories would share the same requirements which we could also make use of here rather than setting this parameter in two locations?

If not perhaps this be a workload Parameter with a default so a user can understand the requirements for the directory and optionally change this for their system if required?

[1] https://github.com/ARM-software/devlib/blob/7276097d4e12ff2b3cfa1bb0ba40cee24ae3372b/devlib/target.py#L2532

Copy link
Author

@kbaladurin kbaladurin May 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main requirement for assert_directory for applaunch workload is a ability to create readonly files there and looks like executables_directory in general doesn't require it. But adding a new parameter for applauch workload with a default value is a good idea, thank you!

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):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about this a bit more, do other workloads that require root work on these engineering versions if they are relying on the su -c format?

In devlib we try to detect [1] which su command (su -c or 'echo {} | su) [2] we should use for a device so I'm wondering if it would be beneficial to move this check to the common code and then we can query it from here?

[1] https://github.com/ARM-software/devlib/blob/master/devlib/utils/android.py#L466
[2] https://github.com/ARM-software/devlib/blob/master/devlib/utils/android.py#L258

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think moving this check to devlib is a good idea, thank you! I will create a PR for it.

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(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing import for 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
Expand All @@ -112,6 +160,7 @@ def pass_parameters(self):
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
Expand Down
Binary file modified wa/workloads/applaunch/com.arm.wa.uiauto.applaunch.apk
Binary file not shown.
14 changes: 4 additions & 10 deletions wa/workloads/applaunch/uiauto/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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')
}
Original file line number Diff line number Diff line change
@@ -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">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,7 +32,10 @@
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;

Expand All @@ -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);
Expand Down Expand Up @@ -153,7 +159,6 @@ private class AppLaunch {
private String testTag;
private String launchCommand;
private ActionLogger logger;
Process launch_p;

public AppLaunch(String testTag, String launchCommand) {
this.testTag = testTag;
Expand All @@ -169,24 +174,13 @@ public void startLaunch() throws Exception{

// 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();
}
}

Expand All @@ -203,15 +197,14 @@ else if(applaunchType.equals("launch_from_long-idle")) {

// 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();
Expand All @@ -220,15 +213,80 @@ public void killBackground() throws Exception{

// Drop the caches
public void dropCaches() throws Exception{
Process sync;
sync = Runtime.getRuntime().exec(new String[] { "su", "-c", "sync"});
sync.waitFor();
sync.destroy();

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();
executeShellCommand("sync", /*asRoot=*/true);
executeShellCommand("echo 3 > /proc/sys/vm/drop_caches", /*asRoot=*/true);
}

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();
}
}
}
2 changes: 1 addition & 1 deletion wa/workloads/applaunch/uiauto/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading