Bug 1492700 - Split adb.js in several files;r=daisuke

Differential Revision: https://phabricator.services.mozilla.com/D13473

--HG--
rename : devtools/shared/adb/adb.js => devtools/shared/adb/commands/list-devices.js
rename : devtools/shared/adb/adb.js => devtools/shared/adb/commands/prepare-tcp-connection.js
rename : devtools/shared/adb/adb.js => devtools/shared/adb/commands/run-command.js
rename : devtools/shared/adb/adb.js => devtools/shared/adb/commands/shell.js
rename : devtools/shared/adb/adb.js => devtools/shared/adb/commands/track-devices.js
extra : moz-landing-system : lando
This commit is contained in:
Julian Descottes 2018-12-05 20:47:34 +00:00
parent ec7b320b38
commit b77ab873fa
14 changed files with 389 additions and 289 deletions

View File

@ -4,7 +4,7 @@
"use strict"; "use strict";
const { ADB } = require("devtools/shared/adb/adb"); const { prepareTCPConnection } = require("devtools/shared/adb/commands/index");
const { DebuggerClient } = require("devtools/shared/client/debugger-client"); const { DebuggerClient } = require("devtools/shared/client/debugger-client");
const { DebuggerServer } = require("devtools/server/main"); const { DebuggerServer } = require("devtools/server/main");
const { ClientWrapper } = require("./client-wrapper"); const { ClientWrapper } = require("./client-wrapper");
@ -29,7 +29,7 @@ async function createNetworkClient(host, port) {
} }
async function createUSBClient(socketPath) { async function createUSBClient(socketPath) {
const port = await ADB.prepareTCPConnection(socketPath); const port = await prepareTCPConnection(socketPath);
return createNetworkClient("localhost", port); return createNetworkClient("localhost", port);
} }

View File

@ -4,7 +4,7 @@
"use strict"; "use strict";
const { ADB } = require("devtools/shared/adb/adb"); const { shell } = require("devtools/shared/adb/commands/index");
/** /**
* A Device instance is created and registered with the Devices module whenever * A Device instance is created and registered with the Devices module whenever
@ -19,7 +19,7 @@ class AdbDevice {
if (this._model) { if (this._model) {
return this._model; return this._model;
} }
const model = await ADB.shell("getprop ro.product.model"); const model = await shell("getprop ro.product.model");
this._model = model.trim(); this._model = model.trim();
return this._model; return this._model;
} }
@ -33,7 +33,7 @@ class AdbDevice {
// 00000000: 00000002 00000000 00010000 0001 01 6551588 // 00000000: 00000002 00000000 00010000 0001 01 6551588
// /data/data/org.mozilla.fennec/firefox-debugger-socket // /data/data/org.mozilla.fennec/firefox-debugger-socket
const query = "cat /proc/net/unix"; const query = "cat /proc/net/unix";
const rawSocketInfo = await ADB.shell(query); const rawSocketInfo = await shell(query);
// Filter to lines with "firefox-debugger-socket" // Filter to lines with "firefox-debugger-socket"
let socketInfos = rawSocketInfo.split(/\r?\n/); let socketInfos = rawSocketInfo.split(/\r?\n/);

View File

@ -5,7 +5,7 @@
"use strict"; "use strict";
const { RuntimeTypes } = require("devtools/client/webide/modules/runtime-types"); const { RuntimeTypes } = require("devtools/client/webide/modules/runtime-types");
const { ADB } = require("devtools/shared/adb/adb"); const { prepareTCPConnection } = require("devtools/shared/adb/commands/index");
class AdbRuntime { class AdbRuntime {
constructor(adbDevice, model, socketPath) { constructor(adbDevice, model, socketPath) {
@ -33,7 +33,7 @@ class AdbRuntime {
} }
connect(connection) { connect(connection) {
return ADB.prepareTCPConnection(this._socketPath).then(port => { return prepareTCPConnection(this._socketPath).then(port => {
connection.host = "localhost"; connection.host = "localhost";
connection.port = port; connection.port = port;
connection.connect(); connection.connect();

View File

@ -7,6 +7,7 @@
const EventEmitter = require("devtools/shared/event-emitter"); const EventEmitter = require("devtools/shared/event-emitter");
const { dumpn } = require("devtools/shared/DevToolsUtils"); const { dumpn } = require("devtools/shared/DevToolsUtils");
const { ADB } = require("devtools/shared/adb/adb"); const { ADB } = require("devtools/shared/adb/adb");
const { trackDevices } = require("devtools/shared/adb/commands/index");
const { adbDevicesRegistry } = require("devtools/shared/adb/adb-devices-registry"); const { adbDevicesRegistry } = require("devtools/shared/adb/adb-devices-registry");
const { AdbRuntime } = require("devtools/shared/adb/adb-runtime"); const { AdbRuntime } = require("devtools/shared/adb/adb-runtime");
@ -30,7 +31,7 @@ class ADBScanner extends EventEmitter {
adbDevicesRegistry.on("unregister", this._updateRuntimes); adbDevicesRegistry.on("unregister", this._updateRuntimes);
ADB.start().then(() => { ADB.start().then(() => {
ADB.trackDevices(); trackDevices();
}); });
this._updateRuntimes(); this._updateRuntimes();
} }

View File

@ -7,21 +7,17 @@
"use strict"; "use strict";
const { Cc, Ci } = require("chrome"); const { Cc, Ci } = require("chrome");
const EventEmitter = require("devtools/shared/event-emitter");
const client = require("./adb-client");
const { dumpn } = require("devtools/shared/DevToolsUtils"); const { dumpn } = require("devtools/shared/DevToolsUtils");
const { getFileForBinary } = require("./adb-binary"); const { getFileForBinary } = require("./adb-binary");
const { setTimeout } = require("resource://gre/modules/Timer.jsm"); const { setTimeout } = require("resource://gre/modules/Timer.jsm");
const { Services } = require("resource://gre/modules/Services.jsm"); const { Services } = require("resource://gre/modules/Services.jsm");
const { ConnectionManager } = require("devtools/shared/client/connection-manager"); const { runCommand } = require("./commands/index");
loader.lazyRequireGetter(this, "check", loader.lazyRequireGetter(this, "check",
"devtools/shared/adb/adb-running-checker", true); "devtools/shared/adb/adb-running-checker", true);
let ready = false; let ready = false;
let didRunInitially = false; let didRunInitially = false;
const OKAY = 0x59414b4f;
const ADB = { const ADB = {
get didRunInitially() { get didRunInitially() {
return didRunInitially; return didRunInitially;
@ -137,7 +133,7 @@ const ADB = {
*/ */
async kill() { async kill() {
try { try {
await this.runCommand("host:kill"); await runCommand("host:kill");
} catch (e) { } catch (e) {
dumpn("Failed to send host:kill command"); dumpn("Failed to send host:kill command");
} }
@ -145,280 +141,6 @@ const ADB = {
this.ready = false; this.ready = false;
this.didRunInitially = false; this.didRunInitially = false;
}, },
// Start tracking devices connecting and disconnecting from the host.
// We can't reuse runCommand here because we keep the socket alive.
// @return The socket used.
trackDevices() {
dumpn("trackDevices");
const socket = client.connect();
let waitForFirst = true;
const devices = {};
socket.s.onopen = function() {
dumpn("trackDevices onopen");
Services.obs.notifyObservers(null, "adb-track-devices-start");
const req = client.createRequest("host:track-devices");
socket.send(req);
};
socket.s.onerror = function(event) {
dumpn("trackDevices onerror: " + event);
Services.obs.notifyObservers(null, "adb-track-devices-stop");
};
socket.s.onclose = function() {
dumpn("trackDevices onclose");
// Report all devices as disconnected
for (const dev in devices) {
devices[dev] = false;
EventEmitter.emit(ADB, "device-disconnected", dev);
}
Services.obs.notifyObservers(null, "adb-track-devices-stop");
// When we lose connection to the server,
// and the adb is still on, we most likely got our server killed
// by local adb. So we do try to reconnect to it.
setTimeout(function() { // Give some time to the new adb to start
if (ADB.ready) { // Only try to reconnect/restart if the add-on is still enabled
ADB.start().then(function() { // try to connect to the new local adb server
// or, spawn a new one
ADB.trackDevices(); // Re-track devices
});
}
}, 2000);
};
socket.s.ondata = function(event) {
dumpn("trackDevices ondata");
const data = event.data;
dumpn("length=" + data.byteLength);
const dec = new TextDecoder();
dumpn(dec.decode(new Uint8Array(data)).trim());
// check the OKAY or FAIL on first packet.
if (waitForFirst) {
if (!client.checkResponse(data, OKAY)) {
socket.close();
return;
}
}
const packet = client.unpackPacket(data, !waitForFirst);
waitForFirst = false;
if (packet.data == "") {
// All devices got disconnected.
for (const dev in devices) {
devices[dev] = false;
EventEmitter.emit(ADB, "device-disconnected", dev);
}
} else {
// One line per device, each line being $DEVICE\t(offline|device)
const lines = packet.data.split("\n");
const newDev = {};
lines.forEach(function(line) {
if (line.length == 0) {
return;
}
const [dev, status] = line.split("\t");
newDev[dev] = status !== "offline";
});
// Check which device changed state.
for (const dev in newDev) {
if (devices[dev] != newDev[dev]) {
if (dev in devices || newDev[dev]) {
const topic = newDev[dev] ? "device-connected"
: "device-disconnected";
EventEmitter.emit(ADB, topic, dev);
}
devices[dev] = newDev[dev];
}
}
}
};
},
// Sends back an array of device names.
listDevices() {
dumpn("listDevices");
return this.runCommand("host:devices").then(
function onSuccess(data) {
const lines = data.split("\n");
const res = [];
lines.forEach(function(line) {
if (line.length == 0) {
return;
}
const [ device ] = line.split("\t");
res.push(device);
});
return res;
}
);
},
// sends adb forward localPort devicePort
forwardPort(localPort, devicePort) {
dumpn("forwardPort " + localPort + " -- " + devicePort);
// <host-prefix>:forward:<local>;<remote>
return this.runCommand("host:forward:" + localPort + ";" + devicePort)
.then(function onSuccess(data) {
return data;
});
},
// Prepare TCP connection for provided socket path.
// The returned value is a port number of localhost for the connection.
async prepareTCPConnection(socketPath) {
const port = ConnectionManager.getFreeTCPPort();
const local = `tcp:${ port }`;
const remote = socketPath.startsWith("@")
? `localabstract:${ socketPath.substring(1) }`
: `localfilesystem:${ socketPath }`;
await this.forwardPort(local, remote);
return port;
},
// Run a shell command
async shell(command) {
let state;
let stdout = "";
dumpn("shell " + command);
return new Promise((resolve, reject) => {
const shutdown = function() {
dumpn("shell shutdown");
socket.close();
reject("BAD_RESPONSE");
};
const runFSM = function runFSM(data) {
dumpn("runFSM " + state);
let req;
let ignoreResponseCode = false;
switch (state) {
case "start":
state = "send-transport";
runFSM();
break;
case "send-transport":
req = client.createRequest("host:transport-any");
socket.send(req);
state = "wait-transport";
break;
case "wait-transport":
if (!client.checkResponse(data, OKAY)) {
shutdown();
return;
}
state = "send-shell";
runFSM();
break;
case "send-shell":
req = client.createRequest("shell:" + command);
socket.send(req);
state = "rec-shell";
break;
case "rec-shell":
if (!client.checkResponse(data, OKAY)) {
shutdown();
return;
}
state = "decode-shell";
if (client.getBuffer(data).byteLength == 4) {
break;
}
ignoreResponseCode = true;
// eslint-disable-next-lined no-fallthrough
case "decode-shell":
const decoder = new TextDecoder();
const text = new Uint8Array(client.getBuffer(data),
ignoreResponseCode ? 4 : 0);
stdout += decoder.decode(text);
break;
default:
dumpn("shell Unexpected State: " + state);
reject("UNEXPECTED_STATE");
}
};
const socket = client.connect();
socket.s.onerror = function(event) {
dumpn("shell onerror");
reject("SOCKET_ERROR");
};
socket.s.onopen = function(event) {
dumpn("shell onopen");
state = "start";
runFSM();
};
socket.s.onclose = function(event) {
resolve(stdout);
dumpn("shell onclose");
};
socket.s.ondata = function(event) {
dumpn("shell ondata");
runFSM(event.data);
};
});
},
// Asynchronously runs an adb command.
// @param command The command as documented in
// http://androidxref.com/4.0.4/xref/system/core/adb/SERVICES.TXT
runCommand(command) {
dumpn("runCommand " + command);
return new Promise((resolve, reject) => {
if (!this.ready) {
setTimeout(function() {
reject("ADB_NOT_READY");
});
return;
}
const socket = client.connect();
socket.s.onopen = function() {
dumpn("runCommand onopen");
const req = client.createRequest(command);
socket.send(req);
};
socket.s.onerror = function() {
dumpn("runCommand onerror");
reject("NETWORK_ERROR");
};
socket.s.onclose = function() {
dumpn("runCommand onclose");
};
socket.s.ondata = function(event) {
dumpn("runCommand ondata");
const data = event.data;
const packet = client.unpackPacket(data, false);
if (!client.checkResponse(data, OKAY)) {
socket.close();
dumpn("Error: " + packet.data);
reject("PROTOCOL_ERROR");
return;
}
resolve(packet.data);
};
});
},
}; };
exports.ADB = ADB; exports.ADB = ADB;

View File

@ -0,0 +1,19 @@
/* 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";
const { listDevices } = require("./list-devices");
const { prepareTCPConnection } = require("./prepare-tcp-connection");
const { runCommand } = require("./run-command");
const { shell } = require("./shell");
const { trackDevices } = require("./track-devices");
module.exports = {
listDevices,
prepareTCPConnection,
runCommand,
shell,
trackDevices,
};

View File

@ -0,0 +1,31 @@
/* 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";
const { dumpn } = require("devtools/shared/DevToolsUtils");
/**
* The listDevices command is currently unused in DevTools. We are keeping it while
* working on RemoteDebugging NG, in case it becomes needed later. We will remove it from
* the codebase if unused at the end of the project. See Bug 1511779.
*/
const listDevices = function() {
dumpn("listDevices");
return this.runCommand("host:devices").then(
function onSuccess(data) {
const lines = data.split("\n");
const res = [];
lines.forEach(function(line) {
if (line.length == 0) {
return;
}
const [ device ] = line.split("\t");
res.push(device);
});
return res;
}
);
};
exports.listDevices = listDevices;

View File

@ -0,0 +1,12 @@
# 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/.
DevToolsModules(
'index.js',
'list-devices.js',
'prepare-tcp-connection.js',
'run-command.js',
'shell.js',
'track-devices.js',
)

View File

@ -0,0 +1,33 @@
/* 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";
const { dumpn } = require("devtools/shared/DevToolsUtils");
const { ConnectionManager } = require("devtools/shared/client/connection-manager");
const { runCommand } = require("./run-command");
// sends adb forward localPort devicePort
const forwardPort = function(localPort, devicePort) {
dumpn("forwardPort " + localPort + " -- " + devicePort);
// <host-prefix>:forward:<local>;<remote>
return runCommand("host:forward:" + localPort + ";" + devicePort)
.then(function onSuccess(data) {
return data;
});
};
// Prepare TCP connection for provided socket path.
// The returned value is a port number of localhost for the connection.
const prepareTCPConnection = async function(socketPath) {
const port = ConnectionManager.getFreeTCPPort();
const local = `tcp:${ port }`;
const remote = socketPath.startsWith("@")
? `localabstract:${ socketPath.substring(1) }`
: `localfilesystem:${ socketPath }`;
await forwardPort(local, remote);
return port;
};
exports.prepareTCPConnection = prepareTCPConnection;

View File

@ -0,0 +1,62 @@
/* 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/. */
// Wrapper around the ADB utility.
"use strict";
const { dumpn } = require("devtools/shared/DevToolsUtils");
const { setTimeout } = require("resource://gre/modules/Timer.jsm");
const { ADB } = require("../adb");
const client = require("../adb-client");
const OKAY = 0x59414b4f;
// Asynchronously runs an adb command.
// @param command The command as documented in
// http://androidxref.com/4.0.4/xref/system/core/adb/SERVICES.TXT
const runCommand = function(command) {
dumpn("runCommand " + command);
return new Promise((resolve, reject) => {
if (!ADB.ready) {
setTimeout(function() {
reject("ADB_NOT_READY");
});
return;
}
const socket = client.connect();
socket.s.onopen = function() {
dumpn("runCommand onopen");
const req = client.createRequest(command);
socket.send(req);
};
socket.s.onerror = function() {
dumpn("runCommand onerror");
reject("NETWORK_ERROR");
};
socket.s.onclose = function() {
dumpn("runCommand onclose");
};
socket.s.ondata = function(event) {
dumpn("runCommand ondata");
const data = event.data;
const packet = client.unpackPacket(data, false);
if (!client.checkResponse(data, OKAY)) {
socket.close();
dumpn("Error: " + packet.data);
reject("PROTOCOL_ERROR");
return;
}
resolve(packet.data);
};
});
};
exports.runCommand = runCommand;

View File

@ -0,0 +1,101 @@
/* 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/. */
// Wrapper around the ADB utility.
"use strict";
const { dumpn } = require("devtools/shared/DevToolsUtils");
const client = require("../adb-client");
const OKAY = 0x59414b4f;
const shell = async function(command) {
let state;
let stdout = "";
dumpn("shell " + command);
return new Promise((resolve, reject) => {
const shutdown = function() {
dumpn("shell shutdown");
socket.close();
reject("BAD_RESPONSE");
};
const runFSM = function runFSM(data) {
dumpn("runFSM " + state);
let req;
let ignoreResponseCode = false;
switch (state) {
case "start":
state = "send-transport";
runFSM();
break;
case "send-transport":
req = client.createRequest("host:transport-any");
socket.send(req);
state = "wait-transport";
break;
case "wait-transport":
if (!client.checkResponse(data, OKAY)) {
shutdown();
return;
}
state = "send-shell";
runFSM();
break;
case "send-shell":
req = client.createRequest("shell:" + command);
socket.send(req);
state = "rec-shell";
break;
case "rec-shell":
if (!client.checkResponse(data, OKAY)) {
shutdown();
return;
}
state = "decode-shell";
if (client.getBuffer(data).byteLength == 4) {
break;
}
ignoreResponseCode = true;
// eslint-disable-next-lined no-fallthrough
case "decode-shell":
const decoder = new TextDecoder();
const text = new Uint8Array(client.getBuffer(data),
ignoreResponseCode ? 4 : 0);
stdout += decoder.decode(text);
break;
default:
dumpn("shell Unexpected State: " + state);
reject("UNEXPECTED_STATE");
}
};
const socket = client.connect();
socket.s.onerror = function(event) {
dumpn("shell onerror");
reject("SOCKET_ERROR");
};
socket.s.onopen = function(event) {
dumpn("shell onopen");
state = "start";
runFSM();
};
socket.s.onclose = function(event) {
resolve(stdout);
dumpn("shell onclose");
};
socket.s.ondata = function(event) {
dumpn("shell ondata");
runFSM(event.data);
};
});
};
exports.shell = shell;

View File

@ -0,0 +1,114 @@
/* 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/. */
// Wrapper around the ADB utility.
"use strict";
const EventEmitter = require("devtools/shared/event-emitter");
const { dumpn } = require("devtools/shared/DevToolsUtils");
const { setTimeout } = require("resource://gre/modules/Timer.jsm");
const { Services } = require("resource://gre/modules/Services.jsm");
const { ADB } = require("../adb");
const client = require("../adb-client");
const OKAY = 0x59414b4f;
// Start tracking devices connecting and disconnecting from the host.
// We can't reuse runCommand here because we keep the socket alive.
// @return The socket used.
const trackDevices = function() {
dumpn("trackDevices");
const socket = client.connect();
let waitForFirst = true;
const devices = {};
socket.s.onopen = function() {
dumpn("trackDevices onopen");
Services.obs.notifyObservers(null, "adb-track-devices-start");
const req = client.createRequest("host:track-devices");
socket.send(req);
};
socket.s.onerror = function(event) {
dumpn("trackDevices onerror: " + event);
Services.obs.notifyObservers(null, "adb-track-devices-stop");
};
socket.s.onclose = function() {
dumpn("trackDevices onclose");
// Report all devices as disconnected
for (const dev in devices) {
devices[dev] = false;
EventEmitter.emit(ADB, "device-disconnected", dev);
}
Services.obs.notifyObservers(null, "adb-track-devices-stop");
// When we lose connection to the server,
// and the adb is still on, we most likely got our server killed
// by local adb. So we do try to reconnect to it.
setTimeout(function() { // Give some time to the new adb to start
if (ADB.ready) { // Only try to reconnect/restart if the add-on is still enabled
ADB.start().then(function() { // try to connect to the new local adb server
// or, spawn a new one
trackDevices(); // Re-track devices
});
}
}, 2000);
};
socket.s.ondata = function(event) {
dumpn("trackDevices ondata");
const data = event.data;
dumpn("length=" + data.byteLength);
const dec = new TextDecoder();
dumpn(dec.decode(new Uint8Array(data)).trim());
// check the OKAY or FAIL on first packet.
if (waitForFirst) {
if (!client.checkResponse(data, OKAY)) {
socket.close();
return;
}
}
const packet = client.unpackPacket(data, !waitForFirst);
waitForFirst = false;
if (packet.data == "") {
// All devices got disconnected.
for (const dev in devices) {
devices[dev] = false;
EventEmitter.emit(ADB, "device-disconnected", dev);
}
} else {
// One line per device, each line being $DEVICE\t(offline|device)
const lines = packet.data.split("\n");
const newDev = {};
lines.forEach(function(line) {
if (line.length == 0) {
return;
}
const [dev, status] = line.split("\t");
newDev[dev] = status !== "offline";
});
// Check which device changed state.
for (const dev in newDev) {
if (devices[dev] != newDev[dev]) {
if (dev in devices || newDev[dev]) {
const topic = newDev[dev] ? "device-connected"
: "device-disconnected";
EventEmitter.emit(ADB, topic, dev);
}
devices[dev] = newDev[dev];
}
}
}
};
};
exports.trackDevices = trackDevices;

View File

@ -2,6 +2,10 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this # 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/. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
DIRS += [
'commands',
]
DevToolsModules( DevToolsModules(
'adb-addon.js', 'adb-addon.js',
'adb-binary.js', 'adb-binary.js',

View File

@ -9,6 +9,7 @@ const { NetUtil } = require("resource://gre/modules/NetUtil.jsm");
const { getFileForBinary } = require("devtools/shared/adb/adb-binary"); const { getFileForBinary } = require("devtools/shared/adb/adb-binary");
const { check } = require("devtools/shared/adb/adb-running-checker"); const { check } = require("devtools/shared/adb/adb-running-checker");
const { ADB } = require("devtools/shared/adb/adb"); const { ADB } = require("devtools/shared/adb/adb");
const { trackDevices } = require("devtools/shared/adb/commands/index");
const ADB_JSON = { const ADB_JSON = {
"Linux": { "Linux": {
@ -229,7 +230,7 @@ add_task({
EventEmitter.on(ADB, "device-connected", deviceId => { EventEmitter.on(ADB, "device-connected", deviceId => {
resolve(deviceId); resolve(deviceId);
}); });
ADB.trackDevices(); trackDevices();
}); });
equal(receivedDeviceId, "1234567890"); equal(receivedDeviceId, "1234567890");