зеркало из https://github.com/mozilla/gecko-dev.git
Backed out changeset 93166201fca0 (bug 941085) for Gip failures.
CLOSED TREE
This commit is contained in:
Родитель
4ed9fdd437
Коммит
d2e5e61478
|
@ -18,6 +18,7 @@ from runner import (
|
||||||
MarionetteTestResult,
|
MarionetteTestResult,
|
||||||
MarionetteTextTestRunner,
|
MarionetteTextTestRunner,
|
||||||
MemoryEnduranceTestCaseMixin,
|
MemoryEnduranceTestCaseMixin,
|
||||||
|
MozHttpd,
|
||||||
OptionParser,
|
OptionParser,
|
||||||
TestManifest,
|
TestManifest,
|
||||||
TestResult,
|
TestResult,
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
from base import (
|
from base import (
|
||||||
B2GTestResultMixin, BaseMarionetteOptions, BaseMarionetteTestRunner,
|
B2GTestResultMixin, BaseMarionetteOptions, BaseMarionetteTestRunner,
|
||||||
Marionette, MarionetteTest, MarionetteTestResult, MarionetteTextTestRunner,
|
Marionette, MarionetteTest, MarionetteTestResult, MarionetteTextTestRunner,
|
||||||
OptionParser, TestManifest, TestResult, TestResultCollection
|
MozHttpd, OptionParser, TestManifest, TestResult, TestResultCollection
|
||||||
)
|
)
|
||||||
from mixins import (
|
from mixins import (
|
||||||
B2GTestCaseMixin, B2GTestResultMixin, EnduranceOptionsMixin,
|
B2GTestCaseMixin, B2GTestResultMixin, EnduranceOptionsMixin,
|
||||||
|
|
|
@ -20,13 +20,12 @@ from manifestparser import TestManifest
|
||||||
from manifestparser.filters import tags
|
from manifestparser.filters import tags
|
||||||
from marionette_driver.marionette import Marionette
|
from marionette_driver.marionette import Marionette
|
||||||
from mixins.b2g import B2GTestResultMixin, get_b2g_pid, get_dm
|
from mixins.b2g import B2GTestResultMixin, get_b2g_pid, get_dm
|
||||||
|
from mozhttpd import MozHttpd
|
||||||
from mozlog.structured.structuredlog import get_default_logger
|
from mozlog.structured.structuredlog import get_default_logger
|
||||||
from moztest.adapters.unit import StructuredTestRunner, StructuredTestResult
|
from moztest.adapters.unit import StructuredTestRunner, StructuredTestResult
|
||||||
from moztest.results import TestResultCollection, TestResult, relevant_line
|
from moztest.results import TestResultCollection, TestResult, relevant_line
|
||||||
import mozversion
|
import mozversion
|
||||||
|
|
||||||
import httpd
|
|
||||||
|
|
||||||
|
|
||||||
here = os.path.abspath(os.path.dirname(__file__))
|
here = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
@ -639,6 +638,24 @@ class BaseMarionetteTestRunner(object):
|
||||||
self.skipped = 0
|
self.skipped = 0
|
||||||
self.failures = []
|
self.failures = []
|
||||||
|
|
||||||
|
def start_httpd(self, need_external_ip):
|
||||||
|
if self.server_root is None or os.path.isdir(self.server_root):
|
||||||
|
host = '127.0.0.1'
|
||||||
|
if need_external_ip:
|
||||||
|
host = moznetwork.get_ip()
|
||||||
|
docroot = self.server_root or os.path.join(os.path.dirname(here), 'www')
|
||||||
|
if not os.path.isdir(docroot):
|
||||||
|
raise Exception('Server root %s is not a valid path' % docroot)
|
||||||
|
self.httpd = MozHttpd(host=host,
|
||||||
|
port=0,
|
||||||
|
docroot=docroot)
|
||||||
|
self.httpd.start()
|
||||||
|
self.marionette.baseurl = 'http://%s:%d/' % (host, self.httpd.httpd.server_port)
|
||||||
|
self.logger.info('running webserver on %s' % self.marionette.baseurl)
|
||||||
|
else:
|
||||||
|
self.marionette.baseurl = self.server_root
|
||||||
|
self.logger.info('using content from %s' % self.marionette.baseurl)
|
||||||
|
|
||||||
def _build_kwargs(self):
|
def _build_kwargs(self):
|
||||||
kwargs = {
|
kwargs = {
|
||||||
'device_serial': self.device_serial,
|
'device_serial': self.device_serial,
|
||||||
|
@ -763,9 +780,7 @@ setReq.onerror = function() {
|
||||||
|
|
||||||
if not self.httpd:
|
if not self.httpd:
|
||||||
self.logger.info("starting httpd")
|
self.logger.info("starting httpd")
|
||||||
self.httpd = self.create_httpd(need_external_ip)
|
self.start_httpd(need_external_ip)
|
||||||
self.marionette.baseurl = self.httpd.get_url()
|
|
||||||
self.logger.info("running httpd on %s" % self.marionette.baseurl)
|
|
||||||
|
|
||||||
for test in tests:
|
for test in tests:
|
||||||
self.add_test(test)
|
self.add_test(test)
|
||||||
|
@ -846,15 +861,6 @@ setReq.onerror = function() {
|
||||||
|
|
||||||
self.logger.suite_end()
|
self.logger.suite_end()
|
||||||
|
|
||||||
def create_httpd(self, need_external_ip):
|
|
||||||
ip = "127.0.0.1"
|
|
||||||
if need_external_ip:
|
|
||||||
ip = moznetwork.get_ip()
|
|
||||||
root = self.server_root or os.path.join(os.path.dirname(here), "www")
|
|
||||||
rv = httpd.FixtureServer(root, host=ip)
|
|
||||||
rv.start()
|
|
||||||
return rv
|
|
||||||
|
|
||||||
def add_test(self, test, expected='pass', test_container=None):
|
def add_test(self, test, expected='pass', test_container=None):
|
||||||
filepath = os.path.abspath(test)
|
filepath = os.path.abspath(test)
|
||||||
|
|
||||||
|
|
|
@ -1,57 +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/.
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from mozhttpd import MozHttpd
|
|
||||||
|
|
||||||
|
|
||||||
class FixtureServer(object):
|
|
||||||
|
|
||||||
def __init__(self, root, host="127.0.0.1", port=0):
|
|
||||||
if not os.path.isdir(root):
|
|
||||||
raise Exception("Server root is not a valid path: %s" % root)
|
|
||||||
self.root = root
|
|
||||||
self.host = host
|
|
||||||
self.port = port
|
|
||||||
self._server = None
|
|
||||||
|
|
||||||
def start(self, block=False):
|
|
||||||
if self.alive:
|
|
||||||
return
|
|
||||||
self._server = MozHttpd(host=self.host, port=self.port, docroot=self.root, urlhandlers=[
|
|
||||||
{"method": "POST", "path": "/file_upload", "function": upload_handler}])
|
|
||||||
self._server.start(block=block)
|
|
||||||
self.port = self._server.httpd.server_port
|
|
||||||
self.base_url = self.get_url()
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
if not self.alive:
|
|
||||||
return
|
|
||||||
self._server.stop()
|
|
||||||
self._server = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def alive(self):
|
|
||||||
return self._server is not None
|
|
||||||
|
|
||||||
def get_url(self, path="/"):
|
|
||||||
if not self.alive:
|
|
||||||
raise "Server not started"
|
|
||||||
return self._server.get_url(path)
|
|
||||||
|
|
||||||
|
|
||||||
def upload_handler(query, postdata=None):
|
|
||||||
return (200, {}, query.headers.getheader("Content-Type"))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
here = os.path.abspath(os.path.dirname(__file__))
|
|
||||||
root = os.path.join(os.path.dirname(here), "www")
|
|
||||||
httpd = FixtureServer(root, port=2829)
|
|
||||||
print "Started fixture server on http://%s:%d/" % (httpd.host, httpd.port)
|
|
||||||
try:
|
|
||||||
httpd.start(True)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
pass
|
|
|
@ -1,135 +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/.
|
|
||||||
|
|
||||||
import contextlib
|
|
||||||
import urllib
|
|
||||||
|
|
||||||
from tempfile import NamedTemporaryFile as tempfile
|
|
||||||
|
|
||||||
from marionette import MarionetteTestCase, skip
|
|
||||||
from marionette_driver import By, errors, expected
|
|
||||||
from marionette_driver.wait import Wait
|
|
||||||
|
|
||||||
|
|
||||||
single = "data:text/html,%s" % urllib.quote("<input type=file>")
|
|
||||||
multiple = "data:text/html,%s" % urllib.quote("<input type=file multiple>")
|
|
||||||
upload = lambda url: "data:text/html,%s" % urllib.quote("""
|
|
||||||
<form action='%s' method=post enctype='multipart/form-data'>
|
|
||||||
<input type=file>
|
|
||||||
<input type=submit>
|
|
||||||
</form>""" % url)
|
|
||||||
|
|
||||||
|
|
||||||
class TestFileUpload(MarionetteTestCase):
|
|
||||||
|
|
||||||
def test_sets_one_file(self):
|
|
||||||
self.marionette.navigate(single)
|
|
||||||
input = self.input
|
|
||||||
|
|
||||||
exp = None
|
|
||||||
with tempfile() as f:
|
|
||||||
input.send_keys(f.name)
|
|
||||||
exp = [f.name]
|
|
||||||
|
|
||||||
files = self.get_files(input)
|
|
||||||
self.assertEqual(len(files), 1)
|
|
||||||
self.assertFilesEqual(files, exp)
|
|
||||||
|
|
||||||
def test_sets_multiple_files(self):
|
|
||||||
self.marionette.navigate(multiple)
|
|
||||||
input = self.input
|
|
||||||
|
|
||||||
exp = None
|
|
||||||
with contextlib.nested(tempfile(), tempfile()) as (a, b):
|
|
||||||
input.send_keys(a.name)
|
|
||||||
input.send_keys(b.name)
|
|
||||||
exp = [a.name, b.name]
|
|
||||||
|
|
||||||
files = self.get_files(input)
|
|
||||||
self.assertEqual(len(files), 2)
|
|
||||||
self.assertFilesEqual(files, exp)
|
|
||||||
|
|
||||||
def test_sets_multiple_indentical_files(self):
|
|
||||||
self.marionette.navigate(multiple)
|
|
||||||
input = self.input
|
|
||||||
|
|
||||||
exp = []
|
|
||||||
with tempfile() as f:
|
|
||||||
input.send_keys(f.name)
|
|
||||||
input.send_keys(f.name)
|
|
||||||
exp = f.name
|
|
||||||
|
|
||||||
files = self.get_files(input)
|
|
||||||
self.assertEqual(len(files), 2)
|
|
||||||
self.assertFilesEqual(files, exp)
|
|
||||||
|
|
||||||
def test_clear_file(self):
|
|
||||||
self.marionette.navigate(single)
|
|
||||||
input = self.input
|
|
||||||
|
|
||||||
with tempfile() as f:
|
|
||||||
input.send_keys(f.name)
|
|
||||||
|
|
||||||
self.assertEqual(len(self.get_files(input)), 1)
|
|
||||||
input.clear()
|
|
||||||
self.assertEqual(len(self.get_files(input)), 0)
|
|
||||||
|
|
||||||
def test_clear_files(self):
|
|
||||||
self.marionette.navigate(multiple)
|
|
||||||
input = self.input
|
|
||||||
|
|
||||||
with contextlib.nested(tempfile(), tempfile()) as (a, b):
|
|
||||||
input.send_keys(a.name)
|
|
||||||
input.send_keys(b.name)
|
|
||||||
|
|
||||||
self.assertEqual(len(self.get_files(input)), 2)
|
|
||||||
input.clear()
|
|
||||||
self.assertEqual(len(self.get_files(input)), 0)
|
|
||||||
|
|
||||||
def test_illegal_file(self):
|
|
||||||
self.marionette.navigate(single)
|
|
||||||
with self.assertRaisesRegexp(errors.MarionetteException, "File not found"):
|
|
||||||
self.input.send_keys("rochefort")
|
|
||||||
|
|
||||||
def test_upload(self):
|
|
||||||
self.marionette.navigate(
|
|
||||||
upload(self.marionette.absolute_url("file_upload")))
|
|
||||||
|
|
||||||
with tempfile() as f:
|
|
||||||
f.write("camembert")
|
|
||||||
f.flush()
|
|
||||||
self.input.send_keys(f.name)
|
|
||||||
self.submit.click()
|
|
||||||
|
|
||||||
self.assertIn("multipart/form-data", self.body.text)
|
|
||||||
|
|
||||||
def find_inputs(self):
|
|
||||||
return self.marionette.find_elements("tag name", "input")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def input(self):
|
|
||||||
return self.find_inputs()[0]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def submit(self):
|
|
||||||
return self.find_inputs()[1]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def body(self):
|
|
||||||
return Wait(self.marionette).until(
|
|
||||||
expected.element_present(By.TAG_NAME, "body"))
|
|
||||||
|
|
||||||
def get_files(self, el):
|
|
||||||
# This is horribly complex because (1) Marionette doesn't serialise arrays properly,
|
|
||||||
# and (2) accessing File.name in the content JS throws a permissions
|
|
||||||
# error.
|
|
||||||
fl = self.marionette.execute_script(
|
|
||||||
"return arguments[0].files", script_args=[el])
|
|
||||||
return [f["name"] for f in [v for k, v in fl.iteritems() if k.isdigit()]]
|
|
||||||
|
|
||||||
def assertFilesEqual(self, act, exp):
|
|
||||||
# File array returned from browser doesn't contain full path names,
|
|
||||||
# this cuts off the path of the expected files.
|
|
||||||
filenames = [f.rsplit("/", 0)[-1] for f in act]
|
|
||||||
self.assertListEqual(filenames, act)
|
|
|
@ -154,6 +154,3 @@ b2g = false
|
||||||
b2g = false
|
b2g = false
|
||||||
[test_teardown_context_preserved.py]
|
[test_teardown_context_preserved.py]
|
||||||
b2g = false
|
b2g = false
|
||||||
[test_file_upload.py]
|
|
||||||
b2g = false
|
|
||||||
skip-if = os == "win" # http://bugs.python.org/issue14574
|
|
||||||
|
|
|
@ -884,9 +884,8 @@ GeckoDriver.prototype.executeScriptInSandbox = function(
|
||||||
directInject,
|
directInject,
|
||||||
async,
|
async,
|
||||||
timeout) {
|
timeout) {
|
||||||
if (directInject && async && (timeout == null || timeout == 0)) {
|
if (directInject && async && (timeout == null || timeout == 0))
|
||||||
throw new TimeoutError("Please set a timeout");
|
throw new TimeoutError("Please set a timeout");
|
||||||
}
|
|
||||||
|
|
||||||
if (this.importedScripts.exists()) {
|
if (this.importedScripts.exists()) {
|
||||||
let stream = Cc["@mozilla.org/network/file-input-stream;1"]
|
let stream = Cc["@mozilla.org/network/file-input-stream;1"]
|
||||||
|
@ -2308,10 +2307,6 @@ GeckoDriver.prototype.getElementRect = function(cmd, resp) {
|
||||||
GeckoDriver.prototype.sendKeysToElement = function(cmd, resp) {
|
GeckoDriver.prototype.sendKeysToElement = function(cmd, resp) {
|
||||||
let {id, value} = cmd.parameters;
|
let {id, value} = cmd.parameters;
|
||||||
|
|
||||||
if (!value) {
|
|
||||||
throw new IllegalArgumentError(`Expected character sequence: ${value}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (this.context) {
|
switch (this.context) {
|
||||||
case Context.CHROME:
|
case Context.CHROME:
|
||||||
let win = this.getCurrentWindow();
|
let win = this.getCurrentWindow();
|
||||||
|
@ -2327,36 +2322,7 @@ GeckoDriver.prototype.sendKeysToElement = function(cmd, resp) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Context.CONTENT:
|
case Context.CONTENT:
|
||||||
let err;
|
|
||||||
let listener = function(msg) {
|
|
||||||
this.mm.removeMessageListener("Marionette:setElementValue", listener);
|
|
||||||
|
|
||||||
let val = msg.data.value;
|
|
||||||
let el = msg.objects.element;
|
|
||||||
let win = this.getCurrentWindow();
|
|
||||||
|
|
||||||
if (el.type == "file") {
|
|
||||||
Cu.importGlobalProperties(["File"]);
|
|
||||||
let fs = Array.prototype.slice.call(el.files);
|
|
||||||
let file;
|
|
||||||
try {
|
|
||||||
file = new File(val);
|
|
||||||
} catch (e) {
|
|
||||||
err = new IllegalArgumentError(`File not found: ${val}`);
|
|
||||||
}
|
|
||||||
fs.push(file);
|
|
||||||
el.mozSetFileArray(fs);
|
|
||||||
} else {
|
|
||||||
el.value = val;
|
|
||||||
}
|
|
||||||
}.bind(this);
|
|
||||||
|
|
||||||
this.mm.addMessageListener("Marionette:setElementValue", listener);
|
|
||||||
yield this.listener.sendKeysToElement({id: id, value: value});
|
yield this.listener.sendKeysToElement({id: id, value: value});
|
||||||
this.mm.removeMessageListener("Marionette:setElementValue", listener);
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,7 +10,6 @@ const errors = [
|
||||||
"ElementNotVisibleError",
|
"ElementNotVisibleError",
|
||||||
"FrameSendFailureError",
|
"FrameSendFailureError",
|
||||||
"FrameSendNotInitializedError",
|
"FrameSendNotInitializedError",
|
||||||
"IllegalArgumentError",
|
|
||||||
"InvalidElementStateError",
|
"InvalidElementStateError",
|
||||||
"JavaScriptError",
|
"JavaScriptError",
|
||||||
"NoAlertOpenError",
|
"NoAlertOpenError",
|
||||||
|
@ -159,14 +158,6 @@ this.FrameSendNotInitializedError = function(frame) {
|
||||||
};
|
};
|
||||||
FrameSendNotInitializedError.prototype = Object.create(WebDriverError.prototype);
|
FrameSendNotInitializedError.prototype = Object.create(WebDriverError.prototype);
|
||||||
|
|
||||||
this.IllegalArgumentError = function(msg) {
|
|
||||||
WebDriverError.call(this, msg);
|
|
||||||
this.name = "IllegalArgumentError";
|
|
||||||
this.status = "illegal argument";
|
|
||||||
this.code = 13; // unknown error
|
|
||||||
};
|
|
||||||
IllegalArgumentError.prototype = Object.create(WebDriverError.prototype);
|
|
||||||
|
|
||||||
this.InvalidElementStateError = function(msg) {
|
this.InvalidElementStateError = function(msg) {
|
||||||
WebDriverError.call(this, msg);
|
WebDriverError.call(this, msg);
|
||||||
this.name = "InvalidElementStateError";
|
this.name = "InvalidElementStateError";
|
||||||
|
@ -308,7 +299,6 @@ const errorObjs = [
|
||||||
this.ElementNotVisibleError,
|
this.ElementNotVisibleError,
|
||||||
this.FrameSendFailureError,
|
this.FrameSendFailureError,
|
||||||
this.FrameSendNotInitializedError,
|
this.FrameSendNotInitializedError,
|
||||||
this.IllegalArgumentError,
|
|
||||||
this.InvalidElementStateError,
|
this.InvalidElementStateError,
|
||||||
this.JavaScriptError,
|
this.JavaScriptError,
|
||||||
this.NoAlertOpenError,
|
this.NoAlertOpenError,
|
||||||
|
|
|
@ -35,10 +35,9 @@ let isB2G = false;
|
||||||
|
|
||||||
let marionetteTestName;
|
let marionetteTestName;
|
||||||
let winUtil = content.QueryInterface(Ci.nsIInterfaceRequestor)
|
let winUtil = content.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
.getInterface(Ci.nsIDOMWindowUtils);
|
.getInterface(Ci.nsIDOMWindowUtils);
|
||||||
let listenerId = null; // unique ID of this listener
|
let listenerId = null; //unique ID of this listener
|
||||||
let curFrame = content;
|
let curFrame = content;
|
||||||
let isRemoteBrowser = () => curFrame.contentWindow !== null;
|
|
||||||
let previousFrame = null;
|
let previousFrame = null;
|
||||||
let elementManager = new ElementManager([]);
|
let elementManager = new ElementManager([]);
|
||||||
let accessibility = new Accessibility();
|
let accessibility = new Accessibility();
|
||||||
|
@ -1549,39 +1548,11 @@ function isElementSelected(msg) {
|
||||||
*/
|
*/
|
||||||
function sendKeysToElement(msg) {
|
function sendKeysToElement(msg) {
|
||||||
let command_id = msg.json.command_id;
|
let command_id = msg.json.command_id;
|
||||||
let val = msg.json.value;
|
|
||||||
|
|
||||||
let el = elementManager.getKnownElement(msg.json.id, curFrame);
|
let el = elementManager.getKnownElement(msg.json.id, curFrame);
|
||||||
if (el.type == "file") {
|
let keysToSend = msg.json.value;
|
||||||
let p = val.join("");
|
|
||||||
|
|
||||||
// for some reason using mozSetFileArray doesn't work with e10s
|
utils.sendKeysToElement(curFrame, el, keysToSend, sendOk, sendError, command_id);
|
||||||
// enabled (probably a bug), but a workaround is to elevate the element's
|
|
||||||
// privileges with SpecialPowers
|
|
||||||
//
|
|
||||||
// this extra branch can be removed when the e10s bug 1149998 is fixed
|
|
||||||
if (isRemoteBrowser()) {
|
|
||||||
let fs = Array.prototype.slice.call(el.files);
|
|
||||||
let file;
|
|
||||||
try {
|
|
||||||
file = new File(p);
|
|
||||||
} catch (e) {
|
|
||||||
let err = new IllegalArgumentError(`File not found: ${val}`);
|
|
||||||
sendError(err.message, err.code, err.stack, command_id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fs.push(file);
|
|
||||||
|
|
||||||
let wel = new SpecialPowers(utils.window).wrap(el);
|
|
||||||
wel.mozSetFileArray(fs);
|
|
||||||
} else {
|
|
||||||
sendSyncMessage("Marionette:setElementValue", {value: p}, {element: el});
|
|
||||||
}
|
|
||||||
|
|
||||||
sendOk(command_id);
|
|
||||||
} else {
|
|
||||||
utils.sendKeysToElement(curFrame, el, val, sendOk, sendError, command_id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1611,13 +1582,10 @@ function clearElement(msg) {
|
||||||
let command_id = msg.json.command_id;
|
let command_id = msg.json.command_id;
|
||||||
try {
|
try {
|
||||||
let el = elementManager.getKnownElement(msg.json.id, curFrame);
|
let el = elementManager.getKnownElement(msg.json.id, curFrame);
|
||||||
if (el.type == "file") {
|
utils.clearElement(el);
|
||||||
el.value = null;
|
|
||||||
} else {
|
|
||||||
utils.clearElement(el);
|
|
||||||
}
|
|
||||||
sendOk(command_id);
|
sendOk(command_id);
|
||||||
} catch (e) {
|
}
|
||||||
|
catch (e) {
|
||||||
sendError(e.message, e.code, e.stack, command_id);
|
sendError(e.message, e.code, e.stack, command_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче