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>
This commit is contained in:
parent
37155d476c
commit
28f89389f0
13
BUILD.gn
13
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") {
|
||||
|
@ -1 +1,2 @@
|
||||
include ':viewer'
|
||||
include ':skqp'
|
||||
|
30
platform_tools/android/apps/skqp/build.gradle
Normal file
30
platform_tools/android/apps/skqp/build.gradle
Normal file
@ -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")
|
||||
}
|
@ -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>
|
@ -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 {}
|
||||
|
@ -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); }
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
8
tools/skqp/inflate.py
Executable file
8
tools/skqp/inflate.py
Executable file
@ -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()))
|
||||
|
195
tools/skqp/jni/org_skia_skqp_SkQPRunner.cpp
Normal file
195
tools/skqp/jni/org_skia_skqp_SkQPRunner.cpp
Normal file
@ -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);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
Loading…
Reference in New Issue
Block a user