summaryrefslogtreecommitdiffstats
path: root/devtools/client/shared/remote-debugging/adb/xpcshell
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/shared/remote-debugging/adb/xpcshell')
-rw-r--r--devtools/client/shared/remote-debugging/adb/xpcshell/.eslintrc.js9
-rw-r--r--devtools/client/shared/remote-debugging/adb/xpcshell/adb.py72
-rw-r--r--devtools/client/shared/remote-debugging/adb/xpcshell/test_adb.js247
-rw-r--r--devtools/client/shared/remote-debugging/adb/xpcshell/test_prepare-tcp-connection.js78
-rw-r--r--devtools/client/shared/remote-debugging/adb/xpcshell/xpcshell-head.js10
-rw-r--r--devtools/client/shared/remote-debugging/adb/xpcshell/xpcshell.toml14
6 files changed, 430 insertions, 0 deletions
diff --git a/devtools/client/shared/remote-debugging/adb/xpcshell/.eslintrc.js b/devtools/client/shared/remote-debugging/adb/xpcshell/.eslintrc.js
new file mode 100644
index 0000000000..e4da98dd14
--- /dev/null
+++ b/devtools/client/shared/remote-debugging/adb/xpcshell/.eslintrc.js
@@ -0,0 +1,9 @@
+/* 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/. */
+
+"use strict";
+
+module.exports = {
+ extends: "../../../../../.eslintrc.xpcshell.js",
+};
diff --git a/devtools/client/shared/remote-debugging/adb/xpcshell/adb.py b/devtools/client/shared/remote-debugging/adb/xpcshell/adb.py
new file mode 100644
index 0000000000..4b720a1f86
--- /dev/null
+++ b/devtools/client/shared/remote-debugging/adb/xpcshell/adb.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env 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/.
+
+"""
+A fake ADB binary
+"""
+
+import os
+import socketserver
+import sys
+
+HOST = "127.0.0.1"
+PORT = 5037
+
+
+class ADBRequestHandler(socketserver.BaseRequestHandler):
+ def sendData(self, data):
+ header = "OKAY%04x" % len(data)
+ all_data = header + data
+ total_length = len(all_data)
+ sent_length = 0
+ # Make sure send all data to the client.
+ # Though the data length here is pretty small but sometimes when the
+ # client is on heavy load (e.g. MOZ_CHAOSMODE) we can't send the whole
+ # data at once.
+ while sent_length < total_length:
+ sent = self.request.send(all_data[sent_length:].encode("utf-8", "replace"))
+ sent_length = sent_length + sent
+
+ def handle(self):
+ while True:
+ data = self.request.recv(4096).decode("utf-8", "replace")
+ if "host:kill" in data:
+ self.sendData("")
+ # Implicitly close all open sockets by exiting the program.
+ # This should be done ASAP, because upon receiving the OKAY,
+ # the client expects adb to have released the server's port.
+ os._exit(0)
+ break
+ elif "host:version" in data:
+ self.sendData("001F")
+ self.request.close()
+ break
+ elif "host:track-devices" in data:
+ self.sendData("1234567890\tdevice")
+ break
+
+
+class ADBServer(socketserver.TCPServer):
+ def __init__(self, server_address):
+ # Create a socketserver with bind_and_activate 'False' to set
+ # allow_reuse_address before binding.
+ socketserver.TCPServer.__init__(
+ self, server_address, ADBRequestHandler, bind_and_activate=False
+ )
+
+
+if len(sys.argv) == 2 and sys.argv[1] == "start-server":
+ # daemonize
+ if os.fork() > 0:
+ sys.exit(0)
+ os.setsid()
+ if os.fork() > 0:
+ sys.exit(0)
+
+ server = ADBServer((HOST, PORT))
+ server.allow_reuse_address = True
+ server.server_bind()
+ server.server_activate()
+ server.serve_forever()
diff --git a/devtools/client/shared/remote-debugging/adb/xpcshell/test_adb.js b/devtools/client/shared/remote-debugging/adb/xpcshell/test_adb.js
new file mode 100644
index 0000000000..dfbaab5a6b
--- /dev/null
+++ b/devtools/client/shared/remote-debugging/adb/xpcshell/test_adb.js
@@ -0,0 +1,247 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { ExtensionTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/ExtensionXPCShellUtils.sys.mjs"
+);
+const { NetUtil } = ChromeUtils.importESModule(
+ "resource://gre/modules/NetUtil.sys.mjs"
+);
+const {
+ getFileForBinary,
+} = require("resource://devtools/client/shared/remote-debugging/adb/adb-binary.js");
+const {
+ check,
+} = require("resource://devtools/client/shared/remote-debugging/adb/adb-running-checker.js");
+const {
+ adbProcess,
+} = require("resource://devtools/client/shared/remote-debugging/adb/adb-process.js");
+const {
+ TrackDevicesCommand,
+} = require("resource://devtools/client/shared/remote-debugging/adb/commands/index.js");
+
+const ADB_JSON = {
+ Linux: {
+ x86: ["linux/adb"],
+ x86_64: ["linux64/adb"],
+ },
+ Darwin: {
+ x86_64: ["mac64/adb"],
+ },
+ WINNT: {
+ x86: ["win32/adb.exe", "win32/AdbWinApi.dll", "win32/AdbWinUsbApi.dll"],
+ x86_64: ["win32/adb.exe", "win32/AdbWinApi.dll", "win32/AdbWinUsbApi.dll"],
+ },
+};
+let extension_version = 1.0;
+
+ExtensionTestUtils.init(this);
+
+function readAdbMockContent() {
+ const adbMockFile = do_get_file("adb.py", false);
+ const s = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ s.init(adbMockFile, -1, -1, false);
+ try {
+ return NetUtil.readInputStreamToString(s, s.available());
+ } finally {
+ s.close();
+ }
+}
+
+const adbMock = readAdbMockContent();
+
+add_task(async function setup() {
+ // Prepare the profile directory where the adb extension will be installed.
+ do_get_profile();
+});
+
+add_task(async function testAdbIsNotRunningInitially() {
+ const isAdbRunning = await check();
+ // Assume that no adb server running.
+ ok(!isAdbRunning, "adb is not running initially");
+});
+
+add_task(async function testNoAdbExtension() {
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ version: (extension_version++).toString(),
+ browser_specific_settings: {
+ gecko: { id: "not-adb@mozilla.org" },
+ },
+ },
+ });
+
+ await extension.startup();
+
+ const adbBinary = await getFileForBinary();
+ equal(adbBinary, null);
+
+ await extension.unload();
+});
+
+add_task(async function testNoAdbJSON() {
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ version: (extension_version++).toString(),
+ browser_specific_settings: {
+ // The extension id here and in later test cases should match the
+ // corresponding prefrece value.
+ gecko: { id: "adb@mozilla.org" },
+ },
+ },
+ });
+
+ await extension.startup();
+
+ const adbBinary = await getFileForBinary();
+ equal(adbBinary, null);
+
+ await extension.unload();
+});
+
+add_task(async function testNoTargetBinaries() {
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ version: (extension_version++).toString(),
+ browser_specific_settings: {
+ gecko: { id: "adb@mozilla.org" },
+ },
+ },
+ files: {
+ "adb.json": JSON.stringify(ADB_JSON),
+ },
+ });
+
+ await extension.startup();
+
+ const adbBinary = await getFileForBinary();
+ equal(adbBinary, null);
+
+ await extension.unload();
+});
+
+add_task(async function testExtract() {
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ version: (extension_version++).toString(),
+ browser_specific_settings: {
+ gecko: { id: "adb@mozilla.org" },
+ },
+ },
+ files: {
+ "adb.json": JSON.stringify(ADB_JSON),
+ "linux/adb": "adb",
+ "linux64/adb": "adb",
+ "mac64/adb": "adb",
+ "win32/adb.exe": "adb.exe",
+ "win32/AdbWinApi.dll": "AdbWinApi.dll",
+ "win32/AdbWinUsbApi.dll": "AdbWinUsbApi.dll",
+ },
+ });
+
+ await extension.startup();
+
+ const adbBinary = await getFileForBinary();
+ ok(await adbBinary.exists());
+
+ await extension.unload();
+});
+
+add_task(
+ {
+ skip_if: () => mozinfo.os == "win", // bug 1482008
+ },
+ async function testStartAndStop() {
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ version: (extension_version++).toString(),
+ browser_specific_settings: {
+ gecko: { id: "adb@mozilla.org" },
+ },
+ },
+ files: {
+ "adb.json": JSON.stringify(ADB_JSON),
+ "linux/adb": adbMock,
+ "linux64/adb": adbMock,
+ "mac64/adb": adbMock,
+ "win32/adb.exe": adbMock,
+ "win32/AdbWinApi.dll": "dummy",
+ "win32/AdbWinUsbApi.dll": "dummy",
+ },
+ });
+
+ await extension.startup();
+
+ // Call start() once and call stop() afterwards.
+ await adbProcess.start();
+ ok(adbProcess.ready);
+ ok(await check(), "adb is now running");
+
+ await adbProcess.stop();
+ ok(!adbProcess.ready);
+ ok(!(await check()), "adb is no longer running");
+
+ // Call start() twice and call stop() afterwards.
+ await adbProcess.start();
+ await adbProcess.start();
+ ok(adbProcess.ready);
+ ok(await check(), "adb is now running");
+
+ await adbProcess.stop();
+ ok(!adbProcess.ready);
+ ok(!(await check()), "adb is no longer running");
+
+ await extension.unload();
+ }
+);
+
+add_task(
+ {
+ skip_if: () => mozinfo.os == "win", // bug 1482008
+ },
+ async function testTrackDevices() {
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ version: (extension_version++).toString(),
+ browser_specific_settings: {
+ gecko: { id: "adb@mozilla.org" },
+ },
+ },
+ files: {
+ "adb.json": JSON.stringify(ADB_JSON),
+ "linux/adb": adbMock,
+ "linux64/adb": adbMock,
+ "mac64/adb": adbMock,
+ "win32/adb.exe": adbMock,
+ "win32/AdbWinApi.dll": "dummy",
+ "win32/AdbWinUsbApi.dll": "dummy",
+ },
+ });
+
+ await extension.startup();
+
+ await adbProcess.start();
+ ok(adbProcess.ready);
+
+ ok(await check(), "adb is now running");
+
+ const receivedDeviceId = await new Promise(resolve => {
+ const trackDevicesCommand = new TrackDevicesCommand();
+ trackDevicesCommand.on("device-connected", deviceId => {
+ resolve(deviceId);
+ });
+ trackDevicesCommand.run();
+ });
+
+ equal(receivedDeviceId, "1234567890");
+
+ await adbProcess.stop();
+ ok(!adbProcess.ready);
+
+ await extension.unload();
+ }
+);
diff --git a/devtools/client/shared/remote-debugging/adb/xpcshell/test_prepare-tcp-connection.js b/devtools/client/shared/remote-debugging/adb/xpcshell/test_prepare-tcp-connection.js
new file mode 100644
index 0000000000..09dad165f2
--- /dev/null
+++ b/devtools/client/shared/remote-debugging/adb/xpcshell/test_prepare-tcp-connection.js
@@ -0,0 +1,78 @@
+"use strict";
+
+const DEVICE_ID = "FAKE_DEVICE_ID";
+const SOCKET_PATH = "fake/socket/path";
+
+/**
+ * Test the prepare-tcp-connection command in isolation.
+ */
+add_task(async function testParseFileUri() {
+ info("Enable devtools.testing for this test to allow mocked modules");
+ Services.prefs.setBoolPref("devtools.testing", true);
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("devtools.testing");
+ });
+
+ // Mocks are not supported for the regular DevTools loader.
+ info("Create a BrowserLoader to enable mocks in the test");
+ const { BrowserLoader } = ChromeUtils.import(
+ "resource://devtools/shared/loader/browser-loader.js"
+ );
+ const mockedRequire = BrowserLoader({
+ baseURI: "resource://devtools/client/shared/remote-debugging/adb",
+ window: {},
+ }).require;
+
+ // Prepare a mocked version of the run-command.js module to test
+ // prepare-tcp-connection in isolation.
+ info("Create a run-command module mock");
+ let createdPort;
+ const mock = {
+ runCommand: command => {
+ // Check that we only receive the expected command and extract the port
+ // number.
+
+ // The command should follow the pattern
+ // `host-serial:${deviceId}:forward:tcp:${localPort};${devicePort}`
+ // with devicePort = `localfilesystem:${socketPath}`
+ const socketPathRe = SOCKET_PATH.replace(/\//g, "\\/");
+ const regexp = new RegExp(
+ `host-serial:${DEVICE_ID}:forward:tcp:(\\d+);localfilesystem:${socketPathRe}`
+ );
+ const matches = regexp.exec(command);
+ equal(matches.length, 2, "The command is the expected");
+ createdPort = matches[1];
+
+ // Finally return a Promise-like object.
+ return {
+ then: () => {},
+ };
+ },
+ };
+
+ info("Enable the mocked run-command module");
+ const { setMockedModule, removeMockedModule } = mockedRequire(
+ "devtools/shared/loader/browser-loader-mocks"
+ );
+ setMockedModule(
+ mock,
+ "devtools/client/shared/remote-debugging/adb/commands/run-command"
+ );
+ registerCleanupFunction(() => {
+ removeMockedModule(
+ "devtools/client/shared/remote-debugging/adb/commands/run-command"
+ );
+ });
+
+ const { prepareTCPConnection } = mockedRequire(
+ "devtools/client/shared/remote-debugging/adb/commands/prepare-tcp-connection"
+ );
+
+ const port = await prepareTCPConnection(DEVICE_ID, SOCKET_PATH);
+ ok(!Number.isNaN(port), "prepareTCPConnection returned a valid port");
+ equal(
+ port,
+ Number(createdPort),
+ "prepareTCPConnection returned the port sent to the host-serial command"
+ );
+});
diff --git a/devtools/client/shared/remote-debugging/adb/xpcshell/xpcshell-head.js b/devtools/client/shared/remote-debugging/adb/xpcshell/xpcshell-head.js
new file mode 100644
index 0000000000..733c0400da
--- /dev/null
+++ b/devtools/client/shared/remote-debugging/adb/xpcshell/xpcshell-head.js
@@ -0,0 +1,10 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+
+const { require } = ChromeUtils.importESModule(
+ "resource://devtools/shared/loader/Loader.sys.mjs"
+);
diff --git a/devtools/client/shared/remote-debugging/adb/xpcshell/xpcshell.toml b/devtools/client/shared/remote-debugging/adb/xpcshell/xpcshell.toml
new file mode 100644
index 0000000000..c31ffcecd2
--- /dev/null
+++ b/devtools/client/shared/remote-debugging/adb/xpcshell/xpcshell.toml
@@ -0,0 +1,14 @@
+[DEFAULT]
+tags = "devtools"
+head = "xpcshell-head.js"
+firefox-appdir = "browser"
+skip-if = [
+ "os == 'android'",
+ "socketprocess_networking",
+]
+support-files = ["adb.py"]
+
+["test_adb.js"]
+run-sequentially = "An extension having the same id is installed/uninstalled in different tests"
+
+["test_prepare-tcp-connection.js"]