aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Hal Canary <halcanary@google.com>2017-12-12 09:42:14 -0500
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2017-12-18 18:58:52 +0000
commit28f89389f026379aad6f90254ba37f4d37272bcb (patch)
tree7736d9a3fdd88d628bb070bc5348096d3e363a9a
parent37155d476cd727329d985b75ecedbefe380a0f23 (diff)
SkQP: add junit app
Change-Id: Ic32eaec6cce1509f07e7cf610717d3b12d335c89 Reviewed-on: https://skia-review.googlesource.com/83921 Reviewed-by: Hal Canary <halcanary@google.com> Commit-Queue: Hal Canary <halcanary@google.com>
-rw-r--r--BUILD.gn13
-rw-r--r--platform_tools/android/apps/settings.gradle1
-rw-r--r--platform_tools/android/apps/skqp/build.gradle30
-rw-r--r--platform_tools/android/apps/skqp/src/main/AndroidManifest.xml9
-rw-r--r--platform_tools/android/apps/skqp/src/main/java/org/skia/skqp/SkQP.java14
-rw-r--r--platform_tools/android/apps/skqp/src/main/java/org/skia/skqp/SkQPException.java12
-rw-r--r--platform_tools/android/apps/skqp/src/main/java/org/skia/skqp/SkQPRunner.java133
-rw-r--r--tools/skqp/README.md18
-rwxr-xr-xtools/skqp/inflate.py8
-rw-r--r--tools/skqp/jni/org_skia_skqp_SkQPRunner.cpp195
10 files changed, 432 insertions, 1 deletions
diff --git a/BUILD.gn b/BUILD.gn
index b03f1117d9..96c76235f7 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1729,6 +1729,19 @@ if (skia_enable_tools) {
]
}
}
+ if (is_android && skia_enable_gpu) {
+ test_app("skqp_app") {
+ is_shared_library = true
+ sources = [
+ "tools/skqp/jni/org_skia_skqp_SkQPRunner.cpp",
+ ]
+ deps = [
+ ":skia",
+ ":skqp_lib",
+ ]
+ libs = [ "android" ]
+ }
+ }
if (skia_enable_gpu) {
test_lib("sk_app") {
diff --git a/platform_tools/android/apps/settings.gradle b/platform_tools/android/apps/settings.gradle
index 75ce0a6db1..a8c2cb3fb4 100644
--- a/platform_tools/android/apps/settings.gradle
+++ b/platform_tools/android/apps/settings.gradle
@@ -1 +1,2 @@
include ':viewer'
+include ':skqp'
diff --git a/platform_tools/android/apps/skqp/build.gradle b/platform_tools/android/apps/skqp/build.gradle
new file mode 100644
index 0000000000..0a883ca869
--- /dev/null
+++ b/platform_tools/android/apps/skqp/build.gradle
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+apply plugin: 'com.android.application'
+
+dependencies {
+ compile 'com.android.support:support-annotations:24.0.0'
+ compile 'com.android.support.test:runner:0.5'
+ compile group: 'junit', name: 'junit', version: '4.+'
+}
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion "22.0.1"
+ defaultConfig {
+ applicationId "org.skia.skqp"
+ minSdkVersion 19
+ targetSdkVersion 19
+ versionCode 1
+ versionName "1.0"
+ signingConfig signingConfigs.debug
+ }
+ sourceSets.main.jni.srcDirs = []
+ sourceSets.main.jniLibs.srcDir "src/main/libs"
+ productFlavors { arm {}; arm64 {}; x86 {}; x64 {}; arm64vulkan{}; }
+ setupSkiaLibraryBuild(project, applicationVariants, "libskqp_app")
+}
diff --git a/platform_tools/android/apps/skqp/src/main/AndroidManifest.xml b/platform_tools/android/apps/skqp/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..30028fb436
--- /dev/null
+++ b/platform_tools/android/apps/skqp/src/main/AndroidManifest.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="org.skia.skqp"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <application><uses-library android:name="android.test.runner" /></application>
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="org.skia.skqp"></instrumentation>
+</manifest>
diff --git a/platform_tools/android/apps/skqp/src/main/java/org/skia/skqp/SkQP.java b/platform_tools/android/apps/skqp/src/main/java/org/skia/skqp/SkQP.java
new file mode 100644
index 0000000000..c5843f013e
--- /dev/null
+++ b/platform_tools/android/apps/skqp/src/main/java/org/skia/skqp/SkQP.java
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2017 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 org.junit.runner.RunWith;
+
+@RunWith(SkQPRunner.class)
+public class SkQP {}
+
diff --git a/platform_tools/android/apps/skqp/src/main/java/org/skia/skqp/SkQPException.java b/platform_tools/android/apps/skqp/src/main/java/org/skia/skqp/SkQPException.java
new file mode 100644
index 0000000000..a7c8721bfb
--- /dev/null
+++ b/platform_tools/android/apps/skqp/src/main/java/org/skia/skqp/SkQPException.java
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2017 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;
+
+public class SkQPException extends Exception {
+ public SkQPException(String m) { super(m); }
+}
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
new file mode 100644
index 0000000000..1cf3aef9e2
--- /dev/null
+++ b/platform_tools/android/apps/skqp/src/main/java/org/skia/skqp/SkQPRunner.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2017 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.content.Context;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.support.test.InstrumentationRegistry;
+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.Runner;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunNotifier;
+
+public class SkQPRunner extends Runner {
+ private native void nInit(AssetManager assetManager, String dataDir);
+ private native float nExecuteGM(int gm, int backend) throws SkQPException;
+ private native String[] nExecuteUnitTest(int test);
+
+ private AssetManager mAssetManager;
+ private String[] mGMs;
+ private String[] mBackends;
+ private String[] mUnitTests;
+
+ private static boolean sOnceFlag = false;
+ private static final String kSkiaGM = "SkiaGM_";
+ private static final String kSkiaUnitTests = "Skia_UnitTests";
+
+ private Description mDescription;
+
+ private static void DeleteDirectoryContents(File f) throws IOException {
+ for (File s : f.listFiles()) {
+ if (s.isDirectory()) {
+ SkQPRunner.DeleteDirectoryContents(s);
+ }
+ s.delete();
+ }
+ }
+
+ private static void Fail(Description desc, RunNotifier notifier, String failure) {
+ notifier.fireTestFailure(new Failure(desc, new Throwable(failure)));
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+
+ public SkQPRunner(Class testClass) {
+ synchronized (SkQPRunner.class) {
+ if (sOnceFlag) {
+ throw new IllegalStateException("Error multiple SkQPs defined");
+ }
+ sOnceFlag = true;
+ }
+ System.loadLibrary("skqp_app");
+
+ Context context = InstrumentationRegistry.getTargetContext();
+ File filesDir = context.getFilesDir();
+ try {
+ SkQPRunner.DeleteDirectoryContents(filesDir);
+ } catch (IOException e) {
+ Log.w("org.skis.skqp", "DeleteDirectoryContents: " + e.getMessage());
+ }
+
+ Resources resources = context.getResources();
+ mAssetManager = resources.getAssets();
+ this.nInit(mAssetManager, filesDir.getAbsolutePath());
+
+ mDescription = Description.createSuiteDescription(testClass);
+ Annotation annots[] = new Annotation[0];
+ for (int backend = 0; backend < mBackends.length; backend++) {
+ String classname = kSkiaGM + mBackends[backend];
+ for (int gm = 0; gm < mGMs.length; gm++) {
+ mDescription.addChild(Description.createTestDescription(classname, mGMs[gm], annots));
+ }
+ }
+ for (int unitTest = 0; unitTest < mUnitTests.length; unitTest++) {
+ mDescription.addChild(Description.createTestDescription(kSkiaUnitTests,
+ mUnitTests[unitTest], annots));
+ }
+ }
+
+ @Override
+ public Description getDescription() { return mDescription; }
+
+ @Override
+ public int testCount() { return mUnitTests.length + mGMs.length * mBackends.length; }
+
+ @Override
+ public void run(RunNotifier notifier) {
+ Annotation annots[] = new Annotation[0];
+ for (int backend = 0; backend < mBackends.length; backend++) {
+ String classname = kSkiaGM + mBackends[backend];
+ for (int gm = 0; gm < mGMs.length; gm++) {
+ Description desc = Description.createTestDescription(classname, mGMs[gm], annots);
+ notifier.fireTestStarted(desc);
+ float value = java.lang.Float.MAX_VALUE;
+ String error = null;
+ try {
+ value = this.nExecuteGM(gm, backend);
+ } catch (SkQPException exept) {
+ error = exept.getMessage();
+ }
+ if (error != null) {
+ SkQPRunner.Fail(desc, notifier, String.format("Exception: %s", error));
+ } else if (value != 0) {
+ SkQPRunner.Fail(desc, notifier, String.format(
+ "Image mismatch: max channel diff = %f", value));
+ }
+ notifier.fireTestFinished(desc);
+ }
+ }
+ for (int unitTest = 0; unitTest < mUnitTests.length; unitTest++) {
+ Description desc = Description.createTestDescription(
+ kSkiaUnitTests, mUnitTests[unitTest], annots);
+ notifier.fireTestStarted(desc);
+ String[] errors = this.nExecuteUnitTest(unitTest);
+ if (errors != null && errors.length > 0) {
+ for (String error : errors) {
+ SkQPRunner.Fail(desc, notifier, error);
+ }
+ }
+ notifier.fireTestFinished(desc);
+ }
+ }
+}
+
diff --git a/tools/skqp/README.md b/tools/skqp/README.md
index beac911ba0..d3156a0573 100644
--- a/tools/skqp/README.md
+++ b/tools/skqp/README.md
@@ -63,4 +63,20 @@ Run as an executable
Run as an APK
-------------
-[TODO]
+1. Build the skqp.apk, load it on the device, and run the tests
+
+ platform_tools/android/bin/android_build_app -C out/${arch}-rel skqp
+ adb install -r out/${arch}-rel/skqp.apk
+ adb shell am instrument -w \
+ org.skia.skqp/android.support.test.runner.AndroidJUnitRunner
+
+2. Retrieve the report if there are any errors:
+
+ rm -rf /tmp/skqp
+ mkdir /tmp/skqp
+ adb backup -f /tmp/skqp/backup.ab org.skia.skqp
+ dd if=/tmp/skqp/backup.ab bs=24 skip=1 | tools/skqp/inflate.py | \
+ ( cd /tmp/skqp; tar x )
+ rm /tmp/skqp/backup.ab
+ tools/skqp/make_report.py /tmp/skqp/apps/org.skia.skqp/f
+
diff --git a/tools/skqp/inflate.py b/tools/skqp/inflate.py
new file mode 100755
index 0000000000..97cc3a405d
--- /dev/null
+++ b/tools/skqp/inflate.py
@@ -0,0 +1,8 @@
+#! /usr/bin/env python2
+# Copyright 2017 Google Inc.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+import sys
+import zlib
+sys.stdout.write(zlib.decompress(sys.stdin.read()))
+
diff --git a/tools/skqp/jni/org_skia_skqp_SkQPRunner.cpp b/tools/skqp/jni/org_skia_skqp_SkQPRunner.cpp
new file mode 100644
index 0000000000..9c9c9dfa10
--- /dev/null
+++ b/tools/skqp/jni/org_skia_skqp_SkQPRunner.cpp
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <mutex>
+#include <vector>
+
+#include <jni.h>
+#include <android/asset_manager.h>
+#include <android/asset_manager_jni.h>
+
+#include "gm_runner.h"
+#include "skqp_asset_manager.h"
+#include "SkStream.h"
+
+////////////////////////////////////////////////////////////////////////////////
+extern "C" {
+JNIEXPORT void JNICALL Java_org_skia_skqp_SkQPRunner_nInit(JNIEnv*, jobject, jobject, jstring);
+JNIEXPORT jfloat JNICALL Java_org_skia_skqp_SkQPRunner_nExecuteGM(JNIEnv*, jobject, jint, jint);
+JNIEXPORT jobjectArray JNICALL Java_org_skia_skqp_SkQPRunner_nExecuteUnitTest(JNIEnv*, jobject,
+ jint);
+} // extern "C"
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+struct AndroidAssetManager : public skqp::AssetManager {
+ AAssetManager* fMgr = nullptr;
+ std::unique_ptr<SkStreamAsset> open(const char* path) override {
+ struct AAStrm : public SkStreamAsset {
+ AAssetManager* fMgr;
+ std::string fPath;
+ AAsset* fAsset;
+ AAStrm(AAssetManager* m, std::string p, AAsset* a)
+ : fMgr(m), fPath(std::move(p)), fAsset(a) {}
+ ~AAStrm() override { AAsset_close(fAsset); }
+ size_t read(void* buffer, size_t size) override {
+ size_t r = SkTMin(size, SkToSizeT(AAsset_getRemainingLength(fAsset)));
+ if (buffer) {
+ return SkToSizeT(AAsset_read(fAsset, buffer, r));
+ } else {
+ this->move(SkTo<long>(r));
+ return r;
+ }
+ }
+ size_t getLength() const override { return SkToSizeT(AAsset_getLength(fAsset)); }
+ size_t peek(void* buffer, size_t size) const override {
+ size_t r = const_cast<AAStrm*>(this)->read(buffer, size);
+ const_cast<AAStrm*>(this)->move(-(long)r);
+ return r;
+ }
+ bool isAtEnd() const override { return 0 == AAsset_getRemainingLength(fAsset); }
+ bool rewind() override { return this->seek(0); }
+ size_t getPosition() const override {
+ return SkToSizeT(AAsset_seek(fAsset, 0, SEEK_CUR));
+ }
+ bool seek(size_t position) override {
+ return -1 != AAsset_seek(fAsset, SkTo<off_t>(position), SEEK_SET);
+ }
+ bool move(long offset) override {
+ return -1 != AAsset_seek(fAsset, SkTo<off_t>(offset), SEEK_CUR);
+ }
+ SkStreamAsset* onDuplicate() const override {
+ AAsset* dupAsset = AndroidAssetManager::OpenAsset(fMgr, fPath.c_str());
+ return dupAsset ? new AAStrm(fMgr, fPath, dupAsset) : nullptr;
+ }
+ SkStreamAsset* onFork() const override {
+ SkStreamAsset* dup = this->onDuplicate();
+ if (dup) { (void)dup->seek(this->getPosition()); }
+ return dup;
+ }
+ };
+ AAsset* asset = AndroidAssetManager::OpenAsset(fMgr, path);
+ return asset ? std::unique_ptr<SkStreamAsset>(new AAStrm(fMgr, std::string(path), asset))
+ : nullptr;
+ }
+ static AAsset* OpenAsset(AAssetManager* mgr, const char* path) {
+ std::string fullPath = std::string("gmkb/") + path;
+ return mgr ? AAssetManager_open(mgr, fullPath.c_str(), AASSET_MODE_STREAMING) : nullptr;
+ }
+};
+}
+
+static void set_string_array_element(JNIEnv* env, jobjectArray a, const char* s, unsigned i) {
+ jstring jstr = env->NewStringUTF(s);
+ env->SetObjectArrayElement(a, (jsize)i, jstr);
+ env->DeleteLocalRef(jstr);
+}
+
+#define jassert(env, cond) do { if (!(cond)) { \
+ (env)->ThrowNew((env)->FindClass("java/lang/Exception"), \
+ __FILE__ ": assert(" #cond ") failed."); } } while (0)
+
+////////////////////////////////////////////////////////////////////////////////
+
+static std::mutex gMutex;
+static std::vector<gm_runner::SkiaBackend> gBackends;
+static std::vector<gm_runner::GMFactory> gGMs;
+static std::vector<gm_runner::UnitTest> gUnitTests;
+static AndroidAssetManager gAssetManager;
+static std::string gReportDirectory;
+static jclass gStringClass = nullptr;
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <typename T, typename F>
+jobjectArray to_java_string_array(JNIEnv* env,
+ const std::vector<T>& array,
+ F toString) {
+ jobjectArray jarray = env->NewObjectArray((jint)array.size(), gStringClass, nullptr);
+ for (unsigned i = 0; i < array.size(); ++i) {
+ set_string_array_element(env, jarray, std::string(toString(array[i])).c_str(), i);
+ }
+ return jarray;
+}
+
+void Java_org_skia_skqp_SkQPRunner_nInit(JNIEnv* env, jobject object, jobject assetManager,
+ jstring dataDir) {
+ jclass clazz = env->GetObjectClass(object);
+ jassert(env, assetManager);
+
+ gm_runner::InitSkia();
+
+ std::lock_guard<std::mutex> lock(gMutex);
+ gAssetManager.fMgr = AAssetManager_fromJava(env, assetManager);
+ jassert(env, gAssetManager.fMgr);
+
+ const char* dataDirString = env->GetStringUTFChars(dataDir, nullptr);
+ gReportDirectory = dataDirString;
+ env->ReleaseStringUTFChars(dataDir, dataDirString);
+
+ gBackends = gm_runner::GetSupportedBackends();
+ gGMs = gm_runner::GetGMFactories(&gAssetManager);
+ gUnitTests = gm_runner::GetUnitTests();
+ gStringClass = env->FindClass("java/lang/String");
+
+ constexpr char stringArrayType[] = "[Ljava/lang/String;";
+ env->SetObjectField(object, env->GetFieldID(clazz, "mBackends", stringArrayType),
+ to_java_string_array(env, gBackends, gm_runner::GetBackendName));
+ env->SetObjectField(object, env->GetFieldID(clazz, "mUnitTests", stringArrayType),
+ to_java_string_array(env, gUnitTests, gm_runner::GetUnitTestName));
+ env->SetObjectField(object, env->GetFieldID(clazz, "mGMs", stringArrayType),
+ to_java_string_array(env, gGMs, gm_runner::GetGMName));
+}
+
+jfloat Java_org_skia_skqp_SkQPRunner_nExecuteGM(JNIEnv* env,
+ jobject object,
+ jint gmIndex,
+ jint backendIndex) {
+ jassert(env, gmIndex < (jint)gGMs.size());
+ jassert(env, backendIndex < (jint)gBackends.size());
+ gm_runner::GMFactory gm;
+ gm_runner::SkiaBackend backend;
+ std::string reportDirectoryPath;
+ {
+ std::lock_guard<std::mutex> lock(gMutex);
+ backend = gBackends[backendIndex];
+ gm = gGMs[gmIndex];
+ reportDirectoryPath = gReportDirectory;
+ }
+ float result;
+ gm_runner::Error error;
+ std::tie(result, error) = gm_runner::EvaluateGM(backend, gm, &gAssetManager,
+ reportDirectoryPath.c_str());
+ if (error != gm_runner::Error::None) {
+ (void)env->ThrowNew(env->FindClass("org/skia/skqp/SkQPException"),
+ gm_runner::GetErrorString(error));
+ }
+ return result;
+}
+
+jobjectArray Java_org_skia_skqp_SkQPRunner_nExecuteUnitTest(JNIEnv* env,
+ jobject object,
+ jint index) {
+ jassert(env, index < (jint)gUnitTests.size());
+ gm_runner::UnitTest test;
+ {
+ std::lock_guard<std::mutex> lock(gMutex);
+ test = gUnitTests[index];
+ }
+ std::vector<std::string> errors = gm_runner::ExecuteTest(test);
+ if (errors.size() == 0) {
+ return nullptr;
+ }
+ jobjectArray array = env->NewObjectArray(errors.size(), gStringClass, nullptr);
+ for (unsigned i = 0; i < errors.size(); ++i) {
+ set_string_array_element(env, array, errors[i].c_str(), i);
+ }
+ return (jobjectArray)env->NewGlobalRef(array);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+