diff options
Diffstat (limited to 'tests/DumpRenderTree2')
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 Binary files differnew file mode 100644 index 0000000..5b3fcec --- /dev/null +++ b/tests/DumpRenderTree2/res/drawable/folder.png diff --git a/tests/DumpRenderTree2/res/drawable/runtest.png b/tests/DumpRenderTree2/res/drawable/runtest.png Binary files differnew file mode 100644 index 0000000..910c654 --- /dev/null +++ b/tests/DumpRenderTree2/res/drawable/runtest.png 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 |