iorap_functional_test: Add iorap function test.
Changes:
* Check logcat instead of db to confirm each step.
* Include version invalidation.
Bug: 152443508
Bug: 148936893
Test: atest iorap-functional-tests on cuttlefish.
Change-Id: I27e8fc8705889a04cb6f38295b64a25c1aceb742
diff --git a/startop/iorap/functional_tests/Android.bp b/startop/iorap/functional_tests/Android.bp
index ce9dc32..ad85f14 100644
--- a/startop/iorap/functional_tests/Android.bp
+++ b/startop/iorap/functional_tests/Android.bp
@@ -15,6 +15,7 @@
android_test {
name: "iorap-functional-tests",
srcs: ["src/**/*.java"],
+ data: ["test_data/*"],
static_libs: [
// Non-test dependencies
// library under test
diff --git a/startop/iorap/functional_tests/AndroidTest.xml b/startop/iorap/functional_tests/AndroidTest.xml
index 3d5a229..31d4f6c 100644
--- a/startop/iorap/functional_tests/AndroidTest.xml
+++ b/startop/iorap/functional_tests/AndroidTest.xml
@@ -45,6 +45,20 @@
<option name="run-command" value="sleep 1" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="abort-on-push-failure" value="true" />
+ <option name="push-file"
+ key="iorap_test_app_v1.apk"
+ value="/data/misc/iorapd/iorap_test_app_v1.apk" />
+ <option name="push-file"
+ key="iorap_test_app_v2.apk"
+ value="/data/misc/iorapd/iorap_test_app_v2.apk" />
+ <option name="push-file"
+ key="iorap_test_app_v3.apk"
+ value="/data/misc/iorapd/iorap_test_app_v3.apk" />
+ </target_preparer>
+
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.google.android.startop.iorap.tests" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
diff --git a/startop/iorap/functional_tests/src/com/google/android/startop/iorap/IorapWorkFlowTest.java b/startop/iorap/functional_tests/src/com/google/android/startop/iorap/IorapWorkFlowTest.java
index 9abbcd7..c35dd3b 100644
--- a/startop/iorap/functional_tests/src/com/google/android/startop/iorap/IorapWorkFlowTest.java
+++ b/startop/iorap/functional_tests/src/com/google/android/startop/iorap/IorapWorkFlowTest.java
@@ -37,45 +37,46 @@
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.Until;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.time.Duration;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
+import java.util.Date;
import java.util.function.BooleanSupplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.List;
-
+import java.text.SimpleDateFormat;
/**
* Test for the work flow of iorap.
*
- * <p> This test tests the function of iorap from perfetto collection -> compilation ->
- * prefetching.
- * </p>
+ * <p> This test tests the function of iorap from:
+ * perfetto collection -> compilation -> prefetching -> version update -> perfetto collection.
*/
@RunWith(AndroidJUnit4.class)
public class IorapWorkFlowTest {
-
private static final String TAG = "IorapWorkFlowTest";
- private static final String TEST_PACKAGE_NAME = "com.android.settings";
- private static final String TEST_ACTIVITY_NAME = "com.android.settings.Settings";
+ private static final String TEST_APP_VERSION_ONE_PATH = "/data/misc/iorapd/iorap_test_app_v1.apk";
+ private static final String TEST_APP_VERSION_TWO_PATH = "/data/misc/iorapd/iorap_test_app_v2.apk";
+ private static final String TEST_APP_VERSION_THREE_PATH = "/data/misc/iorapd/iorap_test_app_v3.apk";
private static final String DB_PATH = "/data/misc/iorapd/sqlite.db";
private static final Duration TIMEOUT = Duration.ofSeconds(300L);
- private static final String READAHEAD_INDICATOR =
- "Description = /data/misc/iorapd/com.android.settings/-?\\d+/com.android.settings.Settings/compiled_traces/compiled_trace.pb";
-
private UiDevice mDevice;
@Before
- public void startMainActivityFromHomeScreen() throws Exception {
+ public void setUp() throws Exception {
// Initialize UiDevice instance
mDevice = UiDevice.getInstance(getInstrumentation());
@@ -88,21 +89,81 @@
mDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), TIMEOUT.getSeconds());
}
+ @After
+ public void tearDown() throws Exception {
+ String packageName = "com.example.ioraptestapp";
+ uninstallApk(packageName);
+ }
+
@Test (timeout = 300000)
- public void testApp() throws Exception {
+ public void testNormalWorkFlow() throws Exception {
assertThat(mDevice, notNullValue());
+ // Install test app version one
+ installApk(TEST_APP_VERSION_ONE_PATH);
+ String packageName = "com.example.ioraptestapp";
+ String activityName = "com.example.ioraptestapp.MainActivity";
+
// Perfetto trace collection phase.
- assertTrue(startAppForPerfettoTrace(/*expectPerfettoTraceCount=*/1));
- assertTrue(startAppForPerfettoTrace(/*expectPerfettoTraceCount=*/2));
- assertTrue(startAppForPerfettoTrace(/*expectPerfettoTraceCount=*/3));
- assertTrue(checkPerfettoTracesExistence(TIMEOUT, 3));
+ assertTrue(startAppForPerfettoTrace(
+ packageName, activityName, /*version=*/1L));
+ assertTrue(startAppForPerfettoTrace(
+ packageName, activityName, /*version=*/1L));
+ assertTrue(startAppForPerfettoTrace(
+ packageName, activityName, /*version=*/1L));
// Trigger maintenance service for compilation.
- assertTrue(compile(TIMEOUT));
+ TimeUnit.SECONDS.sleep(5L);
+ assertTrue(compile(packageName, activityName, /*version=*/1L));
- // Check if prefetching works.
- assertTrue(waitForPrefetchingFromLogcat(/*expectPerfettoTraceCount=*/3));
+ // Run app with prefetching
+ assertTrue(startAppWithCompiledTrace(
+ packageName, activityName, /*version=*/1L));
+ }
+
+ @Test (timeout = 300000)
+ public void testUpdateApp() throws Exception {
+ assertThat(mDevice, notNullValue());
+
+ // Install test app version two,
+ String packageName = "com.example.ioraptestapp";
+ String activityName = "com.example.ioraptestapp.MainActivity";
+ installApk(TEST_APP_VERSION_TWO_PATH);
+
+ // Perfetto trace collection phase.
+ assertTrue(startAppForPerfettoTrace(
+ packageName, activityName, /*version=*/2L));
+ assertTrue(startAppForPerfettoTrace(
+ packageName, activityName, /*version=*/2L));
+ assertTrue(startAppForPerfettoTrace(
+ packageName, activityName, /*version=*/2L));
+
+ // Trigger maintenance service for compilation.
+ TimeUnit.SECONDS.sleep(5L);
+ assertTrue(compile(packageName, activityName, /*version=*/2L));
+
+ // Run app with prefetching
+ assertTrue(startAppWithCompiledTrace(
+ packageName, activityName, /*version=*/2L));
+
+ // Update test app to version 3
+ installApk(TEST_APP_VERSION_THREE_PATH);
+
+ // Rerun app, should do pefetto tracing.
+ assertTrue(startAppForPerfettoTrace(
+ packageName, activityName, /*version=*/3L));
+ }
+
+ private static void installApk(String apkPath) throws Exception {
+ // Disable the selinux to allow pm install apk in the dir.
+ executeShellCommand("setenforce 0");
+ executeShellCommand("pm install -r -d " + apkPath);
+ executeShellCommand("setenforce 1");
+
+ }
+
+ private static void uninstallApk(String apkPath) throws Exception {
+ executeShellCommand("pm uninstall " + apkPath);
}
/**
@@ -110,43 +171,81 @@
*
* @param expectPerfettoTraceCount is the expected count of perfetto traces.
*/
- private boolean startAppForPerfettoTrace(long expectPerfettoTraceCount)
+ private boolean startAppForPerfettoTrace(
+ String packageName, String activityName, long version)
throws Exception {
- // Close the specified app if it's open
- closeApp();
- // Launch the specified app
- startApp();
- // Wait for the app to appear
- mDevice.wait(Until.hasObject(By.pkg(TEST_PACKAGE_NAME).depth(0)), TIMEOUT.getSeconds());
+ LogcatTimestamp timestamp = runAppOnce(packageName, activityName);
+ return waitForPerfettoTraceSavedFromLogcat(
+ packageName, activityName, version, timestamp);
+ }
- String sql = "SELECT COUNT(*) FROM activities "
- + "JOIN app_launch_histories ON activities.id = app_launch_histories.activity_id "
- + "JOIN raw_traces ON raw_traces.history_id = app_launch_histories.id "
- + "WHERE activities.name = ?";
- return checkAndWaitEntriesNum(sql, new String[]{TEST_ACTIVITY_NAME}, expectPerfettoTraceCount,
- TIMEOUT);
+ private boolean startAppWithCompiledTrace(
+ String packageName, String activityName, long version)
+ throws Exception {
+ LogcatTimestamp timestamp = runAppOnce(packageName, activityName);
+ return waitForPrefetchingFromLogcat(
+ packageName, activityName, version, timestamp);
+ }
+
+ private LogcatTimestamp runAppOnce(String packageName, String activityName) throws Exception {
+ // Close the specified app if it's open
+ closeApp(packageName);
+ LogcatTimestamp timestamp = new LogcatTimestamp();
+ // Launch the specified app
+ startApp(packageName, activityName);
+ // Wait for the app to appear
+ mDevice.wait(Until.hasObject(By.pkg(packageName).depth(0)), TIMEOUT.getSeconds());
+ return timestamp;
}
// Invokes the maintenance to compile the perfetto traces to compiled trace.
- private boolean compile(Duration timeout) throws Exception {
+ private boolean compile(
+ String packageName, String activityName, long version) throws Exception {
// The job id (283673059) is defined in class IorapForwardingService.
- executeShellCommand("cmd jobscheduler run -f android 283673059");
+ executeShellCommandViaTmpFile("cmd jobscheduler run -f android 283673059");
+ return waitForFileExistence(getCompiledTracePath(packageName, activityName, version));
+ }
- // Wait for the compilation.
- String sql = "SELECT COUNT(*) FROM activities JOIN prefetch_files ON "
- + "activities.id = prefetch_files.activity_id "
- + "WHERE activities.name = ?";
- boolean result = checkAndWaitEntriesNum(sql, new String[]{TEST_ACTIVITY_NAME}, /*count=*/1,
- timeout);
- if (!result) {
- return false;
+ private String getCompiledTracePath(
+ String packageName, String activityName, long version) {
+ return String.format(
+ "/data/misc/iorapd/%s/%d/%s/compiled_traces/compiled_trace.pb",
+ packageName, version, activityName);
+ }
+
+ /**
+ * Starts the testing app.
+ */
+ private void startApp(String packageName, String activityName) throws Exception {
+ executeShellCommandViaTmpFile(
+ String.format("am start %s/%s", packageName, activityName));
+ }
+
+ /**
+ * Closes the testing app.
+ * <p> Keep trying to kill the process of the app until no process of the app package
+ * appears.</p>
+ */
+ private void closeApp(String packageName) throws Exception {
+ while (true) {
+ String pid = executeShellCommand("pidof " + packageName);
+ if (pid.isEmpty()) {
+ Log.i(TAG, "Closed app " + packageName);
+ return;
+ }
+ executeShellCommand("kill -9 " + pid);
+ TimeUnit.SECONDS.sleep(1L);
}
+ }
- return retryWithTimeout(timeout, () -> {
+ /** Waits for a file to appear. */
+ private boolean waitForFileExistence(String fileName) throws Exception {
+ return retryWithTimeout(TIMEOUT, () -> {
try {
- String compiledTrace = getCompiledTraceFilePath();
- File compiledTraceLocal = copyFileToLocal(compiledTrace, "compiled_trace.tmp");
- return compiledTraceLocal.exists();
+ String fileExists = executeShellCommandViaTmpFile(
+ String.format("test -f %s; echo $?", fileName));
+ Log.i(TAG, fileName + " existence is " + fileExists);
+ return fileExists.trim().equals("0");
} catch (Exception e) {
Log.i(TAG, e.getMessage());
return false;
@@ -154,92 +253,96 @@
});
}
- /**
- * Check if all the perfetto traces in the db exist.
- */
- private boolean checkPerfettoTracesExistence(Duration timeout, int expectPerfettoTraceCount)
+ /** Waits for the perfetto trace saved message from logcat. */
+ private boolean waitForPerfettoTraceSavedFromLogcat(
+ String packageName, String activityName, long version, LogcatTimestamp timestamp)
throws Exception {
- return retryWithTimeout(timeout, () -> {
- try {
- File dbFile = getIorapDb();
- List<String> traces = getPerfettoTracePaths(dbFile);
- assertEquals(traces.size(), expectPerfettoTraceCount);
+ Pattern p = Pattern.compile(".*"
+ + getPerfettoTraceSavedIndicator(packageName, activityName, version)
+ + "(.*[.]perfetto_trace[.]pb)\n.*", Pattern.DOTALL);
- int count = 0;
- for (String trace : traces) {
- File tmp = copyFileToLocal(trace, "perfetto_trace.tmp" + count);
- ++count;
- Log.i(TAG, "Check perfetto trace: " + trace);
- if (!tmp.exists()) {
- Log.i(TAG, "Perfetto trace does not exist: " + trace);
- return false;
- }
+ return retryWithTimeout(TIMEOUT, () -> {
+ try {
+ String log = timestamp.getLogcatAfter();
+ Matcher m = p.matcher(log);
+ Log.d(TAG, "Tries to find perfetto trace...");
+ if (!m.matches()) {
+ Log.i(TAG, "Cannot find perfetto trace saved in log.");
+ return false;
}
+ String filePath = m.group(1);
+ Log.i(TAG, "Perfetto trace is saved to " + filePath);
return true;
- } catch (Exception e) {
- Log.i(TAG, e.getMessage());
+ } catch(Exception e) {
+ Log.e(TAG, e.getMessage());
return false;
}
- });
+ });
+ }
+
+ private String getPerfettoTraceSavedIndicator(
+ String packageName, String activityName, long version) {
+ return String.format(
+ "Perfetto TraceBuffer saved to file: /data/misc/iorapd/%s/%d/%s/raw_traces/",
+ packageName, version, activityName);
}
/**
- * Gets the perfetto traces file path from the db.
- */
- private List<String> getPerfettoTracePaths(File dbFile) throws Exception {
- String sql = "SELECT raw_traces.file_path FROM activities "
- + "JOIN app_launch_histories ON activities.id = app_launch_histories.activity_id "
- + "JOIN raw_traces ON raw_traces.history_id = app_launch_histories.id "
- + "WHERE activities.name = ?";
-
- List<String> perfettoTraces = new ArrayList<>();
- try (SQLiteDatabase db = SQLiteDatabase
- .openDatabase(dbFile.getPath(), null, SQLiteDatabase.OPEN_READONLY)) {
- Cursor cursor = db.rawQuery(sql, new String[]{TEST_ACTIVITY_NAME});
- while (cursor.moveToNext()) {
- perfettoTraces.add(cursor.getString(0));
- }
- }
- return perfettoTraces;
- }
-
- private String getCompiledTraceFilePath() throws Exception {
- File dbFile = getIorapDb();
- try (SQLiteDatabase db = SQLiteDatabase
- .openDatabase(dbFile.getPath(), null, SQLiteDatabase.OPEN_READONLY)) {
- String sql = "SELECT prefetch_files.file_path FROM activities JOIN prefetch_files ON "
- + "activities.id = prefetch_files.activity_id "
- + "WHERE activities.name = ?";
- return DatabaseUtils.stringForQuery(db, sql, new String[]{TEST_ACTIVITY_NAME});
- }
- }
-
- /**
- * Checks the number of entries in the database table.
+ * Waits for the prefetching log in the logcat.
*
- * <p> Keep checking until the timeout.
+ * <p> When prefetching works, the perfetto traces should not be collected. </p>
*/
- private boolean checkAndWaitEntriesNum(String sql, String[] selectionArgs, long count,
- Duration timeout)
+ private boolean waitForPrefetchingFromLogcat(
+ String packageName, String activityName, long version, LogcatTimestamp timestamp)
throws Exception {
- return retryWithTimeout(timeout, () -> {
+ Pattern p = Pattern.compile(
+ ".*" + getReadaheadIndicator(packageName, activityName, version) +
+ ".*Total File Paths=(\\d+) \\(good: (\\d+[.]?\\d*)%\\)\n"
+ + ".*Total Entries=(\\d+) \\(good: (\\d+[.]?\\d*)%\\)\n"
+ + ".*Total Bytes=(\\d+) \\(good: (\\d+[.]?\\d*)%\\).*",
+ Pattern.DOTALL);
+
+ return retryWithTimeout(TIMEOUT, () -> {
try {
- File db = getIorapDb();
- long curCount = getEntriesNum(db, selectionArgs, sql);
- Log.i(TAG, String
- .format("For %s, current count is %d, expected count is :%d.", sql, curCount,
- count));
- return curCount == count;
- } catch (Exception e) {
- Log.i(TAG, e.getMessage());
+ String log = timestamp.getLogcatAfter();
+ Matcher m = p.matcher(log);
+ if (!m.matches()) {
+ Log.i(TAG, "Cannot find readahead log.");
+ return false;
+ }
+
+ int totalFilePath = Integer.parseInt(m.group(1));
+ float totalFilePathGoodRate = Float.parseFloat(m.group(2)) / 100;
+ int totalEntries = Integer.parseInt(m.group(3));
+ float totalEntriesGoodRate = Float.parseFloat(m.group(4)) / 100;
+ int totalBytes = Integer.parseInt(m.group(5));
+ float totalBytesGoodRate = Float.parseFloat(m.group(6)) / 100;
+
+ Log.i(TAG, String.format(
+ "totalFilePath: %d (good %.2f) totalEntries: %d (good %.2f) totalBytes: %d (good %.2f)",
+ totalFilePath, totalFilePathGoodRate, totalEntries, totalEntriesGoodRate, totalBytes,
+ totalBytesGoodRate));
+
+ return totalFilePath > 0 &&
+ totalEntries > 0 &&
+ totalBytes > 0 &&
+ totalFilePathGoodRate > 0.5 &&
+ totalEntriesGoodRate > 0.5 &&
+ totalBytesGoodRate > 0.5;
+ } catch(Exception e) {
return false;
}
- });
+ });
}
- /**
- * Retry until timeout.
- */
+ private static String getReadaheadIndicator(
+ String packageName, String activityName, long version) {
+ return String.format(
+ "Description = /data/misc/iorapd/%s/%d/%s/compiled_traces/compiled_trace.pb",
+ packageName, version, activityName);
+ }
+
+ /** Retry until timeout. */
private boolean retryWithTimeout(Duration timeout, BooleanSupplier supplier) throws Exception {
long totalSleepTimeSeconds = 0L;
long sleepIntervalSeconds = 2L;
@@ -256,113 +359,28 @@
}
/**
- * Gets the number of entries in the query of sql.
- */
- private long getEntriesNum(File dbFile, String[] selectionArgs, String sql) throws Exception {
- try (SQLiteDatabase db = SQLiteDatabase
- .openDatabase(dbFile.getPath(), null, SQLiteDatabase.OPEN_READONLY)) {
- return DatabaseUtils.longForQuery(db, sql, selectionArgs);
- }
- }
-
- /**
- * Gets the iorapd sqlite db file.
+ * Executes command in adb shell via a tmp file.
*
- * <p> The test cannot access the db file directly under "/data/misc/iorapd".
- * Copy it to the local directory and change the mode.
+ * <p> This should be run as root.</p>
*/
- private File getIorapDb() throws Exception {
- File tmpDb = copyFileToLocal("/data/misc/iorapd/sqlite.db", "tmp.db");
- // Change the mode of the file to allow the access from test.
- executeShellCommand("chmod 777 " + tmpDb.getPath());
- return tmpDb;
- }
-
- /**
- * Copys a file to local directory.
- */
- private File copyFileToLocal(String src, String tgtFileName) throws Exception {
- File localDir = getApplicationContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE);
- File localFile = new File(localDir, tgtFileName);
- executeShellCommand(String.format("cp %s %s", src, localFile.getPath()));
- return localFile;
- }
-
- /**
- * Starts the testing app.
- */
- private void startApp() throws Exception {
- Context context = getApplicationContext();
- final Intent intent = context.getPackageManager()
- .getLaunchIntentForPackage(TEST_PACKAGE_NAME);
- context.startActivity(intent);
- Log.i(TAG, "Started app " + TEST_PACKAGE_NAME);
- }
-
- /**
- * Closes the testing app.
- * <p> Keep trying to kill the process of the app until no process of the app package
- * appears.</p>
- */
- private void closeApp() throws Exception {
- while (true) {
- String pid = executeShellCommand("pidof " + TEST_PACKAGE_NAME);
- if (pid.isEmpty()) {
- Log.i(TAG, "Closed app " + TEST_PACKAGE_NAME);
- return;
+ private static String executeShellCommandViaTmpFile(String cmd) throws Exception {
+ Log.i(TAG, "Execute via tmp file: " + cmd);
+ Path tmp = null;
+ try {
+ tmp = Files.createTempFile(/*prefix=*/null, /*suffix=*/".sh");
+ Files.write(tmp, cmd.getBytes(StandardCharsets.UTF_8));
+ tmp.toFile().setExecutable(true);
+ return UiDevice.getInstance(
+ InstrumentationRegistry.getInstrumentation()).
+ executeShellCommand(tmp.toString());
+ } finally {
+ if (tmp != null) {
+ Files.delete(tmp);
}
- executeShellCommand("kill -9 " + pid);
- TimeUnit.SECONDS.sleep(1L);
}
}
/**
- * Waits for the prefetching log in the logcat.
- *
- * <p> When prefetching works, the perfetto traces should not be collected. </p>
- */
- private boolean waitForPrefetchingFromLogcat(long expectPerfettoTraceCount) throws Exception {
- if (!startAppForPerfettoTrace(expectPerfettoTraceCount)) {
- return false;
- }
-
- String log = executeShellCommand("logcat -d");
-
- Pattern p = Pattern.compile(
- ".*" + READAHEAD_INDICATOR
- + ".*Total File Paths=(\\d+) \\(good: (\\d+[.]?\\d*)%\\)\n"
- + ".*Total Entries=(\\d+) \\(good: (\\d+[.]?\\d*)%\\)\n"
- + ".*Total Bytes=(\\d+) \\(good: (\\d+[.]?\\d*)%\\).*",
- Pattern.DOTALL);
- Matcher m = p.matcher(log);
-
- if (!m.matches()) {
- Log.i(TAG, "Cannot find readahead log.");
- return false;
- }
-
- int totalFilePath = Integer.parseInt(m.group(1));
- float totalFilePathGoodRate = Float.parseFloat(m.group(2)) / 100;
- int totalEntries = Integer.parseInt(m.group(3));
- float totalEntriesGoodRate = Float.parseFloat(m.group(4)) / 100;
- int totalBytes = Integer.parseInt(m.group(5));
- float totalBytesGoodRate = Float.parseFloat(m.group(6)) / 100;
-
- Log.i(TAG, String.format(
- "totalFilePath: %d (good %.2f) totalEntries: %d (good %.2f) totalBytes: %d (good %.2f)",
- totalFilePath, totalFilePathGoodRate, totalEntries, totalEntriesGoodRate, totalBytes,
- totalBytesGoodRate));
-
- return totalFilePath > 0 &&
- totalEntries > 0 &&
- totalBytes > 100000 &&
- totalFilePathGoodRate > 0.5 &&
- totalEntriesGoodRate > 0.5 &&
- totalBytesGoodRate > 0.5;
- }
-
-
- /**
* Executes command in adb shell.
*
* <p> This should be run as root.</p>
@@ -372,6 +390,27 @@
return UiDevice.getInstance(
InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd);
}
-}
+ static class LogcatTimestamp {
+ private String epochTime;
+
+ public LogcatTimestamp() throws Exception{
+ long currentTimeMillis = System.currentTimeMillis();
+ epochTime = String.format(
+ "%d.%d", currentTimeMillis/1000, currentTimeMillis%1000);
+ Log.i(TAG, "Current logcat timestamp is " + epochTime);
+ }
+
+ // For example, 1585264100.000
+ public String getEpochTime() {
+ return epochTime;
+ }
+
+ // Gets the logcat after this epoch time.
+ public String getLogcatAfter() throws Exception {
+ return executeShellCommandViaTmpFile(
+ "logcat -v epoch -t '" + epochTime + "'");
+ }
+ }
+}
diff --git a/startop/iorap/functional_tests/test_data/iorap_test_app_v1.apk b/startop/iorap/functional_tests/test_data/iorap_test_app_v1.apk
new file mode 120000
index 0000000..1c1a437
--- /dev/null
+++ b/startop/iorap/functional_tests/test_data/iorap_test_app_v1.apk
@@ -0,0 +1 @@
+../../../../../../packages/modules/ArtPrebuilt/iorap/test/iorap_test_app_v1.apk
\ No newline at end of file
diff --git a/startop/iorap/functional_tests/test_data/iorap_test_app_v2.apk b/startop/iorap/functional_tests/test_data/iorap_test_app_v2.apk
new file mode 120000
index 0000000..7cd41c4
--- /dev/null
+++ b/startop/iorap/functional_tests/test_data/iorap_test_app_v2.apk
@@ -0,0 +1 @@
+../../../../../../packages/modules/ArtPrebuilt/iorap/test/iorap_test_app_v2.apk
\ No newline at end of file
diff --git a/startop/iorap/functional_tests/test_data/iorap_test_app_v3.apk b/startop/iorap/functional_tests/test_data/iorap_test_app_v3.apk
new file mode 120000
index 0000000..7f4e996
--- /dev/null
+++ b/startop/iorap/functional_tests/test_data/iorap_test_app_v3.apk
@@ -0,0 +1 @@
+../../../../../../packages/modules/ArtPrebuilt/iorap/test/iorap_test_app_v3.apk
\ No newline at end of file