diff options
Diffstat (limited to 'devtools/client/shared/remote-debugging/adb/xpcshell')
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"] |