detox 20.12.2 → 20.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. package/Detox-android/com/wix/detox/{20.12.2/detox-20.12.2-javadoc.jar → 20.13.0/detox-20.13.0-javadoc.jar} +0 -0
  2. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0-javadoc.jar.md5 +1 -0
  3. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0-javadoc.jar.sha1 +1 -0
  4. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0-javadoc.jar.sha256 +1 -0
  5. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0-javadoc.jar.sha512 +1 -0
  6. package/Detox-android/com/wix/detox/{20.12.2/detox-20.12.2-sources.jar → 20.13.0/detox-20.13.0-sources.jar} +0 -0
  7. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0-sources.jar.md5 +1 -0
  8. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0-sources.jar.sha1 +1 -0
  9. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0-sources.jar.sha256 +1 -0
  10. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0-sources.jar.sha512 +1 -0
  11. package/Detox-android/com/wix/detox/{20.12.2/detox-20.12.2.pom → 20.13.0/detox-20.13.0.pom} +1 -1
  12. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0.pom.md5 +1 -0
  13. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0.pom.sha1 +1 -0
  14. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0.pom.sha256 +1 -0
  15. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.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 +5 -6
  25. package/runners/jest/testEnvironment/index.js +1 -1
  26. package/src/DetoxWorker.js +5 -11
  27. package/src/artifacts/providers/index.js +3 -3
  28. package/src/artifacts/screenshot/SimulatorScreenshotPlugin.js +0 -17
  29. package/src/configuration/composeLoggerConfig.js +1 -0
  30. package/src/devices/allocation/DeviceAllocator.js +66 -20
  31. package/src/devices/allocation/DeviceList.js +44 -0
  32. package/src/devices/allocation/DeviceRegistry.js +189 -0
  33. package/src/devices/allocation/drivers/AllocationDriverBase.d.ts +15 -0
  34. package/src/devices/{common/drivers/android/tools → allocation/drivers/android}/FreeDeviceFinder.js +11 -10
  35. package/src/devices/allocation/drivers/android/attached/AttachedAndroidAllocDriver.js +22 -17
  36. package/src/devices/allocation/drivers/android/emulator/EmulatorAllocDriver.js +97 -38
  37. package/src/devices/allocation/drivers/android/emulator/EmulatorLauncher.js +32 -45
  38. package/src/devices/allocation/drivers/android/emulator/FreeEmulatorFinder.js +1 -1
  39. package/src/devices/allocation/drivers/android/emulator/FreePortFinder.js +37 -0
  40. package/src/devices/allocation/drivers/android/emulator/launchEmulatorProcess.js +3 -3
  41. package/src/devices/allocation/drivers/android/genycloud/GenyAllocDriver.js +104 -32
  42. package/src/devices/allocation/drivers/android/genycloud/GenyInstanceLauncher.js +40 -31
  43. package/src/devices/allocation/drivers/android/genycloud/GenyRegistry.js +121 -0
  44. package/src/devices/allocation/drivers/android/genycloud/services/GenyInstanceLifecycleService.js +24 -0
  45. package/src/devices/{common → allocation}/drivers/android/genycloud/services/GenyRecipesService.js +1 -1
  46. package/src/devices/allocation/drivers/android/genycloud/services/dto/GenyInstance.js +83 -0
  47. package/src/devices/allocation/drivers/android/genycloud/services/dto/GenyRecipe.js +25 -0
  48. package/src/devices/allocation/drivers/ios/SimulatorAllocDriver.js +94 -51
  49. package/src/devices/allocation/drivers/ios/SimulatorLauncher.js +11 -7
  50. package/src/devices/allocation/drivers/ios/SimulatorQuery.js +24 -0
  51. package/src/devices/allocation/factories/android.js +29 -35
  52. package/src/devices/allocation/factories/ios.js +7 -5
  53. package/src/devices/common/drivers/DeviceCookie.d.ts +12 -0
  54. package/src/devices/common/drivers/android/cookies.d.ts +11 -0
  55. package/src/devices/common/drivers/android/emulator/exec/EmulatorExec.js +17 -5
  56. package/src/devices/common/drivers/android/exec/ADB.js +1 -0
  57. package/src/devices/common/drivers/ios/cookies.d.ts +9 -0
  58. package/src/devices/cookies/index.js +0 -6
  59. package/src/devices/runtime/drivers/android/genycloud/GenyCloudDriver.js +7 -6
  60. package/src/devices/runtime/factories/android.js +3 -11
  61. package/src/devices/runtime/factories/ios.js +3 -2
  62. package/src/{servicelocator → devices/servicelocator}/android/emulatorServiceLocator.js +1 -1
  63. package/src/devices/servicelocator/android/genycloudServiceLocator.js +17 -0
  64. package/src/devices/servicelocator/android/index.js +23 -0
  65. package/src/{validation → devices/validation}/EnvironmentValidatorBase.js +1 -0
  66. package/src/{validation → devices/validation}/android/GenycloudEnvValidator.js +2 -2
  67. package/src/{validation → devices/validation}/factories/index.js +1 -1
  68. package/src/{validation → devices/validation}/ios/IosSimulatorEnvValidator.js +2 -2
  69. package/src/environmentFactory.js +1 -11
  70. package/src/ipc/IPCClient.js +17 -1
  71. package/src/ipc/IPCServer.js +27 -1
  72. package/src/logger/DetoxLogger.js +2 -2
  73. package/src/realms/DetoxContext.js +6 -0
  74. package/src/realms/DetoxPrimaryContext.js +42 -42
  75. package/src/realms/DetoxSecondaryContext.js +19 -0
  76. package/src/realms/symbols.js +4 -0
  77. package/src/utils/PIDService.js +27 -0
  78. package/src/utils/environment.js +8 -15
  79. package/src/utils/errorUtils.js +2 -2
  80. package/tsconfig.json +5 -3
  81. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2-javadoc.jar.md5 +0 -1
  82. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2-javadoc.jar.sha1 +0 -1
  83. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2-javadoc.jar.sha256 +0 -1
  84. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2-javadoc.jar.sha512 +0 -1
  85. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2-sources.jar.md5 +0 -1
  86. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2-sources.jar.sha1 +0 -1
  87. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2-sources.jar.sha256 +0 -1
  88. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2-sources.jar.sha512 +0 -1
  89. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2.pom.md5 +0 -1
  90. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2.pom.sha1 +0 -1
  91. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2.pom.sha256 +0 -1
  92. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2.pom.sha512 +0 -1
  93. package/src/devices/DeviceRegistry.js +0 -176
  94. package/src/devices/allocation/drivers/AllocationDriverBase.js +0 -30
  95. package/src/devices/allocation/drivers/android/attached/AttachedAndroidLauncher.js +0 -13
  96. package/src/devices/allocation/drivers/android/emulator/EmulatorAllocationHelper.js +0 -72
  97. package/src/devices/allocation/drivers/android/genycloud/GenyDeviceRegistryFactory.js +0 -16
  98. package/src/devices/allocation/drivers/android/genycloud/GenyInstanceAllocationHelper.js +0 -65
  99. package/src/devices/common/drivers/DeviceAllocationHelper.js +0 -20
  100. package/src/devices/common/drivers/DeviceLauncher.js +0 -19
  101. package/src/devices/common/drivers/android/genycloud/services/GenyInstanceLifecycleService.js +0 -25
  102. package/src/devices/common/drivers/android/genycloud/services/GenyInstanceLookupService.js +0 -38
  103. package/src/devices/common/drivers/android/genycloud/services/GenyInstanceNaming.js +0 -14
  104. package/src/devices/common/drivers/android/genycloud/services/dto/GenyInstance.js +0 -66
  105. package/src/devices/common/drivers/android/genycloud/services/dto/GenyRecipe.js +0 -13
  106. package/src/devices/cookies/AndroidDeviceCookie.js +0 -13
  107. package/src/devices/cookies/AndroidEmulatorCookie.js +0 -6
  108. package/src/devices/cookies/AttachedAndroidDeviceCookie.js +0 -12
  109. package/src/devices/cookies/DeviceCookie.js +0 -4
  110. package/src/devices/cookies/GenycloudEmulatorCookie.js +0 -20
  111. package/src/devices/cookies/IosCookie.js +0 -6
  112. package/src/devices/cookies/IosSimulatorCookie.js +0 -10
  113. package/src/devices/lifecycle/GenyGlobalLifecycleHandler.js +0 -71
  114. package/src/devices/lifecycle/factories/GenyGlobalLifecycleHandlerFactory.js +0 -18
  115. package/src/servicelocator/android/genycloudServiceLocator.js +0 -21
  116. package/src/servicelocator/android/index.js +0 -25
  117. package/src/servicelocator/ios.js +0 -7
  118. /package/Detox-android/com/wix/detox/{20.12.2/detox-20.12.2.aar → 20.13.0/detox-20.13.0.aar} +0 -0
  119. /package/Detox-android/com/wix/detox/{20.12.2/detox-20.12.2.aar.md5 → 20.13.0/detox-20.13.0.aar.md5} +0 -0
  120. /package/Detox-android/com/wix/detox/{20.12.2/detox-20.12.2.aar.sha1 → 20.13.0/detox-20.13.0.aar.sha1} +0 -0
  121. /package/Detox-android/com/wix/detox/{20.12.2/detox-20.12.2.aar.sha256 → 20.13.0/detox-20.13.0.aar.sha256} +0 -0
  122. /package/Detox-android/com/wix/detox/{20.12.2/detox-20.12.2.aar.sha512 → 20.13.0/detox-20.13.0.aar.sha512} +0 -0
  123. /package/src/devices/{common → allocation}/drivers/android/genycloud/exec/GenyCloudExec.js +0 -0
  124. /package/src/devices/{common → allocation}/drivers/android/genycloud/services/GenyAuthService.js +0 -0
@@ -1,6 +1,6 @@
1
- const log = require('../../../../../utils/logger').child({ cat: 'device' });
1
+ const log = require('../../../../utils/logger').child({ cat: 'device' });
2
2
 
3
- const DEVICE_LOOKUP_LOG_EVT = 'DEVICE_LOOKUP';
3
+ const DEVICE_LOOKUP = { event: 'DEVICE_LOOKUP' };
4
4
 
5
5
  class FreeDeviceFinder {
6
6
  constructor(adb, deviceRegistry) {
@@ -10,8 +10,9 @@ class FreeDeviceFinder {
10
10
 
11
11
  async findFreeDevice(deviceQuery) {
12
12
  const { devices } = await this.adb.devices();
13
+ const takenDevices = this.deviceRegistry.getTakenDevicesSync();
13
14
  for (const candidate of devices) {
14
- if (await this._isDeviceFreeAndMatching(candidate, deviceQuery)) {
15
+ if (await this._isDeviceFreeAndMatching(takenDevices, candidate, deviceQuery)) {
15
16
  return candidate.adbName;
16
17
  }
17
18
  }
@@ -19,30 +20,30 @@ class FreeDeviceFinder {
19
20
  }
20
21
 
21
22
  /**
22
- * @protected
23
+ * @private
23
24
  */
24
- async _isDeviceFreeAndMatching(candidate, deviceQuery) {
25
+ async _isDeviceFreeAndMatching(takenDevices, candidate, deviceQuery) {
25
26
  const { adbName } = candidate;
26
27
 
27
- const isTaken = this.deviceRegistry.includes(adbName);
28
+ const isTaken = takenDevices.includes(adbName);
28
29
  if (isTaken) {
29
- log.debug({ event: DEVICE_LOOKUP_LOG_EVT }, `Device ${adbName} is already taken, skipping...`);
30
+ log.debug(DEVICE_LOOKUP, `Device ${adbName} is already taken, skipping...`);
30
31
  return false;
31
32
  }
32
33
 
33
34
  const isOffline = candidate.status === 'offline';
34
35
  if (isOffline) {
35
- log.debug({ event: DEVICE_LOOKUP_LOG_EVT }, `Device ${adbName} is offline, skipping...`);
36
+ log.debug(DEVICE_LOOKUP, `Device ${adbName} is offline, skipping...`);
36
37
  return false;
37
38
  }
38
39
 
39
40
  const isMatching = await this._isDeviceMatching(candidate, deviceQuery);
40
41
  if (!isMatching) {
41
- log.debug({ event: DEVICE_LOOKUP_LOG_EVT }, `Device ${adbName} does not match "${deviceQuery}"`);
42
+ log.debug(DEVICE_LOOKUP, `Device ${adbName} does not match "${deviceQuery}"`);
42
43
  return false;
43
44
  }
44
45
 
45
- log.debug({ event: DEVICE_LOOKUP_LOG_EVT }, `Found a matching & free device ${candidate.adbName}`);
46
+ log.debug(DEVICE_LOOKUP, `Found a matching & free device ${candidate.adbName}`);
46
47
  return true;
47
48
  }
48
49
 
@@ -1,20 +1,26 @@
1
- // @ts-nocheck
2
- const AttachedAndroidDeviceCookie = require('../../../../cookies/AttachedAndroidDeviceCookie');
3
- const AllocationDriverBase = require('../../AllocationDriverBase');
1
+ /**
2
+ * @typedef {import('../../AllocationDriverBase').AllocationDriverBase} AllocationDriverBase
3
+ * @typedef {import('../../../../common/drivers/android/cookies').AndroidDeviceCookie} AndroidDeviceCookie
4
+ */
4
5
 
5
- class AttachedAndroidAllocDriver extends AllocationDriverBase {
6
+ /**
7
+ * @implements {AllocationDriverBase}
8
+ */
9
+ class AttachedAndroidAllocDriver {
6
10
  /**
7
- * @param adb { ADB }
8
- * @param deviceRegistry { DeviceRegistry }
9
- * @param freeDeviceFinder { FreeDeviceFinder }
10
- * @param attachedAndroidLauncher { AttachedAndroidLauncher }
11
+ * @param {object} options
12
+ * @param {import('../../../../common/drivers/android/exec/ADB')} options.adb
13
+ * @param {import('../../../DeviceRegistry')} options.deviceRegistry
14
+ * @param {import('../FreeDeviceFinder')} options.freeDeviceFinder
11
15
  */
12
- constructor({ adb, deviceRegistry, freeDeviceFinder, attachedAndroidLauncher }) {
13
- super();
16
+ constructor({ adb, deviceRegistry, freeDeviceFinder }) {
14
17
  this._adb = adb;
15
18
  this._deviceRegistry = deviceRegistry;
16
19
  this._freeDeviceFinder = freeDeviceFinder;
17
- this._attachedAndroidLauncher = attachedAndroidLauncher;
20
+ }
21
+
22
+ async init() {
23
+ await this._deviceRegistry.unregisterZombieDevices();
18
24
  }
19
25
 
20
26
  /**
@@ -23,13 +29,13 @@ class AttachedAndroidAllocDriver extends AllocationDriverBase {
23
29
  */
24
30
  async allocate(deviceConfig) {
25
31
  const adbNamePattern = deviceConfig.device.adbName;
26
- const adbName = await this._deviceRegistry.allocateDevice(() => this._freeDeviceFinder.findFreeDevice(adbNamePattern));
32
+ const adbName = await this._deviceRegistry.registerDevice(() => this._freeDeviceFinder.findFreeDevice(adbNamePattern));
27
33
 
28
- return new AttachedAndroidDeviceCookie(adbName);
34
+ return { id: adbName, adbName };
29
35
  }
30
36
 
31
37
  /**
32
- * @param {AttachedAndroidDeviceCookie} deviceCookie
38
+ * @param {AndroidDeviceCookie} deviceCookie
33
39
  * @returns {Promise<void>}
34
40
  */
35
41
  async postAllocate(deviceCookie) {
@@ -38,16 +44,15 @@ class AttachedAndroidAllocDriver extends AllocationDriverBase {
38
44
  // TODO Also disable native animations?
39
45
  await this._adb.apiLevel(adbName);
40
46
  await this._adb.unlockScreen(adbName);
41
- await this._attachedAndroidLauncher.notifyLaunchCompleted(adbName);
42
47
  }
43
48
 
44
49
  /**
45
- * @param cookie { AttachedAndroidDeviceCookie }
50
+ * @param cookie { AndroidDeviceCookie }
46
51
  * @return {Promise<void>}
47
52
  */
48
53
  async free(cookie) {
49
54
  const { adbName } = cookie;
50
- await this._deviceRegistry.disposeDevice(adbName);
55
+ await this._deviceRegistry.unregisterDevice(adbName);
51
56
  }
52
57
  }
53
58
 
@@ -1,32 +1,58 @@
1
- // @ts-nocheck
1
+ /**
2
+ * @typedef {import('../../AllocationDriverBase').AllocationDriverBase} AllocationDriverBase
3
+ * @typedef {import('../../../../common/drivers/android/cookies').AndroidDeviceCookie} AndroidDeviceCookie
4
+ */
5
+
2
6
  const _ = require('lodash');
3
7
 
4
- const AndroidEmulatorCookie = require('../../../../cookies/AndroidEmulatorCookie');
5
- const AllocationDriverBase = require('../../AllocationDriverBase');
8
+ const Deferred = require('../../../../../utils/Deferred');
9
+ const log = require('../../../../../utils/logger').child({ cat: 'device,device-allocation' });
6
10
 
7
11
  const { patchAvdSkinConfig } = require('./patchAvdSkinConfig');
8
12
 
9
- class EmulatorAllocDriver extends AllocationDriverBase {
13
+ /**
14
+ * @implements {AllocationDriverBase}
15
+ */
16
+ class EmulatorAllocDriver {
10
17
  /**
11
- * @param adb { ADB }
12
- * @param avdValidator { AVDValidator }
13
- * @param emulatorVersionResolver { EmulatorVersionResolver }
14
- * @param emulatorLauncher { EmulatorLauncher }
15
- * @param allocationHelper { EmulatorAllocationHelper }
18
+ * @param {object} options
19
+ * @param {import('../../../../common/drivers/android/exec/ADB')} options.adb
20
+ * @param {import('./AVDValidator')} options.avdValidator
21
+ * @param {DetoxInternals.RuntimeConfig} options.detoxConfig
22
+ * @param {import('../../../DeviceRegistry')} options.deviceRegistry
23
+ * @param {import('./FreeEmulatorFinder')} options.freeDeviceFinder
24
+ * @param {import('./FreePortFinder')} options.freePortFinder
25
+ * @param {import('./EmulatorLauncher')} options.emulatorLauncher
26
+ * @param {import('./EmulatorVersionResolver')} options.emulatorVersionResolver
16
27
  */
17
- constructor({ adb, avdValidator, emulatorVersionResolver, emulatorLauncher, allocationHelper }) {
18
- super();
28
+ constructor({
29
+ adb,
30
+ avdValidator,
31
+ detoxConfig,
32
+ deviceRegistry,
33
+ freeDeviceFinder,
34
+ freePortFinder,
35
+ emulatorVersionResolver,
36
+ emulatorLauncher
37
+ }) {
19
38
  this._adb = adb;
20
39
  this._avdValidator = avdValidator;
40
+ this._deviceRegistry = deviceRegistry;
21
41
  this._emulatorVersionResolver = emulatorVersionResolver;
22
42
  this._emulatorLauncher = emulatorLauncher;
23
- this._allocationHelper = allocationHelper;
24
- this._launchInfo = {};
43
+ this._freeDeviceFinder = freeDeviceFinder;
44
+ this._freePortFinder = freePortFinder;
45
+ this._shouldShutdown = detoxConfig.behavior.cleanup.shutdownDevice;
46
+ this._fixAvdConfigIniSkinNameIfNeeded = _.memoize(this._fixAvdConfigIniSkinNameIfNeeded.bind(this));
47
+ }
48
+
49
+ async init() {
50
+ await this._deviceRegistry.unregisterZombieDevices();
25
51
  }
26
52
 
27
53
  /**
28
54
  * @param deviceConfig
29
- * @returns {Promise<AndroidEmulatorCookie>}
55
+ * @returns {Promise<AndroidDeviceCookie>}
30
56
  */
31
57
  async allocate(deviceConfig) {
32
58
  const avdName = deviceConfig.device.avdName;
@@ -34,50 +60,83 @@ class EmulatorAllocDriver extends AllocationDriverBase {
34
60
  await this._avdValidator.validate(avdName, deviceConfig.headless);
35
61
  await this._fixAvdConfigIniSkinNameIfNeeded(avdName, deviceConfig.headless);
36
62
 
37
- const allocResult = await this._allocationHelper.allocateDevice(avdName);
38
- const { adbName } = allocResult;
39
-
40
- this._launchInfo[adbName] = {
41
- avdName,
42
- isRunning: allocResult.isRunning,
43
- launchOptions: {
44
- bootArgs: deviceConfig.bootArgs,
45
- gpuMode: deviceConfig.gpuMode,
46
- headless: deviceConfig.headless,
47
- readonly: deviceConfig.readonly,
48
- port: allocResult.placeholderPort,
49
- },
50
- };
63
+ const adbName = await this._deviceRegistry.registerDevice(async () => {
64
+ let adbName = await this._freeDeviceFinder.findFreeDevice(avdName);
65
+ if (!adbName) {
66
+ const port = await this._freePortFinder.findFreePort();
67
+ adbName = `emulator-${port}`;
68
+
69
+ await this._emulatorLauncher.launch({
70
+ bootArgs: deviceConfig.bootArgs,
71
+ gpuMode: deviceConfig.gpuMode,
72
+ headless: deviceConfig.headless,
73
+ readonly: deviceConfig.readonly,
74
+ avdName,
75
+ adbName,
76
+ port,
77
+ });
78
+ }
51
79
 
52
- return new AndroidEmulatorCookie(adbName);
80
+ return adbName;
81
+ });
82
+
83
+ return {
84
+ id: adbName,
85
+ adbName,
86
+ name: `${adbName} (${avdName})`,
87
+ };
53
88
  }
54
89
 
55
90
  /**
56
- * @param {AndroidEmulatorCookie} deviceCookie
57
- * @returns {Promise<void>}
91
+ * @param {AndroidDeviceCookie} deviceCookie
58
92
  */
59
93
  async postAllocate(deviceCookie) {
60
94
  const { adbName } = deviceCookie;
61
- const { avdName, isRunning, launchOptions } = this._launchInfo[adbName];
62
95
 
63
- await this._emulatorLauncher.launch(avdName, adbName, isRunning, launchOptions);
96
+ await this._emulatorLauncher.awaitEmulatorBoot(adbName);
64
97
  await this._adb.apiLevel(adbName);
65
98
  await this._adb.disableAndroidAnimations(adbName);
66
99
  await this._adb.unlockScreen(adbName);
67
100
  }
68
101
 
69
102
  /**
70
- * @param cookie { AndroidEmulatorCookie }
71
- * @param options { DeallocOptions }
72
- * @return { Promise<void> }
103
+ * @param cookie {AndroidDeviceCookie}
104
+ * @param options {Partial<import('../../AllocationDriverBase').DeallocOptions>}
105
+ * @return {Promise<void>}
73
106
  */
74
107
  async free(cookie, options = {}) {
75
108
  const { adbName } = cookie;
76
109
 
77
- await this._allocationHelper.deallocateDevice(adbName);
78
-
79
110
  if (options.shutdown) {
111
+ await this._doShutdown(adbName);
112
+ await this._deviceRegistry.unregisterDevice(adbName);
113
+ } else {
114
+ await this._deviceRegistry.releaseDevice(adbName);
115
+ }
116
+ }
117
+
118
+ async cleanup() {
119
+ if (this._shouldShutdown) {
120
+ const { devices } = await this._adb.devices();
121
+ const actualEmulators = devices.map((device) => device.adbName);
122
+ const sessionDevices = await this._deviceRegistry.readSessionDevices();
123
+ const emulatorsToShutdown = _.intersection(sessionDevices.getIds(), actualEmulators);
124
+ const shutdownPromises = emulatorsToShutdown.map((adbName) => this._doShutdown(adbName));
125
+ await Promise.all(shutdownPromises);
126
+ }
127
+
128
+ await this._deviceRegistry.unregisterSessionDevices();
129
+ }
130
+
131
+ /**
132
+ * @param {string} adbName
133
+ * @return {Promise<void>}
134
+ */
135
+ async _doShutdown(adbName) {
136
+ try {
80
137
  await this._emulatorLauncher.shutdown(adbName);
138
+ } catch (err) {
139
+ log.warn({ err }, `Failed to shutdown emulator ${adbName}`);
81
140
  }
82
141
  }
83
142
 
@@ -1,49 +1,53 @@
1
1
  // @ts-nocheck
2
2
  const { DetoxRuntimeError } = require('../../../../../errors');
3
- const log = require('../../../../../utils/logger').child({ cat: 'device' });
4
3
  const retry = require('../../../../../utils/retry');
5
- const traceMethods = require('../../../../../utils/traceMethods');
6
- const DeviceLauncher = require('../../../../common/drivers/DeviceLauncher');
7
4
  const { LaunchCommand } = require('../../../../common/drivers/android/emulator/exec/EmulatorExec');
8
5
 
9
6
  const { launchEmulatorProcess } = require('./launchEmulatorProcess');
10
7
 
11
8
  const isUnknownEmulatorError = (err) => (err.message || '').includes('failed with code null');
12
9
 
13
- class EmulatorLauncher extends DeviceLauncher {
14
- constructor({ adb, emulatorExec, eventEmitter }) {
15
- super(eventEmitter);
10
+ class EmulatorLauncher {
11
+ constructor({ adb, emulatorExec }) {
16
12
  this._adb = adb;
17
13
  this._emulatorExec = emulatorExec;
18
- traceMethods(log, this, ['_awaitEmulatorBoot']);
19
14
  }
20
15
 
21
16
  /**
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 }
17
+ * @param {object} options
18
+ * @param {string} options.avdName
19
+ * @param {string} options.adbName
20
+ * @param {number} options.port
21
+ * @param {string | undefined} options.bootArgs
22
+ * @param {string | undefined} options.gpuMode
23
+ * @param {boolean} options.headless
24
+ * @param {boolean} options.readonly
31
25
  */
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);
26
+ async launch(options) {
27
+ const launchCommand = new LaunchCommand(options);
28
+ await retry({
29
+ retries: 2,
30
+ interval: 100,
31
+ conditionFn: isUnknownEmulatorError,
32
+ }, () => launchEmulatorProcess(this._emulatorExec, this._adb, launchCommand));
33
+ }
34
+
35
+ /**
36
+ * @param {string} adbName
37
+ */
38
+ async awaitEmulatorBoot(adbName) {
39
+ await retry({ retries: 240, interval: 2500, shouldUnref: true }, async () => {
40
+ const isBootComplete = await this._adb.isBootComplete(adbName);
41
+
42
+ if (!isBootComplete) {
43
+ throw new DetoxRuntimeError({
44
+ message: `Waited for ${adbName} to complete booting for too long!`,
45
+ });
46
+ }
47
+ });
43
48
  }
44
49
 
45
50
  async shutdown(adbName) {
46
- await this._notifyPreShutdown(adbName);
47
51
  await this._adb.emuKill(adbName);
48
52
  await retry({
49
53
  retries: 5,
@@ -57,23 +61,6 @@ class EmulatorLauncher extends DeviceLauncher {
57
61
  });
58
62
  }
59
63
  });
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
64
  }
78
65
  }
79
66
 
@@ -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,37 @@
1
+ const net = require('net');
2
+
3
+ class FreePortFinder {
4
+ constructor({ min = 10000, max = 20000 } = {}) {
5
+ this._min = min;
6
+ this._max = max;
7
+ }
8
+
9
+ async findFreePort() {
10
+ let port;
11
+
12
+ do {
13
+ const min = this._min;
14
+ const max = this._max;
15
+ port = Math.random() * (max - min) + min;
16
+ port = port & 0xFFFFFFFE; // Should always be even
17
+ } while (await this.isPortTaken(port));
18
+
19
+ return port;
20
+ }
21
+
22
+ async isPortTaken(port) {
23
+ return new Promise((resolve, reject) => {
24
+ const tester = net.createServer()
25
+ .once('error', /** @param {*} err */ (err) => {
26
+ /* istanbul ignore next */
27
+ return err && err.code === 'EADDRINUSE' ? resolve(true) : reject(err);
28
+ })
29
+ .once('listening', () => {
30
+ tester.once('close', () => resolve(false)).close();
31
+ })
32
+ .listen(port);
33
+ });
34
+ }
35
+ }
36
+
37
+ 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,43 @@
1
+ /**
2
+ * @typedef {import('../../AllocationDriverBase').AllocationDriverBase} AllocationDriverBase
3
+ * @typedef {import('../../../../common/drivers/android/cookies').GenycloudEmulatorCookie} GenycloudEmulatorCookie
4
+ */
5
+
1
6
  const { DetoxRuntimeError } = require('../../../../../errors');
2
7
  const Timer = require('../../../../../utils/Timer');
3
- const GenycloudEmulatorCookie = require('../../../../cookies/GenycloudEmulatorCookie');
4
- const AllocationDriverBase = require('../../AllocationDriverBase');
8
+ const log = require('../../../../../utils/logger').child({ cat: 'device' });
9
+
10
+ const GenyRegistry = require('./GenyRegistry');
5
11
 
6
- class GenyAllocDriver extends AllocationDriverBase {
12
+ const events = {
13
+ GENYCLOUD_TEARDOWN: { event: 'GENYCLOUD_TEARDOWN' },
14
+ };
7
15
 
16
+ /**
17
+ * @implements {AllocationDriverBase}
18
+ */
19
+ class GenyAllocDriver {
8
20
  /**
9
21
  * @param {object} options
10
22
  * @param {import('../../../../common/drivers/android/exec/ADB')} options.adb
11
- * @param {import('./GenyRecipeQuerying')} options.recipeQuerying
12
- * @param {import('./GenyInstanceAllocationHelper')} options.allocationHelper
23
+ * @param {DetoxInternals.SessionState} options.detoxSession
24
+ * @param {import('./GenyRegistry')} options.genyRegistry
13
25
  * @param {import('./GenyInstanceLauncher')} options.instanceLauncher
26
+ * @param {import('./GenyRecipeQuerying')} options.recipeQuerying
14
27
  */
15
- constructor({ adb, recipeQuerying, allocationHelper, instanceLauncher }) {
16
- super();
17
-
28
+ constructor({
29
+ adb,
30
+ detoxSession,
31
+ genyRegistry = new GenyRegistry(),
32
+ instanceLauncher,
33
+ recipeQuerying,
34
+ }) {
18
35
  this._adb = adb;
19
- this._recipeQuerying = recipeQuerying;
36
+ this._detoxSessionId = detoxSession.id;
37
+ this._genyRegistry = genyRegistry;
20
38
  this._instanceLauncher = instanceLauncher;
21
- this._instanceAllocationHelper = allocationHelper;
22
- this._launchInfo = {};
39
+ this._recipeQuerying = recipeQuerying;
40
+ this._instanceCounter = 0;
23
41
  }
24
42
 
25
43
  /**
@@ -27,47 +45,83 @@ class GenyAllocDriver extends AllocationDriverBase {
27
45
  * @return {Promise<GenycloudEmulatorCookie>}
28
46
  */
29
47
  async allocate(deviceConfig) {
48
+ await new Promise((resolve) => setTimeout(resolve, 10000));
30
49
  const deviceQuery = deviceConfig.device;
31
50
  const recipe = await this._recipeQuerying.getRecipeFromQuery(deviceQuery);
32
51
  this._assertRecipe(deviceQuery, recipe);
33
52
 
34
- const { instance, isNew } = await this._instanceAllocationHelper.allocateDevice(recipe);
35
- this._launchInfo[instance.uuid] = { isNew };
36
- return new GenycloudEmulatorCookie(instance);
53
+ let instance = this._genyRegistry.findFreeInstance(recipe);
54
+ if (!instance) {
55
+ const instanceName = `Detox.${this._detoxSessionId}.${this._instanceCounter++}`;
56
+ instance = await this._instanceLauncher.launch(recipe, instanceName);
57
+ this._genyRegistry.addInstance(instance, recipe);
58
+ }
59
+
60
+ return {
61
+ id: instance.uuid,
62
+ adbName: instance.adbName,
63
+ name: instance.name,
64
+ instance,
65
+ };
37
66
  }
38
67
 
39
68
  /**
40
69
  * @param {GenycloudEmulatorCookie} cookie
41
- * @returns {Promise<void>}
42
70
  */
43
71
  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
- });
72
+ const instance = await this._instanceLauncher.connect(cookie.instance);
73
+ this._genyRegistry.updateInstance(instance);
74
+
75
+ if (this._genyRegistry.pollNewInstance(instance.uuid)) {
76
+ const { adbName } = instance;
77
+
78
+ await Timer.run(20000, 'waiting for device to respond', async () => {
79
+ await this._adb.disableAndroidAnimations(adbName);
80
+ await this._adb.setWiFiToggle(adbName, true);
81
+ await this._adb.apiLevel(adbName);
82
+ });
83
+ }
84
+
85
+ return {
86
+ ...cookie,
87
+ adbName: instance.adbName,
88
+ };
54
89
  }
55
90
 
56
91
  /**
57
- * @param cookie { GenycloudEmulatorCookie }
58
- * @param options { DeallocOptions }
92
+ * @param cookie {Omit<GenycloudEmulatorCookie, 'instance'>}
93
+ * @param options {Partial<import('../../AllocationDriverBase').DeallocOptions>}
59
94
  * @return {Promise<void>}
60
95
  */
61
96
  async free(cookie, options = {}) {
62
- const { instance } = cookie;
63
-
64
- await this._instanceAllocationHelper.deallocateDevice(instance.uuid);
65
-
97
+ // Known issue: cookie won't have a proper 'instance' field due to (de)serialization
66
98
  if (options.shutdown) {
67
- await this._instanceLauncher.shutdown(instance);
99
+ this._genyRegistry.removeInstance(cookie.id);
100
+ await this._instanceLauncher.shutdown(cookie.id);
101
+ } else {
102
+ this._genyRegistry.markAsFree(cookie.id);
68
103
  }
69
104
  }
70
105
 
106
+ async cleanup() {
107
+ log.info(events.GENYCLOUD_TEARDOWN, 'Initiating Genymotion SaaS instances teardown...');
108
+
109
+ const killPromises = this._genyRegistry.getInstances().map((instance) => {
110
+ this._genyRegistry.markAsBusy(instance.uuid);
111
+ const onSuccess = () => this._genyRegistry.removeInstance(instance.uuid);
112
+ const onError = (error) => ({ ...instance, error });
113
+ return this._instanceLauncher.shutdown(instance.uuid).then(onSuccess, onError);
114
+ });
115
+
116
+ const deletionLeaks = (await Promise.all(killPromises)).filter(Boolean);
117
+ this._reportGlobalCleanupSummary(deletionLeaks);
118
+ }
119
+
120
+ emergencyCleanup() {
121
+ const instances = this._genyRegistry.getInstances();
122
+ this._reportGlobalCleanupSummary(instances);
123
+ }
124
+
71
125
  _assertRecipe(deviceQuery, recipe) {
72
126
  if (!recipe) {
73
127
  throw new DetoxRuntimeError({
@@ -76,6 +130,24 @@ class GenyAllocDriver extends AllocationDriverBase {
76
130
  });
77
131
  }
78
132
  }
133
+
134
+ _reportGlobalCleanupSummary(deletionLeaks) {
135
+ if (deletionLeaks.length) {
136
+ log.warn(events.GENYCLOUD_TEARDOWN, 'WARNING! Detected a Genymotion SaaS instance leakage, for the following instances:');
137
+
138
+ deletionLeaks.forEach(({ uuid, name, error }) => {
139
+ log.warn(events.GENYCLOUD_TEARDOWN, [
140
+ `Instance ${name} (${uuid})${error ? `: ${error}` : ''}`,
141
+ ` Kill it by visiting https://cloud.geny.io/instance/${uuid}, or by running:`,
142
+ ` gmsaas instances stop ${uuid}`,
143
+ ].join('\n'));
144
+ });
145
+
146
+ log.info(events.GENYCLOUD_TEARDOWN, 'Instances teardown completed with warnings');
147
+ } else {
148
+ log.info(events.GENYCLOUD_TEARDOWN, 'Instances teardown completed successfully');
149
+ }
150
+ }
79
151
  }
80
152
 
81
153
  module.exports = GenyAllocDriver;