detox 20.11.4 → 20.12.0-smoke.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. package/Detox-android/com/wix/detox/{20.11.4/detox-20.11.4-javadoc.jar → 20.12.0-smoke.0/detox-20.12.0-smoke.0-javadoc.jar} +0 -0
  2. package/Detox-android/com/wix/detox/20.12.0-smoke.0/detox-20.12.0-smoke.0-javadoc.jar.md5 +1 -0
  3. package/Detox-android/com/wix/detox/20.12.0-smoke.0/detox-20.12.0-smoke.0-javadoc.jar.sha1 +1 -0
  4. package/Detox-android/com/wix/detox/20.12.0-smoke.0/detox-20.12.0-smoke.0-javadoc.jar.sha256 +1 -0
  5. package/Detox-android/com/wix/detox/20.12.0-smoke.0/detox-20.12.0-smoke.0-javadoc.jar.sha512 +1 -0
  6. package/Detox-android/com/wix/detox/{20.11.4/detox-20.11.4-sources.jar → 20.12.0-smoke.0/detox-20.12.0-smoke.0-sources.jar} +0 -0
  7. package/Detox-android/com/wix/detox/20.12.0-smoke.0/detox-20.12.0-smoke.0-sources.jar.md5 +1 -0
  8. package/Detox-android/com/wix/detox/20.12.0-smoke.0/detox-20.12.0-smoke.0-sources.jar.sha1 +1 -0
  9. package/Detox-android/com/wix/detox/20.12.0-smoke.0/detox-20.12.0-smoke.0-sources.jar.sha256 +1 -0
  10. package/Detox-android/com/wix/detox/20.12.0-smoke.0/detox-20.12.0-smoke.0-sources.jar.sha512 +1 -0
  11. package/Detox-android/com/wix/detox/{20.11.4/detox-20.11.4.pom → 20.12.0-smoke.0/detox-20.12.0-smoke.0.pom} +1 -1
  12. package/Detox-android/com/wix/detox/20.12.0-smoke.0/detox-20.12.0-smoke.0.pom.md5 +1 -0
  13. package/Detox-android/com/wix/detox/20.12.0-smoke.0/detox-20.12.0-smoke.0.pom.sha1 +1 -0
  14. package/Detox-android/com/wix/detox/20.12.0-smoke.0/detox-20.12.0-smoke.0.pom.sha256 +1 -0
  15. package/Detox-android/com/wix/detox/20.12.0-smoke.0/detox-20.12.0-smoke.0.pom.sha512 +1 -0
  16. package/Detox-android/com/wix/detox/maven-metadata.xml +4 -4
  17. package/Detox-android/com/wix/detox/maven-metadata.xml.md5 +1 -1
  18. package/Detox-android/com/wix/detox/maven-metadata.xml.sha1 +1 -1
  19. package/Detox-android/com/wix/detox/maven-metadata.xml.sha256 +1 -1
  20. package/Detox-android/com/wix/detox/maven-metadata.xml.sha512 +1 -1
  21. package/Detox-ios-src.tbz +0 -0
  22. package/Detox-ios.tbz +0 -0
  23. package/local-cli/reset-lock-file.js +5 -9
  24. package/package.json +6 -6
  25. package/src/DetoxWorker.js +5 -9
  26. package/src/artifacts/providers/index.js +3 -3
  27. package/src/artifacts/screenshot/SimulatorScreenshotPlugin.js +0 -17
  28. package/src/devices/allocation/DeviceAllocator.js +50 -15
  29. package/src/devices/allocation/DeviceList.js +44 -0
  30. package/src/devices/allocation/DeviceRegistry.js +186 -0
  31. package/src/devices/allocation/drivers/AllocationDriverBase.js +10 -4
  32. package/src/devices/{common/drivers/android/tools → allocation/drivers/android}/FreeDeviceFinder.js +11 -10
  33. package/src/devices/allocation/drivers/android/attached/AttachedAndroidAllocDriver.js +7 -6
  34. package/src/devices/allocation/drivers/android/emulator/EmulatorAllocDriver.js +108 -26
  35. package/src/devices/allocation/drivers/android/emulator/EmulatorLauncher.js +33 -43
  36. package/src/devices/allocation/drivers/android/emulator/FreeEmulatorFinder.js +1 -1
  37. package/src/devices/allocation/drivers/android/emulator/FreePortFinder.js +16 -0
  38. package/src/devices/allocation/drivers/android/emulator/launchEmulatorProcess.js +3 -3
  39. package/src/devices/allocation/drivers/android/genycloud/GenyAllocDriver.js +87 -27
  40. package/src/devices/allocation/drivers/android/genycloud/GenyInstanceLauncher.js +39 -28
  41. package/src/devices/allocation/drivers/android/genycloud/GenyRegistry.js +93 -0
  42. package/src/devices/allocation/drivers/android/genycloud/services/GenyInstanceLifecycleService.js +24 -0
  43. package/src/devices/{common → allocation}/drivers/android/genycloud/services/GenyRecipesService.js +1 -1
  44. package/src/devices/{common → allocation}/drivers/android/genycloud/services/dto/GenyInstance.js +6 -1
  45. package/src/devices/allocation/drivers/ios/SimulatorAllocDriver.js +70 -40
  46. package/src/devices/allocation/drivers/ios/SimulatorLauncher.js +11 -7
  47. package/src/devices/allocation/drivers/ios/SimulatorQuery.js +24 -0
  48. package/src/devices/allocation/factories/android.js +29 -35
  49. package/src/devices/allocation/factories/ios.js +7 -5
  50. package/src/devices/common/drivers/android/emulator/exec/EmulatorExec.js +17 -5
  51. package/src/devices/common/drivers/android/exec/ADB.js +1 -0
  52. package/src/devices/common/drivers/ios/tools/AppleSimUtils.js +1 -1
  53. package/src/devices/cookies/AndroidDeviceCookie.js +4 -0
  54. package/src/devices/cookies/GenycloudEmulatorCookie.js +3 -5
  55. package/src/devices/cookies/IosSimulatorCookie.js +4 -0
  56. package/src/devices/runtime/drivers/android/genycloud/GenyCloudDriver.js +2 -2
  57. package/src/devices/runtime/factories/android.js +1 -1
  58. package/src/devices/runtime/factories/ios.js +3 -2
  59. package/src/{servicelocator → devices/servicelocator}/android/emulatorServiceLocator.js +1 -1
  60. package/src/devices/servicelocator/android/genycloudServiceLocator.js +17 -0
  61. package/src/devices/servicelocator/android/index.js +23 -0
  62. package/src/{validation → devices/validation}/android/GenycloudEnvValidator.js +2 -2
  63. package/src/{validation → devices/validation}/factories/index.js +1 -1
  64. package/src/{validation → devices/validation}/ios/IosSimulatorEnvValidator.js +2 -2
  65. package/src/environmentFactory.js +1 -11
  66. package/src/ipc/IPCClient.js +17 -1
  67. package/src/ipc/IPCServer.js +25 -1
  68. package/src/realms/DetoxContext.js +6 -0
  69. package/src/realms/DetoxPrimaryContext.js +42 -42
  70. package/src/realms/DetoxSecondaryContext.js +19 -0
  71. package/src/realms/symbols.js +4 -0
  72. package/src/utils/PIDService.js +27 -0
  73. package/src/utils/environment.js +8 -15
  74. package/src/utils/errorUtils.js +2 -2
  75. package/Detox-android/com/wix/detox/20.11.4/detox-20.11.4-javadoc.jar.md5 +0 -1
  76. package/Detox-android/com/wix/detox/20.11.4/detox-20.11.4-javadoc.jar.sha1 +0 -1
  77. package/Detox-android/com/wix/detox/20.11.4/detox-20.11.4-javadoc.jar.sha256 +0 -1
  78. package/Detox-android/com/wix/detox/20.11.4/detox-20.11.4-javadoc.jar.sha512 +0 -1
  79. package/Detox-android/com/wix/detox/20.11.4/detox-20.11.4-sources.jar.md5 +0 -1
  80. package/Detox-android/com/wix/detox/20.11.4/detox-20.11.4-sources.jar.sha1 +0 -1
  81. package/Detox-android/com/wix/detox/20.11.4/detox-20.11.4-sources.jar.sha256 +0 -1
  82. package/Detox-android/com/wix/detox/20.11.4/detox-20.11.4-sources.jar.sha512 +0 -1
  83. package/Detox-android/com/wix/detox/20.11.4/detox-20.11.4.pom.md5 +0 -1
  84. package/Detox-android/com/wix/detox/20.11.4/detox-20.11.4.pom.sha1 +0 -1
  85. package/Detox-android/com/wix/detox/20.11.4/detox-20.11.4.pom.sha256 +0 -1
  86. package/Detox-android/com/wix/detox/20.11.4/detox-20.11.4.pom.sha512 +0 -1
  87. package/src/devices/DeviceRegistry.js +0 -176
  88. package/src/devices/allocation/drivers/android/attached/AttachedAndroidLauncher.js +0 -13
  89. package/src/devices/allocation/drivers/android/emulator/EmulatorAllocationHelper.js +0 -72
  90. package/src/devices/allocation/drivers/android/genycloud/GenyDeviceRegistryFactory.js +0 -16
  91. package/src/devices/allocation/drivers/android/genycloud/GenyInstanceAllocationHelper.js +0 -65
  92. package/src/devices/common/drivers/DeviceAllocationHelper.js +0 -20
  93. package/src/devices/common/drivers/DeviceLauncher.js +0 -19
  94. package/src/devices/common/drivers/android/genycloud/services/GenyInstanceLifecycleService.js +0 -25
  95. package/src/devices/common/drivers/android/genycloud/services/GenyInstanceLookupService.js +0 -38
  96. package/src/devices/common/drivers/android/genycloud/services/GenyInstanceNaming.js +0 -14
  97. package/src/devices/lifecycle/GenyGlobalLifecycleHandler.js +0 -71
  98. package/src/devices/lifecycle/factories/GenyGlobalLifecycleHandlerFactory.js +0 -18
  99. package/src/servicelocator/android/genycloudServiceLocator.js +0 -21
  100. package/src/servicelocator/android/index.js +0 -25
  101. package/src/servicelocator/ios.js +0 -7
  102. /package/Detox-android/com/wix/detox/{20.11.4/detox-20.11.4.aar → 20.12.0-smoke.0/detox-20.12.0-smoke.0.aar} +0 -0
  103. /package/Detox-android/com/wix/detox/{20.11.4/detox-20.11.4.aar.md5 → 20.12.0-smoke.0/detox-20.12.0-smoke.0.aar.md5} +0 -0
  104. /package/Detox-android/com/wix/detox/{20.11.4/detox-20.11.4.aar.sha1 → 20.12.0-smoke.0/detox-20.12.0-smoke.0.aar.sha1} +0 -0
  105. /package/Detox-android/com/wix/detox/{20.11.4/detox-20.11.4.aar.sha256 → 20.12.0-smoke.0/detox-20.12.0-smoke.0.aar.sha256} +0 -0
  106. /package/Detox-android/com/wix/detox/{20.11.4/detox-20.11.4.aar.sha512 → 20.12.0-smoke.0/detox-20.12.0-smoke.0.aar.sha512} +0 -0
  107. /package/src/devices/{common → allocation}/drivers/android/genycloud/exec/GenyCloudExec.js +0 -0
  108. /package/src/devices/{common → allocation}/drivers/android/genycloud/services/GenyAuthService.js +0 -0
  109. /package/src/devices/{common → allocation}/drivers/android/genycloud/services/dto/GenyRecipe.js +0 -0
  110. /package/src/{validation → devices/validation}/EnvironmentValidatorBase.js +0 -0
@@ -7,14 +7,16 @@ class AttachedAndroidAllocDriver extends AllocationDriverBase {
7
7
  * @param adb { ADB }
8
8
  * @param deviceRegistry { DeviceRegistry }
9
9
  * @param freeDeviceFinder { FreeDeviceFinder }
10
- * @param attachedAndroidLauncher { AttachedAndroidLauncher }
11
10
  */
12
- constructor({ adb, deviceRegistry, freeDeviceFinder, attachedAndroidLauncher }) {
11
+ constructor({ adb, deviceRegistry, freeDeviceFinder }) {
13
12
  super();
14
13
  this._adb = adb;
15
14
  this._deviceRegistry = deviceRegistry;
16
15
  this._freeDeviceFinder = freeDeviceFinder;
17
- this._attachedAndroidLauncher = attachedAndroidLauncher;
16
+ }
17
+
18
+ async init() {
19
+ await this._deviceRegistry.unregisterZombieDevices();
18
20
  }
19
21
 
20
22
  /**
@@ -23,7 +25,7 @@ class AttachedAndroidAllocDriver extends AllocationDriverBase {
23
25
  */
24
26
  async allocate(deviceConfig) {
25
27
  const adbNamePattern = deviceConfig.device.adbName;
26
- const adbName = await this._deviceRegistry.allocateDevice(() => this._freeDeviceFinder.findFreeDevice(adbNamePattern));
28
+ const adbName = await this._deviceRegistry.registerDevice(() => this._freeDeviceFinder.findFreeDevice(adbNamePattern));
27
29
 
28
30
  return new AttachedAndroidDeviceCookie(adbName);
29
31
  }
@@ -38,7 +40,6 @@ class AttachedAndroidAllocDriver extends AllocationDriverBase {
38
40
  // TODO Also disable native animations?
39
41
  await this._adb.apiLevel(adbName);
40
42
  await this._adb.unlockScreen(adbName);
41
- await this._attachedAndroidLauncher.notifyLaunchCompleted(adbName);
42
43
  }
43
44
 
44
45
  /**
@@ -47,7 +48,7 @@ class AttachedAndroidAllocDriver extends AllocationDriverBase {
47
48
  */
48
49
  async free(cookie) {
49
50
  const { adbName } = cookie;
50
- await this._deviceRegistry.disposeDevice(adbName);
51
+ await this._deviceRegistry.unregisterDevice(adbName);
51
52
  }
52
53
  }
53
54
 
@@ -1,6 +1,7 @@
1
- // @ts-nocheck
2
1
  const _ = require('lodash');
3
2
 
3
+ const Deferred = require('../../../../../utils/Deferred');
4
+ const log = require('../../../../../utils/logger').child({ cat: 'device,device-allocation' });
4
5
  const AndroidEmulatorCookie = require('../../../../cookies/AndroidEmulatorCookie');
5
6
  const AllocationDriverBase = require('../../AllocationDriverBase');
6
7
 
@@ -8,20 +9,45 @@ const { patchAvdSkinConfig } = require('./patchAvdSkinConfig');
8
9
 
9
10
  class EmulatorAllocDriver extends AllocationDriverBase {
10
11
  /**
11
- * @param adb { ADB }
12
- * @param avdValidator { AVDValidator }
13
- * @param emulatorVersionResolver { EmulatorVersionResolver }
14
- * @param emulatorLauncher { EmulatorLauncher }
15
- * @param allocationHelper { EmulatorAllocationHelper }
12
+ * @param {object} options
13
+ * @param {import('../../../../common/drivers/android/exec/ADB')} options.adb
14
+ * @param {import('./AVDValidator')} options.avdValidator
15
+ * @param {DetoxInternals.RuntimeConfig} options.detoxConfig
16
+ * @param {import('../../../DeviceRegistry')} options.deviceRegistry
17
+ * @param {import('./FreeEmulatorFinder')} options.freeDeviceFinder
18
+ * @param {import('./FreePortFinder')} options.freePortFinder
19
+ * @param {import('./EmulatorLauncher')} options.emulatorLauncher
20
+ * @param {import('./EmulatorVersionResolver')} options.emulatorVersionResolver
16
21
  */
17
- constructor({ adb, avdValidator, emulatorVersionResolver, emulatorLauncher, allocationHelper }) {
22
+ constructor({
23
+ adb,
24
+ avdValidator,
25
+ detoxConfig,
26
+ deviceRegistry,
27
+ freeDeviceFinder,
28
+ freePortFinder,
29
+ emulatorVersionResolver,
30
+ emulatorLauncher
31
+ }) {
18
32
  super();
33
+
34
+ /** @type {Deferred} */
35
+ this._deferredAllocation = Deferred.resolved(null);
36
+ /** @type {Promise<unknown>} */
37
+ this._pendingAllocation = Promise.resolve();
38
+
19
39
  this._adb = adb;
20
40
  this._avdValidator = avdValidator;
41
+ this._deviceRegistry = deviceRegistry;
21
42
  this._emulatorVersionResolver = emulatorVersionResolver;
22
43
  this._emulatorLauncher = emulatorLauncher;
23
- this._allocationHelper = allocationHelper;
24
- this._launchInfo = {};
44
+ this._freeDeviceFinder = freeDeviceFinder;
45
+ this._freePortFinder = freePortFinder;
46
+ this._shouldShutdown = detoxConfig.behavior.cleanup.shutdownDevice;
47
+ }
48
+
49
+ async init() {
50
+ await this._deviceRegistry.unregisterZombieDevices();
25
51
  }
26
52
 
27
53
  /**
@@ -29,55 +55,111 @@ class EmulatorAllocDriver extends AllocationDriverBase {
29
55
  * @returns {Promise<AndroidEmulatorCookie>}
30
56
  */
31
57
  async allocate(deviceConfig) {
58
+ await this._pendingAllocation.catch(() => { /* ignore previous errors */ });
59
+ this._deferredAllocation = new Deferred();
60
+ this._pendingAllocation = this._deviceRegistry.registerDevice(() => this._deferredAllocation.promise);
61
+
62
+ try {
63
+ return await this._doAllocate(deviceConfig);
64
+ } catch (e) {
65
+ this._deferredAllocation.reject(e);
66
+ throw e;
67
+ }
68
+ }
69
+
70
+ /**
71
+ * @param deviceConfig
72
+ * @returns {Promise<AndroidEmulatorCookie>}
73
+ */
74
+ async _doAllocate(deviceConfig) {
32
75
  const avdName = deviceConfig.device.avdName;
33
76
 
34
77
  await this._avdValidator.validate(avdName, deviceConfig.headless);
35
78
  await this._fixAvdConfigIniSkinNameIfNeeded(avdName, deviceConfig.headless);
36
79
 
37
- const allocResult = await this._allocationHelper.allocateDevice(avdName);
38
- const { adbName } = allocResult;
80
+ let adbName = await this._freeDeviceFinder.findFreeDevice(avdName);
81
+ if (!adbName) {
82
+ const port = await this._freePortFinder.findFreePort();
83
+ adbName = `emulator-${port}`;
39
84
 
40
- this._launchInfo[adbName] = {
41
- avdName,
42
- isRunning: allocResult.isRunning,
43
- launchOptions: {
85
+ await this._emulatorLauncher.launch({
44
86
  bootArgs: deviceConfig.bootArgs,
45
87
  gpuMode: deviceConfig.gpuMode,
46
88
  headless: deviceConfig.headless,
47
89
  readonly: deviceConfig.readonly,
48
- port: allocResult.placeholderPort,
49
- },
50
- };
90
+ avdName,
91
+ adbName,
92
+ port,
93
+ });
94
+ }
51
95
 
52
96
  return new AndroidEmulatorCookie(adbName);
53
97
  }
54
98
 
99
+ /**
100
+ * @param {AndroidEmulatorCookie} deviceCookie
101
+ */
102
+ async postAllocate(deviceCookie) {
103
+ try {
104
+ await this._doPostAllocate(deviceCookie);
105
+ this._deferredAllocation.resolve(deviceCookie.adbName);
106
+ } catch (e) {
107
+ this._deferredAllocation.reject(e);
108
+ throw e;
109
+ }
110
+ }
111
+
55
112
  /**
56
113
  * @param {AndroidEmulatorCookie} deviceCookie
57
114
  * @returns {Promise<void>}
58
115
  */
59
- async postAllocate(deviceCookie) {
116
+ async _doPostAllocate(deviceCookie) {
60
117
  const { adbName } = deviceCookie;
61
- const { avdName, isRunning, launchOptions } = this._launchInfo[adbName];
62
118
 
63
- await this._emulatorLauncher.launch(avdName, adbName, isRunning, launchOptions);
119
+ await this._emulatorLauncher.awaitEmulatorBoot(adbName);
64
120
  await this._adb.apiLevel(adbName);
65
121
  await this._adb.disableAndroidAnimations(adbName);
66
122
  await this._adb.unlockScreen(adbName);
67
123
  }
68
124
 
69
125
  /**
70
- * @param cookie { AndroidEmulatorCookie }
71
- * @param options { DeallocOptions }
72
- * @return { Promise<void> }
126
+ * @param cookie {AndroidEmulatorCookie}
127
+ * @param options {Partial<import('../../AllocationDriverBase').DeallocOptions>}
128
+ * @return {Promise<void>}
73
129
  */
74
130
  async free(cookie, options = {}) {
75
131
  const { adbName } = cookie;
76
132
 
77
- await this._allocationHelper.deallocateDevice(adbName);
78
-
79
133
  if (options.shutdown) {
134
+ await this._doShutdown(adbName);
135
+ await this._deviceRegistry.unregisterDevice(adbName);
136
+ } else {
137
+ await this._deviceRegistry.releaseDevice(adbName);
138
+ }
139
+ }
140
+
141
+ async cleanup() {
142
+ if (this._shouldShutdown) {
143
+ const { devices } = await this._adb.devices();
144
+ const actualEmulators = devices.map((device) => device.adbName);
145
+ const sessionDevices = await this._deviceRegistry.readSessionDevices();
146
+ const emulatorsToShutdown = _.intersection(sessionDevices.getIds(), actualEmulators);
147
+ const shutdownPromises = emulatorsToShutdown.map((adbName) => this._doShutdown(adbName));
148
+ await Promise.all(shutdownPromises);
149
+ }
150
+
151
+ await this._deviceRegistry.unregisterSessionDevices();
152
+ }
153
+
154
+ /**
155
+ * @param {string} adbName
156
+ * @return {Promise<void>}
157
+ */
158
+ async _doShutdown(adbName) {
159
+ try {
80
160
  await this._emulatorLauncher.shutdown(adbName);
161
+ } catch (err) {
162
+ log.warn({ err }, `Failed to shutdown emulator ${adbName}`);
81
163
  }
82
164
  }
83
165
 
@@ -3,47 +3,54 @@ const { DetoxRuntimeError } = require('../../../../../errors');
3
3
  const log = require('../../../../../utils/logger').child({ cat: 'device' });
4
4
  const retry = require('../../../../../utils/retry');
5
5
  const traceMethods = require('../../../../../utils/traceMethods');
6
- const DeviceLauncher = require('../../../../common/drivers/DeviceLauncher');
7
6
  const { LaunchCommand } = require('../../../../common/drivers/android/emulator/exec/EmulatorExec');
8
7
 
9
8
  const { launchEmulatorProcess } = require('./launchEmulatorProcess');
10
9
 
11
10
  const isUnknownEmulatorError = (err) => (err.message || '').includes('failed with code null');
12
11
 
13
- class EmulatorLauncher extends DeviceLauncher {
14
- constructor({ adb, emulatorExec, eventEmitter }) {
15
- super(eventEmitter);
12
+ class EmulatorLauncher {
13
+ constructor({ adb, emulatorExec }) {
16
14
  this._adb = adb;
17
15
  this._emulatorExec = emulatorExec;
18
- traceMethods(log, this, ['_awaitEmulatorBoot']);
16
+ traceMethods(log, this, ['awaitEmulatorBoot']);
19
17
  }
20
18
 
21
19
  /**
22
- * @param avdName { String }
23
- * @param adbName { String }
24
- * @param isRunning { Boolean }
25
- * @param options { Object }
26
- * @param options.port { Number | undefined }
27
- * @param options.bootArgs { String | undefined }
28
- * @param options.gpuMode { String | undefined }
29
- * @param options.headless { Boolean }
30
- * @param options.readonly { Boolean }
20
+ * @param {object} options
21
+ * @param {string} options.avdName
22
+ * @param {string} options.adbName
23
+ * @param {number} options.port
24
+ * @param {string | undefined} options.bootArgs
25
+ * @param {string | undefined} options.gpuMode
26
+ * @param {boolean} options.headless
27
+ * @param {boolean} options.readonly
31
28
  */
32
- async launch(avdName, adbName, isRunning, options = { port: undefined }) {
33
- if (!isRunning) {
34
- const launchCommand = new LaunchCommand(avdName, options);
35
- await retry({
36
- retries: 2,
37
- interval: 100,
38
- conditionFn: isUnknownEmulatorError,
39
- }, () => this._launchEmulator(avdName, launchCommand, adbName));
40
- }
41
- await this._awaitEmulatorBoot(adbName);
42
- await this._notifyBootEvent(adbName, avdName, !isRunning);
29
+ async launch(options) {
30
+ const launchCommand = new LaunchCommand(options);
31
+ await retry({
32
+ retries: 2,
33
+ interval: 100,
34
+ conditionFn: isUnknownEmulatorError,
35
+ }, () => launchEmulatorProcess(this._emulatorExec, this._adb, launchCommand));
36
+ }
37
+
38
+ /**
39
+ * @param {string} adbName
40
+ */
41
+ async awaitEmulatorBoot(adbName) {
42
+ await retry({ retries: 240, interval: 2500, shouldUnref: true }, async () => {
43
+ const isBootComplete = await this._adb.isBootComplete(adbName);
44
+
45
+ if (!isBootComplete) {
46
+ throw new DetoxRuntimeError({
47
+ message: `Waited for ${adbName} to complete booting for too long!`,
48
+ });
49
+ }
50
+ });
43
51
  }
44
52
 
45
53
  async shutdown(adbName) {
46
- await this._notifyPreShutdown(adbName);
47
54
  await this._adb.emuKill(adbName);
48
55
  await retry({
49
56
  retries: 5,
@@ -57,23 +64,6 @@ class EmulatorLauncher extends DeviceLauncher {
57
64
  });
58
65
  }
59
66
  });
60
- await this._notifyShutdownCompleted(adbName);
61
- }
62
-
63
- _launchEmulator(emulatorName, launchCommand, adbName) {
64
- return launchEmulatorProcess(emulatorName, this._emulatorExec, launchCommand, this._adb, adbName);
65
- }
66
-
67
- async _awaitEmulatorBoot(adbName) {
68
- await retry({ retries: 240, interval: 2500, shouldUnref: true }, async () => {
69
- const isBootComplete = await this._adb.isBootComplete(adbName);
70
-
71
- if (!isBootComplete) {
72
- throw new DetoxRuntimeError({
73
- message: `Waited for ${adbName} to complete booting for too long!`,
74
- });
75
- }
76
- });
77
67
  }
78
68
  }
79
69
 
@@ -1,4 +1,4 @@
1
- const FreeDeviceFinder = require('../../../../common/drivers/android/tools/FreeDeviceFinder');
1
+ const FreeDeviceFinder = require('../FreeDeviceFinder');
2
2
 
3
3
  class FreeEmulatorFinder extends FreeDeviceFinder {
4
4
  /**
@@ -0,0 +1,16 @@
1
+ class FreePortFinder {
2
+ constructor({ min = 10000, max = 20000 } = {}) {
3
+ this._min = min;
4
+ this._max = max;
5
+ }
6
+
7
+ async findFreePort() {
8
+ const min = this._min;
9
+ const max = this._max;
10
+ let port = Math.random() * (max - min) + min;
11
+ port = port & 0xFFFFFFFE; // Should always be even
12
+ return port;
13
+ }
14
+ }
15
+
16
+ module.exports = FreePortFinder;
@@ -4,10 +4,10 @@ const _ = require('lodash');
4
4
 
5
5
  const unitLogger = require('../../../../../utils/logger').child({ cat: 'device' });
6
6
 
7
- function launchEmulatorProcess(emulatorName, emulatorExec, emulatorLaunchCommand, adb, adbName) {
7
+ function launchEmulatorProcess(emulatorExec, adb, emulatorLaunchCommand) {
8
8
  let childProcessOutput;
9
9
  const portName = emulatorLaunchCommand.port ? `-${emulatorLaunchCommand.port}` : '';
10
- const tempLog = `./${emulatorName}${portName}.log`;
10
+ const tempLog = `./${emulatorLaunchCommand.avdName}${portName}.log`;
11
11
  const stdout = fs.openSync(tempLog, 'a');
12
12
  const stderr = fs.openSync(tempLog, 'a');
13
13
 
@@ -31,7 +31,7 @@ function launchEmulatorProcess(emulatorName, emulatorExec, emulatorLaunchCommand
31
31
 
32
32
  log = log.child({ child_pid: childProcessPromise.childProcess.pid });
33
33
 
34
- adb.waitForDevice(adbName).then(() => childProcessPromise._cpResolve());
34
+ adb.waitForDevice(emulatorLaunchCommand.adbName).then(() => childProcessPromise._cpResolve());
35
35
 
36
36
  return childProcessPromise.then(() => true).catch((err) => {
37
37
  detach();
@@ -1,25 +1,39 @@
1
1
  const { DetoxRuntimeError } = require('../../../../../errors');
2
2
  const Timer = require('../../../../../utils/Timer');
3
+ const log = require('../../../../../utils/logger').child({ cat: 'device' });
3
4
  const GenycloudEmulatorCookie = require('../../../../cookies/GenycloudEmulatorCookie');
4
5
  const AllocationDriverBase = require('../../AllocationDriverBase');
5
6
 
6
- class GenyAllocDriver extends AllocationDriverBase {
7
+ const GenyRegistry = require('./GenyRegistry');
8
+
9
+ const events = {
10
+ GENYCLOUD_TEARDOWN: { event: 'GENYCLOUD_TEARDOWN' },
11
+ };
7
12
 
13
+ class GenyAllocDriver extends AllocationDriverBase {
8
14
  /**
9
15
  * @param {object} options
10
16
  * @param {import('../../../../common/drivers/android/exec/ADB')} options.adb
11
- * @param {import('./GenyRecipeQuerying')} options.recipeQuerying
12
- * @param {import('./GenyInstanceAllocationHelper')} options.allocationHelper
17
+ * @param {DetoxInternals.SessionState} options.detoxSession
18
+ * @param {import('./GenyRegistry')} options.genyRegistry
13
19
  * @param {import('./GenyInstanceLauncher')} options.instanceLauncher
20
+ * @param {import('./GenyRecipeQuerying')} options.recipeQuerying
14
21
  */
15
- constructor({ adb, recipeQuerying, allocationHelper, instanceLauncher }) {
22
+ constructor({
23
+ adb,
24
+ detoxSession,
25
+ genyRegistry = new GenyRegistry(),
26
+ instanceLauncher,
27
+ recipeQuerying,
28
+ }) {
16
29
  super();
17
30
 
18
31
  this._adb = adb;
19
- this._recipeQuerying = recipeQuerying;
32
+ this._detoxSessionId = detoxSession.id;
33
+ this._genyRegistry = genyRegistry;
20
34
  this._instanceLauncher = instanceLauncher;
21
- this._instanceAllocationHelper = allocationHelper;
22
- this._launchInfo = {};
35
+ this._recipeQuerying = recipeQuerying;
36
+ this._instanceCounter = 0;
23
37
  }
24
38
 
25
39
  /**
@@ -27,47 +41,74 @@ class GenyAllocDriver extends AllocationDriverBase {
27
41
  * @return {Promise<GenycloudEmulatorCookie>}
28
42
  */
29
43
  async allocate(deviceConfig) {
44
+ await new Promise((resolve) => setTimeout(resolve, 10000));
30
45
  const deviceQuery = deviceConfig.device;
31
46
  const recipe = await this._recipeQuerying.getRecipeFromQuery(deviceQuery);
32
47
  this._assertRecipe(deviceQuery, recipe);
33
48
 
34
- const { instance, isNew } = await this._instanceAllocationHelper.allocateDevice(recipe);
35
- this._launchInfo[instance.uuid] = { isNew };
49
+ let instance = this._genyRegistry.findFreeInstance(recipe);
50
+ if (!instance) {
51
+ const instanceName = `Detox.${this._detoxSessionId}.${this._instanceCounter++}`;
52
+ instance = await this._instanceLauncher.launch(recipe, instanceName);
53
+ this._genyRegistry.addInstance(instance, recipe);
54
+ }
55
+
36
56
  return new GenycloudEmulatorCookie(instance);
37
57
  }
38
58
 
39
59
  /**
40
60
  * @param {GenycloudEmulatorCookie} cookie
41
- * @returns {Promise<void>}
42
61
  */
43
62
  async postAllocate(cookie) {
44
- const { instance } = cookie;
45
- const { isNew } = this._launchInfo[instance.uuid];
46
- const readyInstance = cookie.instance = await this._instanceLauncher.launch(instance, isNew);
47
-
48
- const { adbName } = readyInstance;
49
- await Timer.run(20000, 'waiting for device to respond', async () => {
50
- await this._adb.disableAndroidAnimations(adbName);
51
- await this._adb.setWiFiToggle(adbName, true);
52
- await this._adb.apiLevel(adbName);
53
- });
63
+ const instance = await this._instanceLauncher.connect(cookie.instance);
64
+ this._genyRegistry.updateInstance(instance);
65
+
66
+ if (this._genyRegistry.pollNewInstance(instance)) {
67
+ const { adbName } = instance;
68
+
69
+ await Timer.run(20000, 'waiting for device to respond', async () => {
70
+ await this._adb.disableAndroidAnimations(adbName);
71
+ await this._adb.setWiFiToggle(adbName, true);
72
+ await this._adb.apiLevel(adbName);
73
+ });
74
+ }
75
+
76
+ return new GenycloudEmulatorCookie(instance);
54
77
  }
55
78
 
56
79
  /**
57
- * @param cookie { GenycloudEmulatorCookie }
58
- * @param options { DeallocOptions }
80
+ * @param cookie {GenycloudEmulatorCookie}
81
+ * @param options {Partial<import('../../AllocationDriverBase').DeallocOptions>}
59
82
  * @return {Promise<void>}
60
83
  */
61
84
  async free(cookie, options = {}) {
62
- const { instance } = cookie;
63
-
64
- await this._instanceAllocationHelper.deallocateDevice(instance.uuid);
65
-
66
85
  if (options.shutdown) {
67
- await this._instanceLauncher.shutdown(instance);
86
+ this._genyRegistry.removeInstance(cookie.instance);
87
+ await this._instanceLauncher.shutdown(cookie.instance);
88
+ } else {
89
+ this._genyRegistry.markAsFree(cookie.instance);
68
90
  }
69
91
  }
70
92
 
93
+ async cleanup() {
94
+ log.info(events.GENYCLOUD_TEARDOWN, 'Initiating Genymotion SaaS instances teardown...');
95
+
96
+ const killPromises = this._genyRegistry.getInstances().map((instance) => {
97
+ this._genyRegistry.markAsBusy(instance);
98
+ const onSuccess = () => this._genyRegistry.removeInstance(instance);
99
+ const onError = (error) => ({ ...instance, error });
100
+ return this._instanceLauncher.shutdown(instance).then(onSuccess, onError);
101
+ });
102
+
103
+ const deletionLeaks = (await Promise.all(killPromises)).filter(Boolean);
104
+ this._reportGlobalCleanupSummary(deletionLeaks);
105
+ }
106
+
107
+ emergencyCleanup() {
108
+ const instances = this._genyRegistry.getInstances();
109
+ this._reportGlobalCleanupSummary(instances);
110
+ }
111
+
71
112
  _assertRecipe(deviceQuery, recipe) {
72
113
  if (!recipe) {
73
114
  throw new DetoxRuntimeError({
@@ -76,6 +117,25 @@ class GenyAllocDriver extends AllocationDriverBase {
76
117
  });
77
118
  }
78
119
  }
120
+
121
+ _reportGlobalCleanupSummary(deletionLeaks) {
122
+ if (deletionLeaks.length) {
123
+ log.warn(events.GENYCLOUD_TEARDOWN, 'WARNING! Detected a Genymotion SaaS instance leakage, for the following instances:');
124
+
125
+ deletionLeaks.forEach(({ uuid, name, error }) => {
126
+ log.warn(events.GENYCLOUD_TEARDOWN, [
127
+ `Instance ${name} (${uuid})${error ? `: ${error}` : ''}`,
128
+ ` Kill it by visiting https://cloud.geny.io/instance/${uuid}, or by running:`,
129
+ ` gmsaas instances stop ${uuid}`,
130
+ ].join('\n'));
131
+ });
132
+
133
+ log.info(events.GENYCLOUD_TEARDOWN, 'Instances teardown completed with warnings');
134
+ } else {
135
+ log.info(events.GENYCLOUD_TEARDOWN, 'Instances teardown completed successfully');
136
+ }
137
+ }
79
138
  }
80
139
 
140
+
81
141
  module.exports = GenyAllocDriver;
@@ -1,44 +1,51 @@
1
- // @ts-nocheck
2
1
  const DetoxRuntimeError = require('../../../../../errors/DetoxRuntimeError');
2
+ const logger = require('../../../../../utils/logger').child({ cat: 'device' });
3
3
  const retry = require('../../../../../utils/retry');
4
- const DeviceLauncher = require('../../../../common/drivers/DeviceLauncher');
5
4
 
6
- class GenyInstanceLauncher extends DeviceLauncher {
7
- constructor({ instanceLifecycleService, instanceLookupService, deviceCleanupRegistry, eventEmitter }) {
8
- super(eventEmitter);
5
+ const GenyInstance = require('./services/dto/GenyInstance');
9
6
 
7
+ const events = {
8
+ CREATE_DEVICE: { event: 'CREATE_DEVICE' },
9
+ };
10
+
11
+ class GenyInstanceLauncher {
12
+ constructor({ genyCloudExec, instanceLifecycleService }) {
13
+ this._genyCloudExec = genyCloudExec;
10
14
  this._instanceLifecycleService = instanceLifecycleService;
11
- this._instanceLookupService = instanceLookupService;
12
- this._deviceCleanupRegistry = deviceCleanupRegistry;
13
15
  }
14
16
 
15
17
  /**
16
- * Note:
17
- * In the context of Genymotion-cloud (as opposed to local emulators), emulators are
18
- * not launched per-se, as with local emulators. Rather, we just need to sync-up with
19
- * them and connect, if needed.
20
- *
21
- * @param instance {GenyInstance} The freshly allocated cloud-instance.
22
- * @param isNew { boolean }
18
+ * @param {import('./services/dto/GenyRecipe')} recipe
19
+ * @param {string} instanceName
23
20
  * @returns {Promise<GenyInstance>}
24
21
  */
25
- async launch(instance, isNew = true) {
26
- if (isNew) {
27
- await this._deviceCleanupRegistry.allocateDevice(instance.uuid, { name: instance.name });
28
- }
29
- instance = await this._waitForInstanceBoot(instance);
30
- instance = await this._adbConnectIfNeeded(instance);
31
- await this._notifyBootEvent(instance.adbName, instance.recipeName, isNew);
22
+ async launch(recipe, instanceName) {
23
+ logger.debug(events.CREATE_DEVICE, `Trying to create a device based on "${recipe}"`);
24
+ const instance = await this._instanceLifecycleService.createInstance(recipe.uuid, instanceName);
25
+ const { name, uuid } = instance;
26
+ logger.info(events.CREATE_DEVICE, `Allocating Genymotion Cloud instance ${name} for testing. To access it via a browser, go to: https://cloud.geny.io/instance/${uuid}`);
27
+
32
28
  return instance;
33
29
  }
34
30
 
31
+ /**
32
+ * @param {GenyInstance} instance The freshly allocated cloud-instance.
33
+ * @returns {Promise<GenyInstance>}
34
+ */
35
+ async connect(instance) {
36
+ const bootedInstance = await this._waitForInstanceBoot(instance);
37
+ const connectedInstance = await this._adbConnectIfNeeded(bootedInstance);
38
+
39
+ return connectedInstance;
40
+ }
41
+
42
+ /**
43
+ * @param {import('./services/dto/GenyInstance')} instance The freshly allocated cloud-instance.
44
+ */
35
45
  async shutdown(instance) {
36
46
  const { uuid } = instance;
37
47
 
38
- await this._notifyPreShutdown(uuid);
39
48
  await this._instanceLifecycleService.deleteInstance(uuid);
40
- await this._deviceCleanupRegistry.disposeDevice(uuid);
41
- await this._notifyShutdownCompleted(uuid);
42
49
  }
43
50
 
44
51
  async _waitForInstanceBoot(instance) {
@@ -48,17 +55,21 @@ class GenyInstanceLauncher extends DeviceLauncher {
48
55
 
49
56
  const options = {
50
57
  backoff: 'none',
51
- retries: 25,
58
+ retries: 20,
52
59
  interval: 5000,
53
60
  initialSleep: 45000,
61
+ shouldUnref: true,
54
62
  };
55
63
 
56
64
  return await retry(options, async () => {
57
- const _instance = await this._instanceLookupService.getInstance(instance.uuid);
58
- if (!_instance.isOnline()) {
65
+ const { instance: _instance } = await this._genyCloudExec.getInstance(instance.uuid);
66
+ const anInstance = new GenyInstance(_instance);
67
+
68
+ if (!anInstance.isOnline()) {
59
69
  throw new DetoxRuntimeError(`Timeout waiting for instance ${instance.uuid} to be ready`);
60
70
  }
61
- return _instance;
71
+
72
+ return anInstance;
62
73
  });
63
74
  }
64
75