@xenon-device-management/xenon 2.7.0 → 2.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/package.json +1 -1
- package/lib/public/assets/{AnnotationOverlay-oISPBknJ.js → AnnotationOverlay-D9bdTRcw.js} +1 -1
- package/lib/public/assets/{ApiKeyGate-BOm-SKAJ.js → ApiKeyGate-CHrLtNyc.js} +1 -1
- package/lib/public/assets/{BugReportButton-C1WMf_3A.js → BugReportButton-Cvnfp_lP.js} +1 -1
- package/lib/public/assets/DeviceMosaicView-B--dBAh0.js +2 -0
- package/lib/public/assets/{EmptyState-CZg80GnN.js → EmptyState-Bd-GNhqS.js} +1 -1
- package/lib/public/assets/{FieldGroup-D7zftz-q.js → FieldGroup-B6TJlJGM.js} +1 -1
- package/lib/public/assets/{FilterMenu-BKHKlX-8.js → FilterMenu-Cp_hdlhf.js} +1 -1
- package/lib/public/assets/{Menu-Ccznqijj.js → Menu-shaiCwvG.js} +1 -1
- package/lib/public/assets/{Modal-FxdOVK12.js → Modal-CVlHheGn.js} +1 -1
- package/lib/public/assets/{RecordingPage-BL8B1IKY.js → RecordingPage-BglT6aA7.js} +1 -1
- package/lib/public/assets/{RecordingsPage-CCCL2xwa.js → RecordingsPage-y3OOMfJ2.js} +1 -1
- package/lib/public/assets/{SegmentedControl-B3EMys8L.js → SegmentedControl-Btui_BrO.js} +1 -1
- package/lib/public/assets/{SettingCard-BwvdX-C6.js → SettingCard-BojNKc9f.js} +1 -1
- package/lib/public/assets/{Table-sGNs22pM.js → Table-CgoNnHi5.js} +1 -1
- package/lib/public/assets/{activity-rQQOy6SD.js → activity-BB1GKieH.js} +1 -1
- package/lib/public/assets/{ai-settings-uIrII3el.js → ai-settings-DabayswA.js} +1 -1
- package/lib/public/assets/{api-keys-B1-QmrZJ.js → api-keys-azY7czw6.js} +1 -1
- package/lib/public/assets/{apps-B8gYjYhN.js → apps-DFvDvrJh.js} +1 -1
- package/lib/public/assets/{arrow-left-CLT_rU6o.js → arrow-left-D2R0-wu3.js} +1 -1
- package/lib/public/assets/{arrow-right-DqK8eHkB.js → arrow-right-XChCsVs4.js} +1 -1
- package/lib/public/assets/{arrow-up-right-D96XxKQY.js → arrow-up-right-DMqbmzLo.js} +1 -1
- package/lib/public/assets/{auth-shell-DpBFmfin.js → auth-shell-DXDgAjsz.js} +2 -2
- package/lib/public/assets/{builds-page-DqD7ySkz.js → builds-page-3L3rEp7Q.js} +1 -1
- package/lib/public/assets/{button-BrVVNvSB.js → button-BZ8Paskr.js} +1 -1
- package/lib/public/assets/{calendar-FX_no0De.js → calendar-N2T0cT9m.js} +1 -1
- package/lib/public/assets/{check-C6RAxVq9.js → check-iKGL7VPu.js} +1 -1
- package/lib/public/assets/{chevron-left-CcTJUfpS.js → chevron-left-C0H3PyOa.js} +1 -1
- package/lib/public/assets/{chevron-right-C39WUDQP.js → chevron-right-jSMYhKfu.js} +1 -1
- package/lib/public/assets/{circle-check-Fo1eEzZe.js → circle-check-8drTpet1.js} +1 -1
- package/lib/public/assets/{circle-x-CXy5XzIu.js → circle-x-dWikpR-a.js} +1 -1
- package/lib/public/assets/{clock-PBGPHMrI.js → clock-Gn9P_xeq.js} +1 -1
- package/lib/public/assets/{copy-ZGa-scbM.js → copy-CRO4NZQZ.js} +1 -1
- package/lib/public/assets/{device-explorer-BRYO7iUM.js → device-explorer-Dt31Uik2.js} +54 -54
- package/lib/public/assets/{download-vaODl7Bn.js → download-DPxWk60W.js} +1 -1
- package/lib/public/assets/{forgot-password-YfGbiIOM.js → forgot-password-vNOHTq63.js} +1 -1
- package/lib/public/assets/{funnel-C05BIqke.js → funnel-0f0ehxyn.js} +1 -1
- package/lib/public/assets/{index-DdsuniRs.js → index-BKtEL_K4.js} +2 -2
- package/lib/public/assets/{index-1ST_YG3o.js → index-D_T9qS1E.js} +1 -1
- package/lib/public/assets/{input-C6Zp2Ibb.js → input-CC4-VE9G.js} +1 -1
- package/lib/public/assets/{keycodes-BlkuYXfQ.js → keycodes-BWUMrNPy.js} +5 -5
- package/lib/public/assets/{layers-B-3IDp3k.js → layers-C8yrdLpc.js} +1 -1
- package/lib/public/assets/{list-checks-BUUYV0Vf.js → list-checks-C2BNGDYf.js} +1 -1
- package/lib/public/assets/{lock-BUdNqIao.js → lock-CrwjpPiF.js} +1 -1
- package/lib/public/assets/{login-CD8bag9i.js → login-eVu1fnMN.js} +1 -1
- package/lib/public/assets/{maintenance-settings-BWQP_nfK.js → maintenance-settings-CMFadU0m.js} +1 -1
- package/lib/public/assets/{monitor-qfb5f9oQ.js → monitor-Dpxo7IUi.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-DAAlUY2l.js → mouse-pointer-2-Bd9UBsUB.js} +1 -1
- package/lib/public/assets/{network-Qib4-4Yj.js → network-BeTXa8Ww.js} +1 -1
- package/lib/public/assets/{overview-BZQf20qe.js → overview-cVHbQJBa.js} +1 -1
- package/lib/public/assets/{page-header-4jNxdU1M.js → page-header-Dtt6ySD7.js} +1 -1
- package/lib/public/assets/{plus-DVhPNSQy.js → plus-Dalpx132.js} +1 -1
- package/lib/public/assets/{profile-page-DfITRDDC.js → profile-page-CLVVG6gR.js} +1 -1
- package/lib/public/assets/{reset-password-s2xU05oa.js → reset-password-BCBz06iw.js} +1 -1
- package/lib/public/assets/{runbook-page-DdEiXQcm.js → runbook-page-2nwa4t1z.js} +1 -1
- package/lib/public/assets/{select-Bzs3ZUvb.js → select-DP8zFEM7.js} +1 -1
- package/lib/public/assets/{selector-detail-page-QvRRVchI.js → selector-detail-page-Cbh2njcH.js} +1 -1
- package/lib/public/assets/{selector-health-DwUFxrZx.js → selector-health-Bvsjfwv0.js} +1 -1
- package/lib/public/assets/{selector-health-page-N0tA9q9c.js → selector-health-page-DaUsNk74.js} +1 -1
- package/lib/public/assets/{session-detail-page-DcGGXXxL.js → session-detail-page-Bxvmu6zZ.js} +1 -1
- package/lib/public/assets/{settings-BUqAjzeY.js → settings-DhK4nDW3.js} +1 -1
- package/lib/public/assets/{stat-tile-BwZr7q7_.js → stat-tile-C6UElbbT.js} +1 -1
- package/lib/public/assets/{tablet-DiOf-kLM.js → tablet-BxwMEQ5P.js} +1 -1
- package/lib/public/assets/{teams-DA6AfWGO.js → teams-CoHMZ77q.js} +1 -1
- package/lib/public/assets/{trash-2-BZuuog-n.js → trash-2-BBdLg7FK.js} +1 -1
- package/lib/public/assets/{upload-DLHV2k4Z.js → upload-Bph2BQlh.js} +1 -1
- package/lib/public/assets/{use-password-reset-mode-DRY4lLdd.js → use-password-reset-mode-NQmLPUN0.js} +1 -1
- package/lib/public/assets/{users-Bd4LKBWY.js → users-D8F7_TeC.js} +1 -1
- package/lib/public/assets/{users-BlhcXeTG.js → users-TdXvYiCq.js} +1 -1
- package/lib/public/assets/{video-off-DIaDZGNl.js → video-off-Dw7aZgDU.js} +1 -1
- package/lib/public/assets/{webhook-settings-IWvqbRk2.js → webhook-settings-BqcYFTth.js} +1 -1
- package/lib/public/assets/{zap-4Qy4hVbF.js → zap-Z2pU9Fzi.js} +1 -1
- package/lib/public/index.html +1 -1
- package/lib/src/device-managers/ios/IOSLogStreamService.js +6 -3
- package/lib/src/device-managers/ios/IOSStreamService.js +38 -122
- package/lib/src/device-managers/ios/IOSTunnels.js +376 -0
- package/lib/src/device-managers/ios/WDAClient.js +5 -2
- package/lib/src/services/PortAllocator.js +65 -0
- package/lib/test/unit/ios-go-ios-tunnel-env.spec.js +154 -0
- package/lib/test/unit/ios-stream-appium-tunnel.spec.js +122 -32
- package/lib/test/unit/ios-stream-port-leases.spec.js +26 -5
- package/lib/test/unit/ios-tunnels.spec.js +387 -0
- package/lib/test/unit/port-allocator-pairs.spec.js +110 -0
- package/lib/test/unit/stream-own-device-row.spec.js +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/lib/public/assets/DeviceMosaicView-BPJ5n5fY.js +0 -2
|
@@ -54,6 +54,7 @@ const tcp_port_used_1 = __importDefault(require("tcp-port-used"));
|
|
|
54
54
|
const typedi_1 = require("typedi");
|
|
55
55
|
const util_1 = require("util");
|
|
56
56
|
const IOSStreamService_1 = __importDefault(require("../../src/device-managers/ios/IOSStreamService"));
|
|
57
|
+
const IOSTunnels_1 = require("../../src/device-managers/ios/IOSTunnels");
|
|
57
58
|
const singleFlight_1 = require("../../src/helpers/singleFlight");
|
|
58
59
|
const PortAllocator_1 = require("../../src/services/PortAllocator");
|
|
59
60
|
const ProcessRegistry_1 = require("../../src/services/ProcessRegistry");
|
|
@@ -61,23 +62,26 @@ const device_store_1 = require("../../src/data-service/device-store");
|
|
|
61
62
|
const deviceService = __importStar(require("../../src/data-service/device-service"));
|
|
62
63
|
const control_1 = require("../../src/app/routers/control");
|
|
63
64
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
65
|
+
* Each iOS 17+ phone has its own go-ios tunnel (IOSTunnels) on its own pair of
|
|
66
|
+
* leased ports. On iOS 17+ a WDA that go-ios launched (`runwda`) reaches the
|
|
67
|
+
* phone through it, and an Appium session rides such a WDA whenever it was
|
|
68
|
+
* allocated while a stream ran: iOSCapabilities points `webDriverAgentUrl` at
|
|
69
|
+
* the stream's WDA.
|
|
69
70
|
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
71
|
+
* So no go-ios cleanup runs, and a viewer's stop stops nothing, while an
|
|
72
|
+
* Appium session holds the phone. And nothing is killed by port any more:
|
|
73
|
+
* through 2.7 the orphan sweep kill -9'd whatever listened on go-ios's default
|
|
74
|
+
* ports, 60105 and 60106, which was another iPhone's live tunnel.
|
|
73
75
|
*
|
|
74
76
|
* Nothing real runs here. Every `exec` goes to a fake `execFile` (exec calls
|
|
75
77
|
* `module.exports.execFile`, promisified or not), `process.kill` is stubbed,
|
|
76
|
-
* and processes are fakes.
|
|
78
|
+
* IOSTunnels is a fake, and processes are fakes.
|
|
77
79
|
*/
|
|
78
80
|
const IPHONE = 'test-iphone-00008150-tunnel';
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
+
const OTHER_IPHONE = 'test-iphone-00008110-other';
|
|
82
|
+
const TUNNEL_PORT = 12100;
|
|
83
|
+
/** Another iPhone's tunnel, on go-ios's default ports, as `lsof` would name it. */
|
|
84
|
+
const DEFAULT_PORT_LISTENER = '515151';
|
|
81
85
|
class FakeProcess extends events_1.EventEmitter {
|
|
82
86
|
constructor(pid) {
|
|
83
87
|
super();
|
|
@@ -111,24 +115,26 @@ describe('go-ios tunnels under a live Appium session (fake exec, fake processes)
|
|
|
111
115
|
let commands;
|
|
112
116
|
let kill;
|
|
113
117
|
let allocator;
|
|
118
|
+
let tunnels;
|
|
114
119
|
let svc;
|
|
115
120
|
const killed = () => commands.filter((c) => /^kill\b|^pkill\b/.test(c));
|
|
116
121
|
const swept = () => commands.filter((c) => /^lsof\b|^pgrep\b/.test(c));
|
|
122
|
+
const defaultPorts = () => commands.filter((c) => /6010[56]/.test(c));
|
|
117
123
|
/** A stream that launched its own WDA, forwarders and go-ios tunnel. */
|
|
118
|
-
function ownStream() {
|
|
124
|
+
function ownStream(udid = IPHONE) {
|
|
119
125
|
const s = {
|
|
120
|
-
udid
|
|
126
|
+
udid,
|
|
121
127
|
wdaProcess: new FakeProcess(),
|
|
122
128
|
forwardWDAProcess: new FakeProcess(),
|
|
123
129
|
forwardMJPEGProcess: new FakeProcess(),
|
|
124
|
-
|
|
130
|
+
tunnelPort: TUNNEL_PORT,
|
|
125
131
|
wdaPort: 28101,
|
|
126
132
|
mjpegPort: 29101,
|
|
127
133
|
status: 'running',
|
|
128
134
|
lastViewerAt: Date.now(),
|
|
129
135
|
viewerCount: 0,
|
|
130
136
|
};
|
|
131
|
-
svc.sessions.set(
|
|
137
|
+
svc.sessions.set(udid, s);
|
|
132
138
|
return s;
|
|
133
139
|
}
|
|
134
140
|
/** A stream attached to the WDA the Appium session forwards: only its MJPEG forwarder is its own. */
|
|
@@ -138,7 +144,7 @@ describe('go-ios tunnels under a live Appium session (fake exec, fake processes)
|
|
|
138
144
|
wdaProcess: null,
|
|
139
145
|
forwardWDAProcess: null,
|
|
140
146
|
forwardMJPEGProcess: new FakeProcess(),
|
|
141
|
-
|
|
147
|
+
tunnelPort: null,
|
|
142
148
|
wdaPort: 28123,
|
|
143
149
|
mjpegPort: 29101,
|
|
144
150
|
status: 'running',
|
|
@@ -158,11 +164,12 @@ describe('go-ios tunnels under a live Appium session (fake exec, fake processes)
|
|
|
158
164
|
wdaLocalPort: 28123,
|
|
159
165
|
};
|
|
160
166
|
commands = [];
|
|
161
|
-
// Refuse everything real. lsof names
|
|
162
|
-
//
|
|
167
|
+
// Refuse everything real. lsof names a listener on go-ios's default ports
|
|
168
|
+
// (another iPhone's tunnel), which must never be killed; pgrep finds no
|
|
169
|
+
// tunnel for this udid. Both exit 1 with nothing to report otherwise.
|
|
163
170
|
sinon_1.default.stub(child_process_1.default, 'execFile').callsFake(((cmd, _opts, cb) => {
|
|
164
171
|
commands.push(cmd);
|
|
165
|
-
const stdout = /^lsof -ti :6010[56]$/.test(cmd) ? `${
|
|
172
|
+
const stdout = /^lsof -ti :6010[56]$/.test(cmd) ? `${DEFAULT_PORT_LISTENER}\n` : '';
|
|
166
173
|
const nothingFound = /^(lsof|pgrep)\b/.test(cmd) && !stdout;
|
|
167
174
|
const done = typeof cb === 'function' ? cb : () => undefined;
|
|
168
175
|
process.nextTick(() => nothingFound
|
|
@@ -183,6 +190,14 @@ describe('go-ios tunnels under a live Appium session (fake exec, fake processes)
|
|
|
183
190
|
claim: sinon_1.default.stub().resolves(true),
|
|
184
191
|
acquire: sinon_1.default.stub().resolves(29102),
|
|
185
192
|
release: sinon_1.default.stub().resolves(),
|
|
193
|
+
touch: sinon_1.default.stub().resolves(),
|
|
194
|
+
releasePurpose: sinon_1.default.stub().resolves(),
|
|
195
|
+
};
|
|
196
|
+
tunnels = {
|
|
197
|
+
ensure: sinon_1.default.stub().resolves(null),
|
|
198
|
+
envFor: sinon_1.default.stub().callsFake(() => (Object.assign(Object.assign({}, process.env), { ENABLE_GO_IOS_AGENT: 'yes' }))),
|
|
199
|
+
stop: sinon_1.default.stub().resolves(),
|
|
200
|
+
touch: sinon_1.default.stub().resolves(),
|
|
186
201
|
};
|
|
187
202
|
svc = iosService();
|
|
188
203
|
const real = typedi_1.Container.get.bind(typedi_1.Container);
|
|
@@ -191,6 +206,8 @@ describe('go-ios tunnels under a live Appium session (fake exec, fake processes)
|
|
|
191
206
|
return svc;
|
|
192
207
|
if (token === PortAllocator_1.PortAllocator)
|
|
193
208
|
return allocator;
|
|
209
|
+
if (token === IOSTunnels_1.IOSTunnels)
|
|
210
|
+
return tunnels;
|
|
194
211
|
if (token === ProcessRegistry_1.ProcessRegistry)
|
|
195
212
|
return { track: () => undefined };
|
|
196
213
|
return real(token);
|
|
@@ -209,32 +226,34 @@ describe('go-ios tunnels under a live Appium session (fake exec, fake processes)
|
|
|
209
226
|
yield control_1.previewLeaveDeps.stop(IPHONE);
|
|
210
227
|
(0, chai_1.expect)(stream.wdaProcess.killed, 'the WDA the session may be driving').to.equal(false);
|
|
211
228
|
(0, chai_1.expect)(stream.forwardWDAProcess.killed, "that WDA's forwarder").to.equal(false);
|
|
212
|
-
(0, chai_1.expect)(
|
|
229
|
+
(0, chai_1.expect)(tunnels.stop.called, "the phone's tunnel").to.equal(false);
|
|
230
|
+
(0, chai_1.expect)(kill.called, 'no process group is signalled').to.equal(false);
|
|
213
231
|
(0, chai_1.expect)(killed(), 'no kill is run').to.deep.equal([]);
|
|
214
232
|
(0, chai_1.expect)(svc.getStreamStatus(IPHONE), 'the stream stays, for the session teardown').to.equal(stream);
|
|
215
233
|
(0, chai_1.expect)(allocator.release.called, 'its ports stay leased').to.equal(false);
|
|
216
234
|
}));
|
|
217
|
-
it(
|
|
235
|
+
it("stops no tunnel for a stream attached to the Appium session's own WDA", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
218
236
|
const stream = attachedStream();
|
|
219
237
|
yield control_1.previewLeaveDeps.stop(IPHONE);
|
|
220
238
|
(0, chai_1.expect)(swept(), 'no lsof/pgrep for go-ios processes').to.deep.equal([]);
|
|
221
|
-
(0, chai_1.expect)(killed()
|
|
239
|
+
(0, chai_1.expect)(killed()).to.deep.equal([]);
|
|
240
|
+
(0, chai_1.expect)(tunnels.stop.called, 'it started no tunnel').to.equal(false);
|
|
222
241
|
(0, chai_1.expect)(kill.called).to.equal(false);
|
|
223
242
|
(0, chai_1.expect)(stream.forwardMJPEGProcess.killed, 'its own MJPEG forwarder still goes').to.equal(true);
|
|
224
243
|
(0, chai_1.expect)(svc.getStreamStatus(IPHONE)).to.equal(undefined);
|
|
225
244
|
}));
|
|
226
|
-
it('
|
|
245
|
+
it("stops the stream's tunnel and this phone's leftovers, and nothing by port, once no Appium session holds the phone", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
227
246
|
Object.assign(device, { busy: false, session_id: null });
|
|
228
247
|
const stream = ownStream();
|
|
229
248
|
yield control_1.previewLeaveDeps.stop(IPHONE);
|
|
230
249
|
(0, chai_1.expect)(stream.wdaProcess.killed).to.equal(true);
|
|
231
250
|
(0, chai_1.expect)(stream.forwardWDAProcess.killed).to.equal(true);
|
|
232
251
|
(0, chai_1.expect)(stream.forwardMJPEGProcess.killed).to.equal(true);
|
|
233
|
-
(0, chai_1.expect)(
|
|
252
|
+
(0, chai_1.expect)(tunnels.stop.calledWith(IPHONE), "the stream's tunnel").to.equal(true);
|
|
253
|
+
(0, chai_1.expect)(tunnels.stop.alwaysCalledWith(IPHONE), 'and no other phone’s').to.equal(true);
|
|
234
254
|
(0, chai_1.expect)(commands).to.include(`pgrep -f "ios tunnel.*${IPHONE}"`);
|
|
235
|
-
(0, chai_1.expect)(
|
|
236
|
-
(0, chai_1.expect)(
|
|
237
|
-
(0, chai_1.expect)(killed()).to.include(`kill -9 ${AGENT_LISTENER_PID}`);
|
|
255
|
+
(0, chai_1.expect)(defaultPorts(), 'nothing on 60105/60106 is looked at').to.deep.equal([]);
|
|
256
|
+
(0, chai_1.expect)(killed(), "another iPhone's tunnel is never kill -9'd").to.deep.equal([]);
|
|
238
257
|
(0, chai_1.expect)(svc.getStreamStatus(IPHONE)).to.equal(undefined);
|
|
239
258
|
}));
|
|
240
259
|
it('treats a live-preview hold as no Appium session', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -242,7 +261,18 @@ describe('go-ios tunnels under a live Appium session (fake exec, fake processes)
|
|
|
242
261
|
const stream = ownStream();
|
|
243
262
|
yield control_1.previewLeaveDeps.stop(IPHONE);
|
|
244
263
|
(0, chai_1.expect)(stream.wdaProcess.killed).to.equal(true);
|
|
245
|
-
(0, chai_1.expect)(
|
|
264
|
+
(0, chai_1.expect)(tunnels.stop.calledWith(IPHONE)).to.equal(true);
|
|
265
|
+
}));
|
|
266
|
+
it("stopping one iPhone's stream leaves another iPhone's tunnel alone", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
267
|
+
Object.assign(device, { busy: false, session_id: null });
|
|
268
|
+
ownStream(IPHONE);
|
|
269
|
+
const other = ownStream(OTHER_IPHONE);
|
|
270
|
+
yield svc.stopStream(IPHONE);
|
|
271
|
+
(0, chai_1.expect)(tunnels.stop.calledWith(OTHER_IPHONE)).to.equal(false);
|
|
272
|
+
(0, chai_1.expect)(other.wdaProcess.killed).to.equal(false);
|
|
273
|
+
(0, chai_1.expect)(svc.getStreamStatus(OTHER_IPHONE)).to.equal(other);
|
|
274
|
+
(0, chai_1.expect)(commands.some((c) => c.includes(OTHER_IPHONE))).to.equal(false);
|
|
275
|
+
(0, chai_1.expect)(defaultPorts()).to.deep.equal([]);
|
|
246
276
|
}));
|
|
247
277
|
});
|
|
248
278
|
describe('a start that attaches to the WDA the Appium session forwards', () => {
|
|
@@ -253,7 +283,7 @@ describe('go-ios tunnels under a live Appium session (fake exec, fake processes)
|
|
|
253
283
|
wdaProcess: null,
|
|
254
284
|
forwardWDAProcess: null,
|
|
255
285
|
forwardMJPEGProcess: null,
|
|
256
|
-
|
|
286
|
+
tunnelPort: null,
|
|
257
287
|
wdaPort: 28101,
|
|
258
288
|
mjpegPort: 29101,
|
|
259
289
|
status: 'error',
|
|
@@ -266,8 +296,10 @@ describe('go-ios tunnels under a live Appium session (fake exec, fake processes)
|
|
|
266
296
|
const { wdaPort } = yield svc.startStream(IPHONE);
|
|
267
297
|
(0, chai_1.expect)(wdaPort, 'attached to the session’s WDA').to.equal(device.wdaLocalPort);
|
|
268
298
|
(0, chai_1.expect)(swept(), 'no lsof/pgrep for go-ios processes').to.deep.equal([]);
|
|
269
|
-
(0, chai_1.expect)(killed()
|
|
299
|
+
(0, chai_1.expect)(killed()).to.deep.equal([]);
|
|
270
300
|
(0, chai_1.expect)(kill.called).to.equal(false);
|
|
301
|
+
(0, chai_1.expect)(tunnels.ensure.called, 'no tunnel of its own').to.equal(false);
|
|
302
|
+
(0, chai_1.expect)(tunnels.stop.called).to.equal(false);
|
|
271
303
|
}));
|
|
272
304
|
});
|
|
273
305
|
describe('a start over a running stream whose WDA does not answer', () => {
|
|
@@ -284,7 +316,8 @@ describe('go-ios tunnels under a live Appium session (fake exec, fake processes)
|
|
|
284
316
|
(0, chai_1.expect)(err === null || err === void 0 ? void 0 : err.message).to.match(/Appium session .* holds the device/);
|
|
285
317
|
(0, chai_1.expect)(stream.wdaProcess.killed, 'the WDA the session may be driving').to.equal(false);
|
|
286
318
|
(0, chai_1.expect)(stream.forwardWDAProcess.killed).to.equal(false);
|
|
287
|
-
(0, chai_1.expect)(
|
|
319
|
+
(0, chai_1.expect)(tunnels.stop.called, "the phone's tunnel").to.equal(false);
|
|
320
|
+
(0, chai_1.expect)(kill.called).to.equal(false);
|
|
288
321
|
(0, chai_1.expect)(commands, 'nothing is exec’d').to.deep.equal([]);
|
|
289
322
|
(0, chai_1.expect)((_a = svc.getStreamStatus(IPHONE)) === null || _a === void 0 ? void 0 : _a.status, 'the stream is left as it was').to.equal('running');
|
|
290
323
|
(0, chai_1.expect)(svc.recoveryCooldowns.has(IPHONE), 'no failed start to cool down from').to.equal(false);
|
|
@@ -302,10 +335,66 @@ describe('go-ios tunnels under a live Appium session (fake exec, fake processes)
|
|
|
302
335
|
const stream = ownStream();
|
|
303
336
|
const err = yield svc.startStream(IPHONE).then(() => null, (e) => e);
|
|
304
337
|
(0, chai_1.expect)(stream.wdaProcess.killed, 'the dead stream is stopped').to.equal(true);
|
|
305
|
-
(0, chai_1.expect)(
|
|
338
|
+
(0, chai_1.expect)(tunnels.stop.calledWith(IPHONE), 'with its tunnel').to.equal(true);
|
|
306
339
|
(0, chai_1.expect)(err === null || err === void 0 ? void 0 : err.message, 'and a new start is attempted').to.equal('go-ios not available');
|
|
307
340
|
}));
|
|
308
341
|
});
|
|
342
|
+
describe('a start after a failed start', () => {
|
|
343
|
+
it("stops the failed start's tunnel before starting again", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
344
|
+
Object.assign(device, { busy: false, session_id: null });
|
|
345
|
+
// The failed start brought its tunnel up, then WDA never answered.
|
|
346
|
+
svc.sessions.set(IPHONE, {
|
|
347
|
+
udid: IPHONE,
|
|
348
|
+
wdaProcess: null,
|
|
349
|
+
forwardWDAProcess: null,
|
|
350
|
+
forwardMJPEGProcess: null,
|
|
351
|
+
tunnelPort: TUNNEL_PORT,
|
|
352
|
+
wdaPort: 28101,
|
|
353
|
+
mjpegPort: 29101,
|
|
354
|
+
status: 'error',
|
|
355
|
+
lastViewerAt: Date.now(),
|
|
356
|
+
viewerCount: 0,
|
|
357
|
+
});
|
|
358
|
+
svc.isGoIOSAvailable = () => __awaiter(void 0, void 0, void 0, function* () { return false; });
|
|
359
|
+
const err = yield svc.startStream(IPHONE).then(() => null, (e) => e);
|
|
360
|
+
(0, chai_1.expect)(err === null || err === void 0 ? void 0 : err.message).to.equal('go-ios not available');
|
|
361
|
+
(0, chai_1.expect)(tunnels.stop.calledWith(IPHONE), "the failed start's tunnel").to.equal(true);
|
|
362
|
+
(0, chai_1.expect)(tunnels.stop.alwaysCalledWith(IPHONE)).to.equal(true);
|
|
363
|
+
(0, chai_1.expect)(defaultPorts()).to.deep.equal([]);
|
|
364
|
+
}));
|
|
365
|
+
});
|
|
366
|
+
describe('boot', () => {
|
|
367
|
+
it('reaps every go-ios process, then drops every tunnel port lease', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
368
|
+
yield svc.reapOrphanTunnels();
|
|
369
|
+
(0, chai_1.expect)(commands).to.include('pgrep -f "/nonexistent/go-ios"');
|
|
370
|
+
(0, chai_1.expect)(allocator.releasePurpose.calledOnceWithExactly('tunnel')).to.equal(true);
|
|
371
|
+
}));
|
|
372
|
+
});
|
|
373
|
+
describe('the hourly watchdog', () => {
|
|
374
|
+
it("keeps a running stream's tunnel ports leased", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
375
|
+
const clock = sinon_1.default.useFakeTimers({ toFake: ['setInterval'] });
|
|
376
|
+
try {
|
|
377
|
+
sinon_1.default.stub(IOSStreamService_1.default.prototype, 'isStreamResponsive').resolves(true);
|
|
378
|
+
const watched = new IOSStreamService_1.default();
|
|
379
|
+
watched.sessions.set(IPHONE, {
|
|
380
|
+
udid: IPHONE,
|
|
381
|
+
status: 'running',
|
|
382
|
+
tunnelPort: TUNNEL_PORT,
|
|
383
|
+
wdaPort: 28101,
|
|
384
|
+
mjpegPort: 29101,
|
|
385
|
+
lastViewerAt: Date.now(),
|
|
386
|
+
viewerCount: 1,
|
|
387
|
+
});
|
|
388
|
+
yield clock.tickAsync(60 * 60 * 1000);
|
|
389
|
+
yield new Promise((resolve) => setImmediate(resolve)); // let the tick's awaits finish
|
|
390
|
+
(0, chai_1.expect)(allocator.touch.calledWith(28101), 'its WDA port, as before').to.equal(true);
|
|
391
|
+
(0, chai_1.expect)(tunnels.touch.calledWith(IPHONE), 'and its tunnel').to.equal(true);
|
|
392
|
+
}
|
|
393
|
+
finally {
|
|
394
|
+
clock.restore();
|
|
395
|
+
}
|
|
396
|
+
}));
|
|
397
|
+
});
|
|
309
398
|
it("leaves go-ios processes alone when the phone's row can't be read", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
310
399
|
attachedStream();
|
|
311
400
|
findDevice.rejects(new Error('database is locked'));
|
|
@@ -313,5 +402,6 @@ describe('go-ios tunnels under a live Appium session (fake exec, fake processes)
|
|
|
313
402
|
(0, chai_1.expect)(swept()).to.deep.equal([]);
|
|
314
403
|
(0, chai_1.expect)(killed()).to.deep.equal([]);
|
|
315
404
|
(0, chai_1.expect)(kill.called).to.equal(false);
|
|
405
|
+
(0, chai_1.expect)(tunnels.stop.called).to.equal(false);
|
|
316
406
|
}));
|
|
317
407
|
});
|
|
@@ -23,6 +23,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
23
23
|
const sinon_1 = __importDefault(require("sinon"));
|
|
24
24
|
const typedi_1 = require("typedi");
|
|
25
25
|
const IOSStreamService_1 = __importDefault(require("../../src/device-managers/ios/IOSStreamService"));
|
|
26
|
+
const IOSTunnels_1 = require("../../src/device-managers/ios/IOSTunnels");
|
|
26
27
|
const AndroidStreamService_1 = __importDefault(require("../../src/device-managers/android/AndroidStreamService"));
|
|
27
28
|
const singleFlight_1 = require("../../src/helpers/singleFlight");
|
|
28
29
|
const PortAllocator_1 = require("../../src/services/PortAllocator");
|
|
@@ -51,11 +52,12 @@ const scratch_port_leases_1 = require("../helpers/scratch-port-leases");
|
|
|
51
52
|
const IPHONE = 'test-iphone-00008150';
|
|
52
53
|
const S9 = 'test-android-s9';
|
|
53
54
|
class FakeProcess extends events_1.EventEmitter {
|
|
54
|
-
constructor(command, args, server) {
|
|
55
|
+
constructor(command, args, server, opts) {
|
|
55
56
|
super();
|
|
56
57
|
this.command = command;
|
|
57
58
|
this.args = args;
|
|
58
59
|
this.server = server;
|
|
60
|
+
this.opts = opts;
|
|
59
61
|
this.pid = undefined;
|
|
60
62
|
this.exitCode = null;
|
|
61
63
|
this.stdout = new events_1.EventEmitter();
|
|
@@ -106,6 +108,7 @@ describe('iOS stream port leases (fake processes, real sockets, scratch DB)', ()
|
|
|
106
108
|
let appiumWdaPorts;
|
|
107
109
|
let ios;
|
|
108
110
|
let android;
|
|
111
|
+
let tunnels;
|
|
109
112
|
beforeEach(() => {
|
|
110
113
|
allocator = new PortAllocator_1.PortAllocator();
|
|
111
114
|
// iOS and Android share the mjpeg range, as they do on the lab.
|
|
@@ -120,11 +123,19 @@ describe('iOS stream port leases (fake processes, real sockets, scratch DB)', ()
|
|
|
120
123
|
spawned = [];
|
|
121
124
|
extraServers = [];
|
|
122
125
|
appiumWdaPorts = new Set();
|
|
126
|
+
tunnels = {
|
|
127
|
+
ensure: sinon_1.default.stub().resolves(null),
|
|
128
|
+
envFor: sinon_1.default.stub().callsFake(() => (Object.assign(Object.assign({}, process.env), { ENABLE_GO_IOS_AGENT: 'yes' }))),
|
|
129
|
+
stop: sinon_1.default.stub().resolves(),
|
|
130
|
+
touch: sinon_1.default.stub().resolves(),
|
|
131
|
+
};
|
|
123
132
|
sinon_1.default.stub(process, 'kill');
|
|
124
133
|
const real = typedi_1.Container.get.bind(typedi_1.Container);
|
|
125
134
|
sinon_1.default.stub(typedi_1.Container, 'get').callsFake((token) => {
|
|
126
135
|
if (token === PortAllocator_1.PortAllocator)
|
|
127
136
|
return allocator;
|
|
137
|
+
if (token === IOSTunnels_1.IOSTunnels)
|
|
138
|
+
return tunnels;
|
|
128
139
|
if (token === ProcessRegistry_1.ProcessRegistry)
|
|
129
140
|
return { track: () => undefined };
|
|
130
141
|
return real(token);
|
|
@@ -133,12 +144,12 @@ describe('iOS stream port leases (fake processes, real sockets, scratch DB)', ()
|
|
|
133
144
|
findDevice: (_a) => __awaiter(void 0, [_a], void 0, function* ({ udid }) { return devices.get(udid); }),
|
|
134
145
|
updateDevice: (udid, _host, data) => __awaiter(void 0, void 0, void 0, function* () { return Object.assign(devices.get(udid), data); }),
|
|
135
146
|
});
|
|
136
|
-
sinon_1.default.stub(child_process_1.default, 'spawn').callsFake(((command, args) => {
|
|
147
|
+
sinon_1.default.stub(child_process_1.default, 'spawn').callsFake(((command, args, opts) => {
|
|
137
148
|
let server;
|
|
138
149
|
if (command === 'iproxy') {
|
|
139
150
|
server = listenNow(Number(args[args.length - 1].split(':')[0]));
|
|
140
151
|
}
|
|
141
|
-
const proc = new FakeProcess(command, args, server);
|
|
152
|
+
const proc = new FakeProcess(command, args, server, opts);
|
|
142
153
|
spawned.push(proc);
|
|
143
154
|
return proc;
|
|
144
155
|
}));
|
|
@@ -157,7 +168,6 @@ describe('iOS stream port leases (fake processes, real sockets, scratch DB)', ()
|
|
|
157
168
|
}));
|
|
158
169
|
sinon_1.default.stub(ios, 'cleanupOrphanTunnels').resolves();
|
|
159
170
|
sinon_1.default.stub(ios, 'killStaleProcesses').resolves();
|
|
160
|
-
sinon_1.default.stub(ios, 'ensureTunnel').resolves(null);
|
|
161
171
|
sinon_1.default.stub(ios, 'detectWDABundleId').resolves('com.test.WebDriverAgentRunner.xctrunner');
|
|
162
172
|
sinon_1.default.stub(ios, 'updateWDASettings').resolves();
|
|
163
173
|
sinon_1.default.stub(ios, 'createWDASession').resolves(null);
|
|
@@ -215,6 +225,17 @@ describe('iOS stream port leases (fake processes, real sockets, scratch DB)', ()
|
|
|
215
225
|
(0, chai_1.expect)(mjpeg === null || mjpeg === void 0 ? void 0 : mjpeg.leasedToUdid, `mjpeg port ${mjpegPort} must stay leased to the iPhone`).to.equal(IPHONE);
|
|
216
226
|
(0, chai_1.expect)(mjpeg === null || mjpeg === void 0 ? void 0 : mjpeg.purpose).to.equal('mjpeg');
|
|
217
227
|
}));
|
|
228
|
+
it("runs the iPhone's WDA through the iPhone's own go-ios tunnel", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
229
|
+
var _a, _b, _c;
|
|
230
|
+
tunnels.ensure.resolves(12100);
|
|
231
|
+
tunnels.envFor.callsFake((udid) => (Object.assign(Object.assign(Object.assign({}, process.env), { ENABLE_GO_IOS_AGENT: 'yes' }), (udid === IPHONE ? { GO_IOS_AGENT_PORT: '12100' } : {}))));
|
|
232
|
+
sinon_1.default.stub(ios, 'isGoIOSAvailable').resolves(true);
|
|
233
|
+
yield ios.startStream(IPHONE);
|
|
234
|
+
(0, chai_1.expect)(tunnels.ensure.calledWith(IPHONE)).to.equal(true);
|
|
235
|
+
(0, chai_1.expect)((_a = ios.getStreamStatus(IPHONE)) === null || _a === void 0 ? void 0 : _a.tunnelPort).to.equal(12100);
|
|
236
|
+
const runwda = spawned.find((p) => p.command === ios.goIOSPath && p.args[0] === 'runwda');
|
|
237
|
+
(0, chai_1.expect)((_c = (_b = runwda === null || runwda === void 0 ? void 0 : runwda.opts) === null || _b === void 0 ? void 0 : _b.env) === null || _c === void 0 ? void 0 : _c.GO_IOS_AGENT_PORT, "runwda finds this phone's tunnel").to.equal('12100');
|
|
238
|
+
}));
|
|
218
239
|
it('stopping a stale iOS session never deletes a lease another device holds', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
219
240
|
var _a;
|
|
220
241
|
// The S9+ has since been leased the port the stale iPhone session names.
|
|
@@ -233,7 +254,7 @@ describe('iOS stream port leases (fake processes, real sockets, scratch DB)', ()
|
|
|
233
254
|
wdaProcess: null,
|
|
234
255
|
forwardWDAProcess: null,
|
|
235
256
|
forwardMJPEGProcess: null,
|
|
236
|
-
|
|
257
|
+
tunnelPort: null,
|
|
237
258
|
wdaPort: scratch.base + 50,
|
|
238
259
|
mjpegPort: scratch.base,
|
|
239
260
|
status: 'error',
|