Bug 873591 - Make better errors for execute_script failures, r=mdas

This commit is contained in:
Jonathan Griffin 2013-06-21 17:13:35 -07:00
parent 8ec6ea7e2b
commit 5b221bb19d
5 changed files with 139 additions and 29 deletions

View File

@ -3,9 +3,11 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import datetime
import os
import socket
import sys
import time
import traceback
from client import MarionetteClient
from application_cache import ApplicationCache
@ -602,26 +604,34 @@ class Marionette(object):
if script_args is None:
script_args = []
args = self.wrapArguments(script_args)
stack = traceback.extract_stack()
frame = stack[-2:-1][0] # grab the second-to-last frame
response = self._send_message('executeScript',
'value',
'value',
value=script,
args=args,
newSandbox=new_sandbox,
specialPowers=special_powers,
scriptTimeout=script_timeout)
scriptTimeout=script_timeout,
line=int(frame[1]),
filename=os.path.basename(frame[0]))
return self.unwrapValue(response)
def execute_async_script(self, script, script_args=None, new_sandbox=True, special_powers=False, script_timeout=None):
if script_args is None:
script_args = []
args = self.wrapArguments(script_args)
stack = traceback.extract_stack()
frame = stack[-2:-1][0] # grab the second-to-last frame
response = self._send_message('executeAsyncScript',
'value',
value=script,
args=args,
newSandbox=new_sandbox,
specialPowers=special_powers,
scriptTimeout=script_timeout)
scriptTimeout=script_timeout,
line=int(frame[1]),
filename=os.path.basename(frame[0]))
return self.unwrapValue(response)
def find_element(self, method, target, id=None):

View File

@ -55,8 +55,14 @@ class TestExecuteAsyncContent(MarionetteTestCase):
self.assertEqual(self.marionette.execute_async_script("marionetteScriptFinished()"), None)
def test_execute_js_exception(self):
self.assertRaises(JavascriptException,
self.marionette.execute_async_script, "foo(bar);")
try:
self.marionette.execute_async_script("""
let a = 1;
foo(bar);
""")
self.assertFalse(True)
except JavascriptException, inst:
self.assertTrue('foo(bar)' in inst.stacktrace)
def test_execute_async_js_exception(self):
self.assertRaises(JavascriptException,

View File

@ -6,6 +6,16 @@ from marionette_test import MarionetteTestCase
from errors import JavascriptException, MarionetteException
class TestExecuteContent(MarionetteTestCase):
def test_stack_trace(self):
try:
self.marionette.execute_script("""
let a = 1;
return b;
""")
self.assertFalse(True)
except JavascriptException, inst:
self.assertTrue('return b' in inst.stacktrace)
def test_execute_simple(self):
self.assertEqual(1, self.marionette.execute_script("return 1;"))
@ -43,13 +53,13 @@ let prefs = Components.classes["@mozilla.org/preferences-service;1"]
self.marionette.execute_script("global.barfoo = [42, 23];")
self.assertEqual(self.marionette.execute_script("return global.barfoo;", new_sandbox=False), [42, 23])
def test_that_we_can_pass_in_floats(self):
expected_result = 1.2
result = self.marionette.execute_script("return arguments[0]",
[expected_result])
self.assertTrue(isinstance(result, float))
self.assertEqual(result, expected_result)
expected_result = 1.2
result = self.marionette.execute_script("return arguments[0]",
[expected_result])
self.assertTrue(isinstance(result, float))
self.assertEqual(result, expected_result)
class TestExecuteChrome(TestExecuteContent):
def setUp(self):

View File

@ -279,6 +279,36 @@ function resetValues() {
mouseEventsOnly = false;
}
/**
* Creates an error message for a JavaScript exception thrown during
* execute_(async_)script.
*
* This will generate a [msg, trace] pair like:
*
* ['ReferenceError: foo is not defined',
* 'execute_script @test_foo.py, line 10
* inline javascript, line 2
* src: "return foo;"']
*
* @param error An Error object passed to a catch() clause.
fnName The name of the function to use in the stack trace message
(e.g., 'execute_script').
pythonFile The filename of the test file containing the Marionette
command that caused this exception to occur.
pythonLine The line number of the above test file.
script The JS script being executed in text form.
*/
function createStackMessage(error, fnName, pythonFile, pythonLine, script) {
let python_stack = fnName + " @" + pythonFile + ", line " + pythonLine;
let stack = error.stack.split("\n");
let line = stack[0].substr(stack[0].lastIndexOf(':') + 1);
let msg = error.name + ": " + error.message;
let trace = python_stack +
"\ninline javascript, line " + line +
"\nsrc: \"" + script.split("\n")[line] + "\"";
return [msg, trace];
}
/*
* Marionette Methods
*/
@ -384,7 +414,7 @@ function executeScript(msg, directInject) {
let data = NetUtil.readInputStreamToString(stream, stream.available());
script = data + script;
}
let res = Cu.evalInSandbox(script, sandbox, "1.8");
let res = Cu.evalInSandbox(script, sandbox, "1.8", "dummy file" ,0);
sendSyncMessage("Marionette:shareData",
{log: elementManager.wrapValue(marionetteLogObj.getLogs())});
marionetteLogObj.clearLogs();
@ -406,16 +436,16 @@ function executeScript(msg, directInject) {
return;
}
let scriptSrc = "let __marionetteFunc = function(){" + script + "};" +
"__marionetteFunc.apply(null, __marionetteParams);";
script = "let __marionetteFunc = function(){" + script + "};" +
"__marionetteFunc.apply(null, __marionetteParams);";
if (importedScripts.exists()) {
let stream = Components.classes["@mozilla.org/network/file-input-stream;1"].
createInstance(Components.interfaces.nsIFileInputStream);
stream.init(importedScripts, -1, 0, 0);
let data = NetUtil.readInputStreamToString(stream, stream.available());
scriptSrc = data + scriptSrc;
script = data + script;
}
let res = Cu.evalInSandbox(scriptSrc, sandbox, "1.8");
let res = Cu.evalInSandbox(script, sandbox, "1.8", "dummy file", 0);
sendSyncMessage("Marionette:shareData",
{log: elementManager.wrapValue(marionetteLogObj.getLogs())});
marionetteLogObj.clearLogs();
@ -424,7 +454,12 @@ function executeScript(msg, directInject) {
}
catch (e) {
// 17 = JavascriptException
sendError(e.name + ': ' + e.message, 17, e.stack, asyncTestCommandId);
let error = createStackMessage(e,
"execute_script",
msg.json.filename,
msg.json.line,
script);
sendError(error[0], 17, error[1], asyncTestCommandId);
}
}
@ -531,11 +566,15 @@ function executeWithCallback(msg, useFinish) {
let data = NetUtil.readInputStreamToString(stream, stream.available());
scriptSrc = data + scriptSrc;
}
Cu.evalInSandbox(scriptSrc, sandbox, "1.8");
Cu.evalInSandbox(scriptSrc, sandbox, "1.8", "dummy file", 0);
} catch (e) {
// 17 = JavascriptException
sandbox.asyncComplete(e.name + ': ' + e.message, 17,
e.stack, asyncTestCommandId);
let error = createStackMessage(e,
"execute_async_script",
msg.json.filename,
msg.json.line,
scriptSrc);
sandbox.asyncComplete(error[0], 17, error[1], asyncTestCommandId);
}
}

View File

@ -380,6 +380,37 @@ MarionetteServerConnection.prototype = {
this.sendToClient({from:this.actorID, error: error_msg}, command_id);
},
/**
* Creates an error message for a JavaScript exception thrown during
* execute_(async_)script.
*
* This will generate a [msg, trace] pair like:
*
* ['ReferenceError: foo is not defined',
* 'execute_script @test_foo.py, line 10
* inline javascript, line 2
* src: "return foo;"']
*
* @param error An Error object passed to a catch() clause.
fnName The name of the function to use in the stack trace message
(e.g., 'execute_script').
pythonFile The filename of the test file containing the Marionette
command that caused this exception to occur.
pythonLine The line number of the above test file.
script The JS script being executed in text form.
*/
createStackMessage: function MDA_createStackMessage(error, fnName, pythonFile,
pythonLine, script) {
let python_stack = fnName + " @" + pythonFile + ", line " + pythonLine;
let stack = error.stack.split("\n");
let line = stack[0].substr(stack[0].lastIndexOf(':') + 1);
let msg = error.name + ": " + error.message;
let trace = python_stack +
"\ninline javascript, line " + line +
"\nsrc: \"" + script.split("\n")[line] + "\"";
return [msg, trace];
},
/**
* Gets the current active window
*
@ -737,7 +768,7 @@ MarionetteServerConnection.prototype = {
script = data + script;
}
let res = Cu.evalInSandbox(script, sandbox, "1.8");
let res = Cu.evalInSandbox(script, sandbox, "1.8", "dummy file", 0);
if (directInject && !async &&
(res == undefined || res.passed == undefined)) {
@ -765,6 +796,7 @@ MarionetteServerConnection.prototype = {
execute: function MDA_execute(aRequest, directInject) {
let timeout = aRequest.scriptTimeout ? aRequest.scriptTimeout : this.scriptTimeout;
let command_id = this.command_id = this.getCommandId();
let script;
this.logRequest("execute", aRequest);
if (aRequest.newSandbox == undefined) {
//if client does not send a value in newSandbox,
@ -778,7 +810,9 @@ MarionetteServerConnection.prototype = {
args: aRequest.args,
newSandbox: aRequest.newSandbox,
timeout: timeout,
specialPowers: aRequest.specialPowers
specialPowers: aRequest.specialPowers,
filename: aRequest.filename,
line: aRequest.line
},
command_id);
return;
@ -801,7 +835,6 @@ MarionetteServerConnection.prototype = {
return marionette.generate_results();
};
let script;
if (directInject) {
script = aRequest.value;
}
@ -815,7 +848,12 @@ MarionetteServerConnection.prototype = {
false, command_id, timeout);
}
catch (e) {
this.sendError(e.name + ': ' + e.message, 17, e.stack, command_id);
let error = this.createStackMessage(e,
"execute_script",
aRequest.filename,
aRequest.line,
script);
this.sendError(error[0], 17, error[1], command_id);
}
},
@ -894,6 +932,7 @@ MarionetteServerConnection.prototype = {
executeWithCallback: function MDA_executeWithCallback(aRequest, directInject) {
let timeout = aRequest.scriptTimeout ? aRequest.scriptTimeout : this.scriptTimeout;
let command_id = this.command_id = this.getCommandId();
let script;
this.logRequest("executeWithCallback", aRequest);
if (aRequest.newSandbox == undefined) {
//if client does not send a value in newSandbox,
@ -909,7 +948,9 @@ MarionetteServerConnection.prototype = {
id: this.command_id,
newSandbox: aRequest.newSandbox,
timeout: timeout,
specialPowers: aRequest.specialPowers
specialPowers: aRequest.specialPowers,
filename: aRequest.filename,
line: aRequest.line
},
command_id);
return;
@ -924,7 +965,7 @@ MarionetteServerConnection.prototype = {
timeout, this.testName);
marionette.command_id = this.command_id;
function chromeAsyncReturnFunc(value, status) {
function chromeAsyncReturnFunc(value, status, stacktrace) {
if (that._emu_cbs && Object.keys(that._emu_cbs).length) {
value = "Emulator callback still pending when finish() called";
status = 500;
@ -946,7 +987,7 @@ MarionetteServerConnection.prototype = {
marionette.command_id);
}
else {
let error_msg = {message: value, status: status, stacktrace: null};
let error_msg = {message: value, status: status, stacktrace: stacktrace};
that.sendToClient({from: that.actorID, error: error_msg},
marionette.command_id);
}
@ -982,7 +1023,6 @@ MarionetteServerConnection.prototype = {
_chromeSandbox.returnFunc = chromeAsyncReturnFunc;
_chromeSandbox.finish = chromeAsyncFinish;
let script;
if (directInject) {
script = aRequest.value;
}
@ -996,7 +1036,12 @@ MarionetteServerConnection.prototype = {
this.executeScriptInSandbox(_chromeSandbox, script, directInject,
true, command_id, timeout);
} catch (e) {
chromeAsyncReturnFunc(e.name + ": " + e.message, 17);
let error = this.createStackMessage(e,
"execute_async_script",
aRequest.filename,
aRequest.line,
script);
chromeAsyncReturnFunc(error[0], 17, error[1]);
}
},