summaryrefslogtreecommitdiffstats
path: root/tests/DumpRenderTree2
diff options
context:
space:
mode:
Diffstat (limited to 'tests/DumpRenderTree2')
-rw-r--r--tests/DumpRenderTree2/Android.mk10
-rw-r--r--tests/DumpRenderTree2/AndroidManifest.xml44
-rw-r--r--tests/DumpRenderTree2/res/drawable/folder.pngbin0 -> 4865 bytes
-rw-r--r--tests/DumpRenderTree2/res/drawable/runtest.pngbin0 -> 3146 bytes
-rw-r--r--tests/DumpRenderTree2/res/layout/dirlist_row.xml43
-rw-r--r--tests/DumpRenderTree2/res/values/strings.xml28
-rw-r--r--tests/DumpRenderTree2/src/com/android/dumprendertree2/AbstractResult.java114
-rw-r--r--tests/DumpRenderTree2/src/com/android/dumprendertree2/FileFilter.java288
-rw-r--r--tests/DumpRenderTree2/src/com/android/dumprendertree2/FsUtils.java78
-rw-r--r--tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTest.java183
-rw-r--r--tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecuter.java240
-rw-r--r--tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsRunner.java94
-rw-r--r--tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsRunnerThread.java304
-rw-r--r--tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java60
-rw-r--r--tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java231
-rw-r--r--tests/DumpRenderTree2/src/com/android/dumprendertree2/TextResult.java180
-rw-r--r--tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java400
17 files changed, 2297 insertions, 0 deletions
diff --git a/tests/DumpRenderTree2/Android.mk b/tests/DumpRenderTree2/Android.mk
new file mode 100644
index 0000000..2aa6799
--- /dev/null
+++ b/tests/DumpRenderTree2/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := DumpRenderTree2
+
+include $(BUILD_PACKAGE) \ No newline at end of file
diff --git a/tests/DumpRenderTree2/AndroidManifest.xml b/tests/DumpRenderTree2/AndroidManifest.xml
new file mode 100644
index 0000000..14df611
--- /dev/null
+++ b/tests/DumpRenderTree2/AndroidManifest.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2010 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.dumprendertree2">
+ <application>
+ <activity android:name=".ui.DirListActivity"
+ android:label="Dump Render Tree 2"
+ android:configChanges="orientation">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name=".LayoutTestsRunner"
+ android:label="Layout tests' runner">
+ </activity>
+
+ <activity android:name=".LayoutTestsExecuter"
+ android:label="Layout tests' executer"
+ android:process=":executer">
+ </activity>
+
+ <service android:name="ManagerService">
+ </service>
+ </application>
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.WRITE_SDCARD" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+</manifest> \ No newline at end of file
diff --git a/tests/DumpRenderTree2/res/drawable/folder.png b/tests/DumpRenderTree2/res/drawable/folder.png
new file mode 100644
index 0000000..5b3fcec
--- /dev/null
+++ b/tests/DumpRenderTree2/res/drawable/folder.png
Binary files differ
diff --git a/tests/DumpRenderTree2/res/drawable/runtest.png b/tests/DumpRenderTree2/res/drawable/runtest.png
new file mode 100644
index 0000000..910c654
--- /dev/null
+++ b/tests/DumpRenderTree2/res/drawable/runtest.png
Binary files differ
diff --git a/tests/DumpRenderTree2/res/layout/dirlist_row.xml b/tests/DumpRenderTree2/res/layout/dirlist_row.xml
new file mode 100644
index 0000000..e5578a6
--- /dev/null
+++ b/tests/DumpRenderTree2/res/layout/dirlist_row.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2010 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="80px"
+ android:adjustViewBounds="true"
+ android:paddingLeft="15px"
+ android:paddingRight="15px"
+ android:paddingTop="15px"
+ android:paddingBottom="15px"
+ android:layout_height="wrap_content"
+ />
+
+ <TextView
+ android:id="@+id/label"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="60px"
+ android:gravity="center_vertical"
+ android:textSize="14sp"
+ />
+
+</LinearLayout> \ No newline at end of file
diff --git a/tests/DumpRenderTree2/res/values/strings.xml b/tests/DumpRenderTree2/res/values/strings.xml
new file mode 100644
index 0000000..5fd1eb9
--- /dev/null
+++ b/tests/DumpRenderTree2/res/values/strings.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2010 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<resources>
+ <string name="dialog_run_abort_dir_title_prefix">Directory:</string>
+ <string name="dialog_run_abort_dir_msg">This will run all the tests in this directory and all
+ the subdirectories. It may take a few hours!</string>
+ <string name="dialog_run_abort_dir_ok_button">Run tests!</string>
+ <string name="dialog_run_abort_dir_abort_button">Abort</string>
+
+ <string name="dialog_progress_title">Loading items.</string>
+ <string name="dialog_progress_msg">Please wait...</string>
+
+ <string name="runner_preloading_title">Preloading tests...</string>
+</resources> \ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/AbstractResult.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/AbstractResult.java
new file mode 100644
index 0000000..3c7dee2
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/AbstractResult.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dumprendertree2;
+
+import android.os.Bundle;
+import android.os.Message;
+import android.webkit.WebView;
+
+/**
+ * A class that represent a result of the test. It is responsible for returning the result's
+ * raw data and generating its own diff in HTML format.
+ */
+public abstract class AbstractResult {
+
+ public enum TestType {
+ TEXT,
+ PIXEL
+ }
+
+ public enum ResultCode {
+ PASS("Passed"),
+ FAIL_RESULT_DIFFERS("Failed: different results"),
+ FAIL_NO_EXPECTED_RESULT("Failed: no expected result"),
+ FAIL_TIMED_OUT("Failed: timed out"),
+ FAIL_CRASHED("Failed: crashed");
+
+ private String mTitle;
+
+ private ResultCode(String title) {
+ mTitle = title;
+ }
+
+ @Override
+ public String toString() {
+ return mTitle;
+ }
+ }
+
+ /**
+ * Makes the result object obtain the results of the test from the webview
+ * and store them in the format that suits itself bests. This method is asynchronous.
+ * The message passed as a parameter is a message that should be sent to its target
+ * when the result finishes obtaining the result.
+ *
+ * @param webview
+ * @param resultObtainedMsg
+ */
+ public abstract void obtainActualResults(WebView webview, Message resultObtainedMsg);
+
+ public abstract void setExpectedImageResult(byte[] expectedResult);
+
+ public abstract void setExpectedTextResult(String expectedResult);
+
+ /**
+ * Returns result's image data that can be written to the disk. It can be null
+ * if there is an error of some sort or for example the test times out.
+ *
+ * <p> Some tests will not provide data (like text tests)
+ *
+ * @return
+ * results image data
+ */
+ public abstract byte[] getActualImageResult();
+
+ /**
+ * Returns result's text data. It can be null
+ * if there is an error of some sort or for example the test times out.
+ *
+ * @return
+ * results text data
+ */
+ public abstract String getActualTextResult();
+
+ /**
+ * Returns the code of this result.
+ *
+ * @return
+ * the code of this result
+ */
+ public abstract ResultCode getResultCode();
+
+ /**
+ * Return the type of the result data.
+ *
+ * @return
+ * the type of the result data.
+ */
+ public abstract TestType getType();
+
+ /**
+ * Returns a piece of HTML code that presents a visual diff between a result and
+ * the expected result.
+ *
+ * @return
+ * a piece of HTML code with a visual diff between the result and the expected result
+ */
+ public abstract String getDiffAsHtml();
+
+ public abstract Bundle getBundle();
+} \ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/FileFilter.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/FileFilter.java
new file mode 100644
index 0000000..cf82d24
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/FileFilter.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dumprendertree2;
+
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A utility to filter out some files/directories from the views and tests that run.
+ */
+public class FileFilter {
+ private static final String LOG_TAG = "FileFilter";
+
+ private static final String TEST_EXPECTATIONS_TXT_PATH =
+ "platform/android/test_expectations.txt";
+
+ private static final String TOKEN_SKIP = "SKIP";
+ private static final String TOKEN_IGNORE_RESULT = "IGNORE_RESULT";
+ private static final String TOKEN_SLOW = "SLOW";
+
+ private final Set<String> mSkipList = new HashSet<String>();
+ private final Set<String> mIgnoreResultList = new HashSet<String>();
+ private final Set<String> mSlowList = new HashSet<String>();
+
+ private final String mRootDirPath;
+
+ public FileFilter(String rootDirPath) {
+ /** It may or may not contain a trailing slash */
+ this.mRootDirPath = rootDirPath;
+
+ reloadConfiguration();
+ }
+
+ private static final String trimTrailingSlashIfPresent(String path) {
+ File file = new File(path);
+ return file.getPath();
+ }
+
+ public void reloadConfiguration() {
+ File txt_exp = new File(mRootDirPath, TEST_EXPECTATIONS_TXT_PATH);
+
+ BufferedReader bufferedReader;
+ try {
+ bufferedReader =
+ new BufferedReader(new FileReader(txt_exp));
+
+ String line;
+ String entry;
+ String[] parts;
+ String path;
+ Set<String> tokens;
+ Boolean skipped;
+ while (true) {
+ line = bufferedReader.readLine();
+ if (line == null) {
+ break;
+ }
+
+ /** Remove the comment and trim */
+ entry = line.split("//", 2)[0].trim();
+
+ /** Omit empty lines, advance to next line */
+ if (entry.isEmpty()) {
+ continue;
+ }
+
+ /** Split on whitespace into path part and the rest */
+ parts = entry.split("\\s", 2);
+
+ /** At this point parts.length >= 1 */
+ if (parts.length == 1) {
+ Log.w(LOG_TAG + "::reloadConfiguration",
+ "There are no options specified for the test!");
+ continue;
+ }
+
+ path = trimTrailingSlashIfPresent(parts[0]);
+
+ /** Split on whitespace */
+ tokens = new HashSet<String>(Arrays.asList(parts[1].split("\\s", 0)));
+
+ /** Chose the right collections to add to */
+ skipped = false;
+ if (tokens.contains(TOKEN_SKIP)) {
+ mSkipList.add(path);
+ skipped = true;
+ }
+
+ /** If test is on skip list we ignore any further options */
+ if (skipped) {
+ continue;
+ }
+
+ if (tokens.contains(TOKEN_IGNORE_RESULT)) {
+ mIgnoreResultList.add(path);
+ }
+
+ if (tokens.contains(TOKEN_SLOW)) {
+ mSlowList.add(path);
+ }
+ }
+ } catch (FileNotFoundException e) {
+ Log.w(LOG_TAG + "::reloadConfiguration", "File not found: " + txt_exp.getPath());
+ } catch (IOException e) {
+ Log.e(LOG_TAG + "::reloadConfiguration", "IOException: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Checks if test is supposed to be skipped.
+ *
+ * <p>
+ * Path given should relative within LayoutTests folder, e.g. fast/dom/foo.html
+ *
+ * @param testPath
+ * - a relative path within LayoutTests folder
+ * @return if the test is supposed to be skipped
+ */
+ public boolean isSkip(String testPath) {
+ for (String prefix : getPrefixes(testPath)) {
+ if (mSkipList.contains(prefix)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks if test result is supposed to be ignored.
+ *
+ * <p>
+ * Path given should relative within LayoutTests folder, e.g. fast/dom/foo.html
+ *
+ * @param testPath
+ * - a relative path within LayoutTests folder
+ * @return if the test result is supposed to be ignored
+ */
+ public boolean isIgnoreRes(String testPath) {
+ for (String prefix : getPrefixes(testPath)) {
+ if (mIgnoreResultList.contains(prefix)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks if test is slow and should have timeout increased.
+ *
+ * <p>
+ * Path given should relative within LayoutTests folder, e.g. fast/dom/foo.html
+ *
+ * @param testPath
+ * - a relative path within LayoutTests folder
+ * @return if the test is slow and should have timeout increased.
+ */
+ public boolean isSlow(String testPath) {
+ for (String prefix : getPrefixes(testPath)) {
+ if (mSlowList.contains(prefix)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the list of all path prefixes of the given path.
+ *
+ * <p>
+ * e.g. this/is/a/path returns the list: this this/is this/is/a this/is/a/path
+ *
+ * @param path
+ * @return the list of all path prefixes of the given path.
+ */
+ private static List<String> getPrefixes(String path) {
+ File file = new File(path);
+ List<String> prefixes = new ArrayList<String>(8);
+
+ do {
+ prefixes.add(file.getPath());
+ file = file.getParentFile();
+ } while (file != null);
+
+ return prefixes;
+ }
+
+ /**
+ * Checks if the directory may contain tests or contains just helper files.
+ *
+ * @param dirName
+ * @return
+ * if the directory may contain tests
+ */
+ public static boolean isTestDir(String dirName) {
+ return (!dirName.equals("script-tests")
+ && !dirName.equals("resources") && !dirName.startsWith("."));
+ }
+
+ /**
+ * Checks if the file is a test.
+ * Currently we run .html and .xhtml tests.
+ *
+ * @param testName
+ * @return
+ * if the file is a test
+ */
+ public static boolean isTestFile(String testName) {
+ return testName.endsWith(".html") || testName.endsWith(".xhtml");
+ }
+
+ /**
+ * Return the path to the file relative to the tests root dir
+ *
+ * @param filePath
+ * @return
+ * the path relative to the tests root dir
+ */
+ public String getRelativePath(String filePath) {
+ File rootDir = new File(mRootDirPath);
+ return filePath.replaceFirst(rootDir.getPath() + File.separator, "");
+ }
+
+ /**
+ * Return the path to the file relative to the tests root dir
+ *
+ * @param filePath
+ * @return
+ * the path relative to the tests root dir
+ */
+ public String getRelativePath(File file) {
+ return getRelativePath(file.getAbsolutePath());
+ }
+
+ public File getAbsoluteFile(String relativePath) {
+ return new File(mRootDirPath, relativePath);
+ }
+
+ public String getAboslutePath(String relativePath) {
+ return getAbsoluteFile(relativePath).getAbsolutePath();
+ }
+
+ /**
+ * If the path contains extension (e.g .foo at the end of the file) then it changes
+ * this (.foo) into newEnding (so it has to contain the dot if we want to preserve it).
+ *
+ * <p>If the path doesn't contain an extension, it adds the ending to the path.
+ *
+ * @param relativePath
+ * @param newEnding
+ * @return
+ * a new path, containing the newExtension
+ */
+ public static String setPathEnding(String relativePath, String newEnding) {
+ int dotPos = relativePath.lastIndexOf('.');
+ if (dotPos == -1) {
+ return relativePath + newEnding;
+ }
+
+ return relativePath.substring(0, dotPos) + newEnding;
+ }
+} \ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/FsUtils.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/FsUtils.java
new file mode 100644
index 0000000..212c187
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/FsUtils.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dumprendertree2;
+
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ *
+ */
+public class FsUtils {
+ public static final String LOG_TAG = "FsUtils";
+
+ public static void writeDataToStorage(File file, byte[] bytes, boolean append) {
+ Log.d(LOG_TAG + "::writeDataToStorage", file.getAbsolutePath());
+ try {
+ OutputStream outputStream = null;
+ try {
+ file.getParentFile().mkdirs();
+ file.createNewFile();
+ Log.d(LOG_TAG + "::writeDataToStorage", "File created.");
+ outputStream = new FileOutputStream(file, append);
+ outputStream.write(bytes);
+ } finally {
+ if (outputStream != null) {
+ outputStream.close();
+ }
+ }
+ } catch (IOException e) {
+ Log.e(LOG_TAG + "::writeDataToStorage", e.getMessage());
+ }
+ }
+
+ public static byte[] readDataFromStorage(File file) {
+ if (!file.exists()) {
+ Log.d(LOG_TAG + "::readDataFromStorage", "File does not exist: "
+ + file.getAbsolutePath());
+ return null;
+ }
+
+ byte[] bytes = null;
+ try {
+ FileInputStream fis = null;
+ try {
+ fis = new FileInputStream(file);
+ bytes = new byte[(int) file.length()];
+ fis.read(bytes);
+ } finally {
+ if (fis != null) {
+ fis.close();
+ }
+ }
+ } catch (IOException e) {
+ Log.e(LOG_TAG + "::readDataFromStorage", e.getMessage());
+ }
+
+ return bytes;
+ }
+} \ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTest.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTest.java
new file mode 100644
index 0000000..1312ef9
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTest.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dumprendertree2;
+
+import android.app.Activity;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.webkit.JsPromptResult;
+import android.webkit.JsResult;
+import android.webkit.WebChromeClient;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.webkit.WebStorage.QuotaUpdater;
+
+import java.io.File;
+
+/**
+ * A class that represents a single layout test. It is responsible for running the test,
+ * checking its result and creating an AbstractResult object.
+ */
+public class LayoutTest {
+
+ private static final String LOG_TAG = "LayoutTest";
+
+ public static final int MSG_ACTUAL_RESULT_OBTAINED = 0;
+
+ private String mRelativePath;
+ private String mTestsRootDirPath;
+ private String mUrl;
+ private boolean mOnTestFinishedCalled;
+ private Message mTestFinishedMsg;
+ private AbstractResult mResult;
+
+ private WebView mWebView;
+ private Activity mActivity;
+
+ private final Handler mResultHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_ACTUAL_RESULT_OBTAINED) {
+ mResult.setExpectedTextResult(LayoutTestsRunnerThread
+ .getExpectedTextResult(mRelativePath));
+ mResult.setExpectedImageResult(LayoutTestsRunnerThread
+ .getExpectedImageResult(mRelativePath));
+ mTestFinishedMsg.sendToTarget();
+ }
+ }
+ };
+
+ private WebViewClient mWebViewClient = new WebViewClient() {
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ /** Some tests fire up many page loads, we don't want to detect them */
+ if (!url.equals(mUrl)) {
+ return;
+ }
+
+ onTestFinished();
+ }
+ };
+
+ private WebChromeClient mWebChromeClient = new WebChromeClient() {
+ @Override
+ public void onExceededDatabaseQuota(String url, String databaseIdentifier,
+ long currentQuota, long estimatedSize, long totalUsedQuota,
+ QuotaUpdater quotaUpdater) {
+ /** TODO: This should be recorded as part of the text result */
+ quotaUpdater.updateQuota(currentQuota + 5 * 1024 * 1024);
+ }
+
+ @Override
+ public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
+ /** TODO: Alerts should be recorded as part of text result */
+ result.confirm();
+ return true;
+ }
+
+ @Override
+ public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
+ /** TODO: Alerts should be recorded as part of text result */
+ result.confirm();
+ return true;
+ }
+
+ @Override
+ public boolean onJsPrompt(WebView view, String url, String message, String defaultValue,
+ JsPromptResult result) {
+ /** TODO: Alerts should be recorded as part of text result */
+ result.confirm();
+ return true;
+ }
+
+ };
+
+ public LayoutTest(String relativePath, String testsRootDirPath, Message testFinishedMsg,
+ LayoutTestsRunner activity) {
+ mRelativePath = relativePath;
+ mTestsRootDirPath = testsRootDirPath;
+ mTestFinishedMsg = testFinishedMsg;
+ mActivity = activity;
+ }
+
+ public LayoutTest(AbstractResult result, String relativePath) {
+ mResult = result;
+ mRelativePath = relativePath;
+ }
+
+ public void run() {
+ mWebView = new WebView(mActivity);
+ mActivity.setContentView(mWebView);
+
+ setupWebView();
+
+ /** TODO: Add timeout msg */
+ mUrl = Uri.fromFile(new File(mTestsRootDirPath, mRelativePath)).toString();
+ mWebView.loadUrl(mUrl);
+ }
+
+ private void onTestFinished() {
+ if (mOnTestFinishedCalled) {
+ return;
+ }
+
+ mOnTestFinishedCalled = true;
+
+ /**
+ * If the result has not been set by the time the test finishes we create
+ * a default type of result.
+ */
+ if (mResult == null) {
+ /** TODO: Default type should be RenderTreeResult. We don't support it now. */
+ mResult = new TextResult(mRelativePath);
+ }
+
+ /** TODO: Implement waitUntilDone */
+
+ mResult.obtainActualResults(mWebView,
+ mResultHandler.obtainMessage(MSG_ACTUAL_RESULT_OBTAINED));
+ }
+
+ private void setupWebView() {
+ WebSettings webViewSettings = mWebView.getSettings();
+ webViewSettings.setAppCacheEnabled(true);
+ webViewSettings.setAppCachePath(mActivity.getApplicationContext().getCacheDir().getPath());
+ webViewSettings.setAppCacheMaxSize(Long.MAX_VALUE);
+ webViewSettings.setJavaScriptEnabled(true);
+ webViewSettings.setJavaScriptCanOpenWindowsAutomatically(true);
+ webViewSettings.setSupportMultipleWindows(true);
+ webViewSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
+ webViewSettings.setDatabaseEnabled(true);
+ webViewSettings.setDatabasePath(mActivity.getDir("databases", 0).getAbsolutePath());
+ webViewSettings.setDomStorageEnabled(true);
+ webViewSettings.setWorkersEnabled(false);
+ webViewSettings.setXSSAuditorEnabled(false);
+
+ mWebView.setWebViewClient(mWebViewClient);
+ mWebView.setWebChromeClient(mWebChromeClient);
+ }
+
+ public AbstractResult getResult() {
+ return mResult;
+ }
+
+ public String getRelativePath() {
+ return mRelativePath;
+ }
+} \ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecuter.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecuter.java
new file mode 100644
index 0000000..6fd3085
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecuter.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dumprendertree2;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+import android.webkit.JsPromptResult;
+import android.webkit.JsResult;
+import android.webkit.WebChromeClient;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.webkit.WebStorage.QuotaUpdater;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * This activity executes the test. It contains WebView and logic of LayoutTestController
+ * functions. It runs in a separate process and sends the results of running the test
+ * to ManagerService. The reason why is to handle crashing (test that crashes brings down
+ * whole process with it).
+ */
+public class LayoutTestsExecuter extends Activity {
+
+ /** TODO: make it a setting */
+ static final String TESTS_ROOT_DIR_PATH =
+ Environment.getExternalStorageDirectory() +
+ File.separator + "android" +
+ File.separator + "LayoutTests";
+
+ private static final String LOG_TAG = "LayoutTestExecuter";
+
+ public static final String EXTRA_TESTS_LIST = "TestsList";
+
+ private static final int MSG_ACTUAL_RESULT_OBTAINED = 0;
+
+ private List<String> mTestsList;
+ private int mCurrentTestCount = 0;
+
+ private WebView mCurrentWebView;
+ private String mCurrentTestRelativePath;
+ private String mCurrentTestUri;
+
+ private boolean mOnTestFinishedCalled;
+ private AbstractResult mCurrentResult;
+
+ /** COMMUNICATION WITH ManagerService */
+
+ private Messenger mManagerServiceMessenger;
+
+ private ServiceConnection mServiceConnection = new ServiceConnection() {
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mManagerServiceMessenger = new Messenger(service);
+ runNextTest();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ /** TODO */
+ }
+ };
+
+ private final Handler mResultHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_ACTUAL_RESULT_OBTAINED) {
+ reportResultToService();
+ runNextTest();
+ }
+ }
+ };
+
+ /** WEBVIEW CONFIGURATION */
+
+ private WebViewClient mWebViewClient = new WebViewClient() {
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ /** Some tests fire up many page loads, we don't want to detect them */
+ if (!url.equals(mCurrentTestUri)) {
+ return;
+ }
+
+ /** TODO: Implement waitUntilDone */
+ onTestFinished();
+ }
+ };
+
+ private WebChromeClient mWebChromeClient = new WebChromeClient() {
+ @Override
+ public void onExceededDatabaseQuota(String url, String databaseIdentifier,
+ long currentQuota, long estimatedSize, long totalUsedQuota,
+ QuotaUpdater quotaUpdater) {
+ /** TODO: This should be recorded as part of the text result */
+ quotaUpdater.updateQuota(currentQuota + 5 * 1024 * 1024);
+ }
+
+ @Override
+ public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
+ /** TODO: Alerts should be recorded as part of text result */
+ result.confirm();
+ return true;
+ }
+
+ @Override
+ public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
+ /** TODO: Alerts should be recorded as part of text result */
+ result.confirm();
+ return true;
+ }
+
+ @Override
+ public boolean onJsPrompt(WebView view, String url, String message, String defaultValue,
+ JsPromptResult result) {
+ /** TODO: Alerts should be recorded as part of text result */
+ result.confirm();
+ return true;
+ }
+
+ };
+
+ /** IMPLEMENTATION */
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+ mTestsList = intent.getStringArrayListExtra(EXTRA_TESTS_LIST);
+
+ bindService(new Intent(this, ManagerService.class), mServiceConnection,
+ Context.BIND_AUTO_CREATE);
+ }
+
+ private void reset() {
+ mOnTestFinishedCalled = false;
+ mCurrentResult = null;
+
+ mCurrentWebView = new WebView(this);
+ mCurrentWebView.setWebViewClient(mWebViewClient);
+ mCurrentWebView.setWebChromeClient(mWebChromeClient);
+
+ WebSettings webViewSettings = mCurrentWebView.getSettings();
+ webViewSettings.setAppCacheEnabled(true);
+ webViewSettings.setAppCachePath(getApplicationContext().getCacheDir().getPath());
+ webViewSettings.setAppCacheMaxSize(Long.MAX_VALUE);
+ webViewSettings.setJavaScriptEnabled(true);
+ webViewSettings.setJavaScriptCanOpenWindowsAutomatically(true);
+ webViewSettings.setSupportMultipleWindows(true);
+ webViewSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
+ webViewSettings.setDatabaseEnabled(true);
+ webViewSettings.setDatabasePath(getDir("databases", 0).getAbsolutePath());
+ webViewSettings.setDomStorageEnabled(true);
+ webViewSettings.setWorkersEnabled(false);
+ webViewSettings.setXSSAuditorEnabled(false);
+
+ setContentView(mCurrentWebView);
+ }
+
+ private void runNextTest() {
+ if (mTestsList.isEmpty()) {
+ onAllTestsFinished();
+ return;
+ }
+
+ mCurrentTestCount++;
+ mCurrentTestRelativePath = mTestsList.remove(0);
+ mCurrentTestUri =
+ Uri.fromFile(new File(TESTS_ROOT_DIR_PATH, mCurrentTestRelativePath)).toString();
+
+ reset();
+ /** TODO: Implement timeout */
+ mCurrentWebView.loadUrl(mCurrentTestUri);
+ }
+
+ private void onTestFinished() {
+ if (mOnTestFinishedCalled) {
+ return;
+ }
+
+ mOnTestFinishedCalled = true;
+
+ /**
+ * If the result has not been set by the time the test finishes we create
+ * a default type of result.
+ */
+ if (mCurrentResult == null) {
+ /** TODO: Default type should be RenderTreeResult. We don't support it now. */
+ mCurrentResult = new TextResult(mCurrentTestRelativePath);
+ }
+
+ mCurrentResult.obtainActualResults(mCurrentWebView,
+ mResultHandler.obtainMessage(MSG_ACTUAL_RESULT_OBTAINED));
+ }
+
+ private void reportResultToService() {
+ try {
+ Message serviceMsg =
+ Message.obtain(null, ManagerService.MSG_PROCESS_ACTUAL_RESULTS);
+ Bundle bundle = mCurrentResult.getBundle();
+ /** TODO: Add timeout info to bundle */
+ serviceMsg.setData(bundle);
+ mManagerServiceMessenger.send(serviceMsg);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG + "::reportResultToService", e.getMessage());
+ }
+ }
+
+ private void onAllTestsFinished() {
+ Log.d(LOG_TAG + "::onAllTestsFisnihed", "Begin.");
+ }
+} \ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsRunner.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsRunner.java
new file mode 100644
index 0000000..4421aba
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsRunner.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dumprendertree2;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.view.Window;
+
+/**
+ * An Activity that is responsible only for updating the UI features, like titles, progress bars,
+ * etc.
+ *
+ * <p>Also, the webview form the test must be running in this activity's thread if we want
+ * to be able to display it on the screen.
+ */
+public class LayoutTestsRunner extends Activity {
+
+ public static final int MSG_UPDATE_PROGRESS = 1;
+ public static final int MSG_SHOW_PROGRESS_DIALOG = 2;
+ public static final int MSG_DISMISS_PROGRESS_DIALOG = 3;
+
+ /** Constants for adding extras to an intent */
+ public static final String EXTRA_TEST_PATH = "TestPath";
+
+ private static ProgressDialog sProgressDialog;
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_UPDATE_PROGRESS:
+ int i = msg.arg1;
+ int size = msg.arg2;
+ getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
+ i * Window.PROGRESS_END / size);
+ setTitle(i * 100 / size + "% (" + i + "/" + size + ")");
+ break;
+
+ case MSG_SHOW_PROGRESS_DIALOG:
+ sProgressDialog.show();
+ break;
+
+ case MSG_DISMISS_PROGRESS_DIALOG:
+ sProgressDialog.dismiss();
+ break;
+ }
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ /** Prepare the progress dialog */
+ sProgressDialog = new ProgressDialog(LayoutTestsRunner.this);
+ sProgressDialog.setCancelable(false);
+ sProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+ sProgressDialog.setTitle(R.string.dialog_progress_title);
+ sProgressDialog.setMessage(getText(R.string.dialog_progress_msg));
+
+ requestWindowFeature(Window.FEATURE_PROGRESS);
+
+ /** Execute the intent */
+ Intent intent = getIntent();
+ if (!intent.getAction().equals(Intent.ACTION_RUN)) {
+ return;
+ }
+ String path = intent.getStringExtra(EXTRA_TEST_PATH);
+
+ new LayoutTestsRunnerThread(path, this).start();
+ }
+
+ public Handler getHandler() {
+ return mHandler;
+ }
+} \ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsRunnerThread.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsRunnerThread.java
new file mode 100644
index 0000000..ac814cb
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsRunnerThread.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dumprendertree2;
+
+import android.content.Intent;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.LinkedList;
+
+/**
+ * A Thread that is responsible for finding and loading the tests, starting them and
+ * generating summaries. The actual running of the test is delegated to LayoutTestsRunner
+ * activity (a UI thread) because of a WebView object that need to be created in UI thread
+ * so it can be displayed on the screen. However, the logic for doing this remains in
+ * this class (in handler created in constructor).
+ */
+public class LayoutTestsRunnerThread extends Thread {
+
+ private static final String LOG_TAG = "LayoutTestsRunnerThread";
+
+ /** Messages for handler on this thread */
+ public static final int MSG_TEST_FINISHED = 0;
+
+ /** Messages for our handler running on UI thread */
+ public static final int MSG_RUN_TEST = 0;
+
+ /** TODO: make it a setting */
+ private static final String TESTS_ROOT_DIR_PATH =
+ Environment.getExternalStorageDirectory() +
+ File.separator + "android" +
+ File.separator + "LayoutTests";
+
+ /** TODO: make it a setting */
+ private static final String RESULTS_ROOT_DIR_PATH =
+ Environment.getExternalStorageDirectory() +
+ File.separator + "android" +
+ File.separator + "LayoutTests-results";
+
+ /** TODO: Make it a setting */
+ private static final String EXPECTED_RESULT_SECONDARY_LOCATION_RELATIVE_DIR_PREFIX =
+ "platform" + File.separator +
+ "android-v8" + File.separator;
+
+ /** TODO: Make these settings */
+ private static final String TEXT_RESULT_EXTENSION = "txt";
+ private static final String IMAGE_RESULT_EXTENSION = "png";
+
+ /** A list containing relative paths of tests to run */
+ private LinkedList<String> mTestsList = new LinkedList<String>();
+
+ private FileFilter mFileFilter;
+ private Summarizer mSummarizer;
+
+ /** Our handler running on this thread. Created in run() method. */
+ private Handler mHandler;
+
+ /** Our handler running on UI thread. Created in constructor of this thread. */
+ private Handler mHandlerOnUiThread;
+
+ /**
+ * A relative path to the folder with the tests we want to run or particular test.
+ * Used up to and including preloadTests().
+ */
+ private String mRelativePath;
+
+ private LayoutTestsRunner mActivity;
+
+ private LayoutTest mCurrentTest;
+ private String mCurrentTestPath;
+ private int mCurrentTestCount = 0;
+ private int mTotalTestCount;
+
+ /**
+ * The given path must be relative to the root dir. The given handler must be
+ * able to handle messages that update the display (UI thread).
+ *
+ * @param path
+ * @param uiDisplayHandler
+ */
+ public LayoutTestsRunnerThread(String path, LayoutTestsRunner activity) {
+ mFileFilter = new FileFilter(TESTS_ROOT_DIR_PATH);
+ mRelativePath = path;
+ mActivity = activity;
+
+ /** This creates a handler that runs on the thread that _created_ this thread */
+ mHandlerOnUiThread = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_RUN_TEST:
+ ((LayoutTest) msg.obj).run();
+ break;
+ }
+ }
+ };
+ }
+
+ @Override
+ public void run() {
+ Looper.prepare();
+
+ mSummarizer = new Summarizer(mFileFilter, RESULTS_ROOT_DIR_PATH);
+
+ /** A handler obtained from UI thread to handle messages concerning updating the display */
+ final Handler uiDisplayHandler = mActivity.getHandler();
+
+ /** Creates a new handler in _this_ thread */
+ mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_TEST_FINISHED:
+ onTestFinished(mCurrentTest);
+ uiDisplayHandler.obtainMessage(LayoutTestsRunner.MSG_UPDATE_PROGRESS,
+ mCurrentTestCount, mTotalTestCount).sendToTarget();
+ runNextTest();
+ break;
+ }
+ }
+ };
+
+ /** Check if the path is correct */
+ File file = new File(TESTS_ROOT_DIR_PATH, mRelativePath);
+ if (!file.exists()) {
+ Log.e(LOG_TAG + "::run", "Path does not exist: " + mRelativePath);
+ return;
+ }
+
+ /** Populate the tests' list accordingly */
+ if (file.isDirectory()) {
+ uiDisplayHandler.sendEmptyMessage(LayoutTestsRunner.MSG_SHOW_PROGRESS_DIALOG);
+ preloadTests(mRelativePath);
+ uiDisplayHandler.sendEmptyMessage(LayoutTestsRunner.MSG_DISMISS_PROGRESS_DIALOG);
+ } else {
+ mTestsList.addLast(mRelativePath);
+ mTotalTestCount = 1;
+ }
+
+ /**
+ * Instead of running next test here, we send a tests' list to Executer activity.
+ * Rest of the code is never executed and will be gradually moved to the service.
+ */
+ Intent intent = new Intent();
+ intent.setClass(mActivity, LayoutTestsExecuter.class);
+ intent.setAction(Intent.ACTION_RUN);
+ intent.putStringArrayListExtra(LayoutTestsExecuter.EXTRA_TESTS_LIST,
+ new ArrayList<String>(mTestsList));
+ mActivity.startActivity(intent);
+
+ Looper.loop();
+ }
+
+ /**
+ * Loads all the tests from the given folders and all the subfolders
+ * into mTestsList.
+ *
+ * @param dirRelativePath
+ */
+ private void preloadTests(String dirRelativePath) {
+ LinkedList<String> foldersList = new LinkedList<String>();
+ foldersList.add(dirRelativePath);
+
+ String relativePath;
+ String currentDirRelativePath;
+ String itemName;
+ File[] items;
+ while (!foldersList.isEmpty()) {
+ currentDirRelativePath = foldersList.removeFirst();
+ items = new File(TESTS_ROOT_DIR_PATH, currentDirRelativePath).listFiles();
+ for (File item : items) {
+ itemName = item.getName();
+ relativePath = currentDirRelativePath + File.separator + itemName;
+
+ if (item.isDirectory() && FileFilter.isTestDir(itemName)) {
+ foldersList.add(relativePath);
+ continue;
+ }
+
+ if (FileFilter.isTestFile(itemName)) {
+ if (!mFileFilter.isSkip(relativePath)) {
+ mTestsList.addLast(relativePath);
+ } else {
+ mSummarizer.addSkippedTest(relativePath);
+ }
+ }
+ }
+ }
+
+ mTotalTestCount = mTestsList.size();
+ }
+
+ private void runNextTest() {
+ if (mTestsList.isEmpty()) {
+ onFinishedTests();
+ return;
+ }
+
+ mCurrentTestCount++;
+ mCurrentTestPath = mTestsList.removeFirst();
+ mCurrentTest = new LayoutTest(mCurrentTestPath, TESTS_ROOT_DIR_PATH,
+ mHandler.obtainMessage(MSG_TEST_FINISHED), mActivity);
+
+ /**
+ * This will run the test on UI thread. The reason why we need to run the test
+ * on UI thread is because of the WebView. If we want to display the webview on
+ * the screen it needs to be in the UI thread. WebView should be created as
+ * part of the LayoutTest.run() method.
+ */
+ mHandlerOnUiThread.obtainMessage(MSG_RUN_TEST, mCurrentTest).sendToTarget();
+ }
+
+ private void onTestFinished(LayoutTest test) {
+ String testPath = test.getRelativePath();
+
+ /** Obtain the result */
+ AbstractResult result = test.getResult();
+ if (result == null) {
+ Log.e(LOG_TAG + "::runTests", testPath + ": result NULL!!");
+ return;
+ }
+
+ dumpResultData(result, testPath);
+
+ mSummarizer.appendTest(test);
+ }
+
+ private void dumpResultData(AbstractResult result, String testPath) {
+ dumpActualTextResult(result, testPath);
+ dumpActualImageResult(result, testPath);
+ }
+
+ private void dumpActualTextResult(AbstractResult result, String testPath) {
+ String actualTextResult = result.getActualTextResult();
+ if (actualTextResult == null) {
+ return;
+ }
+
+ String resultPath = FileFilter.setPathEnding(testPath, "-actual." + TEXT_RESULT_EXTENSION);
+ FsUtils.writeDataToStorage(new File(RESULTS_ROOT_DIR_PATH, resultPath),
+ actualTextResult.getBytes(), false);
+ }
+
+ private void dumpActualImageResult(AbstractResult result, String testPath) {
+ byte[] actualImageResult = result.getActualImageResult();
+ if (actualImageResult == null) {
+ return;
+ }
+
+ String resultPath = FileFilter.setPathEnding(testPath, "-actual." + IMAGE_RESULT_EXTENSION);
+ FsUtils.writeDataToStorage(new File(RESULTS_ROOT_DIR_PATH, resultPath),
+ actualImageResult, false);
+ }
+
+ private void onFinishedTests() {
+ Log.d(LOG_TAG + "::onFinishedTests", "Begin.");
+ Looper.myLooper().quit();
+ mSummarizer.summarize();
+ /** TODO: Present some kind of notification to the user that
+ * allows to chose next action, e.g:
+ * - go to html view of results
+ * - zip results
+ * - run more tests before zipping */
+ }
+
+ public static String getExpectedTextResult(String relativePath) {
+ return new String(getExpectedResult(relativePath, TEXT_RESULT_EXTENSION));
+ }
+
+ public static byte[] getExpectedImageResult(String relativePath) {
+ return getExpectedResult(relativePath, IMAGE_RESULT_EXTENSION);
+ }
+
+ private static byte[] getExpectedResult(String relativePath, String extension) {
+ relativePath = FileFilter.setPathEnding(relativePath, "-expected." + extension);
+
+ byte[] bytes = FsUtils.readDataFromStorage(new File(TESTS_ROOT_DIR_PATH, relativePath));
+ if (bytes == null) {
+ relativePath = EXPECTED_RESULT_SECONDARY_LOCATION_RELATIVE_DIR_PREFIX + relativePath;
+ bytes = FsUtils.readDataFromStorage(new File(TESTS_ROOT_DIR_PATH, relativePath));
+ }
+
+ return bytes;
+ }
+} \ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java
new file mode 100644
index 0000000..e452a38
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dumprendertree2;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.util.Log;
+
+/**
+ * A service that handles managing the results of tests, informing of crashes, generating
+ * summaries, etc.
+ */
+public class ManagerService extends Service {
+
+ private static final String LOG_TAG = "ManagerService";
+
+ static final int MSG_PROCESS_ACTUAL_RESULTS = 0;
+
+ private Handler mIncomingHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_PROCESS_ACTUAL_RESULTS:
+ Log.d(LOG_TAG + ".mIncomingHandler", msg.getData().getString("relativePath"));
+ break;
+ }
+ }
+ };
+
+ private Messenger mMessenger = new Messenger(mIncomingHandler);
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ /** TODO: */
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mMessenger.getBinder();
+ }
+} \ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java
new file mode 100644
index 0000000..4d15bb5
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dumprendertree2;
+
+import android.util.Log;
+
+import java.io.File;
+import java.util.EnumMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class that collects information about tests that ran and can create HTML
+ * files with summaries and easy navigation.
+ */
+public class Summarizer {
+
+ private static final String LOG_TAG = "Summarizer";
+
+ private static final String CSS =
+ "* {" +
+ " font-family: Verdana;" +
+ " border: 0;" +
+ " margin: 0;" +
+ " padding: 0;}" +
+ "body {" +
+ " margin: 10px;}" +
+ "a {" +
+ " font-size: 12px;" +
+ " color: black;}" +
+ "h1 {" +
+ " font-size: 33px;" +
+ " margin: 4px 0 4px 0;}" +
+ "h2 {" +
+ " font-size:22px;" +
+ " margin: 20px 0 3px 0;}" +
+ "h3 {" +
+ " font-size: 20px;" +
+ " margin-bottom: 6px;}" +
+ "table.visual_diff {" +
+ " border-bottom: 0px solid;" +
+ " border-collapse: collapse;" +
+ " width: 100%;" +
+ " margin-bottom: 3px;}" +
+ "table.visual_diff tr.headers td {" +
+ " border-bottom: 1px solid;" +
+ " border-top: 0;" +
+ " padding-bottom: 3px;}" +
+ "table.visual_diff tr.results td {" +
+ " border-top: 1px dashed;" +
+ " border-right: 1px solid;" +
+ " font-size: 15px;" +
+ " vertical-align: top;}" +
+ "table.visual_diff tr.results td.line_count {" +
+ " background-color:#aaa;" +
+ " min-width:20px;" +
+ " text-align: right;" +
+ " border-right: 1px solid;" +
+ " border-left: 1px solid;" +
+ " padding: 2px 1px 2px 0px;}" +
+ "table.visual_diff tr.results td.line {" +
+ " padding: 2px 0px 2px 4px;" +
+ " border-right: 1px solid;" +
+ " width: 49.8%;}" +
+ "table.visual_diff tr.footers td {" +
+ " border-top: 1px solid;" +
+ " border-bottom: 0;}" +
+ "table.visual_diff tr td.space {" +
+ " border: 0;" +
+ " width: 0.4%}" +
+ "div.space {" +
+ " margin-top:30px;}" +
+ "span.eql {" +
+ " background-color: #f3f3f3;}" +
+ "span.del {" +
+ " background-color: #ff8888; }" +
+ "span.ins {" +
+ " background-color: #88ff88; }" +
+ "span.fail {" +
+ " color: red;}" +
+ "span.pass {" +
+ " color: green;}" +
+ "span.time_out {" +
+ " color: orange;}";
+ private static final String HTML_DIFF_BEGINNING = "<html><head><style type=\"text/css\">" +
+ CSS + "</style></head><body>";
+ private static final String HTML_DIFF_ENDING = "</body></html>";
+
+ /** TODO: Make it a setting */
+ private static final String HTML_DIFF_RELATIVE_PATH = "_diff.html";
+ private static final String HTML_DIFF_INDEX_RELATIVE_PATH = "_diff-index.html";
+
+ /** A list containing relatives paths of tests that were skipped */
+ private LinkedList<String> mSkippedTestsList = new LinkedList<String>();
+
+ /** Collection of tests grouped according to result. Sets are initialized lazily. */
+ private Map<AbstractResult.ResultCode, Set<String>> mResults =
+ new EnumMap<AbstractResult.ResultCode, Set<String>>(AbstractResult.ResultCode.class);
+
+ /**
+ * Collection of tests for which results are ignored grouped according to result. Sets are
+ * initialized lazily.
+ */
+ private Map<AbstractResult.ResultCode, Set<String>> mResultsIgnored =
+ new EnumMap<AbstractResult.ResultCode, Set<String>>(AbstractResult.ResultCode.class);
+
+ private FileFilter mFileFilter;
+ private String mResultsRootDirPath;
+
+ public Summarizer(FileFilter fileFilter, String resultsRootDirPath) {
+ mFileFilter = fileFilter;
+ mResultsRootDirPath = resultsRootDirPath;
+ createHtmlDiff();
+ }
+
+ private void createHtmlDiff() {
+ FsUtils.writeDataToStorage(new File(mResultsRootDirPath, HTML_DIFF_RELATIVE_PATH),
+ HTML_DIFF_BEGINNING.getBytes(), false);
+ }
+
+ private void appendHtmlDiff(String relativePath, String diff) {
+ StringBuilder html = new StringBuilder();
+ html.append("<label id=\"" + relativePath + "\" />");
+ html.append(diff);
+ html.append("<a href=\"" + HTML_DIFF_INDEX_RELATIVE_PATH + "\">Back to index</a>");
+ html.append("<div class=\"space\"></div>");
+ FsUtils.writeDataToStorage(new File(mResultsRootDirPath, HTML_DIFF_RELATIVE_PATH),
+ html.toString().getBytes(), true);
+ }
+
+ private void finalizeHtmlDiff() {
+ FsUtils.writeDataToStorage(new File(mResultsRootDirPath, HTML_DIFF_RELATIVE_PATH),
+ HTML_DIFF_ENDING.getBytes(), true);
+ }
+
+ /** TODO: Add settings method, like setIndexSkippedTests(), setIndexTimedOutTests(), etc */
+
+ public void addSkippedTest(String relativePath) {
+ mSkippedTestsList.addLast(relativePath);
+ }
+
+ public void appendTest(LayoutTest test) {
+ String testPath = test.getRelativePath();
+
+ /** Obtain the result */
+ AbstractResult result = test.getResult();
+ if (result == null) {
+ Log.e(LOG_TAG + "::appendTest", testPath + ": result NULL!!");
+ return;
+ }
+
+ AbstractResult.ResultCode resultCode = result.getResultCode();
+
+ /** Add the test to correct collection according to its result code */
+ if (mFileFilter.isIgnoreRes(testPath)) {
+ /** Lazy initialization */
+ if (mResultsIgnored.get(resultCode) == null) {
+ mResultsIgnored.put(resultCode, new HashSet<String>());
+ }
+
+ mResultsIgnored.get(resultCode).add(testPath);
+ } else {
+ /** Lazy initialization */
+ if (mResults.get(resultCode) == null) {
+ mResults.put(resultCode, new HashSet<String>());
+ }
+
+ mResults.get(resultCode).add(testPath);
+ }
+
+ if (resultCode != AbstractResult.ResultCode.PASS) {
+ appendHtmlDiff(testPath, result.getDiffAsHtml());
+ }
+ }
+
+ public void summarize() {
+ finalizeHtmlDiff();
+ createHtmlDiffIndex();
+ }
+
+ private void createHtmlDiffIndex() {
+ StringBuilder html = new StringBuilder();
+ html.append(HTML_DIFF_BEGINNING);
+ Set<String> results;
+ html.append("<h1>NOT ignored</h1>");
+ appendResultsMap(mResults, html);
+ html.append("<h1>Ignored</h1>");
+ appendResultsMap(mResultsIgnored, html);
+ html.append(HTML_DIFF_ENDING);
+ FsUtils.writeDataToStorage(new File(mResultsRootDirPath, HTML_DIFF_INDEX_RELATIVE_PATH),
+ html.toString().getBytes(), false);
+ }
+
+ private void appendResultsMap(Map<AbstractResult.ResultCode, Set<String>> resultsMap,
+ StringBuilder html) {
+ Set<String> results;
+ for (AbstractResult.ResultCode resultCode : AbstractResult.ResultCode.values()) {
+ results = resultsMap.get(resultCode);
+ if (results != null) {
+ html.append("<h2>");
+ html.append(resultCode.toString());
+ html.append("</h2");
+ for (String relativePath : results) {
+ html.append("<a href=\"");
+ html.append(HTML_DIFF_RELATIVE_PATH);
+ html.append("#");
+ html.append(relativePath);
+ html.append("\">");
+ html.append(relativePath);
+ html.append("</a><br />");
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/TextResult.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TextResult.java
new file mode 100644
index 0000000..7bab4ae
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TextResult.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dumprendertree2;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.webkit.WebView;
+
+/**
+ * A result object for which the expected output is text. It does not have an image
+ * expected result.
+ *
+ * <p>Created if layoutTestController.dumpAsText() was called.
+ */
+public class TextResult extends AbstractResult {
+
+ private static final int MSG_DOCUMENT_AS_TEXT = 0;
+
+ private String mExpectedResult;
+ private String mActualResult;
+ private String mRelativePath;
+ private ResultCode mResultCode;
+ private Message mResultObtainedMsg;
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_DOCUMENT_AS_TEXT) {
+ mActualResult = (String) msg.obj;
+ mResultObtainedMsg.sendToTarget();
+ }
+ }
+ };
+
+ public TextResult(String relativePath) {
+ mRelativePath = relativePath;
+ }
+
+ /**
+ * Used to recreate the Result when received by the service.
+ *
+ * @param bundle
+ * bundle with data used to recreate the result
+ */
+ public TextResult(Bundle bundle) {
+ mExpectedResult = bundle.getString("expectedTextualResult");
+ mActualResult = bundle.getString("actualTextualResult");
+ mRelativePath = bundle.getString("relativePath");
+ mResultCode = ResultCode.valueOf(bundle.getString("resultCode"));
+ }
+
+ @Override
+ public ResultCode getResultCode() {
+ if (mResultCode != null) {
+ return mResultCode;
+ }
+
+ if (mExpectedResult == null) {
+ mResultCode = AbstractResult.ResultCode.FAIL_NO_EXPECTED_RESULT;
+ } else if (!mExpectedResult.equals(mActualResult)) {
+ mResultCode = AbstractResult.ResultCode.FAIL_RESULT_DIFFERS;
+ } else {
+ mResultCode = AbstractResult.ResultCode.PASS;
+ }
+
+ return mResultCode;
+ }
+
+ @Override
+ public byte[] getActualImageResult() {
+ return null;
+ }
+
+ @Override
+ public String getActualTextResult() {
+ return mActualResult;
+ }
+
+ @Override
+ public void setExpectedImageResult(byte[] expectedResult) {
+ /** This method is not applicable to this type of result */
+ }
+
+ @Override
+ public void setExpectedTextResult(String expectedResult) {
+ mExpectedResult = expectedResult;
+ }
+
+ @Override
+ public String getDiffAsHtml() {
+ /** TODO: just a stub
+ * Probably needs rethinking anyway - just one table would be much better
+ * This will require some changes in Summarizer in CSS as well */
+ StringBuilder html = new StringBuilder();
+ html.append("<h3>");
+ html.append(mRelativePath);
+ html.append("</h3>");
+ html.append("<table class=\"visual_diff\">");
+
+ html.append("<tr class=\"headers\">");
+ html.append("<td colspan=\"2\">Expected result:</td>");
+ html.append("<td class=\"space\"></td>");
+ html.append("<td colspan=\"2\">Actual result:</td>");
+ html.append("</tr>");
+
+ html.append("<tr class=\"results\">");
+ html.append("<td class=\"line_count\">1:</td>");
+ html.append("<td class=\"line\">");
+ if (mExpectedResult == null) {
+ html.append("NULL");
+ } else {
+ html.append(mExpectedResult.replace("\n", "<br />"));
+ }
+ html.append("</td>");
+ html.append("<td class=\"space\"></td>");
+ html.append("<td class=\"line_count\">1:</td>");
+ html.append("<td class=\"line\">");
+ if (mActualResult == null) {
+ html.append("NULL");
+ } else {
+ html.append(mActualResult.replace("\n", "<br />"));
+ }
+ html.append("</td>");
+ html.append("</tr>");
+
+ html.append("<tr class=\"footers\">");
+ html.append("<td colspan=\"2\"></td>");
+ html.append("<td class=\"space\"></td>");
+ html.append("<td colspan=\"2\"></td>");
+ html.append("</tr>");
+
+ html.append("</table>");
+
+ return html.toString();
+ }
+
+ @Override
+ public TestType getType() {
+ return TestType.TEXT;
+ }
+
+ @Override
+ public void obtainActualResults(WebView webview, Message resultObtainedMsg) {
+ mResultObtainedMsg = resultObtainedMsg;
+ Message msg = mHandler.obtainMessage(MSG_DOCUMENT_AS_TEXT);
+
+ /** TODO: mDumpTopFrameAsText and mDumpChildFramesAsText */
+ msg.arg1 = 1;
+ msg.arg2 = 0;
+ webview.documentAsText(msg);
+ }
+
+ @Override
+ public Bundle getBundle() {
+ Bundle bundle = new Bundle();
+ bundle.putString("expectedTextualResult", mExpectedResult);
+ bundle.putString("actualTextualResult", mActualResult);
+ bundle.putString("relativePath", mRelativePath);
+ if (mResultCode != null) {
+ bundle.putString("resultCode", mResultCode.name());
+ }
+ bundle.putString("type", getType().name());
+ return bundle;
+ }
+} \ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java
new file mode 100644
index 0000000..790d1d3
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dumprendertree2.ui;
+
+import com.android.dumprendertree2.FileFilter;
+import com.android.dumprendertree2.LayoutTestsRunner;
+import com.android.dumprendertree2.R;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.ListActivity;
+import android.app.ProgressDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Message;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * An Activity that allows navigating through tests folders and choosing folders or tests to run.
+ */
+public class DirListActivity extends ListActivity {
+
+ private static final String LOG_TAG = "DirListActivity";
+ private static final String ROOT_DIR_PATH =
+ Environment.getExternalStorageDirectory() +
+ File.separator + "android" +
+ File.separator + "LayoutTests";
+
+ /** TODO: This is just a guess - think of a better way to achieve it */
+ private static final int MEAN_TITLE_CHAR_SIZE = 13;
+
+ private static final int PROGRESS_DIALOG_DELAY_MS = 200;
+
+ /** Code for the dialog, used in showDialog and onCreateDialog */
+ private static final int DIALOG_RUN_ABORT_DIR = 0;
+
+ /** Messages codes */
+ private static final int MSG_LOADED_ITEMS = 0;
+ private static final int MSG_SHOW_PROGRESS_DIALOG = 1;
+
+ /** Initialized lazily before first sProgressDialog.show() */
+ private static ProgressDialog sProgressDialog;
+
+ private ListView mListView;
+
+ /** This is a relative path! */
+ private String mCurrentDirPath;
+
+ /**
+ * TODO: This should not be a constant, but rather be configurable from somewhere.
+ */
+ private String mRootDirPath = ROOT_DIR_PATH;
+
+ private FileFilter mFileFilter;
+
+ /**
+ * A thread responsible for loading the contents of the directory from sd card
+ * and sending them via Message to main thread that then loads them into
+ * ListView
+ */
+ private class LoadListItemsThread extends Thread {
+ private Handler mHandler;
+ private String mRelativePath;
+
+ public LoadListItemsThread(String relativePath, Handler handler) {
+ mRelativePath = relativePath;
+ mHandler = handler;
+ }
+
+ @Override
+ public void run() {
+ Message msg = mHandler.obtainMessage(MSG_LOADED_ITEMS);
+ msg.obj = getDirList(mRelativePath);
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ /**
+ * Very simple object to use inside ListView as an item.
+ */
+ private static class ListItem implements Comparable<ListItem> {
+ private String mRelativePath;
+ private String mName;
+ private boolean mIsDirectory;
+
+ public ListItem(String relativePath, boolean isDirectory) {
+ mRelativePath = relativePath;
+ mName = new File(relativePath).getName();
+ mIsDirectory = isDirectory;
+ }
+
+ public boolean isDirectory() {
+ return mIsDirectory;
+ }
+
+ public String getRelativePath() {
+ return mRelativePath;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ @Override
+ public int compareTo(ListItem another) {
+ return mRelativePath.compareTo(another.getRelativePath());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof ListItem)) {
+ return false;
+ }
+
+ return mRelativePath.equals(((ListItem) o).getRelativePath());
+ }
+
+ @Override
+ public int hashCode() {
+ return mRelativePath.hashCode();
+ }
+
+ }
+
+ /**
+ * A custom adapter that sets the proper icon and label in the list view.
+ */
+ private static class DirListAdapter extends ArrayAdapter<ListItem> {
+ private Activity mContext;
+ private ListItem[] mItems;
+
+ public DirListAdapter(Activity context, ListItem[] items) {
+ super(context, R.layout.dirlist_row, items);
+
+ mContext = context;
+ mItems = items;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ LayoutInflater inflater = mContext.getLayoutInflater();
+ View row = inflater.inflate(R.layout.dirlist_row, null);
+
+ TextView label = (TextView) row.findViewById(R.id.label);
+ label.setText(mItems[position].getName());
+
+ ImageView icon = (ImageView) row.findViewById(R.id.icon);
+ if (mItems[position].isDirectory()) {
+ icon.setImageResource(R.drawable.folder);
+ } else {
+ icon.setImageResource(R.drawable.runtest);
+ }
+
+ return row;
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mFileFilter = new FileFilter(ROOT_DIR_PATH);
+ mListView = getListView();
+
+ mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ ListItem item = (ListItem) parent.getItemAtPosition(position);
+
+ if (item.isDirectory()) {
+ showDir(item.getRelativePath());
+ } else {
+ /** Run the test */
+ Intent intent = new Intent();
+ intent.setClass(DirListActivity.this, LayoutTestsRunner.class);
+ intent.setAction(Intent.ACTION_RUN);
+ intent.putExtra(LayoutTestsRunner.EXTRA_TEST_PATH, item.getRelativePath());
+ startActivity(intent);
+ }
+ }
+ });
+
+ mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
+ @Override
+ public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
+ ListItem item = (ListItem) parent.getItemAtPosition(position);
+
+ if (item.isDirectory()) {
+ Bundle arguments = new Bundle(1);
+ arguments.putString("name", item.getName());
+ arguments.putString("relativePath", item.getRelativePath());
+ showDialog(DIALOG_RUN_ABORT_DIR, arguments);
+ } else {
+ /** TODO: Maybe show some info about a test? */
+ }
+
+ return true;
+ }
+ });
+
+ /** All the paths are relative to test root dir where possible */
+ showDir("");
+ }
+
+ @Override
+ /**
+ * Moves to the parent directory if one exists. Does not allow to move above
+ * the test 'root' directory.
+ */
+ public void onBackPressed() {
+ File currentDirParent = new File(mCurrentDirPath).getParentFile();
+ if (currentDirParent != null) {
+ showDir(currentDirParent.getPath());
+ } else {
+ showDir("");
+ }
+ }
+
+ /**
+ * Prevents the activity from recreating on change of orientation. The title needs to
+ * be recalculated.
+ */
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+
+ setTitle(shortenTitle(mCurrentDirPath));
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id, final Bundle args) {
+ Dialog dialog = null;
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+
+ switch (id) {
+ case DIALOG_RUN_ABORT_DIR:
+ builder.setTitle(getText(R.string.dialog_run_abort_dir_title_prefix) + " " +
+ args.getString("name"));
+ builder.setMessage(R.string.dialog_run_abort_dir_msg);
+ builder.setCancelable(true);
+
+ builder.setPositiveButton(R.string.dialog_run_abort_dir_ok_button,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ removeDialog(DIALOG_RUN_ABORT_DIR);
+ /** Run the tests */
+ Intent intent = new Intent();
+ intent.setClass(DirListActivity.this, LayoutTestsRunner.class);
+ intent.setAction(Intent.ACTION_RUN);
+ intent.putExtra(LayoutTestsRunner.EXTRA_TEST_PATH,
+ args.getString("relativePath"));
+ startActivity(intent);
+ }
+ });
+
+ builder.setNegativeButton(R.string.dialog_run_abort_dir_abort_button,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ removeDialog(DIALOG_RUN_ABORT_DIR);
+ }
+ });
+
+ dialog = builder.create();
+ dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ removeDialog(DIALOG_RUN_ABORT_DIR);
+ }
+ });
+ break;
+ }
+
+ return dialog;
+ }
+
+ /**
+ * Loads the contents of dir into the list view.
+ *
+ * @param dirPath
+ * directory to load into list view
+ */
+ private void showDir(String dirPath) {
+ mCurrentDirPath = dirPath;
+
+ /** Show progress dialog with a delay */
+ final Handler delayedDialogHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_SHOW_PROGRESS_DIALOG) {
+ if (sProgressDialog == null) {
+ sProgressDialog = new ProgressDialog(DirListActivity.this);
+ sProgressDialog.setCancelable(false);
+ sProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+ sProgressDialog.setTitle(R.string.dialog_progress_title);
+ sProgressDialog.setMessage(getText(R.string.dialog_progress_msg));
+ }
+ sProgressDialog.show();
+ }
+ }
+ };
+ Message msgShowDialog = delayedDialogHandler.obtainMessage(MSG_SHOW_PROGRESS_DIALOG);
+ delayedDialogHandler.sendMessageDelayed(msgShowDialog, PROGRESS_DIALOG_DELAY_MS);
+
+ /** Delegate loading contents from SD card to a new thread */
+ new LoadListItemsThread(mCurrentDirPath, new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_LOADED_ITEMS) {
+ setListAdapter(new DirListAdapter(DirListActivity.this,
+ (ListItem[])msg.obj));
+ delayedDialogHandler.removeMessages(MSG_SHOW_PROGRESS_DIALOG);
+ setTitle(shortenTitle(mCurrentDirPath));
+ if (sProgressDialog != null) {
+ sProgressDialog.dismiss();
+ }
+ }
+ }
+ }).start();
+ }
+
+ /**
+ * TODO: find a neat way to determine number of characters that fit in the title
+ * bar.
+ * */
+ private String shortenTitle(String title) {
+ if (title.equals("")) {
+ return "Tests' root dir:";
+ }
+ int charCount = mListView.getWidth() / MEAN_TITLE_CHAR_SIZE;
+
+ if (title.length() > charCount) {
+ return "..." + title.substring(title.length() - charCount);
+ } else {
+ return title;
+ }
+ }
+
+ /**
+ * Return the array with contents of the given directory.
+ * First it contains the subfolders, then the files. Both sorted
+ * alphabetically.
+ *
+ * The dirPath is relative.
+ */
+ private ListItem[] getDirList(String dirPath) {
+ File dir = new File(mRootDirPath, dirPath);
+
+ List<ListItem> subDirs = new ArrayList<ListItem>();
+ List<ListItem> subFiles = new ArrayList<ListItem>();
+
+ for (File item : dir.listFiles()) {
+ if (item.isDirectory() && FileFilter.isTestDir(item.getName())) {
+ subDirs.add(new ListItem(mFileFilter.getRelativePath(item), true));
+ } else if (FileFilter.isTestFile(item.getName())) {
+ subFiles.add(new ListItem(mFileFilter.getRelativePath(item), false));
+ }
+ }
+
+ Collections.sort(subDirs);
+ Collections.sort(subFiles);
+
+ /** Concatenate the two lists */
+ subDirs.addAll(subFiles);
+
+ return subDirs.toArray(new ListItem[subDirs.size()]);
+ }
+} \ No newline at end of file