diff options
author | Stephan Altmueller <stephana@google.com> | 2018-01-08 15:53:37 -0500 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2018-01-08 22:42:21 +0000 |
commit | c35959f3cb855c6500f279b893eea07ce5a2573b (patch) | |
tree | 91b0e0acb79756b4b722bcc2ec875e598817b75c | |
parent | c0034179a1c43e33d3f2d970450c5cf9a68e471c (diff) |
First version of SkQP app to run on Firebase Testlab
Adds activities to the skqp app so it can run as an Android
app (as opposed to just instrumentation tests).
A user can trigger the tests via a button.
Adds the an intent receiver so the tests can be triggered on
Firebase Testlab via the gameloop option.
It adds the run_testlab.go script to run an apk across devices
on Firebase Testlab.
Bug: skia:
Change-Id: I3ff5c37d743fa47913a916a0fa1e7db3c2cc79c7
Reviewed-on: https://skia-review.googlesource.com/89163
Commit-Queue: Stephan Altmueller <stephana@google.com>
Reviewed-by: Kevin Lubick <kjlubick@google.com>
Reviewed-by: Derek Sollenberger <djsollen@google.com>
15 files changed, 566 insertions, 66 deletions
diff --git a/infra/cts/README.md b/infra/cts/README.md new file mode 100644 index 0000000000..d4e8c2c0a2 --- /dev/null +++ b/infra/cts/README.md @@ -0,0 +1,12 @@ +Running CTS on Firebase Testlab +=============================== + +The run_testlab.go script uploads a given apk to Firebase Testlab and +runs them on the list of devices whitelisted in the script. +See the WHITELIST\_DEV\_IDS variable. + +To run 'skqpapp.apk' on Testlab run the following command: + +``` + $ go run run_testlab.go --logtostderr --service_account_file=service-account.json skqpapp.apk +``` diff --git a/infra/cts/run_testlab.go b/infra/cts/run_testlab.go new file mode 100644 index 0000000000..cf88c30cc9 --- /dev/null +++ b/infra/cts/run_testlab.go @@ -0,0 +1,245 @@ +/* + * 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 main + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "net/http" + "os" + "os/exec" + "sort" + "strings" + "syscall" + "time" + + gstorage "google.golang.org/api/storage/v1" + + "go.skia.org/infra/go/auth" + "go.skia.org/infra/go/common" + "go.skia.org/infra/go/sklog" + "go.skia.org/infra/go/util" + "go.skia.org/infra/golden/go/tsuite" +) + +// TODO(stephana): Convert the hard coded whitelist to a command line flag that +// loads a file with the whitelisted devices and versions. Make sure to include +// human readable names for the devices. + +var ( + // WHITELIST_DEV_IDS contains a mapping from the device id to the list of + // Android API versions that we should run agains. Usually this will be the + // latest version. To see available devices and version run with + // --dryrun flag or run '$ gcloud firebase test android models list' + + WHITELIST_DEV_IDS = map[string][]string{ + "A0001": {"22"}, + // "E5803": {"22"}, deprecated + // "F5121": {"23"}, deprecated + "G8142": {"25"}, + "HWMHA": {"24"}, + "SH-04H": {"23"}, + "athene": {"23"}, + "athene_f": {"23"}, + "hammerhead": {"23"}, + "harpia": {"23"}, + "hero2lte": {"23"}, + "herolte": {"24v"}, + "j1acevelte": {"22"}, + "j5lte": {"23"}, + "j7xelte": {"23"}, + "lucye": {"24"}, + "mako": {"22"}, + "osprey_umts": {"22"}, + "p1": {"22"}, + "sailfish": {"26"}, + "shamu": {"23"}, + "trelte": {"22"}, + "zeroflte": {"22"}, + "zerolte": {"22"}, + } +) + +const ( + META_DATA_FILENAME = "meta.json" +) + +// Command line flags. +var ( + serviceAccountFile = flag.String("service_account_file", "", "Credentials file for service account.") + dryRun = flag.Bool("dryrun", false, "Print out the command and quit without triggering tests.") +) + +const ( + RUN_TESTS_TEMPLATE = `gcloud beta firebase test android run + --type=game-loop + --app=%s + --results-bucket=%s + --results-dir=%s + --directories-to-pull=/sdcard/Android/data/org.skia.skqpapp + %s +` + MODEL_VERSION_TMPL = "--device model=%s,version=%s,orientation=portrait" + RESULT_BUCKET = "skia-firebase-test-lab" + RESULT_DIR_TMPL = "testruns/%s/%s" + RUN_ID_TMPL = "testrun-%d" + CMD_AVAILABE_DEVICES = "gcloud firebase test android models list --format json" +) + +func main() { + common.Init() + + // Get the apk. + args := flag.Args() + apk_path := args[0] + + // Make sure we can get the service account client. + client, err := auth.NewJWTServiceAccountClient("", *serviceAccountFile, nil, gstorage.CloudPlatformScope, "https://www.googleapis.com/auth/userinfo.email") + if err != nil { + sklog.Fatalf("Failed to authenticate service account: %s. Run 'get_service_account' to obtain a service account file.", err) + } + + // Get list of all available devices. + devices, ignoredDevices, err := getAvailableDevices(WHITELIST_DEV_IDS) + if err != nil { + sklog.Fatalf("Unable to retrieve available devices: %s", err) + } + sklog.Infof("---") + sklog.Infof("Selected devices:") + logDevices(devices) + + if err := runTests(apk_path, devices, ignoredDevices, client, *dryRun); err != nil { + sklog.Fatalf("Error triggering tests on Firebase: %s", err) + } +} + +// getAvailableDevices is given a whitelist. It queries Firebase Testlab for all +// available devices and then returns a list of devices to be tested and the list +// of ignored devices. +func getAvailableDevices(whiteList map[string][]string) ([]*tsuite.DeviceVersions, []*tsuite.DeviceVersions, error) { + // Get the list of all devices in JSON format from Firebase testlab. + var buf bytes.Buffer + cmd := parseCommand(CMD_AVAILABE_DEVICES) + cmd.Stdout = &buf + cmd.Stderr = os.Stdout + if err := cmd.Run(); err != nil { + return nil, nil, err + } + + // Unmarshal the result. + foundDevices := []*tsuite.FirebaseDevice{} + bufBytes := buf.Bytes() + if err := json.Unmarshal(bufBytes, &foundDevices); err != nil { + return nil, nil, sklog.FmtErrorf("Unmarshal of device information failed: %s \nJSON Input: %s\n", err, string(bufBytes)) + } + + // iterate over the available devices and partition them. + allDevices := make([]*tsuite.DeviceVersions, 0, len(foundDevices)) + ret := make([]*tsuite.DeviceVersions, 0, len(foundDevices)) + ignored := make([]*tsuite.DeviceVersions, 0, len(foundDevices)) + for _, dev := range foundDevices { + // Filter out all the virtual devices. + if dev.Form == "PHYSICAL" { + // Only include devices that are on the whitelist and have versions defined. + if foundVersions, ok := whiteList[dev.ID]; ok && (len(foundVersions) > 0) { + versionSet := util.NewStringSet(dev.VersionIDs) + reqVersions := util.NewStringSet(foundVersions) + whiteListVersions := versionSet.Intersect(reqVersions).Keys() + ignoredVersions := versionSet.Complement(reqVersions).Keys() + sort.Strings(whiteListVersions) + sort.Strings(ignoredVersions) + ret = append(ret, &tsuite.DeviceVersions{Device: dev, Versions: whiteListVersions}) + ignored = append(ignored, &tsuite.DeviceVersions{Device: dev, Versions: ignoredVersions}) + } else { + ignored = append(ignored, &tsuite.DeviceVersions{Device: dev, Versions: dev.VersionIDs}) + } + allDevices = append(allDevices, &tsuite.DeviceVersions{Device: dev, Versions: dev.VersionIDs}) + } + } + + sklog.Infof("All devices:") + logDevices(allDevices) + + return ret, ignored, nil +} + +// runTests runs the given apk on the given list of devices. +func runTests(apk_path string, devices, ignoredDevices []*tsuite.DeviceVersions, client *http.Client, dryRun bool) error { + // Get the model-version we want to test. Assume on average each model has 5 supported versions. + modelSelectors := make([]string, 0, len(devices)*5) + for _, devRec := range devices { + for _, version := range devRec.Versions { + modelSelectors = append(modelSelectors, fmt.Sprintf(MODEL_VERSION_TMPL, devRec.Device.ID, version)) + } + } + + now := time.Now() + nowMs := now.UnixNano() / int64(time.Millisecond) + runID := fmt.Sprintf(RUN_ID_TMPL, nowMs) + resultsDir := fmt.Sprintf(RESULT_DIR_TMPL, now.Format("2006/01/02/15"), runID) + cmdStr := fmt.Sprintf(RUN_TESTS_TEMPLATE, apk_path, RESULT_BUCKET, resultsDir, strings.Join(modelSelectors, "\n")) + cmdStr = strings.TrimSpace(strings.Replace(cmdStr, "\n", " ", -1)) + + // Run the command. + cmd := parseCommand(cmdStr) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stdout + exitCode := 0 + + if dryRun { + fmt.Printf("[dry run]: Would have run this command: %s\n", cmdStr) + return nil + } + + if err := cmd.Run(); err != nil { + // Get the exit code. + if exitError, ok := err.(*exec.ExitError); ok { + ws := exitError.Sys().(syscall.WaitStatus) + exitCode = ws.ExitStatus() + } + sklog.Errorf("Error running tests: %s", err) + sklog.Errorf("Exit code: %d", exitCode) + + // Exit code 10 means triggering on Testlab succeeded, but but some of the + // runs on devices failed. We consider it a success for this script. + if exitCode != 10 { + return err + } + } + + // Store the result in a meta json file. + meta := &tsuite.TestRunMeta{ + ID: runID, + TS: nowMs, + Devices: devices, + IgnoredDevices: ignoredDevices, + ExitCode: exitCode, + } + + meta.WriteToGCS(RESULT_BUCKET, resultsDir+"/"+META_DATA_FILENAME, client) + return nil +} + +// logDevices logs the given list of devices. +func logDevices(devices []*tsuite.DeviceVersions) { + sklog.Infof("Found %d devices.", len(devices)) + for _, dev := range devices { + sklog.Infof("%-15s %-30s %v / %v", dev.Device.ID, dev.Device.Name, dev.Device.VersionIDs, dev.Versions) + } +} + +// parseCommad parses a command line and wraps it in an exec.Command instance. +func parseCommand(cmdStr string) *exec.Cmd { + cmdArgs := strings.Split(strings.TrimSpace(cmdStr), " ") + for idx := range cmdArgs { + cmdArgs[idx] = strings.TrimSpace(cmdArgs[idx]) + } + return exec.Command(cmdArgs[0], cmdArgs[1:]...) +} diff --git a/platform_tools/android/apps/skqp/build.gradle b/platform_tools/android/apps/skqp/build.gradle index 0a883ca869..67a89e52af 100644 --- a/platform_tools/android/apps/skqp/build.gradle +++ b/platform_tools/android/apps/skqp/build.gradle @@ -7,13 +7,13 @@ apply plugin: 'com.android.application' dependencies { - compile 'com.android.support:support-annotations:24.0.0' + compile 'com.android.support.constraint:constraint-layout:1.0.2' + compile 'com.android.support:design:26.+' compile 'com.android.support.test:runner:0.5' - compile group: 'junit', name: 'junit', version: '4.+' } android { - compileSdkVersion 23 + compileSdkVersion 26 buildToolsVersion "22.0.1" defaultConfig { applicationId "org.skia.skqp" diff --git a/platform_tools/android/apps/skqp/src/main/AndroidManifest.xml b/platform_tools/android/apps/skqp/src/main/AndroidManifest.xml index 30028fb436..b080286f2f 100644 --- a/platform_tools/android/apps/skqp/src/main/AndroidManifest.xml +++ b/platform_tools/android/apps/skqp/src/main/AndroidManifest.xml @@ -3,7 +3,30 @@ package="org.skia.skqp" android:versionCode="1" android:versionName="1.0"> - <application><uses-library android:name="android.test.runner" /></application> + <application + android:allowBackup="false" + android:theme="@style/AppTheme" + android:label="SkQP" + android:debuggable="true"> + + <activity android:name=".MainActivity"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <activity + android:name=".SkQPActivity" + android:label="@string/title_activity_skqp" + android:theme="@style/AppTheme.NoActionBar"> + <intent-filter> + <action android:name="com.google.intent.action.TEST_LOOP"/> + <category android:name="android.intent.category.DEFAULT"/> + <data android:mimeType="application/javascript"/> + </intent-filter> + </activity> + <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/MainActivity.java b/platform_tools/android/apps/skqp/src/main/java/org/skia/skqp/MainActivity.java new file mode 100644 index 0000000000..43320077ce --- /dev/null +++ b/platform_tools/android/apps/skqp/src/main/java/org/skia/skqp/MainActivity.java @@ -0,0 +1,21 @@ +package org.skia.skqp; + +import android.content.Intent; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.view.View; +import android.widget.EditText; + +public class MainActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + } + + public void startTests(View view) { + Intent intent = new Intent(this, SkQPActivity.class); + startActivity(intent); + } +} 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 index c5843f013e..8ced43dbe2 100644 --- 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 @@ -7,8 +7,90 @@ package org.skia.skqp; -import org.junit.runner.RunWith; +import android.content.Context; +import android.content.res.AssetManager; +import android.util.Log; +import java.io.File; +import java.io.IOException; -@RunWith(SkQPRunner.class) -public class SkQP {} +public class SkQP { + protected native void nInit(AssetManager assetManager, String dataDir); + protected native float nExecuteGM(int gm, int backend) throws SkQPException; + protected native String[] nExecuteUnitTest(int test); + protected native void nMakeReport(); + + protected String[] mGMs; + protected String[] mBackends; + protected String[] mUnitTests; + + protected static final String kSkiaGM = "SkiaGM_"; + protected static final String kSkiaUnitTests = "Skia_UnitTests"; + protected static final String LOG_PREFIX = "org.skis.skqp"; + + static { + System.loadLibrary("skqp_app"); + } + + protected void runTests(Context context, String outputDirPath) { + Log.w(LOG_PREFIX, "Output Dir: " + outputDirPath); + File outputDir = new File(outputDirPath); + if (outputDir.exists()) { + try { + deleteDirectoryContents(outputDir); + } catch (IOException e) { + Log.w(LOG_PREFIX, "DeleteDirectoryContents: " + e.getMessage()); + } + } + + // Note: nInit will initialize the mGMs, mBackends and mUnitTests fields. + AssetManager assetManager = context.getResources().getAssets(); + this.nInit(assetManager, outputDirPath); + + for (int backend = 0; backend < mBackends.length; backend++) { + String classname = kSkiaGM + mBackends[backend]; + for (int gm = 0; gm < mGMs.length; gm++) { + String testName = kSkiaGM + mBackends[backend] + "_" +mGMs[gm]; + float value = java.lang.Float.MAX_VALUE; + String error = null; + Log.w(LOG_PREFIX, "Running: " + testName); + try { + value = this.nExecuteGM(gm, backend); + } catch (SkQPException exept) { + error = exept.getMessage(); + } + if (error != null) { + // Record error message and carry on. + } else if (value != 0) { + // Record failure and carry on. + // SkQPRunner.Fail(desc, notifier, String.format( + // "Image mismatch: max channel diff = %f", value)); + } else { + // Record success for this test. + } + } + } + for (int unitTest = 0; unitTest < mUnitTests.length; unitTest++) { + String testName = kSkiaUnitTests + "_" + mUnitTests[unitTest]; + Log.w(LOG_PREFIX, "Running: " + testName); + String[] errors = this.nExecuteUnitTest(unitTest); + if (errors != null && errors.length > 0) { + for (String error : errors) { + // Record unit test failures. + } + } else { + // Record success. + } + } + nMakeReport(); + } + + protected static void deleteDirectoryContents(File f) throws IOException { + for (File s : f.listFiles()) { + if (s.isDirectory()) { + deleteDirectoryContents(s); + } + s.delete(); + } + } +} diff --git a/platform_tools/android/apps/skqp/src/main/java/org/skia/skqp/SkQPActivity.java b/platform_tools/android/apps/skqp/src/main/java/org/skia/skqp/SkQPActivity.java new file mode 100644 index 0000000000..ab7ce168b9 --- /dev/null +++ b/platform_tools/android/apps/skqp/src/main/java/org/skia/skqp/SkQPActivity.java @@ -0,0 +1,35 @@ +package org.skia.skqp; + +import android.content.Context; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.util.Log; + +public class SkQPActivity extends AppCompatActivity implements Runnable { + private SkQP testRunner = new SkQP(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_skqp); + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + // Start the tests. + run(); + } + + // run implements the Runnable interface. + public void run() { + // Note: /sdcard/Android/data/<package-name> is a location an app is allowed to write to. + // When running tests on Firebase it expects any result files to have a '/sdcard + // prefix or it won't trigger tests from the CLI. + + Context context = getApplicationContext(); + String outputDirPath = "/sdcard/Android/data/" + context.getPackageName(); + testRunner.runTests(context, outputDirPath); + finish(); + } +} + 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 464c9e2e71..dc9aea3f19 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 @@ -21,30 +21,8 @@ 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 native void nMakeReport(); - - 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 SkQP impl; private static void Fail(Description desc, RunNotifier notifier, String failure) { notifier.fireTestFailure(new Failure(desc, new Throwable(failure))); @@ -53,37 +31,30 @@ public class SkQPRunner extends Runner { //////////////////////////////////////////////////////////////////////////// public SkQPRunner(Class testClass) { - synchronized (SkQPRunner.class) { - if (sOnceFlag) { - throw new IllegalStateException("Error multiple SkQPs defined"); - } - sOnceFlag = true; - } - System.loadLibrary("skqp_app"); - + impl = new SkQP(); Context context = InstrumentationRegistry.getTargetContext(); File filesDir = context.getFilesDir(); try { - SkQPRunner.DeleteDirectoryContents(filesDir); + SkQP.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()); + AssetManager mAssetManager = resources.getAssets(); + impl.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 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)); } } - for (int unitTest = 0; unitTest < mUnitTests.length; unitTest++) { - mDescription.addChild(Description.createTestDescription(kSkiaUnitTests, - mUnitTests[unitTest], annots)); + for (int unitTest = 0; unitTest < impl.mUnitTests.length; unitTest++) { + mDescription.addChild(Description.createTestDescription(SkQP.kSkiaUnitTests, + impl.mUnitTests[unitTest], annots)); } } @@ -91,20 +62,20 @@ public class SkQPRunner extends Runner { public Description getDescription() { return mDescription; } @Override - public int testCount() { return mUnitTests.length + mGMs.length * mBackends.length; } + public int testCount() { return impl.mUnitTests.length + impl.mGMs.length * impl.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); + 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++) { + Description desc = Description.createTestDescription(classname, impl.mGMs[gm], annots); notifier.fireTestStarted(desc); float value = java.lang.Float.MAX_VALUE; String error = null; try { - value = this.nExecuteGM(gm, backend); + value = impl.nExecuteGM(gm, backend); } catch (SkQPException exept) { error = exept.getMessage(); } @@ -117,11 +88,11 @@ public class SkQPRunner extends Runner { notifier.fireTestFinished(desc); } } - for (int unitTest = 0; unitTest < mUnitTests.length; unitTest++) { + for (int unitTest = 0; unitTest < impl.mUnitTests.length; unitTest++) { Description desc = Description.createTestDescription( - kSkiaUnitTests, mUnitTests[unitTest], annots); + SkQP.kSkiaUnitTests, impl.mUnitTests[unitTest], annots); notifier.fireTestStarted(desc); - String[] errors = this.nExecuteUnitTest(unitTest); + String[] errors = impl.nExecuteUnitTest(unitTest); if (errors != null && errors.length > 0) { for (String error : errors) { SkQPRunner.Fail(desc, notifier, error); @@ -129,7 +100,7 @@ public class SkQPRunner extends Runner { } notifier.fireTestFinished(desc); } - this.nMakeReport(); + impl.nMakeReport(); } } diff --git a/platform_tools/android/apps/skqp/src/main/res/layout/activity_main.xml b/platform_tools/android/apps/skqp/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000000..45b9c77f05 --- /dev/null +++ b/platform_tools/android/apps/skqp/src/main/res/layout/activity_main.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context="org.skia.skqp.MainActivity"> + + <LinearLayout + android:layout_width="368dp" + android:layout_height="495dp" + android:orientation="vertical" + tools:layout_editor_absoluteX="8dp" + tools:layout_editor_absoluteY="8dp"> + + <TextView + android:id="@+id/textView2" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="Nothing to see here !" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <Button + android:id="@+id/button_start" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:keyboardNavigationCluster="false" + android:onClick="startTests" + android:text="Start Tests !" /> + </LinearLayout> + +</android.support.constraint.ConstraintLayout> diff --git a/platform_tools/android/apps/skqp/src/main/res/layout/activity_skqp.xml b/platform_tools/android/apps/skqp/src/main/res/layout/activity_skqp.xml new file mode 100644 index 0000000000..b9e733644f --- /dev/null +++ b/platform_tools/android/apps/skqp/src/main/res/layout/activity_skqp.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context="org.skia.skqp.SkQPActivity"> + + <android.support.design.widget.AppBarLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:theme="@style/AppTheme.AppBarOverlay"> + + <android.support.v7.widget.Toolbar + android:id="@+id/toolbar" + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize" + android:background="?attr/colorPrimary" + app:popupTheme="@style/AppTheme.PopupOverlay" /> + + </android.support.design.widget.AppBarLayout> + + <include layout="@layout/content_skqp" /> + +</android.support.design.widget.CoordinatorLayout> diff --git a/platform_tools/android/apps/skqp/src/main/res/layout/content_skqp.xml b/platform_tools/android/apps/skqp/src/main/res/layout/content_skqp.xml new file mode 100644 index 0000000000..352937adf7 --- /dev/null +++ b/platform_tools/android/apps/skqp/src/main/res/layout/content_skqp.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:layout_behavior="@string/appbar_scrolling_view_behavior" + tools:context="org.skia.skqp.SkQPActivity" + tools:showIn="@layout/activity_skqp"> + + <TextView + android:id="@+id/textView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Running SkQP tests ..." + android:textSize="18sp" + android:textStyle="bold" + android:visibility="visible" /> +</android.support.constraint.ConstraintLayout> diff --git a/platform_tools/android/apps/skqp/src/main/res/values/colors.xml b/platform_tools/android/apps/skqp/src/main/res/values/colors.xml new file mode 100644 index 0000000000..3ab3e9cbce --- /dev/null +++ b/platform_tools/android/apps/skqp/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="colorPrimary">#3F51B5</color> + <color name="colorPrimaryDark">#303F9F</color> + <color name="colorAccent">#FF4081</color> +</resources> diff --git a/platform_tools/android/apps/skqp/src/main/res/values/strings.xml b/platform_tools/android/apps/skqp/src/main/res/values/strings.xml new file mode 100644 index 0000000000..fb39759ca1 --- /dev/null +++ b/platform_tools/android/apps/skqp/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ +<resources> + <string name="app_name">SkQP App</string> + <string name="title_activity_skqp">SkQP Activity</string> +</resources> diff --git a/platform_tools/android/apps/skqp/src/main/res/values/styles.xml b/platform_tools/android/apps/skqp/src/main/res/values/styles.xml new file mode 100644 index 0000000000..545b9c6d2c --- /dev/null +++ b/platform_tools/android/apps/skqp/src/main/res/values/styles.xml @@ -0,0 +1,20 @@ +<resources> + + <!-- Base application theme. --> + <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> + <!-- Customize your theme here. --> + <item name="colorPrimary">@color/colorPrimary</item> + <item name="colorPrimaryDark">@color/colorPrimaryDark</item> + <item name="colorAccent">@color/colorAccent</item> + </style> + + <style name="AppTheme.NoActionBar"> + <item name="windowActionBar">false</item> + <item name="windowNoTitle">true</item> + </style> + + <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" /> + + <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" /> + +</resources> diff --git a/tools/skqp/jni/org_skia_skqp_SkQPRunner.cpp b/tools/skqp/jni/org_skia_skqp_SkQPRunner.cpp index 22d2c03488..7347f28ff9 100644 --- a/tools/skqp/jni/org_skia_skqp_SkQPRunner.cpp +++ b/tools/skqp/jni/org_skia_skqp_SkQPRunner.cpp @@ -19,11 +19,11 @@ //////////////////////////////////////////////////////////////////////////////// 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, +JNIEXPORT void JNICALL Java_org_skia_skqp_SkQP_nInit(JNIEnv*, jobject, jobject, jstring); +JNIEXPORT jfloat JNICALL Java_org_skia_skqp_SkQP_nExecuteGM(JNIEnv*, jobject, jint, jint); +JNIEXPORT jobjectArray JNICALL Java_org_skia_skqp_SkQP_nExecuteUnitTest(JNIEnv*, jobject, jint); -JNIEXPORT void JNICALL Java_org_skia_skqp_SkQPRunner_nMakeReport(JNIEnv*, jobject); +JNIEXPORT void JNICALL Java_org_skia_skqp_SkQP_nMakeReport(JNIEnv*, jobject); } // extern "C" //////////////////////////////////////////////////////////////////////////////// @@ -118,7 +118,7 @@ jobjectArray to_java_string_array(JNIEnv* env, return jarray; } -void Java_org_skia_skqp_SkQPRunner_nInit(JNIEnv* env, jobject object, jobject assetManager, +void Java_org_skia_skqp_SkQP_nInit(JNIEnv* env, jobject object, jobject assetManager, jstring dataDir) { jclass clazz = env->GetObjectClass(object); jassert(env, assetManager); @@ -147,7 +147,7 @@ void Java_org_skia_skqp_SkQPRunner_nInit(JNIEnv* env, jobject object, jobject as to_java_string_array(env, gGMs, gm_runner::GetGMName)); } -jfloat Java_org_skia_skqp_SkQPRunner_nExecuteGM(JNIEnv* env, +jfloat Java_org_skia_skqp_SkQP_nExecuteGM(JNIEnv* env, jobject object, jint gmIndex, jint backendIndex) { @@ -173,7 +173,7 @@ jfloat Java_org_skia_skqp_SkQPRunner_nExecuteGM(JNIEnv* env, return result; } -jobjectArray Java_org_skia_skqp_SkQPRunner_nExecuteUnitTest(JNIEnv* env, +jobjectArray Java_org_skia_skqp_SkQP_nExecuteUnitTest(JNIEnv* env, jobject object, jint index) { jassert(env, index < (jint)gUnitTests.size()); @@ -193,7 +193,7 @@ jobjectArray Java_org_skia_skqp_SkQPRunner_nExecuteUnitTest(JNIEnv* env, return (jobjectArray)env->NewGlobalRef(array); } -void Java_org_skia_skqp_SkQPRunner_nMakeReport(JNIEnv*, jobject) { +void Java_org_skia_skqp_SkQP_nMakeReport(JNIEnv*, jobject) { std::string reportDirectoryPath; { std::lock_guard<std::mutex> lock(gMutex); |