mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-15 06:15:43 +00:00
Bug 870908 - Add Robocop Java harness for writing tests in Javascript. r=rnewman
This commit is contained in:
parent
88436ea766
commit
949e13fd1a
@ -71,6 +71,8 @@ MOCHITEST_ROBOCOP_FILES := \
|
||||
$(wildcard $(TESTPATH)/*.html) \
|
||||
$(wildcard $(TESTPATH)/*.jpg) \
|
||||
$(wildcard $(TESTPATH)/*.sjs) \
|
||||
$(wildcard $(TESTPATH)/test*.js) \
|
||||
$(wildcard $(TESTPATH)/robocop*.js) \
|
||||
$(NULL)
|
||||
|
||||
GARBAGE += \
|
||||
|
@ -232,11 +232,38 @@ abstract class BaseTest extends ActivityInstrumentationTestCase2<Activity> {
|
||||
contentEventExpecter.unregisterListener();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load <code>url</code> using the awesome bar UI and sending key strokes.
|
||||
*
|
||||
* This method waits synchronously for the <code>DOMContentLoaded</code>
|
||||
* message from Gecko before returning.
|
||||
*/
|
||||
protected final void loadUrl(String url) {
|
||||
enterUrl(url);
|
||||
hitEnterAndWait();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load <code>url</code> using reflection and the internal
|
||||
* <code>org.mozilla.gecko.Tabs</code> API.
|
||||
*
|
||||
* This method does not wait for any confirmation from Gecko before
|
||||
* returning.
|
||||
*/
|
||||
protected final void loadUrlInTab(final String url) {
|
||||
try {
|
||||
ClassLoader classLoader = getActivity().getClassLoader();
|
||||
Class tabsClass = classLoader.loadClass("org.mozilla.gecko.Tabs");
|
||||
Method getInstance = tabsClass.getMethod("getInstance");
|
||||
Method loadUrlInTab = tabsClass.getMethod("loadUrlInTab", String.class);
|
||||
Object tabs = getInstance.invoke(null);
|
||||
loadUrlInTab.invoke(tabs, new Object[] { url });
|
||||
} catch (Exception e) {
|
||||
mAsserter.dumpLog("Exception in loadUrlInTab", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public final void verifyUrl(String url) {
|
||||
Activity awesomeBarActivity = clickOnAwesomeBar();
|
||||
Element urlbar = mDriver.findElement(awesomeBarActivity, "awesomebar_text");
|
||||
|
148
mobile/android/base/tests/JavascriptTest.java.in
Normal file
148
mobile/android/base/tests/JavascriptTest.java.in
Normal file
@ -0,0 +1,148 @@
|
||||
#filter substitution
|
||||
package @ANDROID_PACKAGE_NAME@.tests;
|
||||
|
||||
import @ANDROID_PACKAGE_NAME@.*;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
|
||||
public class JavascriptTest extends BaseTest {
|
||||
public static final String LOGTAG = "JavascriptTest";
|
||||
|
||||
public final String javascriptUrl;
|
||||
|
||||
public JavascriptTest(String javascriptUrl) {
|
||||
super();
|
||||
this.javascriptUrl = javascriptUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTestType() {
|
||||
return TEST_MOCHITEST;
|
||||
}
|
||||
|
||||
/**
|
||||
* Route messages from Javascript's head.js test framework into Java's
|
||||
* Mochitest framework.
|
||||
*/
|
||||
protected static class JavascriptTestMessageParser {
|
||||
// Messages matching this pattern are handled specially. Messages not
|
||||
// matching this pattern are still printed.
|
||||
private static final Pattern testMessagePattern =
|
||||
Pattern.compile("\n+TEST-(.*) \\| (.*) \\| (.*)\n*");
|
||||
|
||||
private final Assert mAsserter;
|
||||
|
||||
// Used to help print stack traces neatly.
|
||||
private String lastTestName = "";
|
||||
|
||||
// Have we seen a message saying the test is finished?
|
||||
private boolean testFinishedMessageSeen = false;
|
||||
|
||||
public JavascriptTestMessageParser(final Assert asserter) {
|
||||
this.mAsserter = asserter;
|
||||
}
|
||||
|
||||
private boolean testIsFinished() {
|
||||
return testFinishedMessageSeen;
|
||||
}
|
||||
|
||||
private void logMessage(String str) {
|
||||
Matcher m = testMessagePattern.matcher(str);
|
||||
|
||||
if (m.matches()) {
|
||||
String type = m.group(1);
|
||||
String name = m.group(2);
|
||||
String message = m.group(3);
|
||||
|
||||
if ("INFO".equals(type)) {
|
||||
mAsserter.info(name, message);
|
||||
testFinishedMessageSeen = testFinishedMessageSeen ||
|
||||
"exiting test".equals(message);
|
||||
} else if ("PASS".equals(type)) {
|
||||
mAsserter.ok(true, name, message);
|
||||
} else if ("UNEXPECTED-FAIL".equals(type)) {
|
||||
try {
|
||||
mAsserter.ok(false, name, message);
|
||||
} catch (junit.framework.AssertionFailedError e) {
|
||||
// Swallow this exception. We want to see all the
|
||||
// Javascript failures, not die on the very first one!
|
||||
}
|
||||
} else if ("KNOWN-FAIL".equals(type)) {
|
||||
mAsserter.todo(false, name, message);
|
||||
} else if ("UNEXPECTED-PASS".equals(type)) {
|
||||
mAsserter.todo(true, name, message);
|
||||
}
|
||||
|
||||
lastTestName = name;
|
||||
} else {
|
||||
// Generally, these extra lines are stack traces from failures,
|
||||
// so we print them with the name of the last test seen.
|
||||
mAsserter.info(lastTestName, str.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testJavascript() throws Exception {
|
||||
blockForGeckoReady();
|
||||
|
||||
// We want to be waiting for Robocop messages before the page is loaded
|
||||
// because the test harness runs each test in the suite (and possibly
|
||||
// completes testing) before the page load event is fired.
|
||||
final Actions.EventExpecter expecter = mActions.expectGeckoEvent("Robocop:Status");
|
||||
mAsserter.dumpLog("Registered listener for Robocop:Status");
|
||||
|
||||
final String url = getAbsoluteUrl("/robocop/robocop_javascript.html?path=" + javascriptUrl);
|
||||
mAsserter.dumpLog("Loading JavaScript test from " + url);
|
||||
|
||||
loadUrlInTab(url);
|
||||
|
||||
final JavascriptTestMessageParser testMessageParser =
|
||||
new JavascriptTestMessageParser(mAsserter);
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
|
||||
Log.v(LOGTAG, "Waiting for Robocop:Status");
|
||||
}
|
||||
String data = expecter.blockForEventData();
|
||||
if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
|
||||
Log.v(LOGTAG, "Got Robocop:Status with data '" + data + "'");
|
||||
}
|
||||
|
||||
JSONObject o = new JSONObject(data);
|
||||
String innerType = o.getString("innerType");
|
||||
|
||||
if (!"progress".equals(innerType)) {
|
||||
throw new Exception("Unexpected Robocop:Status innerType " + innerType);
|
||||
}
|
||||
|
||||
String message = o.getString("message");
|
||||
if (message == null) {
|
||||
throw new Exception("Robocop:Status progress message must not be null");
|
||||
}
|
||||
|
||||
testMessageParser.logMessage(message);
|
||||
|
||||
if (testMessageParser.testIsFinished()) {
|
||||
if (Log.isLoggable(LOGTAG, Log.DEBUG)) {
|
||||
Log.d(LOGTAG, "Got test finished message");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
expecter.unregisterListener();
|
||||
mAsserter.dumpLog("Unregistered listener for Robocop:Status");
|
||||
}
|
||||
}
|
||||
}
|
1141
mobile/android/base/tests/robocop_head.js
Normal file
1141
mobile/android/base/tests/robocop_head.js
Normal file
File diff suppressed because it is too large
Load Diff
20
mobile/android/base/tests/robocop_javascript.html
Normal file
20
mobile/android/base/tests/robocop_javascript.html
Normal file
@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Mochitest Robotium Javascript Test Harness</title>
|
||||
<link rel="author" title="nalexander" href="mailto:nalexander@mozilla.com">
|
||||
<script type="application/javascript;version=1.8" src="robocop_testharness.js"></script>
|
||||
<script>
|
||||
var param = /[&?]path=([^&]+)/.exec(location.search);
|
||||
if (param) {
|
||||
// We encode so that absolute URLs can be provided. Since the
|
||||
// encoding of a relative filename is just the filename, no special
|
||||
// processing is needed for the most common case.
|
||||
var src = decodeURIComponent(param[1]);
|
||||
document.title = src;
|
||||
|
||||
// Provided by robocop_testharness.js.
|
||||
testOneFile(src);
|
||||
}
|
||||
</script>
|
||||
</head>
|
74
mobile/android/base/tests/robocop_testharness.js
Normal file
74
mobile/android/base/tests/robocop_testharness.js
Normal file
@ -0,0 +1,74 @@
|
||||
// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
||||
/* 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/. */
|
||||
|
||||
let bridge = SpecialPowers.Cc["@mozilla.org/android/bridge;1"]
|
||||
.getService(SpecialPowers.Ci.nsIAndroidBridge);
|
||||
|
||||
function sendMessageToJava(message) {
|
||||
let data = JSON.stringify(message);
|
||||
bridge.handleGeckoMessage(data);
|
||||
}
|
||||
|
||||
function _evalURI(uri, sandbox) {
|
||||
// We explicitly allow Cross-Origin requests, since it is useful for
|
||||
// testing, but we allow relative URLs by maintaining our baseURI.
|
||||
let req = SpecialPowers.Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
||||
.createInstance();
|
||||
|
||||
let baseURI = SpecialPowers.Services.io
|
||||
.newURI(window.document.baseURI, window.document.characterSet, null);
|
||||
let theURI = SpecialPowers.Services.io
|
||||
.newURI(uri, window.document.characterSet, baseURI);
|
||||
|
||||
req.open('GET', theURI.spec, false);
|
||||
req.send();
|
||||
|
||||
return SpecialPowers.Cu.evalInSandbox(req.responseText, sandbox, "1.8", uri, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the Javascript file at `uri` in a testing sandbox populated
|
||||
* with the Javascript test harness.
|
||||
*
|
||||
* `uri` should be a String, relative (to window.document.baseURI) or
|
||||
* absolute.
|
||||
*
|
||||
* The Javascript test harness sends all output to Java via
|
||||
* Robocop:Status messages.
|
||||
*/
|
||||
function testOneFile(uri) {
|
||||
let HEAD_JS = "robocop_head.js";
|
||||
|
||||
// System principal. This is dangerous, but this is test code that
|
||||
// should only run on developer and build farm machines, and the
|
||||
// test harness needs access to a lot of the Components API,
|
||||
// including Components.stack. Wrapping Components.stack in
|
||||
// SpecialPowers magic obfuscates stack traces wonderfully,
|
||||
// defeating much of the point of the test harness.
|
||||
let principal = SpecialPowers.Cc["@mozilla.org/systemprincipal;1"]
|
||||
.createInstance(SpecialPowers.Ci.nsIPrincipal);
|
||||
|
||||
let testScope = SpecialPowers.Cu.Sandbox(principal);
|
||||
|
||||
// Populate test environment with test harness prerequisites.
|
||||
testScope.Components = SpecialPowers.Components;
|
||||
testScope._TEST_FILE = uri;
|
||||
|
||||
// Output from head.js is fed, line by line, to this function. We
|
||||
// send any such output back to the Java Robocop harness.
|
||||
testScope.dump = function (str) {
|
||||
let message = { type: "Robocop:Status",
|
||||
innerType: "progress",
|
||||
message: str,
|
||||
};
|
||||
sendMessageToJava(message);
|
||||
};
|
||||
|
||||
// Populate test environment with test harness. The symbols defined
|
||||
// above must be present before executing the test harness.
|
||||
_evalURI(HEAD_JS, testScope);
|
||||
|
||||
return _evalURI(uri, testScope);
|
||||
}
|
Loading…
Reference in New Issue
Block a user