SkQP: run a single test

PLEASE NOTE:  Instructions on running `am instrument` for the
SkQP APK have changed.

To run a single test, see the section "Running a single test"
in `tools/skqp/README.md`.

No-Try: true
Change-Id: I0a2cbc47755929d6c6a927a3591ff98046779c77
Reviewed-on: https://skia-review.googlesource.com/108780
Reviewed-by: Derek Sollenberger <djsollen@google.com>
Commit-Queue: Hal Canary <halcanary@google.com>
(cherry picked from commit f637cc01f8f28d920a1d5b792193e97e8dd63e42)
Reviewed-on: https://skia-review.googlesource.com/109166
Reviewed-by: Hal Canary <halcanary@google.com>
diff --git a/platform_tools/android/apps/skqp/src/main/AndroidManifest.xml b/platform_tools/android/apps/skqp/src/main/AndroidManifest.xml
index 0459522..c638a87 100644
--- a/platform_tools/android/apps/skqp/src/main/AndroidManifest.xml
+++ b/platform_tools/android/apps/skqp/src/main/AndroidManifest.xml
@@ -29,6 +29,6 @@
       </activity>
       <uses-library android:name="android.test.runner" />
   </application>
-  <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+  <instrumentation android:name="org.skia.skqp.SkQPAndroidRunner"
                    android:targetPackage="org.skia.skqp"></instrumentation>
 </manifest>
diff --git a/platform_tools/android/apps/skqp/src/main/java/org/skia/skqp/SkQPAndroidRunner.java b/platform_tools/android/apps/skqp/src/main/java/org/skia/skqp/SkQPAndroidRunner.java
new file mode 100644
index 0000000..b5711f1
--- /dev/null
+++ b/platform_tools/android/apps/skqp/src/main/java/org/skia/skqp/SkQPAndroidRunner.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+package org.skia.skqp;
+
+import android.os.Bundle;
+import android.support.test.runner.AndroidJUnitRunner;
+import java.util.HashSet;
+
+public class SkQPAndroidRunner extends AndroidJUnitRunner {
+    @Override
+    public void onCreate(Bundle args) {
+        String filter =  args.getString("skqp_filter");
+        if (filter != null) {
+            gFilters = new HashSet<String>();
+            for (String f : filter.split(",")) {
+                gFilters.add(f);
+            }
+        }
+        super.onCreate(args);
+    }
+    public static boolean filter(String s) { return gFilters == null || gFilters.contains(s); }
+    private static HashSet<String> gFilters;
+}
diff --git a/platform_tools/android/apps/skqp/src/main/java/org/skia/skqp/SkQPRunner.java b/platform_tools/android/apps/skqp/src/main/java/org/skia/skqp/SkQPRunner.java
index c5d0124..79ea189 100644
--- a/platform_tools/android/apps/skqp/src/main/java/org/skia/skqp/SkQPRunner.java
+++ b/platform_tools/android/apps/skqp/src/main/java/org/skia/skqp/SkQPRunner.java
@@ -14,7 +14,6 @@
 import android.util.Log;
 import java.io.File;
 import java.io.IOException;
-import java.lang.annotation.Annotation;
 import org.junit.runner.Description;
 import org.junit.runner.RunWith;
 import org.junit.runner.Runner;
@@ -37,6 +36,15 @@
         File f = c.getExternalFilesDir(null);
         return new File(f, "output");
     }
+
+    private Description gmDesc(int backend, int gm) {
+        return Description.createTestDescription(
+                SkQP.kSkiaGM + impl.mBackends[backend], impl.mGMs[gm]);
+    }
+    private Description unitDesc(int test) {
+        return Description.createTestDescription(SkQP.kSkiaUnitTests, impl.mUnitTests[test]);
+    }
+
     ////////////////////////////////////////////////////////////////////////////
 
     public SkQPRunner(Class testClass) {
@@ -54,17 +62,13 @@
         impl.nInit(mAssetManager, filesDir.getAbsolutePath(), false);
 
         mDescription = Description.createSuiteDescription(testClass);
-        Annotation annots[] = new Annotation[0];
         for (int backend = 0; backend < impl.mBackends.length; backend++) {
-            String classname = SkQP.kSkiaGM + impl.mBackends[backend];
             for (int gm = 0; gm < impl.mGMs.length; gm++) {
-                mDescription.addChild(
-                        Description.createTestDescription(classname, impl.mGMs[gm], annots));
+                mDescription.addChild(this.gmDesc(backend, gm));
             }
         }
         for (int unitTest = 0; unitTest < impl.mUnitTests.length; unitTest++) {
-            mDescription.addChild(Description.createTestDescription(SkQP.kSkiaUnitTests,
-                        impl.mUnitTests[unitTest], annots));
+            mDescription.addChild(this.unitDesc(unitTest));
         }
     }
 
@@ -80,16 +84,17 @@
     public void run(RunNotifier notifier) {
         int numberOfTests = this.testCount();
         int testNumber = 1;
-        Annotation annots[] = new Annotation[0];
         for (int backend = 0; backend < impl.mBackends.length; backend++) {
             String classname = SkQP.kSkiaGM + impl.mBackends[backend];
             for (int gm = 0; gm < impl.mGMs.length; gm++) {
                 String gmName = String.format("%s/%s", impl.mBackends[backend], impl.mGMs[gm]);
+                if (!SkQPAndroidRunner.filter(gmName)) {
+                    continue;
+                }
                 Log.v(TAG, String.format("Rendering Test %s started (%d/%d).",
                                          gmName, testNumber, numberOfTests));
                 testNumber++;
-                Description desc =
-                        Description.createTestDescription(classname, impl.mGMs[gm], annots);
+                Description desc = this.gmDesc(backend, gm);
                 notifier.fireTestStarted(desc);
                 float value = java.lang.Float.MAX_VALUE;
                 String error = null;
@@ -113,11 +118,13 @@
         }
         for (int unitTest = 0; unitTest < impl.mUnitTests.length; unitTest++) {
             String utName = impl.mUnitTests[unitTest];
+            if (!SkQPAndroidRunner.filter(String.format("unitTest/%s", utName))) {
+                continue;
+            }
             Log.v(TAG, String.format("Test %s started (%d/%d).",
                                      utName, testNumber, numberOfTests));
             testNumber++;
-            Description desc = Description.createTestDescription(
-                          SkQP.kSkiaUnitTests, utName, annots);
+            Description desc = this.unitDesc(unitTest);
             notifier.fireTestStarted(desc);
             String[] errors = impl.nExecuteUnitTest(unitTest);
             if (errors != null && errors.length > 0) {
diff --git a/site/dev/testing/skqp.md b/site/dev/testing/skqp.md
new file mode 100644
index 0000000..64c43e6
--- /dev/null
+++ b/site/dev/testing/skqp.md
@@ -0,0 +1,47 @@
+SkQP
+====
+
+Development APKs of SkQP are kept in Google storage.  Each file in named
+with a abbreviated Git hash that points at the commit in the Skia repository it
+was built with.
+
+These are universal APKs that contain native libraries for armeabi-v7a,
+arm64-v8a, x86, and x86\_64 architectures. The most recent is listed first:
+
+<!--
+TZ='' git log \
+-\-date='format-local:%Y-%m-%d %H:%M:%S %Z' -5 origin/skqp/dev \
+-\-format='  * [`%h`](https://storage.googleapis.com/skia-skqp/skqp-universal-%h.apk)%n    | `%cd` | %<(30,trunc)%s'
+-->
+
+  * [`d69db48840`](https://storage.googleapis.com/skia-skqp/skqp-universal-d69db48840.apk)
+    | `2018-02-13 21:07:09 UTC` | SkQP: relax five tests
+  * [`337919990b`](https://storage.googleapis.com/skia-skqp/skqp-universal-337919990b.apk)
+    | `2018-02-13 19:33:12 UTC` | SkQP:  debug option, and fix..
+
+To run tests:
+
+    adb install -r skqp-universal-{APK_SHA_HERE}.apk
+    adb logcat -c
+    adb shell am instrument -w org.skia.skqp
+
+Monitor the output with:
+
+    adb logcat org.skia.skqp skia "*:S"
+
+Note the test's output path on the device.  It will look something like this:
+
+    01-23 15:22:12.688 27158 27173 I org.skia.skqp:
+    output written to "/storage/emulated/0/Android/data/org.skia.skqp/files/output"
+
+Retrieve and view the report with:
+
+    OUTPUT_LOCATION="/storage/emulated/0/Android/data/org.skia.skqp/files/output"
+    adb pull $OUTPUT_LOCATION /tmp/
+
+Open the file `/tmp/output/skqp_report/report.html` .
+
+* * *
+
+For more information about building your own APK, refer to
+https://skia.googlesource.com/skia/+/master/tools/skqp/README.md
diff --git a/tools/skqp/README.md b/tools/skqp/README.md
index 9efc202..00c58c8b 100644
--- a/tools/skqp/README.md
+++ b/tools/skqp/README.md
@@ -62,7 +62,7 @@
         platform_tools/android/bin/android_build_app -C out/skqp-arm skqp
         adb install -r out/skqp-arm/skqp.apk
         adb logcat -c
-        adb shell am instrument -w org.skia.skqp/android.support.test.runner.AndroidJUnitRunner
+        adb shell am instrument -w org.skia.skqp
 
 6.  Monitor the output with:
 
@@ -79,6 +79,21 @@
         adb pull $OUTPUT_LOCATION /tmp/
         tools/skqp/sysopen.py /tmp/output/skqp_report/report.html
 
+Running a single test
+---------------------
+
+To run a single test, for example `gles/aarectmodes`:
+
+    adb shell am instrument -e skqp_filter gles/aarectmodes -w org.skia.skqp
+
+Two run multiple tests, simply separate them with commas:
+
+    adb shell am instrument -e skqp_filter gles/aarectmodes,vk/aarectmodes -w org.skia.skqp
+
+Unit tests can be run with the `unitTest/` prefix:
+
+    adb shell am instrument -e skqp_filter unitTest/GrSurface -w org.skia.skqp
+
 Run as a non-APK executable
 ---------------------------