Fix speed-profile compilation in rules.

Bug: b/169651233

Test: CompilationFilterRuleTest
Change-Id: I01bd675f7263087b1847096f4ae280464a5f629c
diff --git a/libraries/health/rules/src/android/platform/test/rule/CompilationFilterRule.java b/libraries/health/rules/src/android/platform/test/rule/CompilationFilterRule.java
index 092aa8d..1316fe9 100644
--- a/libraries/health/rules/src/android/platform/test/rule/CompilationFilterRule.java
+++ b/libraries/health/rules/src/android/platform/test/rule/CompilationFilterRule.java
@@ -18,6 +18,7 @@
 
 import static java.util.stream.Collectors.joining;
 
+import android.os.SystemClock;
 import android.util.Log;
 import androidx.annotation.VisibleForTesting;
 
@@ -26,16 +27,23 @@
 import org.junit.runner.Description;
 import org.junit.runners.model.InitializationError;
 
+import java.util.HashSet;
+import java.util.Set;
+
 /** This rule compiles the applications with the specified filter, or skips if unspecified. */
 public class CompilationFilterRule extends TestWatcher {
     //
     private static final String LOG_TAG = CompilationFilterRule.class.getSimpleName();
     // Compilation constants
     @VisibleForTesting static final String COMPILE_CMD_FORMAT = "cmd package compile -f -m %s %s";
+    @VisibleForTesting static final String DUMP_PROFILE_CMD = "killall -s SIGUSR1 %s";
     private static final ImmutableList<String> COMPILE_FILTER_LIST =
             ImmutableList.of("speed", "speed-profile", "quicken", "verify");
+    @VisibleForTesting static final String SPEED_PROFILE_FILTER = "speed-profile";
+    private static final String PROFILE_SAVE_TIMEOUT = "profile-save-timeout";
     @VisibleForTesting static final String COMPILE_FILTER_OPTION = "compilation-filter";
     @VisibleForTesting static final String COMPILE_SUCCESS = "Success";
+    private static Set<String> mCompiledTests = new HashSet<>();
 
     private final String[] mApplications;
 
@@ -48,9 +56,12 @@
     }
 
     @Override
-    protected void starting(Description description) {
+    protected void finished(Description description) {
         // Identify the filter option to use.
         String filter = getArguments().getString(COMPILE_FILTER_OPTION);
+        // Default speed profile save timeout set to 5 secs.
+        long profileSaveTimeout = Integer
+                .parseInt(getArguments().getString(PROFILE_SAVE_TIMEOUT, "5000"));
         if (filter == null) {
             // No option provided, default to a no-op.
             Log.d(LOG_TAG, "Skipping complation because filter option is unset.");
@@ -61,13 +72,38 @@
                     String.format(
                             "Unknown compiler filter: %s, not part of %s", filter, filterOptions));
         }
-        // Compile each application in sequence.
-        for (String app : mApplications) {
-            String response = executeShellCommand(String.format(COMPILE_CMD_FORMAT, filter, app));
-            if (!response.contains(COMPILE_SUCCESS)) {
-                Log.d(LOG_TAG, String.format("Received response: %s", response));
-                throw new RuntimeException(String.format("Failed to compile %s.", app));
+        // Profile varies based on the test even for the same app. Tracking the test id to make
+        // sure the test compiled once after the first iteration of the test.
+        String testId = description.getDisplayName();
+        if (!mCompiledTests.contains(testId)) {
+            for (String app : mApplications) {
+                // For speed profile compilation, ART team recommended to wait for 5 secs when app
+                // is in the foreground, dump the profile, wait for another 5 secs before
+                // speed-profile compilation.
+                if (filter.equalsIgnoreCase(SPEED_PROFILE_FILTER)) {
+                    SystemClock.sleep(profileSaveTimeout);
+                    // Send SIGUSR1 to force dumping a profile.
+                    String response = executeShellCommand(String.format(DUMP_PROFILE_CMD, app));
+                    if (!response.isEmpty()) {
+                        Log.d(LOG_TAG,
+                                String.format("Received dump profile cmd response: %s", response));
+                        throw new RuntimeException(
+                                String.format("Failed to dump profile %s.", app));
+                    }
+                    // killall is async, wait few seconds to let the app save the profile.
+                    SystemClock.sleep(profileSaveTimeout);
+                }
+                String response = executeShellCommand(
+                        String.format(COMPILE_CMD_FORMAT, filter, app));
+                if (!response.contains(COMPILE_SUCCESS)) {
+                    Log.d(LOG_TAG, String.format("Received compile cmd response: %s", response));
+                    throw new RuntimeException(String.format("Failed to compile %s.", app));
+                } else {
+                    mCompiledTests.add(testId);
+                }
             }
+        } else {
+            Log.d(LOG_TAG, String.format("Test %s already compiled", testId));
         }
     }
 }
diff --git a/libraries/health/rules/tests/src/android/platform/test/rule/CompilationFilterRuleTest.java b/libraries/health/rules/tests/src/android/platform/test/rule/CompilationFilterRuleTest.java
index 42a444d..974c032 100644
--- a/libraries/health/rules/tests/src/android/platform/test/rule/CompilationFilterRuleTest.java
+++ b/libraries/health/rules/tests/src/android/platform/test/rule/CompilationFilterRuleTest.java
@@ -49,8 +49,8 @@
     public void testAppToCompile_badFilterThrows() throws Throwable {
         Bundle badFilterBundle = new Bundle();
         badFilterBundle.putString(CompilationFilterRule.COMPILE_FILTER_OPTION, "bad-option");
-        TestableCompilationFilterRule rule =
-                new TestableCompilationFilterRule(badFilterBundle, "example.package");
+        TestableCompilationFilterRule rule = new TestableCompilationFilterRule(badFilterBundle,
+                "example.package");
         try {
             rule.apply(rule.getTestStatement(), TEST_DESC).evaluate();
             fail("An exception should have been thrown about bad filter, but wasn't.");
@@ -58,19 +58,19 @@
         }
     }
 
-    /** Tests that this rule will compile one app before the test, if supplied. */
+    /** Tests that this rule will compile one app after the test, if supplied. */
     @Test
     public void testAppToCompile_failCompilationThrows() throws Throwable {
-        Bundle badFilterBundle = new Bundle();
-        badFilterBundle.putString(CompilationFilterRule.COMPILE_FILTER_OPTION, "speed");
-        TestableCompilationFilterRule rule =
-                new TestableCompilationFilterRule(badFilterBundle, "example.package") {
-                    @Override
-                    protected String executeShellCommand(String cmd) {
-                        super.executeShellCommand(cmd);
-                        return "Error";
-                    }
-                };
+        Bundle filterBundle = new Bundle();
+        filterBundle.putString(CompilationFilterRule.COMPILE_FILTER_OPTION, "speed");
+        TestableCompilationFilterRule rule = new TestableCompilationFilterRule(filterBundle,
+                "example.package") {
+            @Override
+            protected String executeShellCommand(String cmd) {
+                super.executeShellCommand(cmd);
+                return "Error";
+            }
+        };
         try {
             rule.apply(rule.getTestStatement(), TEST_DESC).evaluate();
             fail("An exception should have been thrown about compilation failure, but wasn't.");
@@ -78,48 +78,81 @@
         }
     }
 
-    /** Tests that this rule will compile one app before the test, if supplied. */
+    /** Tests that this rule will compile one app after the test, if supplied. */
     @Test
     public void testOneAppToCompile() throws Throwable {
-        Bundle badFilterBundle = new Bundle();
-        badFilterBundle.putString(CompilationFilterRule.COMPILE_FILTER_OPTION, "speed");
-        TestableCompilationFilterRule rule =
-                new TestableCompilationFilterRule(badFilterBundle, "example.package") {
-                    @Override
-                    protected String executeShellCommand(String cmd) {
-                        super.executeShellCommand(cmd);
-                        return CompilationFilterRule.COMPILE_SUCCESS;
-                    }
-                };
-        rule.apply(rule.getTestStatement(), TEST_DESC).evaluate();
-        String compileCmd =
-                String.format(CompilationFilterRule.COMPILE_CMD_FORMAT, "speed", "example.package");
-        assertThat(rule.getOperations()).containsExactly(compileCmd, "test").inOrder();
+        Bundle filterBundle = new Bundle();
+        filterBundle.putString(CompilationFilterRule.COMPILE_FILTER_OPTION, "speed");
+        TestableCompilationFilterRule rule = new TestableCompilationFilterRule(filterBundle,
+                "example.package") {
+            @Override
+            protected String executeShellCommand(String cmd) {
+                super.executeShellCommand(cmd);
+                return CompilationFilterRule.COMPILE_SUCCESS;
+            }
+        };
+        rule.apply(rule.getTestStatement(), Description.createTestDescription("clzz", "mthd1"))
+                .evaluate();
+        String compileCmd = String.format(CompilationFilterRule.COMPILE_CMD_FORMAT, "speed",
+                "example.package");
+        assertThat(rule.getOperations()).containsExactly("test", compileCmd)
+                .inOrder();
     }
 
-    /** Tests that this rule will compile multiple apps before the test, if supplied. */
+    /** Tests that this rule will compile multiple apps after the test, if supplied. */
     @Test
     public void testMultipleAppsToCompile() throws Throwable {
-        Bundle badFilterBundle = new Bundle();
-        badFilterBundle.putString(CompilationFilterRule.COMPILE_FILTER_OPTION, "speed");
-        TestableCompilationFilterRule rule =
-                new TestableCompilationFilterRule(
-                        badFilterBundle, "example.package1", "example.package2") {
-                    @Override
-                    protected String executeShellCommand(String cmd) {
-                        super.executeShellCommand(cmd);
-                        return CompilationFilterRule.COMPILE_SUCCESS;
-                    }
-                };
-        rule.apply(rule.getTestStatement(), TEST_DESC).evaluate();
-        String compileCmd1 =
-                String.format(
-                        CompilationFilterRule.COMPILE_CMD_FORMAT, "speed", "example.package1");
-        String compileCmd2 =
-                String.format(
-                        CompilationFilterRule.COMPILE_CMD_FORMAT, "speed", "example.package2");
+        Bundle filterBundle = new Bundle();
+        filterBundle.putString(CompilationFilterRule.COMPILE_FILTER_OPTION, "speed");
+        TestableCompilationFilterRule rule = new TestableCompilationFilterRule(filterBundle,
+                "example.package1", "example.package2") {
+            @Override
+            protected String executeShellCommand(String cmd) {
+                super.executeShellCommand(cmd);
+                return CompilationFilterRule.COMPILE_SUCCESS;
+            }
+        };
+        rule.apply(rule.getTestStatement(), Description.createTestDescription("clzz", "mthd2"))
+                .evaluate();
+        String compileCmd1 = String.format(
+                CompilationFilterRule.COMPILE_CMD_FORMAT, "speed", "example.package1");
+        String compileCmd2 = String.format(
+                CompilationFilterRule.COMPILE_CMD_FORMAT, "speed", "example.package2");
         assertThat(rule.getOperations())
-                .containsExactly(compileCmd1, compileCmd2, "test")
+                .containsExactly("test", compileCmd1, compileCmd2)
+                .inOrder();
+    }
+
+    /** Tests that this rule will speed profile compile multiple apps after the test,
+     *  if supplied. */
+    @Test
+    public void testMultipleAppsToCompileInSpeedProfile() throws Throwable {
+        Bundle filterBundle = new Bundle();
+        filterBundle.putString(CompilationFilterRule.COMPILE_FILTER_OPTION,
+                CompilationFilterRule.SPEED_PROFILE_FILTER);
+        TestableCompilationFilterRule rule = new TestableCompilationFilterRule(filterBundle,
+                "example.package1", "example.package2") {
+            @Override
+            protected String executeShellCommand(String cmd) {
+                super.executeShellCommand(cmd);
+                if (cmd.contains("killall -s SIGUSR1")) {
+                    return "";
+                }
+                return CompilationFilterRule.COMPILE_SUCCESS;
+            }
+        };
+        rule.apply(rule.getTestStatement(), Description.createTestDescription("clzz", "mthd3"))
+                .evaluate();
+        String dumpCmd1 = String.format(CompilationFilterRule.DUMP_PROFILE_CMD,
+                "example.package1");
+        String compileCmd1 = String.format(CompilationFilterRule.COMPILE_CMD_FORMAT,
+                CompilationFilterRule.SPEED_PROFILE_FILTER, "example.package1");
+        String dumpCmd2 = String.format(CompilationFilterRule.DUMP_PROFILE_CMD,
+                "example.package2");
+        String compileCmd2 = String.format(CompilationFilterRule.COMPILE_CMD_FORMAT,
+                CompilationFilterRule.SPEED_PROFILE_FILTER, "example.package2");
+        assertThat(rule.getOperations())
+                .containsExactly("test", dumpCmd1, compileCmd1, dumpCmd2, compileCmd2)
                 .inOrder();
     }