diff options
author | Pale Moon <git-repo@palemoon.org> | 2016-09-01 13:39:08 +0200 |
---|---|---|
committer | Pale Moon <git-repo@palemoon.org> | 2016-09-01 13:39:08 +0200 |
commit | 3d8ce1a11a7347cc94a937719c4bc8df46fb8d14 (patch) | |
tree | 8c26ca375a6312751c00a27e1653fb6f189f0463 /layout/tools | |
parent | e449bdb1ec3a82f204bffdd9c3c54069d086eee3 (diff) | |
download | palemoon-gre-3d8ce1a11a7347cc94a937719c4bc8df46fb8d14.tar.gz |
Base import of Tycho code (warning: huge commit)
Diffstat (limited to 'layout/tools')
38 files changed, 2285 insertions, 819 deletions
diff --git a/layout/tools/layout-debug/moz.build b/layout/tools/layout-debug/moz.build index 3bcc23e38..1dda32f39 100644 --- a/layout/tools/layout-debug/moz.build +++ b/layout/tools/layout-debug/moz.build @@ -5,7 +5,5 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. DIRS += ['src', 'ui'] -TEST_DIRS += ['tests'] - -MODULE = 'layout_debug' +XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini'] diff --git a/layout/tools/layout-debug/src/Makefile.in b/layout/tools/layout-debug/src/Makefile.in deleted file mode 100644 index d7494198c..000000000 --- a/layout/tools/layout-debug/src/Makefile.in +++ /dev/null @@ -1,25 +0,0 @@ -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -DEPTH = @DEPTH@ -topsrcdir = @top_srcdir@ -srcdir = @srcdir@ -VPATH = @srcdir@ - -include $(DEPTH)/config/autoconf.mk - -EXPORT_LIBRARY = 1 -IS_COMPONENT = 1 -MODULE_NAME = nsLayoutDebugModule -LIBXUL_LIBRARY = 1 -FAIL_ON_WARNINGS = 1 - - -include $(topsrcdir)/config/rules.mk - -libs:: - -clobber:: - rm -f $(DIST)\lib\library diff --git a/layout/tools/layout-debug/src/moz.build b/layout/tools/layout-debug/src/moz.build index fd03b5818..5291f8247 100644 --- a/layout/tools/layout-debug/src/moz.build +++ b/layout/tools/layout-debug/src/moz.build @@ -9,18 +9,19 @@ XPIDL_SOURCES += [ 'nsILayoutRegressionTester.idl', ] -MODULE = 'layout_debug' +XPIDL_MODULE = 'layout_debug' EXPORTS += [ 'nsLayoutDebugCIID.h', ] -CPP_SOURCES += [ +UNIFIED_SOURCES += [ 'nsDebugFactory.cpp', 'nsLayoutDebugCLH.cpp', 'nsLayoutDebuggingTools.cpp', 'nsRegressionTester.cpp', ] -LIBRARY_NAME = 'gkdebug' +FAIL_ON_WARNINGS = True +FINAL_LIBRARY = 'xul' diff --git a/layout/tools/layout-debug/src/nsDebugFactory.cpp b/layout/tools/layout-debug/src/nsDebugFactory.cpp index ecb7e894f..c9b7caca4 100644 --- a/layout/tools/layout-debug/src/nsDebugFactory.cpp +++ b/layout/tools/layout-debug/src/nsDebugFactory.cpp @@ -22,22 +22,22 @@ NS_DEFINE_NAMED_CID(NS_LAYOUT_DEBUGGINGTOOLS_CID); NS_DEFINE_NAMED_CID(NS_LAYOUTDEBUGCLH_CID); static const mozilla::Module::CIDEntry kLayoutDebugCIDs[] = { - { &kNS_REGRESSION_TESTER_CID, false, NULL, nsRegressionTesterConstructor }, - { &kNS_LAYOUT_DEBUGGINGTOOLS_CID, false, NULL, nsLayoutDebuggingToolsConstructor }, - { &kNS_LAYOUTDEBUGCLH_CID, false, NULL, nsLayoutDebugCLHConstructor }, - { NULL } + { &kNS_REGRESSION_TESTER_CID, false, nullptr, nsRegressionTesterConstructor }, + { &kNS_LAYOUT_DEBUGGINGTOOLS_CID, false, nullptr, nsLayoutDebuggingToolsConstructor }, + { &kNS_LAYOUTDEBUGCLH_CID, false, nullptr, nsLayoutDebugCLHConstructor }, + { nullptr } }; static const mozilla::Module::ContractIDEntry kLayoutDebugContracts[] = { { "@mozilla.org/layout-debug/regressiontester;1", &kNS_REGRESSION_TESTER_CID }, { NS_LAYOUT_DEBUGGINGTOOLS_CONTRACTID, &kNS_LAYOUT_DEBUGGINGTOOLS_CID }, { "@mozilla.org/commandlinehandler/general-startup;1?type=layoutdebug", &kNS_LAYOUTDEBUGCLH_CID }, - { NULL } + { nullptr } }; static const mozilla::Module::CategoryEntry kLayoutDebugCategories[] = { { "command-line-handler", "m-layoutdebug", "@mozilla.org/commandlinehandler/general-startup;1?type=layoutdebug" }, - { NULL } + { nullptr } }; static const mozilla::Module kLayoutDebugModule = { diff --git a/layout/tools/layout-debug/src/nsLayoutDebugCLH.cpp b/layout/tools/layout-debug/src/nsLayoutDebugCLH.cpp index 74d3b090f..030a75962 100644 --- a/layout/tools/layout-debug/src/nsLayoutDebugCLH.cpp +++ b/layout/tools/layout-debug/src/nsLayoutDebugCLH.cpp @@ -23,7 +23,7 @@ nsLayoutDebugCLH::~nsLayoutDebugCLH() { } -NS_IMPL_ISUPPORTS1(nsLayoutDebugCLH, ICOMMANDLINEHANDLER) +NS_IMPL_ISUPPORTS(nsLayoutDebugCLH, ICOMMANDLINEHANDLER) NS_IMETHODIMP nsLayoutDebugCLH::Handle(nsICommandLine* aCmdLine) @@ -78,7 +78,7 @@ nsLayoutDebugCLH::Handle(nsICommandLine* aCmdLine) NS_IMETHODIMP nsLayoutDebugCLH::GetHelpInfo(nsACString& aResult) { - aResult.Assign(NS_LITERAL_CSTRING(" -layoutdebug [<url>] Start with Layout Debugger\n")); + aResult.AssignLiteral(" -layoutdebug [<url>] Start with Layout Debugger\n"); return NS_OK; } diff --git a/layout/tools/layout-debug/src/nsLayoutDebugCLH.h b/layout/tools/layout-debug/src/nsLayoutDebugCLH.h index c2d5108c2..c79c8d697 100644 --- a/layout/tools/layout-debug/src/nsLayoutDebugCLH.h +++ b/layout/tools/layout-debug/src/nsLayoutDebugCLH.h @@ -18,10 +18,12 @@ class nsLayoutDebugCLH : public ICOMMANDLINEHANDLER { public: nsLayoutDebugCLH(); - virtual ~nsLayoutDebugCLH(); NS_DECL_ISUPPORTS NS_DECL_NSICOMMANDLINEHANDLER + +protected: + virtual ~nsLayoutDebugCLH(); }; #endif /* !defined(nsLayoutDebugCLH_h_) */ diff --git a/layout/tools/layout-debug/src/nsLayoutDebuggingTools.cpp b/layout/tools/layout-debug/src/nsLayoutDebuggingTools.cpp index f9d3d61f3..928d8606e 100644 --- a/layout/tools/layout-debug/src/nsLayoutDebuggingTools.cpp +++ b/layout/tools/layout-debug/src/nsLayoutDebuggingTools.cpp @@ -94,7 +94,7 @@ nsLayoutDebuggingTools::~nsLayoutDebuggingTools() { } -NS_IMPL_ISUPPORTS1(nsLayoutDebuggingTools, nsILayoutDebuggingTools) +NS_IMPL_ISUPPORTS(nsLayoutDebuggingTools, nsILayoutDebuggingTools) NS_IMETHODIMP nsLayoutDebuggingTools::Init(nsIDOMWindow *aWin) @@ -291,7 +291,15 @@ nsLayoutDebuggingTools::SetReflowCounts(bool aShow) NS_ENSURE_TRUE(mDocShell, NS_ERROR_NOT_INITIALIZED); nsCOMPtr<nsIPresShell> shell(pres_shell(mDocShell)); if (shell) { - printf("Sorry, reflow performance tracing is not available\n"); +#ifdef MOZ_REFLOW_PERF + shell->SetPaintFrameCount(aShow); + SetBoolPrefAndRefresh("layout.reflow.showframecounts", aShow); + mReflowCounts = aShow; +#else + printf("************************************************\n"); + printf("Sorry, you have not built with MOZ_REFLOW_PERF=1\n"); + printf("************************************************\n"); +#endif } return NS_OK; } @@ -312,11 +320,10 @@ static void DumpAWebShell(nsIDocShellTreeItem* aShellItem, FILE* out, int32_t aI fprintf(out, "' parent=%p <\n", static_cast<void*>(parent)); ++aIndent; - nsCOMPtr<nsIDocShellTreeNode> shellAsNode(do_QueryInterface(aShellItem)); - shellAsNode->GetChildCount(&n); + aShellItem->GetChildCount(&n); for (i = 0; i < n; ++i) { nsCOMPtr<nsIDocShellTreeItem> child; - shellAsNode->GetChildAt(i, getter_AddRefs(child)); + aShellItem->GetChildAt(i, getter_AddRefs(child)); if (child) { DumpAWebShell(child, out, aIndent); } @@ -384,7 +391,7 @@ DumpFramesRecur(nsIDocShell* aDocShell, FILE* out) if (shell) { nsIFrame* root = shell->GetRootFrame(); if (root) { - root->List(out, 0); + root->List(out); } } else { @@ -495,7 +502,13 @@ nsLayoutDebuggingTools::DumpReflowStats() #ifdef DEBUG nsCOMPtr<nsIPresShell> shell(pres_shell(mDocShell)); if (shell) { - printf("Sorry, reflow performance tracing is not available\n"); +#ifdef MOZ_REFLOW_PERF + shell->DumpReflows(); +#else + printf("************************************************\n"); + printf("Sorry, you have not built with MOZ_REFLOW_PERF=1\n"); + printf("************************************************\n"); +#endif } #endif return NS_OK; diff --git a/layout/tools/layout-debug/src/nsLayoutDebuggingTools.h b/layout/tools/layout-debug/src/nsLayoutDebuggingTools.h index 4826cebe5..6d57c1fa1 100644 --- a/layout/tools/layout-debug/src/nsLayoutDebuggingTools.h +++ b/layout/tools/layout-debug/src/nsLayoutDebuggingTools.h @@ -4,6 +4,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsLayoutDebuggingTools_h +#define nsLayoutDebuggingTools_h + #include "nsILayoutDebuggingTools.h" #include "nsIDocShell.h" #include "nsCOMPtr.h" @@ -12,13 +15,14 @@ class nsLayoutDebuggingTools : public nsILayoutDebuggingTools { public: nsLayoutDebuggingTools(); - virtual ~nsLayoutDebuggingTools(); NS_DECL_ISUPPORTS NS_DECL_NSILAYOUTDEBUGGINGTOOLS protected: + virtual ~nsLayoutDebuggingTools(); + void ForceRefresh(); nsresult GetBoolPref(const char * aPrefName, bool *aValue); nsresult SetBoolPrefAndRefresh(const char * aPrefName, bool aNewValue); @@ -36,3 +40,5 @@ protected: bool mCrossingEventDumping; bool mReflowCounts; }; + +#endif diff --git a/layout/tools/layout-debug/src/nsRegressionTester.cpp b/layout/tools/layout-debug/src/nsRegressionTester.cpp index 652caf1ee..c0d9d7eeb 100644 --- a/layout/tools/layout-debug/src/nsRegressionTester.cpp +++ b/layout/tools/layout-debug/src/nsRegressionTester.cpp @@ -9,7 +9,6 @@ #include "nsXPIDLString.h" #include "nsReadableUtils.h" #include "nsIWindowWatcher.h" -#include "nsVoidArray.h" #include "nsPIDOMWindow.h" #include "nsIPresShell.h" #include "nsIURI.h" @@ -39,7 +38,7 @@ nsRegressionTester::~nsRegressionTester() { } -NS_IMPL_ISUPPORTS1(nsRegressionTester, nsILayoutRegressionTester) +NS_IMPL_ISUPPORTS(nsRegressionTester, nsILayoutRegressionTester) NS_IMETHODIMP nsRegressionTester::DumpFrameModel(nsIDOMWindow *aWindowToDump, diff --git a/layout/tools/layout-debug/src/nsRegressionTester.h b/layout/tools/layout-debug/src/nsRegressionTester.h index 9a5908e6a..0baa5d8d9 100644 --- a/layout/tools/layout-debug/src/nsRegressionTester.h +++ b/layout/tools/layout-debug/src/nsRegressionTester.h @@ -25,9 +25,9 @@ public: NS_DECL_NSILAYOUTREGRESSIONTESTER nsRegressionTester(); - virtual ~nsRegressionTester(); protected: + virtual ~nsRegressionTester(); nsresult GetDocShellFromWindow(nsIDOMWindow* inWindow, nsIDocShell** outShell); }; diff --git a/layout/tools/layout-debug/tests/moz.build b/layout/tools/layout-debug/tests/moz.build deleted file mode 100644 index 191c90f0b..000000000 --- a/layout/tools/layout-debug/tests/moz.build +++ /dev/null @@ -1,7 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini'] diff --git a/layout/tools/layout-debug/ui/moz.build b/layout/tools/layout-debug/ui/moz.build index 895d11993..c97072bba 100644 --- a/layout/tools/layout-debug/ui/moz.build +++ b/layout/tools/layout-debug/ui/moz.build @@ -4,3 +4,4 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. +JAR_MANIFESTS += ['jar.mn']
\ No newline at end of file diff --git a/layout/tools/recording/Makefile.in b/layout/tools/recording/Makefile.in deleted file mode 100644 index 1c11a44b4..000000000 --- a/layout/tools/recording/Makefile.in +++ /dev/null @@ -1,19 +0,0 @@ -# vim: set shiftwidth=8 tabstop=8 autoindent noexpandtab copyindent: -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -DEPTH = @DEPTH@ -topsrcdir = @top_srcdir@ -srcdir = @srcdir@ -VPATH = @srcdir@ - -include $(DEPTH)/config/autoconf.mk - -EXTRA_COMPONENTS = \ - recording-cmdline.js \ - $(NULL) - -EXTRA_COMPONENTS += recording-cmdline.manifest - -include $(topsrcdir)/config/rules.mk diff --git a/layout/tools/recording/moz.build b/layout/tools/recording/moz.build index 9c56aa8c1..1f9a40813 100644 --- a/layout/tools/recording/moz.build +++ b/layout/tools/recording/moz.build @@ -4,4 +4,9 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -MODULE = 'recording' +EXTRA_COMPONENTS += [ + 'recording-cmdline.js', + 'recording-cmdline.manifest', +] + +JAR_MANIFESTS += ['jar.mn']
\ No newline at end of file diff --git a/layout/tools/recording/recording-cmdline.js b/layout/tools/recording/recording-cmdline.js index ec0e8343a..e043aa29c 100644 --- a/layout/tools/recording/recording-cmdline.js +++ b/layout/tools/recording/recording-cmdline.js @@ -67,8 +67,8 @@ RecordingCmdLineHandler.prototype = cmdLine.preventDefault = true; }, - helpInfo : " -recording <file> Record drawing for a given URL.\n" + - " -recording-output <file> Specify destination file for a drawing recording.\n" + helpInfo : " --recording <file> Record drawing for a given URL.\n" + + " --recording-output <file> Specify destination file for a drawing recording.\n" }; this.NSGetFactory = XPCOMUtils.generateNSGetFactory([RecordingCmdLineHandler]); diff --git a/layout/tools/recording/recording.js b/layout/tools/recording/recording.js index 00215915c..5d329e606 100644 --- a/layout/tools/recording/recording.js +++ b/layout/tools/recording/recording.js @@ -1,4 +1,4 @@ -/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / +/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- / /* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -6,24 +6,21 @@ const CC = Components.classes; const CI = Components.interfaces; -const CR = Components.results; -const XHTML_NS = "http://www.w3.org/1999/xhtml"; -const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; const NS_GFXINFO_CONTRACTID = "@mozilla.org/gfx/info;1"; var gContainingWindow = null; var gBrowser; -function OnDocumentLoad() { +function OnDocumentLoad(evt) { + if (evt.target != gBrowser.contentDocument || evt.target.location == "about:blank") + return; + gBrowser.removeEventListener("load", OnDocumentLoad, true); gContainingWindow.close(); } this.OnRecordingLoad = function OnRecordingLoad(win) { - var prefs = Components.classes["@mozilla.org/preferences-service;1"]. - getService(Components.interfaces.nsIPrefBranch); - if (win === undefined || win == null) { win = window; } @@ -35,19 +32,16 @@ this.OnRecordingLoad = function OnRecordingLoad(win) { var gfxInfo = (NS_GFXINFO_CONTRACTID in CC) && CC[NS_GFXINFO_CONTRACTID].getService(CI.nsIGfxInfo); var info = gfxInfo.getInfo(); - dump(info.AzureContentBackend); + dump(info.AzureContentBackend + "\n"); if (info.AzureContentBackend == "none") { alert("Page recordings may only be made with Azure content enabled."); gContainingWindow.close(); return; } - gContainingWindow.document.addEventListener("load", OnDocumentLoad, true); + gBrowser.addEventListener("load", OnDocumentLoad, true); var args = window.arguments[0].wrappedJSObject; gBrowser.loadURI(args.uri); -} - -function OnRecordingUnload() { -} +}; diff --git a/layout/tools/recording/recording.xul b/layout/tools/recording/recording.xul index 189ea25e2..276d3aa71 100644 --- a/layout/tools/recording/recording.xul +++ b/layout/tools/recording/recording.xul @@ -2,19 +2,10 @@ <!-- This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -<?xml-stylesheet type="text/css" href="data:text/css, - -%23_box_windowsDefaultTheme:-moz-system-metric(windows-default-theme) { - display: none; -} - -" ?> - <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="recording-window" hidechrome="true" onload="OnRecordingLoad();" - onunload="OnRecordingUnload();" style="background:white; overflow:hidden; width:800px; height:600px;" > <script type="application/ecmascript" src="recording.js" /> diff --git a/layout/tools/reftest/Makefile.in b/layout/tools/reftest/Makefile.in index a5721b1f0..0400a6502 100644 --- a/layout/tools/reftest/Makefile.in +++ b/layout/tools/reftest/Makefile.in @@ -3,66 +3,30 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -DEPTH = @DEPTH@ -topsrcdir = @top_srcdir@ -srcdir = @srcdir@ -VPATH = @srcdir@ - -include $(DEPTH)/config/autoconf.mk - -EXTRA_COMPONENTS= \ - reftest-cmdline.js \ - $(NULL) - -ifdef XPI_NAME -NO_JS_MANIFEST = 1 DIST_FILES = install.rdf ifeq ($(MOZ_BUILD_APP),mobile/android) -DEFINES += -DBOOTSTRAP DIST_FILES += bootstrap.js endif -ifeq ($(MOZ_BUILD_APP),b2g) -DEFINES += -DBOOTSTRAP -DEFINES += -DREFTEST_B2G -endif - # Used in install.rdf USE_EXTENSION_MANIFEST=1 -else -EXTRA_COMPONENTS += reftest-cmdline.manifest -endif - -include $(topsrcdir)/config/rules.mk - -# We're installing to _tests/reftest -TARGET_DEPTH = ../.. -include $(topsrcdir)/build/automation-build.mk _DEST_DIR = $(DEPTH)/_tests/reftest -# We want to get an extension-packaged version of reftest as well, -# so this seems to be the simplest way to make that happen. -ifndef XPI_NAME -make-xpi: - +$(MAKE) -C $(DEPTH)/netwerk/test/httpserver libs XPI_NAME=reftest - +$(MAKE) libs XPI_NAME=reftest -copy-harness: make-xpi -libs:: copy-harness -endif - _HARNESS_FILES = \ $(srcdir)/runreftest.py \ $(srcdir)/remotereftest.py \ $(srcdir)/runreftestb2g.py \ - $(srcdir)/b2g_start_script.js \ + $(srcdir)/b2g_desktop.py \ + $(srcdir)/gaia_lock_screen.js \ automation.py \ $(topsrcdir)/testing/mozbase/mozdevice/mozdevice/devicemanager.py \ $(topsrcdir)/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py \ $(topsrcdir)/testing/mozbase/mozdevice/mozdevice/devicemanagerSUT.py \ $(topsrcdir)/testing/mozbase/mozdevice/mozdevice/droid.py \ $(topsrcdir)/testing/mozbase/mozdevice/mozdevice/Zeroconf.py \ + $(topsrcdir)/testing/mozbase/moznetwork/moznetwork/moznetwork.py \ $(topsrcdir)/build/mobile/b2gautomation.py \ $(topsrcdir)/build/automationutils.py \ $(topsrcdir)/build/mobile/remoteautomation.py \ @@ -70,25 +34,40 @@ _HARNESS_FILES = \ $(topsrcdir)/build/pgo/server-locations.txt \ $(NULL) +_HARNESS_PP_FILES = \ + b2g_start_script.js \ + $(NULL) +_HARNESS_PP_FILES_PATH = $(_DEST_DIR) +PP_TARGETS += _HARNESS_PP_FILES + +include $(topsrcdir)/config/rules.mk + +# We're installing to _tests/reftest +TARGET_DEPTH = ../.. +include $(topsrcdir)/build/automation-build.mk + $(_DEST_DIR): $(NSINSTALL) -D $@ $(_HARNESS_FILES): $(_DEST_DIR) # copy harness and the reftest extension bits to $(_DEST_DIR) -copy-harness: $(_HARNESS_FILES) +# This needs to happen after jar.mn handling from rules.mk included above. +# The order of the :: rules ensures that. +libs:: $(_HARNESS_FILES) $(addprefix $(_DEST_DIR)/,$(_HARNESS_PP_FILES)) $(INSTALL) $(_HARNESS_FILES) $(_DEST_DIR) (cd $(DIST)/xpi-stage && tar $(TAR_CREATE_FLAGS) - reftest) | (cd $(_DEST_DIR) && tar -xf -) -PKG_STAGE = $(DIST)/test-package-stage +PKG_STAGE = $(DIST)/test-stage # stage harness and tests for packaging stage-package: $(NSINSTALL) -D $(PKG_STAGE)/reftest && $(NSINSTALL) -D $(PKG_STAGE)/reftest/tests - (cd $(DEPTH)/_tests/reftest/ && tar $(TAR_CREATE_FLAGS_QUIET) - *) | (cd $(PKG_STAGE)/reftest && tar -xf -) + (cd $(DEPTH)/_tests/reftest/ && tar $(TAR_CREATE_FLAGS) - *) | (cd $(PKG_STAGE)/reftest && tar -xf -) + @cp $(DEPTH)/mozinfo.json $(PKG_STAGE)/reftest $(PYTHON) $(topsrcdir)/layout/tools/reftest/print-manifest-dirs.py \ $(topsrcdir) \ $(topsrcdir)/layout/reftests/reftest.list \ $(topsrcdir)/testing/crashtest/crashtests.list \ - | (cd $(topsrcdir) && xargs tar $(TAR_CREATE_FLAGS_QUIET) -) \ + | (cd $(topsrcdir) && xargs tar $(TAR_CREATE_FLAGS) -) \ | (cd $(PKG_STAGE)/reftest/tests && tar -xf -) diff --git a/layout/tools/reftest/README.txt b/layout/tools/reftest/README.txt index 2ade610a2..4ebd16a4c 100644 --- a/layout/tools/reftest/README.txt +++ b/layout/tools/reftest/README.txt @@ -8,6 +8,15 @@ in the same format. The tests are run based on a manifest file, and for each test, PASS or FAIL is reported, and UNEXPECTED is reported if the result (PASS or FAIL) was not the expected result noted in the manifest. +Images of the display of both tests are captured, and most test types +involve comparing these images (e.g., test types == or !=) to determine +whether the test passed. The captures of the tests are taken in a +viewport that is 800 pixels wide and 1000 pixels tall, so any content +outside that area will be ignored (except for any scrollbars that are +displayed). Ideally, however, tests should be written so that they fit +within 600x600, since we may in the future want to switch to 600x600 to +match http://lists.w3.org/Archives/Public/www-style/2012Sep/0562.html . + Why this way? ============= @@ -400,8 +409,8 @@ reference identical in as many aspects as possible. For example: <div style="color:green"><table><tr><td>green </td></tr></table></div> -Asynchronous Tests -================== +Asynchronous Tests: class="reftest-wait" +======================================== Normally reftest takes a snapshot of the given markup's rendering right after the load event fires for content. If your test needs to postpone @@ -421,8 +430,8 @@ Note that in layout tests it is often enough to trigger layout using When possible, you should use this technique instead of making your test async. -Invalidation Tests -================== +Invalidation Tests: MozReftestInvalidate Event +============================================== When a test (or reference) uses reftest-wait, reftest tracks invalidation via MozAfterPaint and updates the test image in the same way that @@ -442,16 +451,23 @@ function doTest() { } document.addEventListener("MozReftestInvalidate", doTest, false); -Painting Tests -============== +Painting Tests: class="reftest-no-paint" +======================================== If an element shouldn't be painted, set the class "reftest-no-paint" on it when doing an invalidation test. Causing a repaint in your MozReftestInvalidate handler (for example, by changing the body's background colour) will accurately test whether the element is painted. -Zoom Tests -========== +Snapshot The Whole Window: class="reftest-snapshot-all" +======================================================= + +In a reftest-wait test, to disable testing of invalidation and force the final +snapshot to be taken of the whole window, set the "reftest-snapshot-all" +class on the root element. + +Zoom Tests: reftest-zoom="<float>" +================================== When the root element of a test has a "reftest-zoom" attribute, that zoom factor is applied when rendering the test. The reftest document will be @@ -461,8 +477,42 @@ therefore, choose zoom factors that do not require rounding when we calculate the number of appunits per device pixel; i.e. the zoom factor should divide 60, so 60/zoom is an integer. -Printing Tests -============== +Setting Viewport Size: reftest-viewport-w/h="<int>" +=================================================== + +If either of the "reftest-viewport-w" and "reftest-viewport-h" attributes on +the root element are non-zero, sets the CSS viewport to the given size in +CSS pixels. This does not affect the size of the snapshot that is taken. + +Setting Async Scroll Mode: reftest-async-scroll attribute +========================================================= + +If the "reftest-async-scroll" attribute is set on the root element, we try to +enable async scrolling for the document. This is unsupported in many +configurations. + +Setting Displayport Dimensions: reftest-displayport-x/y/w/h="<int>" +=================================================================== + +If any of the "reftest-displayport-x", "reftest-displayport-y", +"reftest-displayport-w" and "reftest-displayport-h" attributes on the root +element are nonzero, sets the displayport dimensions to the given bounds in +CSS pixels. This does not affect the size of the snapshot that is taken. + +When the "reftest-async-scroll" attribute is set on the root element, *all* +elements in the document are checked for "reftest-displayport-x/y/w/h" and have +displayports set on them when those attributes are present. + +Testing Async Scrolling: reftest-async-scroll-x/y="<int>" +========================================================= + +When the "reftest-async-scroll" attribute is set on the root element, for any +element where either the "reftest-async-scroll-x" or "reftest-async-scroll-y +attributes are nonzero, at the end of the test take the snapshot with the given +offset (in CSS pixels) added to the async scroll offset. + +Printing Tests: class="reftest-print" +===================================== Now that the patch for bug 374050 has landed (https://bugzilla.mozilla.org/show_bug.cgi?id=374050), it is possible to @@ -481,8 +531,10 @@ The suggested first lines for any printing test is <style>html{font-size:12pt}</style> The reftest-print class on the root element triggers the reftest to -switch into page mode on load. Fixing the font size is suggested, -although not required, because the pages are a fixed size in inches. +switch into page mode. Fixing the font size is suggested, although not +required, because the pages are a fixed size in inches. The switch to page mode +happens on load if the reftest-wait class is not present; otherwise it happens +immediately after firing the MozReftestInvalidate event. The underlying layout support for this mode isn't really complete; it doesn't use exactly the same codepath as real print preview/print. In @@ -490,8 +542,8 @@ particular, scripting and frames are likely to cause problems; it is untested, though. That said, it should be sufficient for testing layout issues related to pagination. -Plugin and IPC Process Crash Tests -================================== +Plugin and IPC Process Crash Tests: class="reftest-expect-process-crash" +======================================================================== If you are running a test that causes an out-of-process plugin or IPC process under Electrolysis to crash as part of a reftest, this will cause process @@ -508,3 +560,12 @@ IPC process crash, have the test include "reftest-expect-process-crash" as one of the root element's classes by the time the test has finished. This will cause any minidump files that are generated while running the test to be removed and they won't cause any error messages in the test run output. + +Skip Forcing A Content Process Layer-Tree Update: reftest-no-sync-layers attribute +================================================================================== + +Normally when an multi-process reftest test ends, we force the content process +to push a layer-tree update to the compositor before taking the snapshot. +Setting the "reftest-no-sync-layers" attribute on the root element skips this +step, enabling testing that layer-tree updates are being correctly generated. +However the test must manually wait for a MozAfterPaint event before ending. diff --git a/layout/tools/reftest/b2g_desktop.py b/layout/tools/reftest/b2g_desktop.py new file mode 100644 index 000000000..b4a0b414c --- /dev/null +++ b/layout/tools/reftest/b2g_desktop.py @@ -0,0 +1,218 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. +from __future__ import print_function, unicode_literals + +import os +import signal +import sys + +here = os.path.abspath(os.path.dirname(__file__)) + +from runreftest import RefTest, ReftestOptions + +from marionette_driver import expected +from marionette_driver.by import By +from marionette_driver.marionette import Marionette +from marionette_driver.wait import Wait + +from mozprocess import ProcessHandler +from mozrunner import FirefoxRunner +import mozinfo +import mozlog + +log = mozlog.getLogger('REFTEST') + +class B2GDesktopReftest(RefTest): + build_type = "desktop" + marionette = None + + def __init__(self, marionette_args): + RefTest.__init__(self) + self.last_test = os.path.basename(__file__) + self.marionette_args = marionette_args + self.profile = None + self.runner = None + self.test_script = os.path.join(here, 'b2g_start_script.js') + self.timeout = None + + def run_marionette_script(self): + self.marionette = Marionette(**self.marionette_args) + assert(self.marionette.wait_for_port()) + self.marionette.start_session() + if self.build_type == "mulet": + self._wait_for_homescreen(timeout=15) + self._unlockScreen() + self.marionette.set_context(self.marionette.CONTEXT_CHROME) + + if os.path.isfile(self.test_script): + f = open(self.test_script, 'r') + self.test_script = f.read() + f.close() + self.marionette.execute_script(self.test_script) + + def run_tests(self, test_path, options): + reftestlist = self.getManifestPath(test_path) + if not reftestlist.startswith('file://'): + reftestlist = 'file://%s' % reftestlist + + self.profile = self.create_profile(options, reftestlist, + profile_to_clone=options.profile) + env = self.buildBrowserEnv(options, self.profile.profile) + kp_kwargs = { 'processOutputLine': [self._on_output], + 'onTimeout': [self._on_timeout], + 'kill_on_timeout': False } + + if not options.debugger: + if not options.timeout: + if mozinfo.info['debug']: + options.timeout = 420 + else: + options.timeout = 300 + self.timeout = options.timeout + 30.0 + + log.info("%s | Running tests: start.", os.path.basename(__file__)) + cmd, args = self.build_command_line(options.app, + ignore_window_size=options.ignoreWindowSize, + browser_arg=options.browser_arg) + self.runner = FirefoxRunner(profile=self.profile, + binary=cmd, + cmdargs=args, + env=env, + process_class=ProcessHandler, + process_args=kp_kwargs, + symbols_path=options.symbolsPath) + + status = 0 + try: + self.runner.start(outputTimeout=self.timeout) + log.info("%s | Application pid: %d", + os.path.basename(__file__), + self.runner.process_handler.pid) + + # kick starts the reftest harness + self.run_marionette_script() + status = self.runner.wait() + finally: + self.runner.check_for_crashes(test_name=self.last_test) + self.runner.cleanup() + + if status > 0: + log.testFail("%s | application terminated with exit code %s", + self.last_test, status) + elif status < 0: + log.info("%s | application killed with signal %s", + self.last_test, -status) + + log.info("%s | Running tests: end.", os.path.basename(__file__)) + return status + + def create_profile(self, options, reftestlist, profile_to_clone=None): + profile = RefTest.createReftestProfile(self, options, reftestlist, + profile_to_clone=profile_to_clone) + + prefs = {} + # Turn off the locale picker screen + prefs["browser.firstrun.show.localepicker"] = False + if not self.build_type == "mulet": + # FIXME: With Mulet we can't set this values since Gaia won't launch + prefs["b2g.system_startup_url"] = \ + "app://test-container.gaiamobile.org/index.html" + prefs["b2g.system_manifest_url"] = \ + "app://test-container.gaiamobile.org/manifest.webapp" + # Make sure we disable system updates + prefs["app.update.enabled"] = False + prefs["app.update.url"] = "" + prefs["app.update.url.override"] = "" + # Disable webapp updates + prefs["webapps.update.enabled"] = False + # Disable tiles also + prefs["browser.newtabpage.directory.source"] = "" + prefs["browser.newtabpage.directory.ping"] = "" + prefs["dom.ipc.tabs.disabled"] = False + prefs["dom.mozBrowserFramesEnabled"] = True + prefs["font.size.inflation.emPerLine"] = 0 + prefs["font.size.inflation.minTwips"] = 0 + prefs["network.dns.localDomains"] = "app://test-container.gaiamobile.org" + prefs["reftest.browser.iframe.enabled"] = False + prefs["reftest.remote"] = False + prefs["reftest.uri"] = "%s" % reftestlist + # Set a future policy version to avoid the telemetry prompt. + prefs["toolkit.telemetry.prompted"] = 999 + prefs["toolkit.telemetry.notifiedOptOut"] = 999 + + # Set the extra prefs. + profile.set_preferences(prefs) + return profile + + def build_command_line(self, app, ignore_window_size=False, + browser_arg=None): + cmd = os.path.abspath(app) + args = ['-marionette'] + + if browser_arg: + args += [browser_arg] + + if not ignore_window_size: + args.extend(['--screen', '800x1000']) + + if self.build_type == "mulet": + args += ['-chrome', 'chrome://b2g/content/shell.html'] + return cmd, args + + def _on_output(self, line): + print(line) + # TODO use structured logging + if "TEST-START" in line and "|" in line: + self.last_test = line.split("|")[1].strip() + + def _on_timeout(self): + msg = "%s | application timed out after %s seconds with no output" + log.testFail(msg % (self.last_test, self.timeout)) + + # kill process to get a stack + self.runner.stop(sig=signal.SIGABRT) + +class MuletReftest(B2GDesktopReftest): + build_type = "mulet" + + def _unlockScreen(self): + self.marionette.set_context(self.marionette.CONTEXT_CONTENT) + self.marionette.import_script(os.path.abspath( + os.path.join(__file__, os.path.pardir, "gaia_lock_screen.js"))) + self.marionette.switch_to_frame() + self.marionette.execute_async_script('GaiaLockScreen.unlock()') + + def _wait_for_homescreen(self, timeout): + log.info("Waiting for home screen to load") + Wait(self.marionette, timeout).until(expected.element_present( + By.CSS_SELECTOR, '#homescreen[loading-state=false]')) + +def run_desktop_reftests(parser, options, args): + marionette_args = {} + if options.marionette: + host, port = options.marionette.split(':') + marionette_args['host'] = host + marionette_args['port'] = int(port) + + if options.mulet: + reftest = MuletReftest(marionette_args) + else: + reftest = B2GDesktopReftest(marionette_args) + + options = ReftestOptions.verifyCommonOptions(parser, options, reftest) + if options == None: + sys.exit(1) + + # add a -bin suffix if b2g-bin exists, but just b2g was specified + if options.app[-4:] != '-bin': + if os.path.isfile("%s-bin" % options.app): + options.app = "%s-bin" % options.app + + if options.xrePath is None: + options.xrePath = os.path.dirname(options.app) + + if options.desktop and not options.profile: + raise Exception("must specify --profile when specifying --desktop") + + sys.exit(reftest.run_tests(args[0], options)) diff --git a/layout/tools/reftest/b2g_start_script.js b/layout/tools/reftest/b2g_start_script.js index 87ff726cf..b91dd9e12 100644 --- a/layout/tools/reftest/b2g_start_script.js +++ b/layout/tools/reftest/b2g_start_script.js @@ -1,47 +1,53 @@ -args = __marionetteParams; +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ function setDefaultPrefs() { - // This code sets the preferences for extension-based reftest; for - // command-line based reftest they are set in function handler_handle in - // reftest-cmdline.js. These two locations should stay in sync. - // - // FIXME: These should be in only one place. - var prefs = Components.classes["@mozilla.org/preferences-service;1"]. - getService(Components.interfaces.nsIPrefService); + // This code sets the preferences for extension-based reftest. + var prefs = Cc["@mozilla.org/preferences-service;1"]. + getService(Ci.nsIPrefService); var branch = prefs.getDefaultBranch(""); - branch.setBoolPref("dom.use_xbl_scopes_for_remote_xul", false); - branch.setBoolPref("gfx.color_management.force_srgb", true); - branch.setBoolPref("browser.dom.window.dump.enabled", true); - branch.setIntPref("ui.caretBlinkTime", -1); - branch.setBoolPref("dom.send_after_paint_to_content", true); - // no slow script dialogs - branch.setIntPref("dom.max_script_run_time", 0); - branch.setIntPref("dom.max_chrome_script_run_time", 0); - branch.setIntPref("hangmonitor.timeout", 0); - // Ensure autoplay is enabled for all platforms. - branch.setBoolPref("media.autoplay.enabled", true); - // Disable updates - branch.setBoolPref("app.update.enabled", false); + +#include reftest-preferences.js +} + +function setPermissions() { + if (__marionetteParams.length < 2) { + return; + } + + let serverAddr = __marionetteParams[0]; + let serverPort = __marionetteParams[1]; + let perms = Cc["@mozilla.org/permissionmanager;1"] + .getService(Ci.nsIPermissionManager); + let ioService = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService); + let uri = ioService.newURI("http://" + serverAddr + ":" + serverPort, null, null); + perms.add(uri, "allowXULXBL", Ci.nsIPermissionManager.ALLOW_ACTION); } -function setPermissions(webserver, port) { - var perms = Components.classes["@mozilla.org/permissionmanager;1"] - .getService(Components.interfaces.nsIPermissionManager); - var ioService = Components.classes["@mozilla.org/network/io-service;1"] - .getService(Components.interfaces.nsIIOService); - var uri = ioService.newURI("http://" + webserver + ":" + port, null, null); - perms.add(uri, "allowXULXBL", Components.interfaces.nsIPermissionManager.ALLOW_ACTION); +let cm = Cc["@mozilla.org/categorymanager;1"] + .getService(Components.interfaces.nsICategoryManager); + +// Disable update timers that cause b2g failures. +if (cm) { + cm.deleteCategoryEntry("update-timer", "WebappsUpdateTimer", false); + cm.deleteCategoryEntry("update-timer", "nsUpdateService", false); } // Load into any existing windows -let wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] - .getService(Components.interfaces.nsIWindowMediator); +let wm = Cc["@mozilla.org/appshell/window-mediator;1"] + .getService(Ci.nsIWindowMediator); let win = wm.getMostRecentWindow(''); // Set preferences and permissions setDefaultPrefs(); -setPermissions(args[0], args[1]); +setPermissions(); + +// Loading this into the global namespace causes intermittent failures. +// See bug 882888 for more details. +let reftest = {}; +Cu.import("chrome://reftest/content/reftest.jsm", reftest); // Start the reftests -Components.utils.import("chrome://reftest/content/reftest.jsm"); -OnRefTestLoad(win); +reftest.OnRefTestLoad(win); diff --git a/layout/tools/reftest/bootstrap.js b/layout/tools/reftest/bootstrap.js index 36ab76cc3..e78a32016 100644 --- a/layout/tools/reftest/bootstrap.js +++ b/layout/tools/reftest/bootstrap.js @@ -4,40 +4,12 @@ function loadIntoWindow(window) {} function unloadFromWindow(window) {} function setDefaultPrefs() { - // This code sets the preferences for extension-based reftest; for - // command-line based reftest they are set in function handler_handle in - // reftest-cmdline.js. These two locations should stay in sync. - // - // FIXME: These should be in only one place. + // This code sets the preferences for extension-based reftest. var prefs = Components.classes["@mozilla.org/preferences-service;1"]. getService(Components.interfaces.nsIPrefService); var branch = prefs.getDefaultBranch(""); - // For mochitests, we're more interested in testing the behavior of in- - // content XBL bindings, so we set this pref to true. In reftests, we're - // more interested in testing the behavior of XBL as it works in chrome, - // so we want this pref to be false. - branch.setBoolPref("dom.use_xbl_scopes_for_remote_xul", false); - branch.setBoolPref("gfx.color_management.force_srgb", true); - branch.setBoolPref("browser.dom.window.dump.enabled", true); - branch.setIntPref("ui.caretBlinkTime", -1); - branch.setBoolPref("dom.send_after_paint_to_content", true); - // no slow script dialogs - branch.setIntPref("dom.max_script_run_time", 0); - branch.setIntPref("dom.max_chrome_script_run_time", 0); - branch.setIntPref("hangmonitor.timeout", 0); - // Ensure autoplay is enabled for all platforms. - branch.setBoolPref("media.autoplay.enabled", true); - // Disable updates - branch.setBoolPref("app.update.enabled", false); - // Disable addon updates and prefetching so we don't leak them - branch.setBoolPref("extensions.update.enabled", false); - branch.setBoolPref("extensions.getAddons.cache.enabled", false); - // Disable blocklist updates so we don't have them reported as leaks - branch.setBoolPref("extensions.blocklist.enabled", false); - // Make url-classifier updates so rare that they won't affect tests - branch.setIntPref("urlclassifier.updateinterval", 172800); - // Disable high-quality downscaling, since it makes reftests more difficult. - branch.setBoolPref("image.high_quality_downscaling.enabled", false); + +#include reftest-preferences.js } var windowListener = { diff --git a/layout/tools/reftest/gaia_lock_screen.js b/layout/tools/reftest/gaia_lock_screen.js new file mode 100644 index 000000000..ca0953e91 --- /dev/null +++ b/layout/tools/reftest/gaia_lock_screen.js @@ -0,0 +1,60 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// NOTE: This code was forked from: +// https://github.com/mozilla-b2g/gaia/blob/master/tests/atoms/gaia_lock_screen.js + +'use strict'; +/* globals waitFor, finish */ +/* exported GaiaLockScreen */ + +var GaiaLockScreen = { + + unlock: function(forcibly) { + let setlock = window.wrappedJSObject.SettingsListener.getSettingsLock(); + let service = window.wrappedJSObject.Service; + let obj = {'screen.timeout': 0}; + setlock.set(obj); + + waitFor( + function() { + service.request('unlock', { forcibly: forcibly }); + waitFor( + function() { + finish(service.locked); + }, + function() { + return !service.locked; + } + ); + }, + function() { + return !!service; + } + ); + }, + + lock: function(forcibly) { + let service = window.wrappedJSObject.Service; + let setlock = window.wrappedJSObject.SettingsListener.getSettingsLock(); + let obj = {'screen.timeout': 0}; + setlock.set(obj); + waitFor( + function() { + service.request('lock', { forcibly: forcibly }); + waitFor( + function() { + finish(!service.locked); + }, + function() { + return service.locked; + } + ); + }, + function() { + return !!service; + } + ); + } +}; diff --git a/layout/tools/reftest/jar.mn b/layout/tools/reftest/jar.mn index e9d89b4ea..8a38a910c 100644 --- a/layout/tools/reftest/jar.mn +++ b/layout/tools/reftest/jar.mn @@ -1,14 +1,10 @@ reftest.jar: % content reftest %content/ * content/reftest-content.js (reftest-content.js) + content/httpd.jsm (../../../netwerk/test/httpserver/httpd.js) #ifdef BOOTSTRAP * content/reftest.jsm (reftest.js) #else * content/reftest.js (reftest.js) content/reftest.xul (reftest.xul) -#ifdef XPI_NAME -% component {32530271-8c1b-4b7d-a812-218e42c6bb23} components/reftest-cmdline.js -% contract @mozilla.org/commandlinehandler/general-startup;1?type=reftest {32530271-8c1b-4b7d-a812-218e42c6bb23} -% category command-line-handler m-reftest @mozilla.org/commandlinehandler/general-startup;1?type=reftest -#endif #endif diff --git a/layout/tools/reftest/mach_commands.py b/layout/tools/reftest/mach_commands.py index a7ba5f602..a042f17f6 100644 --- a/layout/tools/reftest/mach_commands.py +++ b/layout/tools/reftest/mach_commands.py @@ -4,12 +4,16 @@ from __future__ import unicode_literals -import mozpack.path +import mozpack.path as mozpath import os import re +import sys +import warnings +import which from mozbuild.base import ( MachCommandBase, + MachCommandConditions as conditions, MozbuildObject, ) @@ -22,6 +26,43 @@ from mach.decorators import ( DEBUGGER_HELP = 'Debugger binary to run test in. Program name or path.' +ADB_NOT_FOUND = ''' +The %s command requires the adb binary to be on your path. + +If you have a B2G build, this can be found in +'%s/out/host/<platform>/bin'. +'''.lstrip() + +GAIA_PROFILE_NOT_FOUND = ''' +The %s command requires a non-debug gaia profile. Either pass in --profile, +or set the GAIA_PROFILE environment variable. + +If you do not have a non-debug gaia profile, you can build one: + $ git clone https://github.com/mozilla-b2g/gaia + $ cd gaia + $ make + +The profile should be generated in a directory called 'profile'. +'''.lstrip() + +GAIA_PROFILE_IS_DEBUG = ''' +The %s command requires a non-debug gaia profile. The specified profile, +%s, is a debug profile. + +If you do not have a non-debug gaia profile, you can build one: + $ git clone https://github.com/mozilla-b2g/gaia + $ cd gaia + $ make + +The profile should be generated in a directory called 'profile'. +'''.lstrip() + +MARIONETTE_DISABLED = ''' +The %s command requires a marionette enabled build. + +Add 'ENABLE_MARIONETTE=1' to your mozconfig file and re-build the application. +Your currently active mozconfig is %s. +'''.lstrip() class ReftestRunner(MozbuildObject): """Easily run reftests. @@ -29,36 +70,167 @@ class ReftestRunner(MozbuildObject): This currently contains just the basics for running reftests. We may want to hook up result parsing, etc. """ + def __init__(self, *args, **kwargs): + MozbuildObject.__init__(self, *args, **kwargs) + + # TODO Bug 794506 remove once mach integrates with virtualenv. + build_path = os.path.join(self.topobjdir, 'build') + if build_path not in sys.path: + sys.path.append(build_path) + + self.tests_dir = os.path.join(self.topobjdir, '_tests') + self.reftest_dir = os.path.join(self.tests_dir, 'reftest') def _manifest_file(self, suite): """Returns the manifest file used for a given test suite.""" files = { - 'reftest': 'reftest.list', - 'reftest-ipc': 'reftest.list', - 'crashtest': 'crashtests.list', - 'crashtest-ipc': 'crashtests.list', + 'reftest': 'reftest.list', + 'reftest-ipc': 'reftest.list', + 'crashtest': 'crashtests.list', + 'crashtest-ipc': 'crashtests.list', + 'jstestbrowser': 'jstests.list' } assert suite in files return files[suite] def _find_manifest(self, suite, test_file): + """Return a tuple of (manifest-path, filter-string) for running test_file. + + test_file can be a relative path to a single test file or manifest from + the top source directory, an absolute path to the same, or a directory + containing a manifest. + """ assert test_file path_arg = self._wrap_path_argument(test_file) relpath = path_arg.relpath() if os.path.isdir(path_arg.srcdir_path()): - return mozpack.path.join(relpath, self._manifest_file(suite)) + return (mozpath.join(relpath, self._manifest_file(suite)), None) if relpath.endswith('.list'): - return relpath + return (relpath, None) - raise Exception('Running a single test is not currently supported') + return (self._find_manifest(suite, mozpath.dirname(test_file))[0], + mozpath.basename(test_file)) def _make_shell_string(self, s): return "'%s'" % re.sub("'", r"'\''", s) - def run_reftest_test(self, test_file=None, filter=None, suite=None, - debugger=None): + def run_b2g_test(self, b2g_home=None, xre_path=None, test_file=None, + suite=None, filter=None, **kwargs): + """Runs a b2g reftest. + + filter is a regular expression (in JS syntax, as could be passed to the + RegExp constructor) to select which reftests to run from the manifest. + + test_file is a path to a test file. It can be a relative path from the + top source directory, an absolute filename, or a directory containing + test files. + + suite is the type of reftest to run. It can be one of ('reftest', + 'crashtest'). + """ + if suite not in ('reftest', 'crashtest'): + raise Exception('None or unrecognized reftest suite type.') + + # Find the manifest file + if not test_file: + if suite == 'reftest': + test_file = mozpath.join('layout', 'reftests') + elif suite == 'crashtest': + test_file = mozpath.join('testing', 'crashtest') + + if not os.path.exists(os.path.join(self.topsrcdir, test_file)): + test_file = mozpath.relpath(os.path.abspath(test_file), + self.topsrcdir) + + (manifest, single_file_filter) = self._find_manifest(suite, test_file) + if not os.path.exists(mozpath.join(self.topsrcdir, manifest)): + raise Exception('No manifest file was found at %s.' % manifest) + if single_file_filter: + if filter: + raise Exception('Cannot run single files in conjunction with --filter') + filter = single_file_filter + + # Need to chdir to reftest_dir otherwise imports fail below. + os.chdir(self.reftest_dir) + + # The imp module can spew warnings if the modules below have + # already been imported, ignore them. + with warnings.catch_warnings(): + warnings.simplefilter('ignore') + + import imp + path = os.path.join(self.reftest_dir, 'runreftestb2g.py') + with open(path, 'r') as fh: + imp.load_module('reftest', fh, path, ('.py', 'r', imp.PY_SOURCE)) + import reftest + + # Set up the reftest options. + parser = reftest.B2GOptions() + options, args = parser.parse_args([]) + + # Tests need to be served from a subdirectory of the server. Symlink + # topsrcdir here to get around this. + tests = os.path.join(self.reftest_dir, 'tests') + if not os.path.isdir(tests): + os.symlink(self.topsrcdir, tests) + args.insert(0, os.path.join('tests', manifest)) + + for k, v in kwargs.iteritems(): + setattr(options, k, v) + + if conditions.is_b2g_desktop(self): + if self.substs.get('ENABLE_MARIONETTE') != '1': + print(MARIONETTE_DISABLED % ('mochitest-b2g-desktop', + self.mozconfig['path'])) + return 1 + + options.profile = options.profile or os.environ.get('GAIA_PROFILE') + if not options.profile: + print(GAIA_PROFILE_NOT_FOUND % 'reftest-b2g-desktop') + return 1 + + if os.path.isfile(os.path.join(options.profile, 'extensions', \ + 'httpd@gaiamobile.org')): + print(GAIA_PROFILE_IS_DEBUG % ('mochitest-b2g-desktop', + options.profile)) + return 1 + + options.desktop = True + options.app = self.get_binary_path() + if options.oop: + options.browser_arg = '-oop' + if not options.app.endswith('-bin'): + options.app = '%s-bin' % options.app + if not os.path.isfile(options.app): + options.app = options.app[:-len('-bin')] + + return reftest.run_desktop_reftests(parser, options, args) + + + try: + which.which('adb') + except which.WhichError: + # TODO Find adb automatically if it isn't on the path + raise Exception(ADB_NOT_FOUND % ('%s-remote' % suite, b2g_home)) + + options.b2gPath = b2g_home + options.logdir = self.reftest_dir + options.httpdPath = os.path.join(self.topsrcdir, 'netwerk', 'test', 'httpserver') + options.xrePath = xre_path + options.ignoreWindowSize = True + options.filter = filter + + # Don't enable oop for crashtest until they run oop in automation + if suite == 'reftest': + options.oop = True + + return reftest.run_remote_reftests(parser, options, args) + + def run_desktop_test(self, test_file=None, filter=None, suite=None, + debugger=None, debugger_args=None, parallel=False, shuffle=False, + e10s=False, extraPrefs=None, this_chunk=None, total_chunks=None): """Runs a reftest. test_file is a path to a test file. It can be a relative path from the @@ -69,22 +241,32 @@ class ReftestRunner(MozbuildObject): RegExp constructor) to select which reftests to run from the manifest. suite is the type of reftest to run. It can be one of ('reftest', - 'crashtest'). + 'crashtest', 'jstestbrowser'). debugger is the program name (in $PATH) or the full path of the debugger to run. + + debugger_args are the arguments passed to the debugger. + + parallel indicates whether tests should be run in parallel or not. + + shuffle indicates whether to run tests in random order. """ - if suite not in ('reftest', 'reftest-ipc', 'crashtest', 'crashtest-ipc'): + if suite not in ('reftest', 'reftest-ipc', 'crashtest', 'crashtest-ipc', 'jstestbrowser'): raise Exception('None or unrecognized reftest suite type.') env = {} extra_args = [] if test_file: - path = self._find_manifest(suite, test_file) - if not os.path.exists(mozpack.path.join(self.topsrcdir, path)): + (path, single_file_filter) = self._find_manifest(suite, test_file) + if not os.path.exists(mozpath.join(self.topsrcdir, path)): raise Exception('No manifest file was found at %s.' % path) + if single_file_filter: + if filter: + raise Exception('Cannot run single files in conjunction with --filter') + filter = single_file_filter env[b'TEST_PATH'] = path if filter: extra_args.extend(['--filter', self._make_shell_string(filter)]) @@ -92,8 +274,35 @@ class ReftestRunner(MozbuildObject): pass_thru = False if debugger: - extra_args.append('--debugger=%s' % debugger) + extra_args.append('--debugger=\'%s\'' % debugger) pass_thru = True + if debugger_args: + # Use _make_shell_string (which quotes) so that we + # handle multiple args being passed to the debugger. + extra_args.extend(['--debugger-args', self._make_shell_string(debugger_args)]) + else: + if debugger_args: + print("--debugger-args passed, but no debugger specified.") + return 1 + + if parallel: + extra_args.append('--run-tests-in-parallel') + + if shuffle: + extra_args.append('--shuffle') + + if e10s: + extra_args.append('--e10s') + + if extraPrefs: + for pref in extraPrefs: + extra_args.extend(['--setpref', pref]) + + if this_chunk: + extra_args.append('--this-chunk=%s' % this_chunk) + + if total_chunks: + extra_args.append('--total-chunks=%s' % total_chunks) if extra_args: args = [os.environ.get(b'EXTRA_TEST_ARGS', '')] @@ -112,6 +321,10 @@ def ReftestCommand(func): help=DEBUGGER_HELP) func = debugger(func) + debugger_args = CommandArgument('--debugger-args', metavar='DEBUGGER_ARGS', + help='Arguments to pass to the debugger.') + func = debugger_args(func) + flter = CommandArgument('--filter', metavar='REGEX', help='A JS regular expression to match test URLs against, to select ' 'a subset of tests to run.') @@ -122,38 +335,165 @@ def ReftestCommand(func): 'reftest.list. If omitted, the entire test suite is executed.') func = path(func) + parallel = CommandArgument('--parallel', action='store_true', + help='Run tests in parallel.') + func = parallel(func) + + shuffle = CommandArgument('--shuffle', action='store_true', + help='Run tests in random order.') + func = shuffle(func) + + e10s = CommandArgument('--e10s', action='store_true', + help='Use content processes.') + func = e10s(func) + + extraPrefs = CommandArgument('--setpref', action='append', + default=[], dest='extraPrefs', metavar='PREF=VALUE', + help='Set prefs in the reftest profile.') + func = extraPrefs(func) + + totalChunks = CommandArgument('--total-chunks', + help = 'How many chunks to split the tests up into.') + func = totalChunks(func) + + thisChunk = CommandArgument('--this-chunk', + help = 'Which chunk to run between 1 and --total-chunks.') + func = thisChunk(func) + + return func + +def B2GCommand(func): + """Decorator that adds shared command arguments to b2g reftest commands.""" + + busybox = CommandArgument('--busybox', default=None, + help='Path to busybox binary to install on device') + func = busybox(func) + + logdir = CommandArgument('--logdir', default=None, + help='directory to store log files') + func = logdir(func) + + sdcard = CommandArgument('--sdcard', default="10MB", + help='Define size of sdcard: 1MB, 50MB...etc') + func = sdcard(func) + + emulator_res = CommandArgument('--emulator-res', default='800x1000', + help='Emulator resolution of the format \'<width>x<height>\'') + func = emulator_res(func) + + marionette = CommandArgument('--marionette', default=None, + help='host:port to use when connecting to Marionette') + func = marionette(func) + + totalChunks = CommandArgument('--total-chunks', dest='totalChunks', + type = int, + help = 'How many chunks to split the tests up into.') + func = totalChunks(func) + + thisChunk = CommandArgument('--this-chunk', dest='thisChunk', + type = int, + help = 'Which chunk to run between 1 and --total-chunks.') + func = thisChunk(func) + + flter = CommandArgument('--filter', metavar='REGEX', + help='A JS regular expression to match test URLs against, to select ' + 'a subset of tests to run.') + func = flter(func) + + oop = CommandArgument('--enable-oop', action='store_true', dest='oop', + help = 'Run tests in out-of-process mode.') + func = oop(func) + + path = CommandArgument('test_file', default=None, nargs='?', + metavar='TEST', + help='Test to run. Can be specified as a single file, a ' \ + 'directory, or omitted. If omitted, the entire test suite is ' \ + 'executed.') + func = path(func) + return func @CommandProvider class MachCommands(MachCommandBase): - @Command('reftest', category='testing', description='Run reftests.') + @Command('reftest', category='testing', description='Run reftests (layout and graphics correctness).') @ReftestCommand def run_reftest(self, test_file, **kwargs): return self._run_reftest(test_file, suite='reftest', **kwargs) + @Command('jstestbrowser', category='testing', + description='Run js/src/tests in the browser.') + @ReftestCommand + def run_jstestbrowser(self, test_file, **kwargs): + return self._run_reftest(test_file, suite='jstestbrowser', **kwargs) + @Command('reftest-ipc', category='testing', - description='Run IPC reftests.') + description='Run IPC reftests (layout and graphics correctness, separate process).') @ReftestCommand def run_ipc(self, test_file, **kwargs): return self._run_reftest(test_file, suite='reftest-ipc', **kwargs) @Command('crashtest', category='testing', - description='Run crashtests.') + description='Run crashtests (Check if crashes on a page).') @ReftestCommand def run_crashtest(self, test_file, **kwargs): return self._run_reftest(test_file, suite='crashtest', **kwargs) @Command('crashtest-ipc', category='testing', - description='Run IPC crashtests.') + description='Run IPC crashtests (Check if crashes on a page, separate process).') @ReftestCommand def run_crashtest_ipc(self, test_file, **kwargs): return self._run_reftest(test_file, suite='crashtest-ipc', **kwargs) - def _run_reftest(self, test_file=None, filter=None, suite=None, - debugger=None): + def _run_reftest(self, test_file=None, suite=None, **kwargs): reftest = self._spawn(ReftestRunner) - return reftest.run_reftest_test(test_file, filter=filter, suite=suite, - debugger=debugger) + return reftest.run_desktop_test(test_file, suite=suite, **kwargs) + + +# TODO For now b2g commands will only work with the emulator, +# they should be modified to work with all devices. +def is_emulator(cls): + """Emulator needs to be configured.""" + return cls.device_name.startswith('emulator') +@CommandProvider +class B2GCommands(MachCommandBase): + def __init__(self, context): + MachCommandBase.__init__(self, context) + + for attr in ('b2g_home', 'xre_path', 'device_name'): + setattr(self, attr, getattr(context, attr, None)) + + @Command('reftest-remote', category='testing', + description='Run a remote reftest (b2g layout and graphics correctness, remote device).', + conditions=[conditions.is_b2g, is_emulator]) + @B2GCommand + def run_reftest_remote(self, test_file, **kwargs): + return self._run_reftest(test_file, suite='reftest', **kwargs) + + @Command('reftest-b2g-desktop', category='testing', + description='Run a b2g desktop reftest (b2g desktop layout and graphics correctness).', + conditions=[conditions.is_b2g_desktop]) + @B2GCommand + def run_reftest_b2g_desktop(self, test_file, **kwargs): + return self._run_reftest(test_file, suite='reftest', **kwargs) + + @Command('crashtest-remote', category='testing', + description='Run a remote crashtest (Check if b2g crashes on a page, remote device).', + conditions=[conditions.is_b2g, is_emulator]) + @B2GCommand + def run_crashtest_remote(self, test_file, **kwargs): + return self._run_reftest(test_file, suite='crashtest', **kwargs) + + def _run_reftest(self, test_file=None, suite=None, **kwargs): + if self.device_name: + if self.device_name.startswith('emulator'): + emulator = 'arm' + if 'x86' in self.device_name: + emulator = 'x86' + kwargs['emulator'] = emulator + + reftest = self._spawn(ReftestRunner) + return reftest.run_b2g_test(self.b2g_home, self.xre_path, + test_file, suite=suite, **kwargs) diff --git a/layout/tools/reftest/moz.build b/layout/tools/reftest/moz.build index de8e39932..ce98ef78e 100644 --- a/layout/tools/reftest/moz.build +++ b/layout/tools/reftest/moz.build @@ -4,5 +4,16 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -MODULE = 'reftest' +if CONFIG['MOZ_BUILD_APP'] in ('b2g', 'b2g/dev', 'mobile/android'): + DEFINES['BOOTSTRAP'] = True + if CONFIG['MOZ_BUILD_APP'] in ('b2g', 'b2g/dev'): + DEFINES['REFTEST_B2G'] = True +else: + EXTRA_PP_COMPONENTS += [ + 'reftest-cmdline.js', + 'reftest-cmdline.manifest', + ] +JAR_MANIFESTS += ['jar.mn'] + +XPI_NAME = 'reftest' diff --git a/layout/tools/reftest/print-manifest-dirs.py b/layout/tools/reftest/print-manifest-dirs.py index 76d95f822..0f90c255a 100644 --- a/layout/tools/reftest/print-manifest-dirs.py +++ b/layout/tools/reftest/print-manifest-dirs.py @@ -3,77 +3,27 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -import sys, os.path, re - -commentRE = re.compile(r"\s+#") -conditionsRE = re.compile(r"^(fails|needs-focus|random|skip|asserts|slow|require-or|silentfail|pref|test-pref|ref-pref|fuzzy)") -httpRE = re.compile(r"HTTP\((\.\.(\/\.\.)*)\)") -protocolRE = re.compile(r"^\w+:") - -def parseManifest(manifest, dirs): - """Parse the reftest manifest |manifest|, adding all directories containing - tests (and the dirs containing the manifests themselves) to the set |dirs|.""" - manifestdir = os.path.dirname(os.path.abspath(manifest)) - dirs.add(manifestdir) - f = file(manifest) - urlprefix = '' - for line in f: - if line[0] == '#': - continue # entire line was a comment - m = commentRE.search(line) - if m: - line = line[:m.start()] - line = line.strip() - if not line: - continue - items = line.split() - while conditionsRE.match(items[0]): - del items[0] - if items[0] == "HTTP": - del items[0] - m = httpRE.match(items[0]) - if m: - # need to package the dir referenced here - d = os.path.normpath(os.path.join(manifestdir, m.group(1))) - dirs.add(d) - del items[0] - - if items[0] == "url-prefix": - urlprefix = items[1] - continue - elif items[0] == "default-preferences": - continue - elif items[0] == "include": - parseManifest(os.path.join(manifestdir, items[1]), dirs) - continue - elif items[0] == "load" or items[0] == "script": - testURLs = [items[1]] - elif items[0] == "==" or items[0] == "!=": - testURLs = items[1:3] - for u in testURLs: - m = protocolRE.match(u) - if m: - # can't very well package about: or data: URIs - continue - d = os.path.dirname(os.path.normpath(os.path.join(manifestdir, urlprefix + u))) - dirs.add(d) - f.close() +import os +import sys +from reftest import ReftestManifest def printTestDirs(topsrcdir, topmanifests): - """Parse |topmanifests| and print a list of directories containing the tests - within (and the manifests including those tests), relative to |topsrcdir|.""" - topsrcdir = os.path.abspath(topsrcdir) - dirs = set() - for manifest in topmanifests: - parseManifest(manifest, dirs) - for dir in sorted(dirs): - d = dir[len(topsrcdir):].replace('\\','/') - if d[0] == '/': - d = d[1:] - print d + """Parse |topmanifests| and print a list of directories containing the tests + within (and the manifests including those tests), relative to |topsrcdir|. + """ + topsrcdir = os.path.abspath(topsrcdir) + dirs = set() + for path in topmanifests: + m = ReftestManifest() + m.load(path) + dirs |= m.dirs + + for d in sorted(dirs): + d = d[len(topsrcdir):].replace('\\', '/').lstrip('/') + print(d) if __name__ == '__main__': - if len(sys.argv) < 3: - print >>sys.stderr, "Usage: %s topsrcdir reftest.list [reftest.list]*" % sys.argv[0] - sys.exit(1) - printTestDirs(sys.argv[1], sys.argv[2:]) + if len(sys.argv) < 3: + print >>sys.stderr, "Usage: %s topsrcdir reftest.list [reftest.list]*" % sys.argv[0] + sys.exit(1) + printTestDirs(sys.argv[1], sys.argv[2:]) diff --git a/layout/tools/reftest/reftest-analyzer.xhtml b/layout/tools/reftest/reftest-analyzer.xhtml index 1b1c81d98..5082028d9 100644 --- a/layout/tools/reftest/reftest-analyzer.xhtml +++ b/layout/tools/reftest/reftest-analyzer.xhtml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> -<!-- -*- Mode: HTML; tab-width: 4; indent-tabs-mode: nil; -*- --> -<!-- vim: set shiftwidth=4 tabstop=4 autoindent noexpandtab: --> +<!-- -*- Mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- --> +<!-- vim: set shiftwidth=2 tabstop=2 autoindent expandtab: --> <!-- This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> @@ -81,6 +81,7 @@ var gMagZoom = 16; // size of the zoomed in pixels var gImage1Data; // ImageData object for the reference image var gImage2Data; // ImageData object for the test output image var gFlashingPixels = []; // array of <path> objects that should be flashed due to pixel color mismatch +var gParams; function ID(id) { if (!(id in gIDCache)) @@ -101,10 +102,20 @@ function hash_parameters() { function load() { gPhases = [ ID("entry"), ID("loading"), ID("viewer") ]; build_mag(); - var params = hash_parameters(); - if (params.log) { - ID("logentry").value = params.log; - log_pasted(); + gParams = hash_parameters(); + if (gParams.log) { + show_phase("loading"); + process_log(gParams.log); + } else if (gParams.logurl) { + show_phase("loading"); + var req = new XMLHttpRequest(); + req.onreadystatechange = function() { + if (req.readyState === 4) { + process_log(req.responseText); + } + }; + req.open('GET', gParams.logurl, true); + req.send(); } window.addEventListener('keypress', maybe_load_image, false); ID("image1").addEventListener('error', image_load_error, false); @@ -179,7 +190,7 @@ function fileentry_changed() { var log = null; log = e.target.result; - + if (log) process_log(log); else @@ -208,7 +219,10 @@ function process_log(contents) { gTestItems = []; for (var j in lines) { var line = lines[j]; - var match = line.match(/^(?:NEXT ERROR |\d\d:\d\d:\d\d +INFO - +)*REFTEST (.*)$/); + // Ignore duplicated output in logcat. + if (line.match(/I\/Goanna.*?REFTEST/)) + continue; + var match = line.match(/^.*?REFTEST (.*)$/); if (!match) continue; line = match[1]; @@ -217,7 +231,7 @@ function process_log(contents) { var state = match[1]; var random = match[2]; var url = match[3]; - var extra = match[4]; + var extra = match[4]; gTestItems.push( { pass: !state.match(/DEBUG-INFO$|FAIL$/), @@ -226,14 +240,16 @@ function process_log(contents) { random: (random == "(EXPECTED RANDOM)"), skip: (extra == " (SKIP)"), url: url, - images: [] + images: [], + imageLabels: [] }); continue; } - match = line.match(/^ IMAGE[^:]*: (.*)$/); + match = line.match(/^(?: |)IMAGE ([^:]*): (data:.*)$/); if (match) { var item = gTestItems[gTestItems.length - 1]; - item.images.push(match[1]); + item.images.push(match[2]); + item.imageLabels.push(match[1]); } } @@ -257,7 +273,11 @@ function build_viewer() { for (var i in gTestItems) { var item = gTestItems[i]; - // XXX skip expected pass items until we have filtering UI + // optional url filter for only showing unexpected results + if (parseInt(gParams.only_show_unexpected) && !item.unexpected) + continue; + + // XXX regardless skip expected pass items until we have filtering UI if (item.pass && !item.unexpected) continue; @@ -301,17 +321,25 @@ function get_image_data(src, whenReady) { var img = new Image(); img.onload = function() { var canvas = document.createElement("canvas"); - canvas.width = 800; - canvas.height = 1000; + canvas.width = img.naturalWidth; + canvas.height = img.naturalHeight; var ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0); - whenReady(ctx.getImageData(0, 0, 800, 1000)); + whenReady(ctx.getImageData(0, 0, img.naturalWidth, img.naturalHeight)); }; img.src = src; } +function sync_svg_size(imageData) { + // We need the size of the 'svg' and its 'image' elements to match the size + // of the ImageData objects that we're going to read pixels from or else our + // magnify() function will be very broken. + ID("svg").setAttribute("width", imageData.width); + ID("svg").setAttribute("height", imageData.height); +} + function show_images(i) { var item = gTestItems[i]; var cell = ID("images"); @@ -332,11 +360,14 @@ function show_images(i) { ID("image2").setAttributeNS(XLINK_NS, "xlink:href", item.images[1]); // Making the href be #image2 doesn't seem to work ID("feimage2").setAttributeNS(XLINK_NS, "xlink:href", item.images[1]); + + ID("label1").textContent = 'Image ' + item.imageLabels[0]; + ID("label2").textContent = 'Image ' + item.imageLabels[1]; } cell.style.display = ""; - get_image_data(item.images[0], function(data) { gImage1Data = data }); + get_image_data(item.images[0], function(data) { gImage1Data = data; sync_svg_size(gImage1Data); }); get_image_data(item.images[1], function(data) { gImage2Data = data }); } @@ -418,7 +449,9 @@ function magnify(evt) { var py = y + j; var p1 = gMagPixPaths[i + dx_hi][j + dy_hi][0]; var p2 = gMagPixPaths[i + dx_hi][j + dy_hi][1]; - if (px < 0 || py < 0 || px >= 800 || py >= 1000) { + // Here we just use the dimensions of gImage1Data since we expect test + // and reference to have the same dimensions. + if (px < 0 || py < 0 || px >= gImage1Data.width || py >= gImage1Data.height) { p1.setAttribute("fill", "#aaa"); p2.setAttribute("fill", "#888"); } else { @@ -502,11 +535,11 @@ function show_pixelinfo(x, y, pix1rgb, pix1hex, pix2rgb, pix2hex) { <div id="itemlist"></div> <div id="images" style="display:none"> <form id="imgcontrols"> - <label title="1"><input id="radio1" type="radio" name="which" value="0" onchange="show_image(1)" checked="checked" />Image 1</label> - <label title="2"><input id="radio2" type="radio" name="which" value="1" onchange="show_image(2)" />Image 2</label> + <input id="radio1" type="radio" name="which" value="0" onchange="show_image(1)" checked="checked" /><label id="label1" title="1" for="radio1">Image 1</label> + <input id="radio2" type="radio" name="which" value="1" onchange="show_image(2)" /><label id="label2" title="2" for="radio2">Image 2</label> <label><input type="checkbox" onchange="show_differences(this)" />Circle differences</label> </form> - <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="800px" height="1000px" viewBox="0 0 800 1000" id="svg"> + <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="800" height="1000" id="svg"> <defs> <!-- use sRGB to avoid loss of data --> <filter id="showDifferences" x="0%" y="0%" width="100%" height="100%" diff --git a/layout/tools/reftest/reftest-cmdline.js b/layout/tools/reftest/reftest-cmdline.js index 107856151..fde70c9c0 100644 --- a/layout/tools/reftest/reftest-cmdline.js +++ b/layout/tools/reftest/reftest-cmdline.js @@ -66,54 +66,46 @@ RefTestCmdLineHandler.prototype = * We want to do this here rather than in reftest.js because it's * important to force sRGB as an output profile for color management * before we load a window. - * - * If you change these, please adjust them in the bootstrap.js function - * setDefaultPrefs(). These are duplicated there so we can have a - * restartless addon for reftest on native Android. - * - * FIXME: These should be in only one place. */ var prefs = Components.classes["@mozilla.org/preferences-service;1"]. getService(Components.interfaces.nsIPrefService); var branch = prefs.getDefaultBranch(""); - // For mochitests, we're more interested in testing the behavior of in- - // content XBL bindings, so we set this pref to true. In reftests, we're - // more interested in testing the behavior of XBL as it works in chrome, - // so we want this pref to be false. - branch.setBoolPref("dom.use_xbl_scopes_for_remote_xul", false); - branch.setBoolPref("gfx.color_management.force_srgb", true); - branch.setBoolPref("browser.dom.window.dump.enabled", true); - branch.setIntPref("ui.caretBlinkTime", -1); - branch.setBoolPref("dom.send_after_paint_to_content", true); - // no slow script dialogs - branch.setIntPref("dom.max_script_run_time", 0); - branch.setIntPref("dom.max_chrome_script_run_time", 0); - branch.setIntPref("hangmonitor.timeout", 0); - // Ensure autoplay is enabled for all platforms. - branch.setBoolPref("media.autoplay.enabled", true); - // Disable updates - branch.setBoolPref("app.update.enabled", false); - // Disable addon updates and prefetching so we don't leak them - branch.setBoolPref("extensions.update.enabled", false); - branch.setBoolPref("extensions.getAddons.cache.enabled", false); - // Disable blocklist updates so we don't have them reported as leaks - branch.setBoolPref("extensions.blocklist.enabled", false); - // Make url-classifier updates so rare that they won't affect tests - branch.setIntPref("urlclassifier.updateinterval", 172800); - // Disable high-quality downscaling, since it makes reftests more difficult. - branch.setBoolPref("image.high_quality_downscaling.enabled", false); - // Checking whether two files are the same is slow on Windows. - // Setting this pref makes tests run much faster there. - branch.setBoolPref("security.fileuri.strict_origin_policy", false); + +#include reftest-preferences.js var wwatch = Components.classes["@mozilla.org/embedcomp/window-watcher;1"] .getService(nsIWindowWatcher); - wwatch.openWindow(null, "chrome://reftest/content/reftest.xul", "_blank", - "chrome,dialog=no,all", args); + + function loadReftests() { + wwatch.openWindow(null, "chrome://reftest/content/reftest.xul", "_blank", + "chrome,dialog=no,all", args); + } + + var remote = false; + try { + remote = prefs.getBoolPref("reftest.remote"); + } catch (ex) { + } + + // If we are running on a remote machine, assume that we can't open another + // window for transferring focus to when tests don't require focus. + if (remote) { + loadReftests(); + } + else { + // This dummy window exists solely for enforcing proper focus discipline. + var dummy = wwatch.openWindow(null, "about:blank", "dummy", + "chrome,dialog=no,left=800,height=200,width=200,all", null); + dummy.onload = function dummyOnload() { + dummy.focus(); + loadReftests(); + } + } + cmdLine.preventDefault = true; }, - helpInfo : " -reftest <file> Run layout acceptance tests on given manifest.\n" + helpInfo : " --reftest <file> Run layout acceptance tests on given manifest.\n" }; this.NSGetFactory = XPCOMUtils.generateNSGetFactory([RefTestCmdLineHandler]); diff --git a/layout/tools/reftest/reftest-content.js b/layout/tools/reftest/reftest-content.js index efadf8235..c49d2e242 100644 --- a/layout/tools/reftest/reftest-content.js +++ b/layout/tools/reftest/reftest-content.js @@ -1,4 +1,4 @@ -/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / +/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- / /* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -50,16 +50,20 @@ const TYPE_LOAD = 'load'; // test without a reference (just test that it does const TYPE_SCRIPT = 'script'; // test contains individual test results function markupDocumentViewer() { - return docShell.contentViewer.QueryInterface(CI.nsIMarkupDocumentViewer); + return docShell.contentViewer; } function webNavigation() { return docShell.QueryInterface(CI.nsIWebNavigation); } +function windowUtilsForWindow(w) { + return w.QueryInterface(CI.nsIInterfaceRequestor) + .getInterface(CI.nsIDOMWindowUtils); +} + function windowUtils() { - return content.QueryInterface(CI.nsIInterfaceRequestor) - .getInterface(CI.nsIDOMWindowUtils); + return windowUtilsForWindow(content); } function IDForEventTarget(event) @@ -182,43 +186,105 @@ function setupPrintMode() { docShell.contentViewer.setPageMode(true, ps); } -function setupDisplayport(contentRootElement) { +function attrOrDefault(element, attr, def) { + return element.hasAttribute(attr) ? Number(element.getAttribute(attr)) : def; +} + +function setupViewport(contentRootElement) { if (!contentRootElement) { return; } - function attrOrDefault(attr, def) { - return contentRootElement.hasAttribute(attr) ? - contentRootElement.getAttribute(attr) : def; - } - - var vw = attrOrDefault("reftest-viewport-w", 0); - var vh = attrOrDefault("reftest-viewport-h", 0); + var vw = attrOrDefault(contentRootElement, "reftest-viewport-w", 0); + var vh = attrOrDefault(contentRootElement, "reftest-viewport-h", 0); if (vw !== 0 || vh !== 0) { LogInfo("Setting viewport to <w="+ vw +", h="+ vh +">"); windowUtils().setCSSViewport(vw, vh); } - // XXX support displayPortX/Y when needed - var dpw = attrOrDefault("reftest-displayport-w", 0); - var dph = attrOrDefault("reftest-displayport-h", 0); - var dpx = attrOrDefault("reftest-displayport-x", 0); - var dpy = attrOrDefault("reftest-displayport-y", 0); - if (dpw !== 0 || dph !== 0) { - LogInfo("Setting displayport to <x="+ dpx +", y="+ dpy +", w="+ dpw +", h="+ dph +">"); - windowUtils().setDisplayPortForElement(dpx, dpy, dpw, dph, content.document.documentElement); + // XXX support resolution when needed + + // XXX support viewconfig when needed +} + +function setupDisplayport(contentRootElement) { + if (!contentRootElement) { + return; } - var asyncScroll = attrOrDefault("reftest-async-scroll", false); - if (asyncScroll) { - SendEnableAsyncScroll(); + + function setupDisplayportForElement(element, winUtils) { + var dpw = attrOrDefault(element, "reftest-displayport-w", 0); + var dph = attrOrDefault(element, "reftest-displayport-h", 0); + var dpx = attrOrDefault(element, "reftest-displayport-x", 0); + var dpy = attrOrDefault(element, "reftest-displayport-y", 0); + if (dpw !== 0 || dph !== 0 || dpx != 0 || dpy != 0) { + LogInfo("Setting displayport to <x="+ dpx +", y="+ dpy +", w="+ dpw +", h="+ dph +">"); + winUtils.setDisplayPortForElement(dpx, dpy, dpw, dph, element, 1); + } } - // XXX support resolution when needed + function setupDisplayportForElementSubtree(element, winUtils) { + setupDisplayportForElement(element, winUtils); + for (var c = element.firstElementChild; c; c = c.nextElementSibling) { + setupDisplayportForElementSubtree(c, winUtils); + } + if (element.contentDocument) { + LogInfo("Descending into subdocument"); + setupDisplayportForElementSubtree(element.contentDocument.documentElement, + windowUtilsForWindow(element.contentWindow)); + } + } - // XXX support viewconfig when needed + if (contentRootElement.hasAttribute("reftest-async-scroll")) { + setupDisplayportForElementSubtree(contentRootElement, windowUtils()); + } else { + setupDisplayportForElement(contentRootElement, windowUtils()); + } } -function resetDisplayport() { +function setupAsyncScrollOffsets(options) { + var currentDoc = content.document; + var contentRootElement = currentDoc ? currentDoc.documentElement : null; + + if (!contentRootElement) { + return; + } + + function setupAsyncScrollOffsetsForElement(element, winUtils) { + var sx = attrOrDefault(element, "reftest-async-scroll-x", 0); + var sy = attrOrDefault(element, "reftest-async-scroll-y", 0); + if (sx != 0 || sy != 0) { + try { + // This might fail when called from RecordResult since layers + // may not have been constructed yet + winUtils.setAsyncScrollOffset(element, sx, sy); + } catch (e) { + if (!options.allowFailure) { + throw e; + } + } + } + } + + function setupAsyncScrollOffsetsForElementSubtree(element, winUtils) { + setupAsyncScrollOffsetsForElement(element, winUtils); + for (var c = element.firstElementChild; c; c = c.nextElementSibling) { + setupAsyncScrollOffsetsForElementSubtree(c, winUtils); + } + if (element.contentDocument) { + LogInfo("Descending into subdocument (async offsets)"); + setupAsyncScrollOffsetsForElementSubtree(element.contentDocument.documentElement, + windowUtilsForWindow(element.contentWindow)); + } + } + + var asyncScroll = contentRootElement.hasAttribute("reftest-async-scroll"); + if (asyncScroll) { + setupAsyncScrollOffsetsForElementSubtree(contentRootElement, windowUtils()); + } +} + +function resetDisplayportAndViewport() { // XXX currently the displayport configuration lives on the // presshell and so is "reset" on nav when we get a new presshell. } @@ -392,6 +458,12 @@ function WaitForTestEnd(contentRootElement, inPrintMode, spellCheckedElements) { notification.initEvent("MozReftestInvalidate", true, false); contentRootElement.dispatchEvent(notification); } + + if (!inPrintMode && doPrintMode(contentRootElement)) { + LogInfo("MakeProgress: setting up print mode"); + setupPrintMode(); + } + if (hasReftestWait && !shouldWaitForReftestWaitRemoval(contentRootElement)) { // MozReftestInvalidate handler removed reftest-wait. // We expect something to have been invalidated... @@ -427,10 +499,6 @@ function WaitForTestEnd(contentRootElement, inPrintMode, spellCheckedElements) { } state = STATE_WAITING_TO_FINISH; - if (!inPrintMode && doPrintMode(contentRootElement)) { - LogInfo("MakeProgress: setting up print mode"); - setupPrintMode(); - } // Try next state MakeProgress(); return; @@ -535,6 +603,7 @@ function OnDocumentLoad(event) var contentRootElement = currentDoc ? currentDoc.documentElement : null; currentDoc = null; setupZoom(contentRootElement); + setupViewport(contentRootElement); setupDisplayport(contentRootElement); var inPrintMode = false; @@ -653,6 +722,9 @@ function RecordResult() return; } + // Setup async scroll offsets now in case SynchronizeForSnapshot is not + // called (due to reftest-no-sync-layers being supplied). + setupAsyncScrollOffsets({allowFailure:true}); SendTestDone(currentTestRunTime); FinishTestItem(); } @@ -726,13 +798,11 @@ function SynchronizeForSnapshot(flags) } } - var dummyCanvas = content.document.createElementNS(XHTML_NS, "canvas"); - dummyCanvas.setAttribute("width", 1); - dummyCanvas.setAttribute("height", 1); + windowUtils().updateLayerTree(); - var ctx = dummyCanvas.getContext("2d"); - var flags = ctx.DRAWWINDOW_DRAW_CARET | ctx.DRAWWINDOW_DRAW_VIEW | ctx.DRAWWINDOW_USE_WIDGET_LAYERS; - ctx.drawWindow(content, 0, 0, 1, 1, "rgb(255,255,255)", flags); + // Setup async scroll offsets now, because any scrollable layers should + // have had their AsyncPanZoomControllers created. + setupAsyncScrollOffsets({allowFailure:false}); } function RegisterMessageListeners() @@ -774,7 +844,7 @@ function RecvLoadScriptTest(uri, timeout) function RecvResetRenderingState() { resetZoom(); - resetDisplayport(); + resetDisplayportAndViewport(); } function SendAssertionCount(numAssertions) @@ -802,11 +872,6 @@ function SendFailedNoPaint() sendAsyncMessage("reftest:FailedNoPaint"); } -function SendEnableAsyncScroll() -{ - sendAsyncMessage("reftest:EnableAsyncScroll"); -} - // Return true if a snapshot was taken. function SendInitCanvasWithSnapshot() { @@ -859,7 +924,7 @@ function SendUpdateCanvasForEvent(event, contentRootElement) var scale = markupDocumentViewer().fullZoom; var rects = [ ]; - if (shouldSnapshotWholePage) { + if (shouldSnapshotWholePage(contentRootElement)) { // See comments in SendInitCanvasWithSnapshot() re: the split // logic here. if (!gBrowserIsRemote) { diff --git a/layout/tools/reftest/reftest-preferences.js b/layout/tools/reftest/reftest-preferences.js new file mode 100644 index 000000000..a4ff8f6e0 --- /dev/null +++ b/layout/tools/reftest/reftest-preferences.js @@ -0,0 +1,63 @@ + // For mochitests, we're more interested in testing the behavior of in- + // content XBL bindings, so we set this pref to true. In reftests, we're + // more interested in testing the behavior of XBL as it works in chrome, + // so we want this pref to be false. + branch.setBoolPref("dom.use_xbl_scopes_for_remote_xul", false); + branch.setBoolPref("gfx.color_management.force_srgb", true); + branch.setBoolPref("browser.dom.window.dump.enabled", true); + branch.setIntPref("ui.caretBlinkTime", -1); + branch.setBoolPref("dom.send_after_paint_to_content", true); + // no slow script dialogs + branch.setIntPref("dom.max_script_run_time", 0); + branch.setIntPref("dom.max_chrome_script_run_time", 0); + branch.setIntPref("hangmonitor.timeout", 0); + // Ensure autoplay is enabled for all platforms. + branch.setBoolPref("media.autoplay.enabled", true); + // Disable updates + branch.setBoolPref("app.update.enabled", false); + // Disable addon updates and prefetching so we don't leak them + branch.setBoolPref("extensions.update.enabled", false); + branch.setBoolPref("extensions.getAddons.cache.enabled", false); + // Disable blocklist updates so we don't have them reported as leaks + branch.setBoolPref("extensions.blocklist.enabled", false); + // Make url-classifier updates so rare that they won't affect tests + branch.setIntPref("urlclassifier.updateinterval", 172800); + // Disable high-quality downscaling, since it makes reftests more difficult. + branch.setBoolPref("image.high_quality_downscaling.enabled", false); + // Disable the single-color optimization, since it can cause intermittent + // oranges and it causes many of our tests to test a different code path + // than the one that normal images on the web use. + branch.setBoolPref("image.single-color-optimization.enabled", false); + // Checking whether two files are the same is slow on Windows. + // Setting this pref makes tests run much faster there. + branch.setBoolPref("security.fileuri.strict_origin_policy", false); + // Disable the thumbnailing service + branch.setBoolPref("browser.pagethumbnails.capturing_disabled", true); + // Since our tests are 800px wide, set the assume-designed-for width of all + // pages to be 800px (instead of the default of 980px). This ensures that + // in our 800px window we don't zoom out by default to try to fit the + // assumed 980px content. + branch.setIntPref("browser.viewport.desktopWidth", 800); + // Disable the fade out (over time) of overlay scrollbars, since we + // can't guarantee taking both reftest snapshots at the same point + // during the fade. + branch.setBoolPref("layout.testing.overlay-scrollbars.always-visible", true); + // Disable interruptible reflow since (1) it's normally not going to + // happen, but (2) it might happen if we somehow end up with both + // pending user events and clock skew. So to avoid having to change + // MakeProgress to deal with waiting for interruptible reflows to + // complete for a rare edge case, we just disable interruptible + // reflow so that that rare edge case doesn't lead to reftest + // failures. + branch.setBoolPref("layout.interruptible-reflow.enabled", false); + // Disable the auto-hide feature of touch caret to avoid potential + // intermittent issues. + branch.setIntPref("touchcaret.expiration.time", 0); + + // Tell the search service we are running in the US. This also has the + // desired side-effect of preventing our geoip lookup. + branch.setBoolPref("browser.search.isUS", true); + branch.setCharPref("browser.search.countryCode", "US"); + + // Make sure SelfSupport doesn't hit the network. + branch.setCharPref("browser.selfsupport.url", "https://%(server)s/selfsupport-dummy/"); diff --git a/layout/tools/reftest/reftest.js b/layout/tools/reftest/reftest.js index 7191df1cb..895c06117 100644 --- a/layout/tools/reftest/reftest.js +++ b/layout/tools/reftest/reftest.js @@ -1,4 +1,4 @@ -/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / +/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- / /* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -12,6 +12,7 @@ this.EXPORTED_SYMBOLS = ["OnRefTestLoad"]; const CC = Components.classes; const CI = Components.interfaces; const CR = Components.results; +const CU = Components.utils; const XHTML_NS = "http://www.w3.org/1999/xhtml"; const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; @@ -35,12 +36,15 @@ const NS_DIRECTORY_SERVICE_CONTRACTID = const NS_OBSERVER_SERVICE_CONTRACTID = "@mozilla.org/observer-service;1"; -Components.utils.import("resource://gre/modules/FileUtils.jsm"); +CU.import("resource://gre/modules/FileUtils.jsm"); +CU.import("chrome://reftest/content/httpd.jsm", this); +CU.import("resource://gre/modules/Services.jsm"); var gLoadTimeout = 0; var gTimeoutHook = null; var gRemote = false; var gIgnoreWindowSize = false; +var gShuffle = false; var gTotalChunks = 0; var gThisChunk = 0; var gContainingWindow = null; @@ -109,6 +113,9 @@ var gUnexpectedCrashDumpFiles = { }; var gCrashDumpDir; var gFailedNoPaint = false; +// The enabled-state of the test-plugins, stored so they can be reset later +var gTestPluginEnabledStates = null; + const TYPE_REFTEST_EQUAL = '=='; const TYPE_REFTEST_NOTEQUAL = '!='; const TYPE_LOAD = 'load'; // test without a reference (just test that it does @@ -208,6 +215,20 @@ function IDForEventTarget(event) } } +function getTestPlugin(aName) { + var ph = CC["@mozilla.org/plugin/host;1"].getService(CI.nsIPluginHost); + var tags = ph.getPluginTags(); + + // Find the test plugin + for (var i = 0; i < tags.length; i++) { + if (tags[i].name == aName) + return tags[i]; + } + + LogWarning("Failed to find the test-plugin."); + return null; +} + this.OnRefTestLoad = function OnRefTestLoad(win) { gCrashDumpDir = CC[NS_DIRECTORY_SERVICE_CONTRACTID] @@ -222,7 +243,7 @@ this.OnRefTestLoad = function OnRefTestLoad(win) var prefs = Components.classes["@mozilla.org/preferences-service;1"]. getService(Components.interfaces.nsIPrefBranch); try { - gBrowserIsRemote = prefs.getBoolPref("browser.tabs.remote"); + gBrowserIsRemote = prefs.getBoolPref("browser.tabs.remote.autostart"); } catch (e) { gBrowserIsRemote = false; } @@ -243,19 +264,21 @@ this.OnRefTestLoad = function OnRefTestLoad(win) if (gBrowserIsIframe) { gBrowser = gContainingWindow.document.createElementNS(XHTML_NS, "iframe"); gBrowser.setAttribute("mozbrowser", ""); + gBrowser.setAttribute("mozapp", prefs.getCharPref("b2g.system_manifest_url")); } else { gBrowser = gContainingWindow.document.createElementNS(XUL_NS, "xul:browser"); } gBrowser.setAttribute("id", "browser"); gBrowser.setAttribute("type", "content-primary"); gBrowser.setAttribute("remote", gBrowserIsRemote ? "true" : "false"); + gBrowser.setAttribute("mozasyncpanzoom", "true"); // Make sure the browser element is exactly 800x1000, no matter // what size our window is - gBrowser.setAttribute("style", "min-width: 800px; min-height: 1000px; max-width: 800px; max-height: 1000px"); + gBrowser.setAttribute("style", "padding: 0px; margin: 0px; border:none; min-width: 800px; min-height: 1000px; max-width: 800px; max-height: 1000px"); -#if BOOTSTRAP -#if REFTEST_B2G - var doc = gContainingWindow.document.getElementsByTagName("window")[0]; +#ifdef BOOTSTRAP +#ifdef REFTEST_B2G + var doc = gContainingWindow.document.getElementsByTagName("html")[0]; #else var doc = gContainingWindow.document.getElementById('main-window'); #endif @@ -267,6 +290,17 @@ this.OnRefTestLoad = function OnRefTestLoad(win) document.getElementById("reftest-window").appendChild(gBrowser); #endif + // reftests should have the test plugins enabled, not click-to-play + let plugin1 = getTestPlugin("Test Plug-in"); + let plugin2 = getTestPlugin("Second Test Plug-in"); + if (plugin1 && plugin2) { + gTestPluginEnabledStates = [plugin1.enabledState, plugin2.enabledState]; + plugin1.enabledState = CI.nsIPluginTag.STATE_ENABLED; + plugin2.enabledState = CI.nsIPluginTag.STATE_ENABLED; + } else { + LogWarning("Could not get test plugin tags."); + } + gBrowserMessageManager = gBrowser.QueryInterface(CI.nsIFrameLoaderOwner) .frameLoader.messageManager; // The content script waits for the initial onload, then notifies @@ -304,8 +338,8 @@ function InitAndStartRefTests() var mfl = FileUtils.openFileOutputStream(f, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE); // Set to mirror to stdout as well as the file gDumpLog = function (msg) { -#if BOOTSTRAP -#if REFTEST_B2G +#ifdef BOOTSTRAP +#ifdef REFTEST_B2G dump(msg); #else //NOTE: on android-xul, we have a libc crash if we do a dump with %7s in the string @@ -365,8 +399,7 @@ function InitAndStartRefTests() if (gRemote) { gServer = null; } else { - gServer = CC["@mozilla.org/server/jshttp;1"]. - createInstance(CI.nsIHttpServer); + gServer = new HttpServer(); } try { if (gServer) @@ -378,8 +411,10 @@ function InitAndStartRefTests() DoneTests(); } - // Focus the content browser - gBrowser.focus(); + // Focus the content browser. + if (gFocusFilterMode != FOCUS_FILTER_NON_NEEDS_FOCUS_TESTS) { + gBrowser.focus(); + } StartTests(); } @@ -391,6 +426,17 @@ function StartHTTPServer() gHttpServerPort = gServer.identity.primaryPort; } +// Perform a Fisher-Yates shuffle of the array. +function Shuffle(array) +{ + for (var i = array.length - 1; i > 0; i--) { + var j = Math.floor(Math.random() * (i + 1)); + var temp = array[i]; + array[i] = array[j]; + array[j] = temp; + } +} + function StartTests() { var uri; @@ -410,6 +456,12 @@ function StartTests() } try { + gShuffle = prefs.getBoolPref("reftest.shuffle"); + } catch (e) { + gShuffle = false; + } + + try { gRunSlowTests = prefs.getIntPref("reftest.skipslowtests"); } catch(e) { gRunSlowTests = false; @@ -443,6 +495,11 @@ function StartTests() DoneTests(); } #endif + + if (gShuffle) { + gNoCanvasCache = true; + } + try { ReadTopManifest(uri); BuildUseCounts(); @@ -481,6 +538,11 @@ function StartTests() gDumpLog("REFTEST INFO | Running chunk " + gThisChunk + " out of " + gTotalChunks + " chunks. "); gDumpLog("tests " + (start+1) + "-" + end + "/" + gURLs.length + "\n"); } + + if (gShuffle) { + Shuffle(gURLs); + } + gTotalTests = gURLs.length; if (!gTotalTests) @@ -498,6 +560,14 @@ function StartTests() function OnRefTestUnload() { + let plugin1 = getTestPlugin("Test Plug-in"); + let plugin2 = getTestPlugin("Second Test Plug-in"); + if (plugin1 && plugin2) { + plugin1.enabledState = gTestPluginEnabledStates[0]; + plugin2.enabledState = gTestPluginEnabledStates[1]; + } else { + LogWarning("Failed to get test plugin tags."); + } } // Read all available data from an input stream and return it @@ -521,16 +591,17 @@ function getStreamContent(inputStream) function BuildConditionSandbox(aURL) { var sandbox = new Components.utils.Sandbox(aURL.spec); var xr = CC[NS_XREAPPINFO_CONTRACTID].getService(CI.nsIXULRuntime); + var appInfo = CC[NS_XREAPPINFO_CONTRACTID].getService(CI.nsIXULAppInfo); sandbox.isDebugBuild = gDebug.isDebugBuild; - sandbox.xulRuntime = {widgetToolkit: xr.widgetToolkit, OS: xr.OS, __exposedProps__: { widgetToolkit: "r", OS: "r", XPCOMABI: "r", shell: "r" } }; // xr.XPCOMABI throws exception for configurations without full ABI // support (mobile builds on ARM) + var XPCOMABI = ""; try { - sandbox.xulRuntime.XPCOMABI = xr.XPCOMABI; - } catch(e) { - sandbox.xulRuntime.XPCOMABI = ""; - } + XPCOMABI = xr.XPCOMABI; + } catch(e) {} + + sandbox.xulRuntime = CU.cloneInto({widgetToolkit: xr.widgetToolkit, OS: xr.OS, XPCOMABI: XPCOMABI}, sandbox); var testRect = gBrowser.getBoundingClientRect(); sandbox.smallScreen = false; @@ -538,13 +609,6 @@ function BuildConditionSandbox(aURL) { sandbox.smallScreen = true; } -#if REFTEST_B2G - // XXX nsIGfxInfo isn't available in B2G - sandbox.d2d = false; - sandbox.azureQuartz = false; - sandbox.azureSkia = false; - sandbox.contentSameGfxBackendAsCanvas = false; -#else var gfxInfo = (NS_GFXINFO_CONTRACTID in CC) && CC[NS_GFXINFO_CONTRACTID].getService(CI.nsIGfxInfo); try { sandbox.d2d = gfxInfo.D2DEnabled; @@ -554,10 +618,11 @@ function BuildConditionSandbox(aURL) { var info = gfxInfo.getInfo(); sandbox.azureQuartz = info.AzureCanvasBackend == "quartz"; sandbox.azureSkia = info.AzureCanvasBackend == "skia"; + sandbox.skiaContent = info.AzureContentBackend == "skia"; + sandbox.azureSkiaGL = info.AzureSkiaAccelerated; // FIXME: assumes GL right now // true if we are using the same Azure backend for rendering canvas and content sandbox.contentSameGfxBackendAsCanvas = info.AzureContentBackend == info.AzureCanvasBackend || (info.AzureContentBackend == "none" && info.AzureCanvasBackend == "cairo"); -#endif sandbox.layersGPUAccelerated = gWindowUtils.layerManagerType != "Basic"; @@ -568,53 +633,56 @@ function BuildConditionSandbox(aURL) { // Shortcuts for widget toolkits. sandbox.B2G = xr.widgetToolkit == "gonk"; + sandbox.B2GDT = appInfo.name.toLowerCase() == "b2g" && !sandbox.B2G; sandbox.Android = xr.OS == "Android" && !sandbox.B2G; sandbox.cocoaWidget = xr.widgetToolkit == "cocoa"; sandbox.gtk2Widget = xr.widgetToolkit == "gtk2"; sandbox.qtWidget = xr.widgetToolkit == "qt"; sandbox.winWidget = xr.widgetToolkit == "windows"; + if (sandbox.Android) { + var sysInfo = CC["@mozilla.org/system-info;1"].getService(CI.nsIPropertyBag2); + + // This is currently used to distinguish Android 4.0.3 (SDK version 15) + // and later from Android 2.x + sandbox.AndroidVersion = sysInfo.getPropertyAsInt32("version"); + } + #if MOZ_ASAN sandbox.AddressSanitizer = true; #else sandbox.AddressSanitizer = false; #endif +#if MOZ_WEBRTC + sandbox.webrtc = true; +#else + sandbox.webrtc = false; +#endif + var hh = CC[NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX + "http"]. getService(CI.nsIHttpProtocolHandler); - sandbox.http = { __exposedProps__: {} }; - for each (var prop in [ "userAgent", "appName", "appVersion", - "vendor", "vendorSub", - "product", "productSub", - "platform", "oscpu", "language", "misc" ]) { - sandbox.http[prop] = hh[prop]; - sandbox.http.__exposedProps__[prop] = "r"; - } - - // Set OSX to the Mac OS X version for Mac, and 0 otherwise. - var osxmatch = /Mac OS X (\d+.\d+)$/.exec(hh.oscpu); - sandbox.OSX = osxmatch ? parseFloat(osxmatch[1]) : 0; + var httpProps = ["userAgent", "appName", "appVersion", "vendor", + "vendorSub", "product", "productSub", "platform", + "oscpu", "language", "misc"]; + sandbox.http = new sandbox.Object(); + httpProps.forEach((x) => sandbox.http[x] = hh[x]); + + // Set OSX to be the Mac OS X version, as an integer, or undefined + // for other platforms. The integer is formed by 100 times the + // major version plus the minor version, so 1006 for 10.6, 1010 for + // 10.10, etc. + var osxmatch = /Mac OS X (\d+).(\d+)$/.exec(hh.oscpu); + sandbox.OSX = osxmatch ? parseInt(osxmatch[1]) * 100 + parseInt(osxmatch[2]) : undefined; // see if we have the test plugin available, // and set a sandox prop accordingly - sandbox.haveTestPlugin = false; - var navigator = gContainingWindow.navigator; - for (var i = 0; i < navigator.mimeTypes.length; i++) { - if (navigator.mimeTypes[i].type == "application/x-test" && - navigator.mimeTypes[i].enabledPlugin != null && - navigator.mimeTypes[i].enabledPlugin.name == "Test Plug-in") { - sandbox.haveTestPlugin = true; - break; - } - } + var testPlugin = navigator.plugins["Test Plug-in"]; + sandbox.haveTestPlugin = !!testPlugin; // Set a flag on sandbox if the windows default theme is active - var box = gContainingWindow.document.createElement("box"); - box.setAttribute("id", "_box_windowsDefaultTheme"); - gContainingWindow.document.documentElement.appendChild(box); - sandbox.windowsDefaultTheme = (gContainingWindow.getComputedStyle(box, null).display == "none"); - gContainingWindow.document.documentElement.removeChild(box); + sandbox.windowsDefaultTheme = gContainingWindow.matchMedia("(-moz-windows-default-theme)").matches; var prefs = CC["@mozilla.org/preferences-service;1"]. getService(CI.nsIPrefBranch); @@ -624,21 +692,12 @@ function BuildConditionSandbox(aURL) { sandbox.nativeThemePref = true; } - sandbox.prefs = { - __exposedProps__: { - getBoolPref: 'r', - getIntPref: 'r', - }, - _prefs: prefs, - getBoolPref: function(p) { return this._prefs.getBoolPref(p); }, - getIntPref: function(p) { return this._prefs.getIntPref(p); } - } + sandbox.prefs = CU.cloneInto({ + getBoolPref: function(p) { return prefs.getBoolPref(p); }, + getIntPref: function(p) { return prefs.getIntPref(p); } + }, sandbox, { cloneFunctions: true }); sandbox.testPluginIsOOP = function () { - try { - netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); - } catch (ex) {} - var prefservice = Components.classes["@mozilla.org/preferences-service;1"] .getService(CI.nsIPrefBranch); @@ -673,13 +732,15 @@ function BuildConditionSandbox(aURL) { // crash the content process sandbox.browserIsRemote = gBrowserIsRemote; - // Distinguish the Fennecs: - sandbox.xulFennec = sandbox.Android && sandbox.browserIsRemote; - sandbox.nativeFennec = sandbox.Android && !sandbox.browserIsRemote; + try { + sandbox.asyncPanZoom = prefs.getBoolPref("layers.async-pan-zoom.enabled"); + } catch (e) { + sandbox.asyncPanZoom = false; + } if (!gDumpedConditionSandbox) { dump("REFTEST INFO | Dumping JSON representation of sandbox \n"); - dump("REFTEST INFO | " + JSON.stringify(sandbox) + " \n"); + dump("REFTEST INFO | " + JSON.stringify(CU.waiveXrays(sandbox)) + " \n"); gDumpedConditionSandbox = true; } return sandbox; @@ -741,7 +802,12 @@ function ReadManifest(aURL, inherited_status) .getService(CI.nsIScriptSecurityManager); var listURL = aURL; - var channel = gIOService.newChannelFromURI(aURL); + var channel = gIOService.newChannelFromURI2(aURL, + null, // aLoadingNode + Services.scriptSecurityManager.getSystemPrincipal(), + null, // aTriggeringPrincipal + CI.nsILoadInfo.SEC_NORMAL, + CI.nsIContentPolicy.TYPE_OTHER); var inputStream = channel.open(); if (channel instanceof Components.interfaces.nsIHttpChannel && channel.responseStatus != 200) { @@ -839,7 +905,7 @@ function ReadManifest(aURL, inherited_status) } else if ((m = item.match(/^require-or\((.*?)\)$/))) { var args = m[1].split(/,/); if (args.length != 2) { - throw "Error 7 in manifest file " + aURL.spec + " line " + lineNo + ": wrong number of args to require-or"; + throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": wrong number of args to require-or"; } var [precondition_str, fallback_action] = args; var preconditions = precondition_str.split(/&&/); @@ -885,7 +951,7 @@ function ReadManifest(aURL, inherited_status) fuzzy_max_pixels = Number(m[3]); } } else { - throw "Error 1 in manifest file " + aURL.spec + " line " + lineNo; + throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": unexpected item " + item; } if (cond) { @@ -936,17 +1002,20 @@ function ReadManifest(aURL, inherited_status) var principal = secMan.getSimpleCodebasePrincipal(aURL); if (items[0] == "include") { - if (items.length != 2 || runHttp) - throw "Error 2 in manifest file " + aURL.spec + " line " + lineNo; + if (items.length != 2) + throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": incorrect number of arguments to include"; + if (runHttp) + throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": use of include with http"; var incURI = gIOService.newURI(items[1], null, listURL); secMan.checkLoadURIWithPrincipal(principal, incURI, CI.nsIScriptSecurityManager.DISALLOW_SCRIPT); ReadManifest(incURI, expected_status); } else if (items[0] == TYPE_LOAD) { - if (items.length != 2 || - (expected_status != EXPECTED_PASS && - expected_status != EXPECTED_DEATH)) - throw "Error 3 in manifest file " + aURL.spec + " line " + lineNo; + if (items.length != 2) + throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": incorrect number of arguments to load"; + if (expected_status != EXPECTED_PASS && + expected_status != EXPECTED_DEATH) + throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": incorrect known failure type for load test"; var [testURI] = runHttp ? ServeFiles(principal, httpDepth, listURL, [items[1]]) @@ -972,7 +1041,7 @@ function ReadManifest(aURL, inherited_status) url2: null }); } else if (items[0] == TYPE_SCRIPT) { if (items.length != 2) - throw "Error 4 in manifest file " + aURL.spec + " line " + lineNo; + throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": incorrect number of arguments to script"; var [testURI] = runHttp ? ServeFiles(principal, httpDepth, listURL, [items[1]]) @@ -998,7 +1067,7 @@ function ReadManifest(aURL, inherited_status) url2: null }); } else if (items[0] == TYPE_REFTEST_EQUAL || items[0] == TYPE_REFTEST_NOTEQUAL) { if (items.length != 3) - throw "Error 5 in manifest file " + aURL.spec + " line " + lineNo; + throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": incorrect number of arguments to " + items[0]; var [testURI, refURI] = runHttp ? ServeFiles(principal, httpDepth, listURL, [items[1], items[2]]) @@ -1026,7 +1095,7 @@ function ReadManifest(aURL, inherited_status) url1: testURI, url2: refURI }); } else { - throw "Error 6 in manifest file " + aURL.spec + " line " + lineNo; + throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": unknown test type " + items[0]; } } } @@ -1117,6 +1186,15 @@ function Focus() return true; } +function Blur() +{ + // On non-remote reftests, this will transfer focus to the dummy window + // we created to hold focus for non-needs-focus tests. Buggy tests + // (ones which require focus but don't request needs-focus) will then + // fail. + gContainingWindow.blur(); +} + function StartCurrentTest() { gTestLog = []; @@ -1149,6 +1227,9 @@ function StartCurrentTest() } else { gDumpLog("REFTEST TEST-START | " + gURLs[0].prettyPath + "\n"); + if (!gURLs[0].needsFocus) { + Blur(); + } var currentTest = gTotalTests - gURLs.length; gContainingWindow.document.title = "reftest: " + currentTest + " / " + gTotalTests + " (" + Math.floor(100 * (currentTest / gTotalTests)) + "%)"; @@ -1399,6 +1480,12 @@ function UpdateCurrentCanvasForInvalidation(rects) var right = Math.ceil(r.right); var bottom = Math.ceil(r.bottom); + // Clamp the values to the canvas size + left = Math.max(0, Math.min(left, gCurrentCanvas.width)); + top = Math.max(0, Math.min(top, gCurrentCanvas.height)); + right = Math.max(0, Math.min(right, gCurrentCanvas.width)); + bottom = Math.max(0, Math.min(bottom, gCurrentCanvas.height)); + ctx.save(); ctx.translate(left, top); DoDrawWindow(ctx, left, top, right - left, bottom - top); @@ -1600,7 +1687,7 @@ function RecordResult(testRunTime, errorMsg, scriptResults) result += "REFTEST IMAGE 2 (REFERENCE): " + gCanvas2.toDataURL() + "\n"; } else { result += "\n"; - gDumpLog("REFTEST IMAGE: " + gCanvas1.toDataURL() + "\n"); + result += "REFTEST IMAGE: " + gCanvas1.toDataURL() + "\n"; } } else { result += "\n"; @@ -1609,7 +1696,7 @@ function RecordResult(testRunTime, errorMsg, scriptResults) gDumpLog(result); } - if (!test_passed && expected == EXPECTED_PASS) { + if ((!test_passed && expected == EXPECTED_PASS) || (test_passed && expected == EXPECTED_FAIL)) { FlushTestLog(); } @@ -1631,6 +1718,11 @@ function RecordResult(testRunTime, errorMsg, scriptResults) function LoadFailed(why) { ++gTestResults.FailedLoad; + // Once bug 896840 is fixed, this can go away, but for now it will give log + // output that is TBPL starable for bug 789751 and bug 720452. + if (!why) { + gDumpLog("REFTEST TEST-UNEXPECTED-FAIL | load failed with unknown reason\n"); + } gDumpLog("REFTEST TEST-UNEXPECTED-FAIL | " + gURLs[0]["url" + gState].spec + " | load failed: " + why + "\n"); FlushTestLog(); @@ -1694,7 +1786,6 @@ function FinishTestItem() gDumpLog("REFTEST INFO | Loading a blank page\n"); // After clearing, content will notify us of the assertion count // and tests will continue. - SetAsyncScroll(false); SendClear(); gFailedNoPaint = false; } @@ -1824,19 +1915,8 @@ function RegisterMessageListenersAndLoadContentScript() "reftest:ExpectProcessCrash", function (m) { RecvExpectProcessCrash(); } ); - gBrowserMessageManager.addMessageListener( - "reftest:EnableAsyncScroll", - function (m) { SetAsyncScroll(true); } - ); - gBrowserMessageManager.loadFrameScript("chrome://reftest/content/reftest-content.js", true); -} - -function SetAsyncScroll(enabled) -{ - gBrowser.QueryInterface(CI.nsIFrameLoaderOwner).frameLoader.renderMode = - enabled ? CI.nsIFrameLoader.RENDER_MODE_ASYNC_SCROLL : - CI.nsIFrameLoader.RENDER_MODE_DEFAULT; + gBrowserMessageManager.loadFrameScript("chrome://reftest/content/reftest-content.js", true, true); } function RecvAssertionCount(count) diff --git a/layout/tools/reftest/reftest.xul b/layout/tools/reftest/reftest.xul index 50580bfed..dfbef5337 100644 --- a/layout/tools/reftest/reftest.xul +++ b/layout/tools/reftest/reftest.xul @@ -2,14 +2,6 @@ <!-- This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -<?xml-stylesheet type="text/css" href="data:text/css, - -%23_box_windowsDefaultTheme:-moz-system-metric(windows-default-theme) { - display: none; -} - -" ?> - <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="reftest-window" hidechrome="true" diff --git a/layout/tools/reftest/reftest/__init__.py b/layout/tools/reftest/reftest/__init__.py new file mode 100644 index 000000000..5044b16a1 --- /dev/null +++ b/layout/tools/reftest/reftest/__init__.py @@ -0,0 +1,125 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from __future__ import unicode_literals + +import os +import re + +RE_COMMENT = re.compile(r'\s+#') +RE_HTTP = re.compile(r'HTTP\((\.\.(\/\.\.)*)\)') +RE_PROTOCOL = re.compile(r'^\w+:') +FAILURE_TYPES = ( + 'fails', + 'fails-if', + 'needs-focus', + 'random', + 'random-if', + 'silentfail', + 'silentfail-if', + 'skip', + 'skip-if', + 'slow', + 'slow-if', + 'fuzzy', + 'fuzzy-if', + 'require-or', + 'asserts', + 'asserts-if', +) +PREF_ITEMS = ( + 'pref', + 'test-pref', + 'ref-pref', +) + +class ReftestManifest(object): + """Represents a parsed reftest manifest. + + We currently only capture file information because that is the only thing + tools require. + """ + def __init__(self): + self.path = None + self.dirs = set() + self.files = set() + self.manifests = set() + + def load(self, path): + """Parse a reftest manifest file.""" + normalized = os.path.normpath(os.path.abspath(path)) + self.manifests.add(normalized) + if not self.path: + self.path = normalized + + mdir = os.path.dirname(normalized) + self.dirs.add(mdir) + + with open(path, 'r') as fh: + urlprefix = '' + for line in fh: + line = line.decode('utf-8') + + # Entire line is a comment. + if line.startswith('#'): + continue + + # Comments can begin mid line. Strip them. + m = RE_COMMENT.search(line) + if m: + line = line[:m.start()] + + line = line.strip() + if not line: + continue + + items = line.split() + tests = [] + + for i in range(len(items)): + item = items[i] + + if item.startswith(FAILURE_TYPES): + continue + if item.startswith(PREF_ITEMS): + continue + if item == 'HTTP': + continue + + m = RE_HTTP.match(item) + if m: + # Need to package the referenced directory. + self.dirs.add(os.path.normpath(os.path.join( + mdir, m.group(1)))) + continue + + if item == 'url-prefix': + urlprefix = items[i+1] + break + + if item == 'default-preferences': + break + + if item == 'include': + self.load(os.path.join(mdir, items[i+1])) + break + + if item == 'load' or item == 'script': + tests.append(items[i+1]) + break + + if item == '==' or item == '!=': + tests.extend(items[i+1:i+3]) + break + + for f in tests: + # We can't package about: or data: URIs. + # Discarding data isn't correct for a parser. But retaining + # all data isn't currently a requirement. + if RE_PROTOCOL.match(f): + continue + + test = os.path.normpath(os.path.join(mdir, urlprefix + f)) + self.files.add(test) + self.dirs.add(os.path.dirname(test)) diff --git a/layout/tools/reftest/remotereftest.py b/layout/tools/reftest/remotereftest.py index eae0afd89..4b237507e 100644 --- a/layout/tools/reftest/remotereftest.py +++ b/layout/tools/reftest/remotereftest.py @@ -16,11 +16,13 @@ from runreftest import ReftestOptions from automation import Automation import devicemanager import droid +import moznetwork from remoteautomation import RemoteAutomation, fennecLogcatFilters class RemoteOptions(ReftestOptions): def __init__(self, automation): - ReftestOptions.__init__(self, automation) + ReftestOptions.__init__(self) + self.automation = automation defaults = {} defaults["logFile"] = "reftest.log" @@ -28,6 +30,7 @@ class RemoteOptions(ReftestOptions): defaults["app"] = "" defaults["xrePath"] = "" defaults["utilityPath"] = "" + defaults["runTestsInParallel"] = False self.add_option("--remote-app-path", action="store", type = "string", dest = "remoteAppPath", @@ -39,6 +42,11 @@ class RemoteOptions(ReftestOptions): help = "ip address of remote device to test") defaults["deviceIP"] = None + self.add_option("--deviceSerial", action="store", + type = "string", dest = "deviceSerial", + help = "adb serial number of remote device to test") + defaults["deviceSerial"] = None + self.add_option("--devicePort", action="store", type = "string", dest = "devicePort", help = "port of remote device to test") @@ -52,7 +60,7 @@ class RemoteOptions(ReftestOptions): self.add_option("--remote-webserver", action="store", type = "string", dest = "remoteWebServer", help = "IP Address of the webserver hosting the reftest content") - defaults["remoteWebServer"] = automation.getLanIp() + defaults["remoteWebServer"] = moznetwork.get_ip() self.add_option("--http-port", action = "store", type = "string", dest = "httpPort", @@ -69,10 +77,6 @@ class RemoteOptions(ReftestOptions): help = "Name of log file on the device relative to device root. PLEASE USE ONLY A FILENAME.") defaults["remoteLogFile"] = None - self.add_option("--enable-privilege", action="store_true", dest = "enablePrivilege", - help = "add webserver and port to the user.js file for remote script access and universalXPConnect") - defaults["enablePrivilege"] = False - self.add_option("--pidfile", action = "store", type = "string", dest = "pidFile", help = "name of the pidfile to generate") @@ -102,9 +106,12 @@ class RemoteOptions(ReftestOptions): self.set_defaults(**defaults) def verifyRemoteOptions(self, options): + if options.runTestsInParallel: + self.error("Cannot run parallel tests here") + # Ensure our defaults are set properly for everything we can infer if not options.remoteTestRoot: - options.remoteTestRoot = self._automation._devicemanager.getDeviceRoot() + '/reftest' + options.remoteTestRoot = self.automation._devicemanager.deviceRoot + '/reftest' options.remoteProfile = options.remoteTestRoot + "/profile" # Verify that our remotewebserver is set properly @@ -162,8 +169,8 @@ class RemoteOptions(ReftestOptions): options.httpdPath = os.path.join(options.utilityPath, "components") # TODO: Copied from main, but I think these are no longer used in a post xulrunner world - #options.xrePath = options.remoteTestRoot + self._automation._product + '/xulrunner' - #options.utilityPath = options.testRoot + self._automation._product + '/bin' + #options.xrePath = options.remoteTestRoot + self.automation._product + '/xulrunner' + #options.utilityPath = options.testRoot + self.automation._product + '/bin' return options class ReftestServer: @@ -174,7 +181,7 @@ class ReftestServer: it's own class and use it in both remote and non-remote testing. """ def __init__(self, automation, options, scriptDir): - self._automation = automation + self.automation = automation self._utilityPath = options.utilityPath self._xrePath = options.xrePath self._profileDir = options.serverProfilePath @@ -188,9 +195,9 @@ class ReftestServer: def start(self): "Run the Refest server, returning the process ID of the server." - env = self._automation.environment(xrePath = self._xrePath) + env = self.automation.environment(xrePath = self._xrePath) env["XPCOM_DEBUG_BREAK"] = "warn" - if self._automation.IS_WIN32: + if self.automation.IS_WIN32: env["PATH"] = env["PATH"] + ";" + self._xrePath args = ["-g", self._xrePath, @@ -201,21 +208,21 @@ class ReftestServer: "-f", os.path.join(self.scriptDir, "server.js")] xpcshell = os.path.join(self._utilityPath, - "xpcshell" + self._automation.BIN_SUFFIX) + "xpcshell" + self.automation.BIN_SUFFIX) if not os.access(xpcshell, os.F_OK): raise Exception('xpcshell not found at %s' % xpcshell) - if self._automation.elf_arm(xpcshell): + if self.automation.elf_arm(xpcshell): raise Exception('xpcshell at %s is an ARM binary; please use ' 'the --utility-path argument to specify the path ' 'to a desktop version.' % xpcshell) - self._process = self._automation.Process([xpcshell] + args, env = env) + self._process = self.automation.Process([xpcshell] + args, env = env) pid = self._process.pid if pid < 0: print "TEST-UNEXPECTED-FAIL | remotereftests.py | Error starting server." return 2 - self._automation.log.info("INFO | remotereftests.py | Server pid: %d", pid) + self.automation.log.info("INFO | remotereftests.py | Server pid: %d", pid) if (self.pidFile != ""): f = open(self.pidFile + ".xpcshell.pid", 'w') @@ -254,7 +261,8 @@ class RemoteReftest(RefTest): remoteApp = '' def __init__(self, automation, devicemanager, options, scriptDir): - RefTest.__init__(self, automation) + RefTest.__init__(self) + self.automation = automation self._devicemanager = devicemanager self.scriptDir = scriptDir self.remoteApp = options.app @@ -268,6 +276,7 @@ class RemoteReftest(RefTest): else: self.SERVER_STARTUP_TIMEOUT = 90 self.automation.deleteANRs() + self.automation.deleteTombstones() def findPath(self, paths, filename = None): for path in paths: @@ -332,55 +341,51 @@ class RemoteReftest(RefTest): def stopWebServer(self, options): self.server.stop() - def createReftestProfile(self, options, profileDir, reftestlist): - RefTest.createReftestProfile(self, options, profileDir, reftestlist, server=options.remoteWebServer) - - # Turn off the locale picker screen - fhandle = open(os.path.join(profileDir, "user.js"), 'a') - fhandle.write(""" -user_pref("browser.firstrun.show.localepicker", false); -user_pref("font.size.inflation.emPerLine", 0); -user_pref("font.size.inflation.minTwips", 0); -user_pref("reftest.remote", true); -// Set a future policy version to avoid the telemetry prompt. -user_pref("toolkit.telemetry.prompted", 999); -user_pref("toolkit.telemetry.notifiedOptOut", 999); -user_pref("reftest.uri", "%s"); -user_pref("datareporting.policy.dataSubmissionPolicyBypassAcceptance", true); - -// Point the url-classifier to the local testing server for fast failures -user_pref("browser.safebrowsing.gethashURL", "http://127.0.0.1:8888/safebrowsing-dummy/gethash"); -user_pref("browser.safebrowsing.keyURL", "http://127.0.0.1:8888/safebrowsing-dummy/newkey"); -user_pref("browser.safebrowsing.updateURL", "http://127.0.0.1:8888/safebrowsing-dummy/update"); -// Point update checks to the local testing server for fast failures -user_pref("extensions.update.url", "http://127.0.0.1:8888/extensions-dummy/updateURL"); -user_pref("extensions.update.background.url", "http://127.0.0.1:8888/extensions-dummy/updateBackgroundURL"); -user_pref("extensions.blocklist.url", "http://127.0.0.1:8888/extensions-dummy/blocklistURL"); -user_pref("extensions.hotfix.url", "http://127.0.0.1:8888/extensions-dummy/hotfixURL"); -// Turn off extension updates so they don't bother tests -user_pref("extensions.update.enabled", false); -// Make sure opening about:addons won't hit the network -user_pref("extensions.webservice.discoverURL", "http://127.0.0.1:8888/extensions-dummy/discoveryURL"); -// Make sure AddonRepository won't hit the network -user_pref("extensions.getAddons.maxResults", 0); -user_pref("extensions.getAddons.get.url", "http://127.0.0.1:8888/extensions-dummy/repositoryGetURL"); -user_pref("extensions.getAddons.getWithPerformance.url", "http://127.0.0.1:8888/extensions-dummy/repositoryGetWithPerformanceURL"); -user_pref("extensions.getAddons.search.browseURL", "http://127.0.0.1:8888/extensions-dummy/repositoryBrowseURL"); -user_pref("extensions.getAddons.search.url", "http://127.0.0.1:8888/extensions-dummy/repositorySearchURL"); -// Make sure that opening the plugins check page won't hit the network -user_pref("plugins.update.url", "http://127.0.0.1:8888/plugins-dummy/updateCheckURL"); - -""" % reftestlist) - - #workaround for jsreftests. - if options.enablePrivilege: - fhandle.write(""" -user_pref("capability.principal.codebase.p2.granted", "UniversalXPConnect"); -user_pref("capability.principal.codebase.p2.id", "http://%s:%s"); -""" % (options.remoteWebServer, options.httpPort)) - - # Close the file - fhandle.close() + def createReftestProfile(self, options, reftestlist): + profile = RefTest.createReftestProfile(self, options, reftestlist, server=options.remoteWebServer) + profileDir = profile.profile + + prefs = {} + prefs["app.update.url.android"] = "" + prefs["browser.firstrun.show.localepicker"] = False + prefs["font.size.inflation.emPerLine"] = 0 + prefs["font.size.inflation.minTwips"] = 0 + prefs["reftest.remote"] = True + # Set a future policy version to avoid the telemetry prompt. + prefs["toolkit.telemetry.prompted"] = 999 + prefs["toolkit.telemetry.notifiedOptOut"] = 999 + prefs["reftest.uri"] = "%s" % reftestlist + prefs["datareporting.policy.dataSubmissionPolicyBypassAcceptance"] = True + + # Point the url-classifier to the local testing server for fast failures + prefs["browser.safebrowsing.gethashURL"] = "http://127.0.0.1:8888/safebrowsing-dummy/gethash" + prefs["browser.safebrowsing.updateURL"] = "http://127.0.0.1:8888/safebrowsing-dummy/update" + # Point update checks to the local testing server for fast failures + prefs["extensions.update.url"] = "http://127.0.0.1:8888/extensions-dummy/updateURL" + prefs["extensions.update.background.url"] = "http://127.0.0.1:8888/extensions-dummy/updateBackgroundURL" + prefs["extensions.blocklist.url"] = "http://127.0.0.1:8888/extensions-dummy/blocklistURL" + prefs["extensions.hotfix.url"] = "http://127.0.0.1:8888/extensions-dummy/hotfixURL" + # Turn off extension updates so they don't bother tests + prefs["extensions.update.enabled"] = False + # Make sure opening about:addons won't hit the network + prefs["extensions.webservice.discoverURL"] = "http://127.0.0.1:8888/extensions-dummy/discoveryURL" + # Make sure AddonRepository won't hit the network + prefs["extensions.getAddons.maxResults"] = 0 + prefs["extensions.getAddons.get.url"] = "http://127.0.0.1:8888/extensions-dummy/repositoryGetURL" + prefs["extensions.getAddons.getWithPerformance.url"] = "http://127.0.0.1:8888/extensions-dummy/repositoryGetWithPerformanceURL" + prefs["extensions.getAddons.search.browseURL"] = "http://127.0.0.1:8888/extensions-dummy/repositoryBrowseURL" + prefs["extensions.getAddons.search.url"] = "http://127.0.0.1:8888/extensions-dummy/repositorySearchURL" + # Make sure that opening the plugins check page won't hit the network + prefs["plugins.update.url"] = "http://127.0.0.1:8888/plugins-dummy/updateCheckURL" + # Make sure the GMPInstallManager won't hit the network + prefs["media.gmp-manager.url.override"] = "http://127.0.0.1:8888/dummy-gmp-manager.xml"; + prefs["layout.css.devPixelsPerPx"] = "1.0" + + # Disable skia-gl: see bug 907351 + prefs["gfx.canvas.azure.accelerated"] = False + + # Set the extra prefs. + profile.set_preferences(prefs) try: self._devicemanager.pushDir(profileDir, options.remoteProfile) @@ -388,8 +393,11 @@ user_pref("capability.principal.codebase.p2.id", "http://%s:%s"); print "Automation Error: Failed to copy profiledir to device" raise - def copyExtraFilesToProfile(self, options, profileDir): - RefTest.copyExtraFilesToProfile(self, options, profileDir) + return profile + + def copyExtraFilesToProfile(self, options, profile): + profileDir = profile.profile + RefTest.copyExtraFilesToProfile(self, options, profile) try: self._devicemanager.pushDir(profileDir, options.remoteProfile) except devicemanager.DMError: @@ -399,6 +407,33 @@ user_pref("capability.principal.codebase.p2.id", "http://%s:%s"); def getManifestPath(self, path): return path + def printDeviceInfo(self, printLogcat=False): + try: + if printLogcat: + logcat = self._devicemanager.getLogcat(filterOutRegexps=fennecLogcatFilters) + print ''.join(logcat) + print "Device info: %s" % self._devicemanager.getInfo() + print "Test root: %s" % self._devicemanager.deviceRoot + except devicemanager.DMError: + print "WARNING: Error getting device information" + + def environment(self, **kwargs): + return self.automation.environment(**kwargs) + + def runApp(self, profile, binary, cmdargs, env, + timeout=None, debuggerInfo=None, + symbolsPath=None, options=None): + status = self.automation.runApp(None, env, + binary, + profile.profile, + cmdargs, + utilityPath=options.utilityPath, + xrePath=options.xrePath, + debuggerInfo=debuggerInfo, + symbolsPath=symbolsPath, + timeout=timeout) + return status + def cleanup(self, profileDir): # Pull results back from device if self.remoteLogFile and \ @@ -422,14 +457,16 @@ def main(args): parser = RemoteOptions(automation) options, args = parser.parse_args() - if (options.deviceIP == None): - print "Error: you must provide a device IP to connect to via the --device option" + if (options.dm_trans == 'sut' and options.deviceIP == None): + print "Error: If --dm_trans = sut, you must provide a device IP to connect to via the --deviceIP option" return 1 try: if (options.dm_trans == "adb"): if (options.deviceIP): dm = droid.DroidADB(options.deviceIP, options.devicePort, deviceRoot=options.remoteTestRoot) + elif (options.deviceSerial): + dm = droid.DroidADB(None, None, deviceSerial=options.deviceSerial, deviceRoot=options.remoteTestRoot) else: dm = droid.DroidADB(None, None, deviceRoot=options.remoteTestRoot) else: @@ -486,7 +523,7 @@ def main(args): if (dm.processExist(procName)): dm.killProcess(procName) - print dm.getInfo() + reftest.printDeviceInfo() #an example manifest name to use on the cli # manifest = "http://" + options.remoteWebServer + "/reftests/layout/reftests/reftest-sanity/reftest.list" @@ -503,12 +540,8 @@ def main(args): retVal = 1 reftest.stopWebServer(options) - try: - logcat = dm.getLogcat(filterOutRegexps=fennecLogcatFilters) - print ''.join(logcat) - print dm.getInfo() - except devicemanager.DMError: - print "WARNING: Error getting device information at end of test" + + reftest.printDeviceInfo(printLogcat=True) return retVal diff --git a/layout/tools/reftest/runreftest.py b/layout/tools/reftest/runreftest.py index c16955a6f..18daa571d 100644 --- a/layout/tools/reftest/runreftest.py +++ b/layout/tools/reftest/runreftest.py @@ -6,21 +6,149 @@ Runs the reftest test harness. """ -import re, sys, shutil, os, os.path +from optparse import OptionParser +from urlparse import urlparse +import collections +import multiprocessing +import os +import re +import shutil +import signal +import subprocess +import sys +import threading + SCRIPT_DIRECTORY = os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0]))) sys.path.insert(0, SCRIPT_DIRECTORY) -from automation import Automation -from automationutils import * -from optparse import OptionParser -from tempfile import mkdtemp +from automationutils import ( + dumpScreen, + environment, + printstatus, + processLeakLog +) +import mozcrash +import mozdebug +import mozinfo +import mozprocess +import mozprofile +import mozrunner + +here = os.path.abspath(os.path.dirname(__file__)) + +try: + from mozbuild.base import MozbuildObject + build_obj = MozbuildObject.from_environment(cwd=here) +except ImportError: + build_obj = None + +# set up logging handler a la automation.py.in for compatability +import logging +log = logging.getLogger() +def resetGlobalLog(): + while log.handlers: + log.removeHandler(log.handlers[0]) + handler = logging.StreamHandler(sys.stdout) + log.setLevel(logging.INFO) + log.addHandler(handler) +resetGlobalLog() + +def categoriesToRegex(categoryList): + return "\\(" + ', '.join(["(?P<%s>\\d+) %s" % c for c in categoryList]) + "\\)" +summaryLines = [('Successful', [('pass', 'pass'), ('loadOnly', 'load only')]), + ('Unexpected', [('fail', 'unexpected fail'), + ('pass', 'unexpected pass'), + ('asserts', 'unexpected asserts'), + ('fixedAsserts', 'unexpected fixed asserts'), + ('failedLoad', 'failed load'), + ('exception', 'exception')]), + ('Known problems', [('knownFail', 'known fail'), + ('knownAsserts', 'known asserts'), + ('random', 'random'), + ('skipped', 'skipped'), + ('slow', 'slow')])] + +# Python's print is not threadsafe. +printLock = threading.Lock() + +class ReftestThread(threading.Thread): + def __init__(self, cmdlineArgs): + threading.Thread.__init__(self) + self.cmdlineArgs = cmdlineArgs + self.summaryMatches = {} + self.retcode = -1 + for text, _ in summaryLines: + self.summaryMatches[text] = None + + def run(self): + with printLock: + print "Starting thread with", self.cmdlineArgs + sys.stdout.flush() + process = subprocess.Popen(self.cmdlineArgs, stdout=subprocess.PIPE) + for chunk in self.chunkForMergedOutput(process.stdout): + with printLock: + print chunk, + sys.stdout.flush() + self.retcode = process.wait() + + def chunkForMergedOutput(self, logsource): + """Gather lines together that should be printed as one atomic unit. + Individual test results--anything between 'REFTEST TEST-START' and + 'REFTEST TEST-END' lines--are an atomic unit. Lines with data from + summaries are parsed and the data stored for later aggregation. + Other lines are considered their own atomic units and are permitted + to intermix freely.""" + testStartRegex = re.compile("^REFTEST TEST-START") + testEndRegex = re.compile("^REFTEST TEST-END") + summaryHeadRegex = re.compile("^REFTEST INFO \\| Result summary:") + summaryRegexFormatString = "^REFTEST INFO \\| (?P<message>{text}): (?P<total>\\d+) {regex}" + summaryRegexStrings = [summaryRegexFormatString.format(text=text, + regex=categoriesToRegex(categories)) + for (text, categories) in summaryLines] + summaryRegexes = [re.compile(regex) for regex in summaryRegexStrings] + + for line in logsource: + if testStartRegex.search(line) is not None: + chunkedLines = [line] + for lineToBeChunked in logsource: + chunkedLines.append(lineToBeChunked) + if testEndRegex.search(lineToBeChunked) is not None: + break + yield ''.join(chunkedLines) + continue -class RefTest(object): + haveSuppressedSummaryLine = False + for regex in summaryRegexes: + match = regex.search(line) + if match is not None: + self.summaryMatches[match.group('message')] = match + haveSuppressedSummaryLine = True + break + if haveSuppressedSummaryLine: + continue + + if summaryHeadRegex.search(line) is None: + yield line +class RefTest(object): oldcwd = os.getcwd() - def __init__(self, automation): - self.automation = automation + def __init__(self): + self.update_mozinfo() + self.lastTestSeen = 'reftest' + self.haveDumpedScreen = False + + def update_mozinfo(self): + """walk up directories to find mozinfo.json update the info""" + # TODO: This should go in a more generic place, e.g. mozinfo + + path = SCRIPT_DIRECTORY + dirs = set() + while path != os.path.expanduser('~'): + if path in dirs: + break + dirs.add(path) + path = os.path.split(path)[0] def getFullPath(self, path): "Get an absolute path relative to self.oldcwd." @@ -42,68 +170,105 @@ class RefTest(object): def makeJSString(self, s): return '"%s"' % re.sub(r'([\\"])', r'\\\1', s) - def createReftestProfile(self, options, profileDir, manifest, server='localhost'): + def createReftestProfile(self, options, manifest, server='localhost', + special_powers=True, profile_to_clone=None): """ Sets up a profile for reftest. 'manifest' is the path to the reftest.list file we want to test with. This is used in - the remote subclass in remotereftest.py so we can write it to a preference for the + the remote subclass in remotereftest.py so we can write it to a preference for the bootstrap extension. """ - self.automation.setupPermissionsDatabase(profileDir, - {'allowXULXBL': [(server, True), ('<file>', True)]}) + locations = mozprofile.permissions.ServerLocations() + locations.add_host(server, port=0) + locations.add_host('<file>', port=0) # Set preferences for communication between our command line arguments # and the reftest harness. Preferences that are required for reftest # to work should instead be set in reftest-cmdline.js . - prefsFile = open(os.path.join(profileDir, "user.js"), "a") - prefsFile.write('user_pref("reftest.timeout", %d);\n' % (options.timeout * 1000)) - - if options.totalChunks != None: - prefsFile.write('user_pref("reftest.totalChunks", %d);\n' % options.totalChunks) - if options.thisChunk != None: - prefsFile.write('user_pref("reftest.thisChunk", %d);\n' % options.thisChunk) - if options.logFile != None: - prefsFile.write('user_pref("reftest.logFile", "%s");\n' % options.logFile) - if options.ignoreWindowSize != False: - prefsFile.write('user_pref("reftest.ignoreWindowSize", true);\n') - if options.filter != None: - prefsFile.write('user_pref("reftest.filter", %s);\n' % self.makeJSString(options.filter)) - prefsFile.write('user_pref("reftest.focusFilterMode", %s);\n' % self.makeJSString(options.focusFilterMode)) + prefs = {} + prefs['reftest.timeout'] = options.timeout * 1000 + if options.totalChunks: + prefs['reftest.totalChunks'] = options.totalChunks + if options.thisChunk: + prefs['reftest.thisChunk'] = options.thisChunk + if options.logFile: + prefs['reftest.logFile'] = options.logFile + if options.ignoreWindowSize: + prefs['reftest.ignoreWindowSize'] = True + if options.filter: + prefs['reftest.filter'] = options.filter + if options.shuffle: + prefs['reftest.shuffle'] = True + prefs['reftest.focusFilterMode'] = options.focusFilterMode # Ensure that telemetry is disabled, so we don't connect to the telemetry # server in the middle of the tests. - prefsFile.write('user_pref("toolkit.telemetry.enabled", false);\n') + prefs['toolkit.telemetry.enabled'] = False # Likewise for safebrowsing. - prefsFile.write('user_pref("browser.safebrowsing.enabled", false);\n') - prefsFile.write('user_pref("browser.safebrowsing.malware.enabled", false);\n') + prefs['browser.safebrowsing.enabled'] = False + prefs['browser.safebrowsing.malware.enabled'] = False # And for snippets. - prefsFile.write('user_pref("browser.snippets.enabled", false);\n') - prefsFile.write('user_pref("browser.snippets.syncPromo.enabled", false);\n') + prefs['browser.snippets.enabled'] = False + prefs['browser.snippets.syncPromo.enabled'] = False + prefs['browser.snippets.firstrunHomepage.enabled'] = False + # And for useragent updates. + prefs['general.useragent.updates.enabled'] = False + # And for webapp updates. Yes, it is supposed to be an integer. + prefs['browser.webapps.checkForUpdates'] = 0 + # And for about:newtab content fetch and pings. + prefs['browser.newtabpage.directory.source'] = 'data:application/json,{"reftest":1}' + prefs['browser.newtabpage.directory.ping'] = '' + + #Don't use auto-enabled e10s + prefs['browser.tabs.remote.autostart.1'] = False + if options.e10s: + prefs['browser.tabs.remote.autostart'] = True for v in options.extraPrefs: - thispref = v.split("=") + thispref = v.split('=') if len(thispref) < 2: print "Error: syntax error in --setpref=" + v sys.exit(1) - part = 'user_pref("%s", %s);\n' % (thispref[0], thispref[1]) - prefsFile.write(part) - prefsFile.close() + prefs[thispref[0]] = mozprofile.Preferences.cast(thispref[1].strip()) # install the reftest extension bits into the profile - self.automation.installExtension(os.path.join(SCRIPT_DIRECTORY, "reftest"), - profileDir, - "reftest@mozilla.org") + addons = [] + addons.append(os.path.join(SCRIPT_DIRECTORY, "reftest")) # I would prefer to use "--install-extension reftest/specialpowers", but that requires tight coordination with # release engineering and landing on multiple branches at once. - if manifest.endswith('crashtests.list'): - self.automation.installExtension(os.path.join(SCRIPT_DIRECTORY, "specialpowers"), - profileDir, - "special-powers@mozilla.org") + if special_powers and (manifest.endswith('crashtests.list') or manifest.endswith('jstests.list')): + addons.append(os.path.join(SCRIPT_DIRECTORY, 'specialpowers')) + # SpecialPowers requires insecure automation-only features that we put behind a pref. + prefs['security.turn_off_all_security_so_that_viruses_can_take_over_this_computer'] = True + + # Install distributed extensions, if application has any. + distExtDir = os.path.join(options.app[ : options.app.rfind(os.sep)], "distribution", "extensions") + if os.path.isdir(distExtDir): + for f in os.listdir(distExtDir): + addons.append(os.path.join(distExtDir, f)) + + # Install custom extensions. + for f in options.extensionsToInstall: + addons.append(self.getFullPath(f)) + + kwargs = { 'addons': addons, + 'preferences': prefs, + 'locations': locations } + if profile_to_clone: + profile = mozprofile.Profile.clone(profile_to_clone, **kwargs) + else: + profile = mozprofile.Profile(**kwargs) + + self.copyExtraFilesToProfile(options, profile) + return profile + + def environment(self, **kwargs): + return environment(**kwargs) def buildBrowserEnv(self, options, profileDir): - browserEnv = self.automation.environment(xrePath = options.xrePath) + browserEnv = self.environment(xrePath = options.xrePath, debugger=options.debugger) browserEnv["XPCOM_DEBUG_BREAK"] = "stack" for v in options.environment: @@ -111,7 +276,7 @@ class RefTest(object): if ix <= 0: print "Error: syntax error in --setenv=" + v return None - browserEnv[v[:ix]] = v[ix + 1:] + browserEnv[v[:ix]] = v[ix + 1:] # Enable leaks detection to its own log file. self.leakLogFile = os.path.join(profileDir, "runreftest_leaks.log") @@ -123,7 +288,269 @@ class RefTest(object): shutil.rmtree(profileDir, True) def runTests(self, testPath, options, cmdlineArgs = None): - debuggerInfo = getDebuggerInfo(self.oldcwd, options.debugger, options.debuggerArgs, + if not options.runTestsInParallel: + return self.runSerialTests(testPath, options, cmdlineArgs) + + cpuCount = multiprocessing.cpu_count() + + # We have the directive, technology, and machine to run multiple test instances. + # Experimentation says that reftests are not overly CPU-intensive, so we can run + # multiple jobs per CPU core. + # + # Our Windows machines in automation seem to get upset when we run a lot of + # simultaneous tests on them, so tone things down there. + if sys.platform == 'win32': + jobsWithoutFocus = cpuCount + else: + jobsWithoutFocus = 2 * cpuCount + + totalJobs = jobsWithoutFocus + 1 + perProcessArgs = [sys.argv[:] for i in range(0, totalJobs)] + + # First job is only needs-focus tests. Remaining jobs are non-needs-focus and chunked. + perProcessArgs[0].insert(-1, "--focus-filter-mode=needs-focus") + for (chunkNumber, jobArgs) in enumerate(perProcessArgs[1:], start=1): + jobArgs[-1:-1] = ["--focus-filter-mode=non-needs-focus", + "--total-chunks=%d" % jobsWithoutFocus, + "--this-chunk=%d" % chunkNumber] + + for jobArgs in perProcessArgs: + try: + jobArgs.remove("--run-tests-in-parallel") + except: + pass + jobArgs.insert(-1, "--no-run-tests-in-parallel") + jobArgs[0:0] = [sys.executable, "-u"] + + threads = [ReftestThread(args) for args in perProcessArgs[1:]] + for t in threads: + t.start() + + while True: + # The test harness in each individual thread will be doing timeout + # handling on its own, so we shouldn't need to worry about any of + # the threads hanging for arbitrarily long. + for t in threads: + t.join(10) + if not any(t.is_alive() for t in threads): + break + + # Run the needs-focus tests serially after the other ones, so we don't + # have to worry about races between the needs-focus tests *actually* + # needing focus and the dummy windows in the non-needs-focus tests + # trying to focus themselves. + focusThread = ReftestThread(perProcessArgs[0]) + focusThread.start() + focusThread.join() + + # Output the summaries that the ReftestThread filters suppressed. + summaryObjects = [collections.defaultdict(int) for s in summaryLines] + for t in threads: + for (summaryObj, (text, categories)) in zip(summaryObjects, summaryLines): + threadMatches = t.summaryMatches[text] + for (attribute, description) in categories: + amount = int(threadMatches.group(attribute) if threadMatches else 0) + summaryObj[attribute] += amount + amount = int(threadMatches.group('total') if threadMatches else 0) + summaryObj['total'] += amount + + print 'REFTEST INFO | Result summary:' + for (summaryObj, (text, categories)) in zip(summaryObjects, summaryLines): + details = ', '.join(["%d %s" % (summaryObj[attribute], description) for (attribute, description) in categories]) + print 'REFTEST INFO | ' + text + ': ' + str(summaryObj['total']) + ' (' + details + ')' + + return int(any(t.retcode != 0 for t in threads)) + + def handleTimeout(self, timeout, proc, utilityPath, debuggerInfo): + """handle process output timeout""" + # TODO: bug 913975 : _processOutput should call self.processOutputLine one more time one timeout (I think) + log.error("TEST-UNEXPECTED-FAIL | %s | application timed out after %d seconds with no output" % (self.lastTestSeen, int(timeout))) + self.killAndGetStack(proc, utilityPath, debuggerInfo, dump_screen=not debuggerInfo) + + def dumpScreen(self, utilityPath): + if self.haveDumpedScreen: + log.info("Not taking screenshot here: see the one that was previously logged") + return + self.haveDumpedScreen = True + dumpScreen(utilityPath) + + def killAndGetStack(self, process, utilityPath, debuggerInfo, dump_screen=False): + """ + Kill the process, preferrably in a way that gets us a stack trace. + Also attempts to obtain a screenshot before killing the process + if specified. + """ + + if dump_screen: + self.dumpScreen(utilityPath) + + try: + process.kill(sig=signal.SIGABRT) + return + process.kill() + + ### output processing + + class OutputHandler(object): + """line output handler for mozrunner""" + def __init__(self, harness, utilityPath, symbolsPath=None, dump_screen_on_timeout=True): + """ + harness -- harness instance + dump_screen_on_timeout -- whether to dump the screen on timeout + """ + self.harness = harness + self.utilityPath = utilityPath + self.symbolsPath = symbolsPath + self.dump_screen_on_timeout = dump_screen_on_timeout + self.stack_fixer_function = self.stack_fixer() + + def processOutputLine(self, line): + """per line handler of output for mozprocess""" + for handler in self.output_handlers(): + line = handler(line) + __call__ = processOutputLine + + def output_handlers(self): + """returns ordered list of output handlers""" + return [self.fix_stack, + self.format, + self.record_last_test, + self.handle_timeout_and_dump_screen, + self.log, + ] + + def stack_fixer(self): + """ + return stackFixerFunction, if any, to use on the output lines + """ + + if not mozinfo.info.get('debug'): + return None + + stack_fixer_function = None + + def import_stack_fixer_module(module_name): + sys.path.insert(0, self.utilityPath) + module = __import__(module_name, globals(), locals(), []) + sys.path.pop(0) + return module + + if self.symbolsPath and os.path.exists(self.symbolsPath): + # Run each line through a function in fix_stack_using_bpsyms.py (uses breakpad symbol files). + # This method is preferred for Tinderbox builds, since native symbols may have been stripped. + stack_fixer_module = import_stack_fixer_module('fix_stack_using_bpsyms') + stack_fixer_function = lambda line: stack_fixer_module.fixSymbols(line, self.symbolsPath) + + elif mozinfo.isMac: + # Run each line through fix_macosx_stack.py (uses atos). + # This method is preferred for developer machines, so we don't have to run "make buildsymbols". + stack_fixer_module = import_stack_fixer_module('fix_macosx_stack') + stack_fixer_function = lambda line: stack_fixer_module.fixSymbols(line) + + elif mozinfo.isLinux: + # Run each line through fix_linux_stack.py (uses addr2line). + # This method is preferred for developer machines, so we don't have to run "make buildsymbols". + stack_fixer_module = import_stack_fixer_module('fix_linux_stack') + stack_fixer_function = lambda line: stack_fixer_module.fixSymbols(line) + + return stack_fixer_function + + # output line handlers: + # these take a line and return a line + def fix_stack(self, line): + if self.stack_fixer_function: + return self.stack_fixer_function(line) + return line + + def format(self, line): + """format the line""" + return line.rstrip().decode("UTF-8", "ignore") + + def record_last_test(self, line): + """record last test on harness""" + if "TEST-START" in line and "|" in line: + self.harness.lastTestSeen = line.split("|")[1].strip() + return line + + def handle_timeout_and_dump_screen(self, line): + if self.dump_screen_on_timeout and "TEST-UNEXPECTED-FAIL" in line and "Test timed out" in line: + self.harness.dumpScreen(self.utilityPath) + return line + + def log(self, line): + log.info(line) + return line + + def runApp(self, profile, binary, cmdargs, env, + timeout=None, debuggerInfo=None, + symbolsPath=None, options=None): + + def timeoutHandler(): + self.handleTimeout(timeout, proc, options.utilityPath, debuggerInfo) + + interactive = False + debug_args = None + if debuggerInfo: + interactive = debuggerInfo.interactive + debug_args = [debuggerInfo.path] + debuggerInfo.args + + outputHandler = self.OutputHandler(harness=self, + utilityPath=options.utilityPath, + symbolsPath=symbolsPath, + dump_screen_on_timeout=not debuggerInfo, + ) + + kp_kwargs = { + 'kill_on_timeout': False, + 'cwd': SCRIPT_DIRECTORY, + 'onTimeout': [timeoutHandler], + 'processOutputLine': [outputHandler], + } + + if interactive: + # If an interactive debugger is attached, + # don't use timeouts, and don't capture ctrl-c. + timeout = None + signal.signal(signal.SIGINT, lambda sigid, frame: None) + + if mozinfo.info.get('appname') == 'b2g' and mozinfo.info.get('toolkit') != 'gonk': + runner_cls = mozrunner.Runner + else: + runner_cls = mozrunner.runners.get(mozinfo.info.get('appname', 'firefox'), + mozrunner.Runner) + runner = runner_cls(profile=profile, + binary=binary, + process_class=mozprocess.ProcessHandlerMixin, + cmdargs=cmdargs, + env=env, + process_args=kp_kwargs) + runner.start(debug_args=debug_args, + interactive=interactive, + outputTimeout=timeout) + proc = runner.process_handler + status = runner.wait() + runner.process_handler = None + if timeout is None: + didTimeout = False + else: + didTimeout = proc.didTimeout + + if status: + log.info("TEST-UNEXPECTED-FAIL | %s | application terminated with exit code %s", self.lastTestSeen, status) + else: + self.lastTestSeen = 'Main app process exited normally' + + crashed = mozcrash.check_for_crashes(os.path.join(profile.profile, "minidumps"), + symbolsPath, test_name=self.lastTestSeen) + runner.cleanup() + if not status and crashed: + status = 1 + return status + + def runSerialTests(self, testPath, options, cmdlineArgs = None): + debuggerInfo = None + if options.debugger: + debuggerInfo = mozdebug.get_debugger_info(options.debugger, options.debuggerArgs, options.debuggerInteractive); profileDir = None @@ -131,93 +558,102 @@ class RefTest(object): reftestlist = self.getManifestPath(testPath) if cmdlineArgs == None: cmdlineArgs = ['-reftest', reftestlist] - profileDir = mkdtemp() - self.copyExtraFilesToProfile(options, profileDir) - self.createReftestProfile(options, profileDir, reftestlist) - self.installExtensionsToProfile(options, profileDir) + profile = self.createReftestProfile(options, reftestlist) + profileDir = profile.profile # name makes more sense # browser environment browserEnv = self.buildBrowserEnv(options, profileDir) - self.automation.log.info("REFTEST INFO | runreftest.py | Running tests: start.\n") - status = self.automation.runApp(None, browserEnv, options.app, profileDir, - cmdlineArgs, - utilityPath = options.utilityPath, - xrePath=options.xrePath, - debuggerInfo=debuggerInfo, - symbolsPath=options.symbolsPath, - # give the JS harness 30 seconds to deal - # with its own timeouts - timeout=options.timeout + 30.0) - processLeakLog(self.leakLogFile, options.leakThreshold) - self.automation.log.info("\nREFTEST INFO | runreftest.py | Running tests: end.") + log.info("REFTEST INFO | runreftest.py | Running tests: start.\n") + status = self.runApp(profile, + binary=options.app, + cmdargs=cmdlineArgs, + # give the JS harness 30 seconds to deal with its own timeouts + env=browserEnv, + timeout=options.timeout + 30.0, + symbolsPath=options.symbolsPath, + options=options, + debuggerInfo=debuggerInfo) + processLeakLog(self.leakLogFile, options) + log.info("\nREFTEST INFO | runreftest.py | Running tests: end.") finally: self.cleanup(profileDir) return status - def copyExtraFilesToProfile(self, options, profileDir): + def copyExtraFilesToProfile(self, options, profile): "Copy extra files or dirs specified on the command line to the testing profile." + profileDir = profile.profile for f in options.extraProfileFiles: abspath = self.getFullPath(f) if os.path.isfile(abspath): - shutil.copy2(abspath, profileDir) + if os.path.basename(abspath) == 'user.js': + extra_prefs = mozprofile.Preferences.read_prefs(abspath) + profile.set_preferences(extra_prefs) + else: + shutil.copy2(abspath, profileDir) elif os.path.isdir(abspath): dest = os.path.join(profileDir, os.path.basename(abspath)) shutil.copytree(abspath, dest) else: - self.automation.log.warning("WARNING | runreftest.py | Failed to copy %s to profile", abspath) + log.warning("WARNING | runreftest.py | Failed to copy %s to profile", abspath) continue - def installExtensionsToProfile(self, options, profileDir): - "Install application distributed extensions and specified on the command line ones to testing profile." - # Install distributed extensions, if application has any. - distExtDir = os.path.join(options.app[ : options.app.rfind(os.sep)], "distribution", "extensions") - if os.path.isdir(distExtDir): - for f in os.listdir(distExtDir): - self.automation.installExtension(os.path.join(distExtDir, f), profileDir) - - # Install custom extensions. - for f in options.extensionsToInstall: - self.automation.installExtension(self.getFullPath(f), profileDir) - class ReftestOptions(OptionParser): - def __init__(self, automation): - self._automation = automation + def __init__(self): OptionParser.__init__(self) defaults = {} - - # we want to pass down everything from automation.__all__ - addCommonOptions(self, - defaults=dict(zip(self._automation.__all__, - [getattr(self._automation, x) for x in self._automation.__all__]))) - self._automation.addCommonOptions(self) + self.add_option("--xre-path", + action = "store", type = "string", dest = "xrePath", + # individual scripts will set a sane default + default = None, + help = "absolute path to directory containing XRE (probably xulrunner)") + self.add_option("--symbols-path", + action = "store", type = "string", dest = "symbolsPath", + default = None, + help = "absolute path to directory containing breakpad symbols, or the URL of a zip file containing symbols") + self.add_option("--debugger", + action = "store", dest = "debugger", + help = "use the given debugger to launch the application") + self.add_option("--debugger-args", + action = "store", dest = "debuggerArgs", + help = "pass the given args to the debugger _before_ " + "the application on the command line") + self.add_option("--debugger-interactive", + action = "store_true", dest = "debuggerInteractive", + help = "prevents the test harness from redirecting " + "stdout and stderr for interactive debuggers") self.add_option("--appname", action = "store", type = "string", dest = "app", - default = os.path.join(SCRIPT_DIRECTORY, automation.DEFAULT_APP), help = "absolute path to application, overriding default") + # Certain paths do not make sense when we're cross compiling Fennec. This + # logic is cribbed from the example in + # python/mozbuild/mozbuild/mach_commands.py. + defaults['app'] = build_obj.get_binary_path() if \ + build_obj and build_obj.substs['MOZ_BUILD_APP'] != 'mobile/android' else None + self.add_option("--extra-profile-file", action = "append", dest = "extraProfileFiles", default = [], help = "copy specified files/dirs to testing profile") - self.add_option("--timeout", - action = "store", dest = "timeout", type = "int", + self.add_option("--timeout", + action = "store", dest = "timeout", type = "int", default = 5 * 60, # 5 minutes per bug 479518 help = "reftest will timeout in specified number of seconds. [default %default s].") self.add_option("--leak-threshold", - action = "store", type = "int", dest = "leakThreshold", + action = "store", type = "int", dest = "defaultLeakThreshold", default = 0, - help = "fail if the number of bytes leaked through " - "refcounted objects (or bytes in classes with " - "MOZ_COUNT_CTOR and MOZ_COUNT_DTOR) is greater " - "than the given number") + help = "fail if the number of bytes leaked in default " + "processes through refcounted objects (or bytes " + "in classes with MOZ_COUNT_CTOR and MOZ_COUNT_DTOR) " + "is greater than the given number") self.add_option("--utility-path", action = "store", type = "string", dest = "utilityPath", - default = self._automation.DIST_BIN, help = "absolute path to directory containing utility " "programs (xpcshell, ssltunnel, certutil)") - defaults["utilityPath"] = self._automation.DIST_BIN + defaults["utilityPath"] = build_obj.bindir if \ + build_obj and build_obj.substs['MOZ_BUILD_APP'] != 'mobile/android' else None self.add_option("--total-chunks", type = "int", dest = "totalChunks", @@ -234,7 +670,7 @@ class ReftestOptions(OptionParser): default = None, help = "file to log output to in addition to stdout") defaults["logFile"] = None - + self.add_option("--skip-slow-tests", dest = "skipSlowTests", action = "store_true", help = "skip tests marked as slow when running") @@ -253,6 +689,14 @@ class ReftestOptions(OptionParser): "An optional path can be specified too.") defaults["extensionsToInstall"] = [] + self.add_option("--run-tests-in-parallel", + action = "store_true", dest = "runTestsInParallel", + help = "run tests in parallel if possible") + self.add_option("--no-run-tests-in-parallel", + action = "store_false", dest = "runTestsInParallel", + help = "do not run tests in parallel") + defaults["runTestsInParallel"] = False + self.add_option("--setenv", action = "append", type = "string", dest = "environment", metavar = "NAME=VALUE", @@ -267,6 +711,11 @@ class ReftestOptions(OptionParser): "only test items that have a matching test URL will be run.") defaults["filter"] = None + self.add_option("--shuffle", + action = "store_true", dest = "shuffle", + help = "run reftests in random order") + defaults["shuffle"] = False + self.add_option("--focus-filter-mode", action = "store", type = "string", dest = "focusFilterMode", help = "filters tests to run by whether they require focus. " @@ -274,6 +723,18 @@ class ReftestOptions(OptionParser): "Defaults to `all'.") defaults["focusFilterMode"] = "all" + self.add_option("--e10s", + action = "store_true", + dest = "e10s", + help = "enables content processes") + defaults["e10s"] = False + + self.add_option("--setpref", + action = "append", type = "string", + default = [], + dest = "extraPrefs", metavar = "PREF=VALUE", + help = "defines an extra user preference") + self.set_defaults(**defaults) def verifyCommonOptions(self, options, reftest): @@ -294,12 +755,23 @@ class ReftestOptions(OptionParser): self.error("--xre-path '%s' is not a directory" % options.xrePath) options.xrePath = reftest.getFullPath(options.xrePath) + if options.runTestsInParallel: + if options.logFile is not None: + self.error("cannot specify logfile with parallel tests") + if options.totalChunks is not None and options.thisChunk is None: + self.error("cannot specify thisChunk or totalChunks with parallel tests") + if options.focusFilterMode != "all": + self.error("cannot specify focusFilterMode with parallel tests") + if options.debugger is not None: + self.error("cannot specify a debugger with parallel tests") + + options.leakThresholds = {"default": options.defaultLeakThreshold} + return options def main(): - automation = Automation() - parser = ReftestOptions(automation) - reftest = RefTest(automation) + parser = ReftestOptions() + reftest = RefTest() options, args = parser.parse_args() if len(args) != 1: @@ -307,6 +779,8 @@ def main(): sys.exit(1) options = parser.verifyCommonOptions(options, reftest) + if options.app is None: + parser.error("could not find the application path, --appname must be specified") options.app = reftest.getFullPath(options.app) if not os.path.exists(options.app): @@ -318,7 +792,7 @@ Are you executing $objdir/_tests/reftest/runreftest.py?""" \ if options.xrePath is None: options.xrePath = os.path.dirname(options.app) - if options.symbolsPath and not isURL(options.symbolsPath): + if options.symbolsPath and len(urlparse(options.symbolsPath).scheme) < 2: options.symbolsPath = reftest.getFullPath(options.symbolsPath) options.utilityPath = reftest.getFullPath(options.utilityPath) diff --git a/layout/tools/reftest/runreftestb2g.py b/layout/tools/reftest/runreftestb2g.py index 672896da9..6ad6aee3a 100644 --- a/layout/tools/reftest/runreftestb2g.py +++ b/layout/tools/reftest/runreftestb2g.py @@ -9,24 +9,31 @@ import tempfile import traceback # We need to know our current directory so that we can serve our test files from it. -SCRIPT_DIRECTORY = os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0]))) -sys.path.insert(0, SCRIPT_DIRECTORY) +here = os.path.abspath(os.path.dirname(__file__)) from automation import Automation from b2gautomation import B2GRemoteAutomation +from b2g_desktop import run_desktop_reftests from runreftest import RefTest from runreftest import ReftestOptions from remotereftest import ReftestServer from mozdevice import DeviceManagerADB, DMError from marionette import Marionette - +import moznetwork class B2GOptions(ReftestOptions): - def __init__(self, automation, **kwargs): + def __init__(self, **kwargs): defaults = {} - ReftestOptions.__init__(self, automation) + ReftestOptions.__init__(self) + # This is only used for procName in run_remote_reftests. + defaults["app"] = Automation.DEFAULT_APP + + self.add_option("--browser-arg", action="store", + type = "string", dest = "browser_arg", + help = "Optional command-line arg to pass to the browser") + defaults["browser_arg"] = None self.add_option("--b2gpath", action="store", type = "string", dest = "b2gPath", @@ -53,9 +60,9 @@ class B2GOptions(ReftestOptions): defaults["noWindow"] = False self.add_option("--adbpath", action="store", - type = "string", dest = "adbPath", + type = "string", dest = "adb_path", help = "path to adb") - defaults["adbPath"] = "adb" + defaults["adb_path"] = "adb" self.add_option("--deviceIP", action="store", type = "string", dest = "deviceIP", @@ -80,12 +87,12 @@ class B2GOptions(ReftestOptions): self.add_option("--http-port", action = "store", type = "string", dest = "httpPort", help = "ip address where the remote web server is hosted at") - defaults["httpPort"] = automation.DEFAULT_HTTP_PORT + defaults["httpPort"] = None self.add_option("--ssl-port", action = "store", type = "string", dest = "sslPort", help = "ip address where the remote web server is hosted at") - defaults["sslPort"] = automation.DEFAULT_SSL_PORT + defaults["sslPort"] = None self.add_option("--pidfile", action = "store", type = "string", dest = "pidFile", @@ -96,10 +103,10 @@ class B2GOptions(ReftestOptions): help="the path to a goanna distribution that should " "be installed on the emulator prior to test") defaults["goannaPath"] = None - self.add_option("--logcat-dir", action="store", - type="string", dest="logcat_dir", - help="directory to store logcat dump files") - defaults["logcat_dir"] = None + self.add_option("--logdir", action="store", + type="string", dest="logdir", + help="directory to store log files") + defaults["logdir"] = None self.add_option('--busybox', action='store', type='string', dest='busybox', help="Path to busybox binary to install on device") @@ -108,37 +115,65 @@ class B2GOptions(ReftestOptions): type = "string", dest = "httpdPath", help = "path to the httpd.js file") defaults["httpdPath"] = None - defaults["remoteTestRoot"] = "/data/local/tests" + self.add_option("--profile", action="store", + type="string", dest="profile", + help="for desktop testing, the path to the " + "gaia profile to use") + defaults["profile"] = None + self.add_option("--desktop", action="store_true", + dest="desktop", + help="Run the tests on a B2G desktop build") + defaults["desktop"] = False + self.add_option("--mulet", action="store_true", + dest="mulet", + help="Run the tests on a B2G desktop build") + defaults["mulet"] = False + self.add_option("--enable-oop", action="store_true", + dest="oop", + help="Run the tests out of process") + defaults["oop"] = False + defaults["remoteTestRoot"] = None defaults["logFile"] = "reftest.log" defaults["autorun"] = True defaults["closeWhenDone"] = True defaults["testPath"] = "" + defaults["runTestsInParallel"] = False self.set_defaults(**defaults) - def verifyRemoteOptions(self, options): + def verifyRemoteOptions(self, options, auto): + if options.runTestsInParallel: + self.error("Cannot run parallel tests here") + if not options.remoteTestRoot: - options.remoteTestRoot = self._automation._devicemanager.getDeviceRoot() + "/reftest" + options.remoteTestRoot = auto._devicemanager.deviceRoot + "/reftest" + options.remoteProfile = options.remoteTestRoot + "/profile" - productRoot = options.remoteTestRoot + "/" + self._automation._product - if options.utilityPath == self._automation.DIST_BIN: + productRoot = options.remoteTestRoot + "/" + auto._product + if options.utilityPath is None: options.utilityPath = productRoot + "/bin" if options.remoteWebServer == None: if os.name != "nt": - options.remoteWebServer = self._automation.getLanIp() + options.remoteWebServer = moznetwork.get_ip() else: print "ERROR: you must specify a --remote-webserver=<ip address>\n" return None options.webServer = options.remoteWebServer + if not options.httpPort: + options.httpPort = auto.DEFAULT_HTTP_PORT + + if not options.sslPort: + options.sslPort = auto.DEFAULT_SSL_PORT + if options.goannaPath and not options.emulator: self.error("You must specify --emulator if you specify --goanna-path") - if options.logcat_dir and not options.emulator: - self.error("You must specify --emulator if you specify --logcat-dir") + if options.logdir and not options.emulator: + self.error("You must specify --emulator if you specify --logdir") #if not options.emulator and not options.deviceIP: # print "ERROR: you must provide a device IP" @@ -202,33 +237,30 @@ class ProfileConfigParser(ConfigParser.RawConfigParser): fp.write("%s\n" % (key)) fp.write("\n") +class B2GRemoteReftest(RefTest): -class B2GReftest(RefTest): - - _automation = None _devicemanager = None localProfile = None remoteApp = '' profile = None def __init__(self, automation, devicemanager, options, scriptDir): - self._automation = automation - RefTest.__init__(self, self._automation) + RefTest.__init__(self) + self.automation = automation self._devicemanager = devicemanager self.runSSLTunnel = False self.remoteTestRoot = options.remoteTestRoot self.remoteProfile = options.remoteProfile - self._automation.setRemoteProfile(self.remoteProfile) + self.automation.setRemoteProfile(self.remoteProfile) self.localLogName = options.localLogName self.remoteLogFile = options.remoteLogFile self.bundlesDir = '/system/b2g/distribution/bundles' - self.userJS = '/data/local/user.js' self.remoteMozillaPath = '/data/b2g/mozilla' self.remoteProfilesIniPath = os.path.join(self.remoteMozillaPath, 'profiles.ini') self.originalProfilesIni = None self.scriptDir = scriptDir self.SERVER_STARTUP_TIMEOUT = 90 - if self._automation.IS_DEBUG_BUILD: + if self.automation.IS_DEBUG_BUILD: self.SERVER_STARTUP_TIMEOUT = 180 def cleanup(self, profileDir): @@ -241,35 +273,32 @@ class B2GReftest(RefTest): sys.exit(5) # Delete any bundled extensions - extensionDir = os.path.join(profileDir, 'extensions', 'staged') - for filename in os.listdir(extensionDir): - try: - self._devicemanager._checkCmdAs(['shell', 'rm', '-rf', - os.path.join(self.bundlesDir, filename)]) - except DMError: - pass + if profileDir: + extensionDir = os.path.join(profileDir, 'extensions', 'staged') + for filename in os.listdir(extensionDir): + try: + self._devicemanager._checkCmd(['shell', 'rm', '-rf', + os.path.join(self.bundlesDir, filename)]) + except DMError: + pass # Restore the original profiles.ini. if self.originalProfilesIni: try: - if not self._automation._is_emulator: + if not self.automation._is_emulator: self.restoreProfilesIni() os.remove(self.originalProfilesIni) except: pass - if not self._automation._is_emulator: + if not self.automation._is_emulator: self._devicemanager.removeFile(self.remoteLogFile) self._devicemanager.removeDir(self.remoteProfile) self._devicemanager.removeDir(self.remoteTestRoot) - # Restore the original user.js. - self._devicemanager._checkCmdAs(['shell', 'rm', '-f', self.userJS]) - self._devicemanager._checkCmdAs(['shell', 'dd', 'if=%s.orig' % self.userJS, 'of=%s' % self.userJS]) - # We've restored the original profile, so reboot the device so that # it gets picked up. - self._automation.rebootDevice() + self.automation.rebootDevice() RefTest.cleanup(self, profileDir) if getattr(self, 'pidFile', '') != '': @@ -310,8 +339,8 @@ class B2GReftest(RefTest): paths = [options.xrePath, localAutomation.DIST_BIN, - self._automation._product, - os.path.join('..', self._automation._product)] + self.automation._product, + os.path.join('..', self.automation._product)] options.xrePath = self.findPath(paths) if options.xrePath == None: print "ERROR: unable to find xulrunner path for %s, please specify with --xre-path" % (os.name) @@ -323,15 +352,13 @@ class B2GReftest(RefTest): if (os.name == "nt"): xpcshell += ".exe" - if (options.utilityPath): - paths.insert(0, options.utilityPath) options.utilityPath = self.findPath(paths, xpcshell) if options.utilityPath == None: print "ERROR: unable to find utility path for %s, please specify with --utility-path" % (os.name) sys.exit(1) xpcshell = os.path.join(options.utilityPath, xpcshell) - if self._automation.elf_arm(xpcshell): + if self.automation.elf_arm(xpcshell): raise Exception('xpcshell at %s is an ARM binary; please use ' 'the --utility-path argument to specify the path ' 'to a desktop version.' % xpcshell) @@ -360,7 +387,6 @@ class B2GReftest(RefTest): if hasattr(self, 'server'): self.server.stop() - def restoreProfilesIni(self): # restore profiles.ini on the device to its previous state if not self.originalProfilesIni or not os.access(self.originalProfilesIni, os.F_OK): @@ -392,41 +418,45 @@ class B2GReftest(RefTest): pass - def createReftestProfile(self, options, profileDir, reftestlist): - print "profileDir: " + str(profileDir) - retVal = RefTest.createReftestProfile(self, options, profileDir, reftestlist, server=options.remoteWebServer) + def createReftestProfile(self, options, reftestlist): + profile = RefTest.createReftestProfile(self, options, reftestlist, + server=options.remoteWebServer, + special_powers=False) + profileDir = profile.profile + + prefs = {} # Turn off the locale picker screen - fhandle = open(os.path.join(profileDir, "user.js"), 'a') - fhandle.write(""" -user_pref("browser.firstrun.show.localepicker", false); -user_pref("browser.homescreenURL","app://system.gaiamobile.org");\n -user_pref("browser.manifestURL","app://system.gaiamobile.org/manifest.webapp");\n -user_pref("browser.tabs.remote", false);\n -user_pref("dom.ipc.browser_frames.oop_by_default", true);\n -user_pref("dom.ipc.tabs.disabled", false);\n -user_pref("dom.mozBrowserFramesEnabled", true);\n -user_pref("dom.mozBrowserFramesWhitelist","app://system.gaiamobile.org");\n -user_pref("network.dns.localDomains","app://system.gaiamobile.org");\n -user_pref("font.size.inflation.emPerLine", 0); -user_pref("font.size.inflation.minTwips", 0); -user_pref("reftest.browser.iframe.enabled", false); -user_pref("reftest.remote", true); -user_pref("reftest.uri", "%s"); -// Set a future policy version to avoid the telemetry prompt. -user_pref("toolkit.telemetry.prompted", 999); -user_pref("toolkit.telemetry.notifiedOptOut", 999); -""" % reftestlist) - - #workaround for jsreftests. - if getattr(options, 'enablePrivilege', False): - fhandle.write(""" -user_pref("capability.principal.codebase.p2.granted", "UniversalXPConnect"); -user_pref("capability.principal.codebase.p2.id", "http://%s:%s"); -""" % (options.remoteWebServer, options.httpPort)) - - # Close the file - fhandle.close() + prefs["browser.firstrun.show.localepicker"] = False + prefs["b2g.system_startup_url"] = "app://test-container.gaiamobile.org/index.html" + prefs["b2g.system_manifest_url"] = "app://test-container.gaiamobile.org/manifest.webapp" + prefs["dom.ipc.tabs.disabled"] = False + prefs["dom.mozBrowserFramesEnabled"] = True + prefs["font.size.inflation.emPerLine"] = 0 + prefs["font.size.inflation.minTwips"] = 0 + prefs["network.dns.localDomains"] = "app://test-container.gaiamobile.org" + prefs["reftest.browser.iframe.enabled"] = False + prefs["reftest.remote"] = True + prefs["reftest.uri"] = "%s" % reftestlist + # Set a future policy version to avoid the telemetry prompt. + prefs["toolkit.telemetry.prompted"] = 999 + prefs["toolkit.telemetry.notifiedOptOut"] = 999 + # Make sure we disable system updates + prefs["app.update.enabled"] = False + prefs["app.update.url"] = "" + prefs["app.update.url.override"] = "" + # Disable webapp updates + prefs["webapps.update.enabled"] = False + # Disable tiles also + prefs["browser.newtabpage.directory.source"] = "" + prefs["browser.newtabpage.directory.ping"] = "" + + if options.oop: + prefs['browser.tabs.remote.autostart'] = True + prefs['reftest.browser.iframe.enabled'] = True + + # Set the extra prefs. + profile.set_preferences(prefs) # Copy the profile to the device. self._devicemanager.removeDir(self.remoteProfile) @@ -439,9 +469,9 @@ user_pref("capability.principal.codebase.p2.id", "http://%s:%s"); # Copy the extensions to the B2G bundles dir. extensionDir = os.path.join(profileDir, 'extensions', 'staged') # need to write to read-only dir - self._devicemanager._checkCmdAs(['remount']) + self._devicemanager._checkCmd(['remount']) for filename in os.listdir(extensionDir): - self._devicemanager._checkCmdAs(['shell', 'rm', '-rf', + self._devicemanager._checkCmd(['shell', 'rm', '-rf', os.path.join(self.bundlesDir, filename)]) try: self._devicemanager.pushDir(extensionDir, self.bundlesDir) @@ -449,19 +479,14 @@ user_pref("capability.principal.codebase.p2.id", "http://%s:%s"); print "Automation Error: Unable to copy extensions to device." raise - # In B2G, user.js is always read from /data/local, not the profile - # directory. Backup the original user.js first so we can restore it. - self._devicemanager._checkCmdAs(['shell', 'rm', '-f', '%s.orig' % self.userJS]) - self._devicemanager._checkCmdAs(['shell', 'dd', 'if=%s' % self.userJS, 'of=%s.orig' % self.userJS]) - self._devicemanager.pushFile(os.path.join(profileDir, "user.js"), self.userJS) - self.updateProfilesIni(self.remoteProfile) options.profilePath = self.remoteProfile - return retVal + return profile - def copyExtraFilesToProfile(self, options, profileDir): - RefTest.copyExtraFilesToProfile(self, options, profileDir) + def copyExtraFilesToProfile(self, options, profile): + profileDir = profile.profile + RefTest.copyExtraFilesToProfile(self, options, profile) try: self._devicemanager.pushDir(profileDir, options.remoteProfile) except DMError: @@ -471,11 +496,26 @@ user_pref("capability.principal.codebase.p2.id", "http://%s:%s"); def getManifestPath(self, path): return path - -def main(args=sys.argv[1:]): + def environment(self, **kwargs): + return self.automation.environment(**kwargs) + + def runApp(self, profile, binary, cmdargs, env, + timeout=None, debuggerInfo=None, + symbolsPath=None, options=None): + status = self.automation.runApp(None, env, + binary, + profile.profile, + cmdargs, + utilityPath=options.utilityPath, + xrePath=options.xrePath, + debuggerInfo=debuggerInfo, + symbolsPath=symbolsPath, + timeout=timeout) + return status + + +def run_remote_reftests(parser, options, args): auto = B2GRemoteAutomation(None, "fennec", context_chrome=True) - parser = B2GOptions(auto) - options, args = parser.parse_args(args) # create our Marionette instance kwargs = {} @@ -486,8 +526,8 @@ def main(args=sys.argv[1:]): kwargs['noWindow'] = True if options.goannaPath: kwargs['goanna_path'] = options.goannaPath - if options.logcat_dir: - kwargs['logcat_dir'] = options.logcat_dir + if options.logdir: + kwargs['logdir'] = options.logdir if options.busybox: kwargs['busybox'] = options.busybox if options.symbolsPath: @@ -500,19 +540,24 @@ def main(args=sys.argv[1:]): host,port = options.marionette.split(':') kwargs['host'] = host kwargs['port'] = int(port) - marionette = Marionette.getMarionetteOrExit(**kwargs) + if options.adb_path: + kwargs['adb_path'] = options.adb_path + marionette = Marionette(**kwargs) auto.marionette = marionette - # create the DeviceManager - kwargs = {'adbPath': options.adbPath, - 'deviceRoot': options.remoteTestRoot} - if options.deviceIP: - kwargs.update({'host': options.deviceIP, - 'port': options.devicePort}) - dm = DeviceManagerADB(**kwargs) + if options.emulator: + dm = marionette.emulator.dm + else: + # create the DeviceManager + kwargs = {'adbPath': options.adb_path, + 'deviceRoot': options.remoteTestRoot} + if options.deviceIP: + kwargs.update({'host': options.deviceIP, + 'port': options.devicePort}) + dm = DeviceManagerADB(**kwargs) auto.setDeviceManager(dm) - options = parser.verifyRemoteOptions(options) + options = parser.verifyRemoteOptions(options, auto) if (options == None): print "ERROR: Invalid options specified, use --help for a list of valid options" @@ -528,11 +573,11 @@ def main(args=sys.argv[1:]): return 1 auto.setProduct("b2g") - auto.test_script = os.path.join(SCRIPT_DIRECTORY, 'b2g_start_script.js') + auto.test_script = os.path.join(here, 'b2g_start_script.js') auto.test_script_args = [options.remoteWebServer, options.httpPort] auto.logFinish = "REFTEST TEST-START | Shutdown" - reftest = B2GReftest(auto, dm, options, SCRIPT_DIRECTORY) + reftest = B2GRemoteReftest(auto, dm, options, here) options = parser.verifyCommonOptions(options, reftest) logParent = os.path.dirname(options.remoteLogFile) @@ -540,12 +585,15 @@ def main(args=sys.argv[1:]): auto.setRemoteLog(options.remoteLogFile) auto.setServerInfo(options.webServer, options.httpPort, options.sslPort) + # Hack in a symbolic link for jsreftest + os.system("ln -s %s %s" % (os.path.join('..', 'jsreftest'), os.path.join(here, 'jsreftest'))) + # Dynamically build the reftest URL if possible, beware that args[0] should exist 'inside' the webroot manifest = args[0] - if os.path.exists(os.path.join(SCRIPT_DIRECTORY, args[0])): + if os.path.exists(os.path.join(here, args[0])): manifest = "http://%s:%s/%s" % (options.remoteWebServer, options.httpPort, args[0]) elif os.path.exists(args[0]): - manifestPath = os.path.abspath(args[0]).split(SCRIPT_DIRECTORY)[1].strip('/') + manifestPath = os.path.abspath(args[0]).split(here)[1].strip('/') manifest = "http://%s:%s/%s" % (options.remoteWebServer, options.httpPort, manifestPath) else: print "ERROR: Could not find test manifest '%s'" % manifest @@ -579,6 +627,15 @@ def main(args=sys.argv[1:]): reftest.stopWebServer(options) return retVal +def main(args=sys.argv[1:]): + parser = B2GOptions() + options, args = parser.parse_args(args) + + if options.desktop or options.mulet: + return run_desktop_reftests(parser, options, args) + return run_remote_reftests(parser, options, args) + + if __name__ == "__main__": sys.exit(main()) diff --git a/layout/tools/tests/content_dumping.html b/layout/tools/tests/content_dumping.html index d0312bbf2..d89b802a3 100644 --- a/layout/tools/tests/content_dumping.html +++ b/layout/tools/tests/content_dumping.html @@ -61,7 +61,7 @@ function DumpWebShells() function InputKey(inEvent) { - if (inEvent.keyCode == KeyEvent.DOM_VK_ENTER || inEvent.keyCode == KeyEvent.DOM_VK_RETURN) + if (inEvent.keyCode == KeyEvent.DOM_VK_RETURN) { var pageFrame = window.frames.pageframe; pageFrame.location.href = document.dumpform.urlfield.value; |