test_discovery.js (4832B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 /* eslint-disable mozilla/no-arbitrary-setTimeout */ 4 5 "use strict"; 6 7 const { require } = ChromeUtils.importESModule( 8 "resource://devtools/shared/loader/Loader.sys.mjs" 9 ); 10 const EventEmitter = require("resource://devtools/shared/event-emitter.js"); 11 const discovery = require("resource://devtools/shared/discovery/discovery.js"); 12 const { setTimeout, clearTimeout } = ChromeUtils.importESModule( 13 "resource://gre/modules/Timer.sys.mjs" 14 ); 15 16 Services.prefs.setBoolPref("devtools.discovery.log", true); 17 18 registerCleanupFunction(() => { 19 Services.prefs.clearUserPref("devtools.discovery.log"); 20 }); 21 22 function log(msg) { 23 info("DISCOVERY: " + msg); 24 } 25 26 // Global map of actively listening ports to TestTransport instances 27 var gTestTransports = {}; 28 29 /** 30 * Implements the same API as Transport in discovery.js. Here, no UDP sockets 31 * are used. Instead, messages are delivered immediately. 32 */ 33 class TestTransport { 34 constructor(port) { 35 EventEmitter.decorate(this); 36 this.port = port; 37 gTestTransports[this.port] = this; 38 } 39 send(object, port) { 40 log("Send to " + port + ":\n" + JSON.stringify(object, null, 2)); 41 if (!gTestTransports[port]) { 42 log("No listener on port " + port); 43 return; 44 } 45 const message = JSON.stringify(object); 46 gTestTransports[port].onPacketReceived(null, message); 47 } 48 49 destroy() { 50 delete gTestTransports[this.port]; 51 } 52 53 // nsIUDPSocketListener 54 onPacketReceived(socket, message) { 55 const object = JSON.parse(message); 56 object.from = "localhost"; 57 log("Recv on " + this.port + ":\n" + JSON.stringify(object, null, 2)); 58 this.emit("message", object); 59 } 60 61 onStopListening() {} 62 } 63 64 // Use TestTransport instead of the usual Transport 65 discovery._factories.Transport = TestTransport; 66 67 // Ignore name generation on b2g and force a fixed value 68 Object.defineProperty(discovery.device, "name", { 69 get() { 70 return "test-device"; 71 }, 72 }); 73 74 add_task(async function () { 75 // At startup, no remote devices are known 76 deepEqual(discovery.getRemoteDevicesWithService("devtools"), []); 77 deepEqual(discovery.getRemoteDevicesWithService("penguins"), []); 78 79 discovery.scan(); 80 81 // No services added yet, still empty 82 deepEqual(discovery.getRemoteDevicesWithService("devtools"), []); 83 deepEqual(discovery.getRemoteDevicesWithService("penguins"), []); 84 85 discovery.addService("devtools", { port: 1234 }); 86 87 // Changes not visible until next scan 88 deepEqual(discovery.getRemoteDevicesWithService("devtools"), []); 89 deepEqual(discovery.getRemoteDevicesWithService("penguins"), []); 90 91 await scanForChange("devtools", "added"); 92 93 // Now we see the new service 94 deepEqual(discovery.getRemoteDevicesWithService("devtools"), ["test-device"]); 95 deepEqual(discovery.getRemoteDevicesWithService("penguins"), []); 96 97 discovery.addService("penguins", { tux: true }); 98 await scanForChange("penguins", "added"); 99 100 deepEqual(discovery.getRemoteDevicesWithService("devtools"), ["test-device"]); 101 deepEqual(discovery.getRemoteDevicesWithService("penguins"), ["test-device"]); 102 deepEqual(discovery.getRemoteDevices(), ["test-device"]); 103 104 deepEqual(discovery.getRemoteService("devtools", "test-device"), { 105 port: 1234, 106 host: "localhost", 107 }); 108 deepEqual(discovery.getRemoteService("penguins", "test-device"), { 109 tux: true, 110 host: "localhost", 111 }); 112 113 discovery.removeService("devtools"); 114 await scanForChange("devtools", "removed"); 115 116 discovery.addService("penguins", { tux: false }); 117 await scanForChange("penguins", "updated"); 118 119 // Scan again, but nothing should be removed 120 await scanForNoChange("penguins", "removed"); 121 122 // Split the scanning side from the service side to simulate the machine with 123 // the service becoming unreachable 124 gTestTransports = {}; 125 126 discovery.removeService("penguins"); 127 await scanForChange("penguins", "removed"); 128 }); 129 130 function scanForChange(service, changeType) { 131 return new Promise((resolve, reject) => { 132 const timer = setTimeout(() => { 133 reject(new Error("Reply never arrived")); 134 }, discovery.replyTimeout + 500); 135 discovery.on(service + "-device-" + changeType, function onChange() { 136 discovery.off(service + "-device-" + changeType, onChange); 137 clearTimeout(timer); 138 resolve(); 139 }); 140 discovery.scan(); 141 }); 142 } 143 144 function scanForNoChange(service, changeType) { 145 return new Promise((resolve, reject) => { 146 const timer = setTimeout(() => { 147 resolve(); 148 }, discovery.replyTimeout + 500); 149 discovery.on(service + "-device-" + changeType, function onChange() { 150 discovery.off(service + "-device-" + changeType, onChange); 151 clearTimeout(timer); 152 reject(new Error("Unexpected change occurred")); 153 }); 154 discovery.scan(); 155 }); 156 }