Repository: wix/Detox
Pull request #1816 (2019-12-26 16:58:59)

Extra step to find config file in case emulator has been renamed
<!---
Thank you for contributing!
Before submitting a pull request that implements a new functionality or fixing a major bug,
please open an issue first and propose your solution. This way we can discuss before time is 
spent on a solution that may not work for us.

Please check the correct option in the below list and delete the irrelevant one.
For the second option, please fill the issue where the solution has been discussed.
-->

- [X] This is a small change 

---

When renaming an emulator on Android Studio, inside home/.android/avd/ only the .ini file gets renamed, the .avd folder stays with the old name. Passing an emulator that has been renamed on detox configuration causes detox to crash as it tries to find the config file inside a folder that doesn't exist. This PR means to fix the crash by adding an extra step, getting path to the .avd folder from the .ini file.



Patch:
diff --git a/detox/src/devices/drivers/EmulatorDriver.js b/detox/src/devices/drivers/EmulatorDriver.js
index 1a69c2492..5ec1acd25 100644
--- a/detox/src/devices/drivers/EmulatorDriver.js
+++ b/detox/src/devices/drivers/EmulatorDriver.js
@@ -133,8 +133,8 @@ class EmulatorDriver extends AndroidDriver {
   }
 
   async _fixEmulatorConfigIniSkinNameIfNeeded(avdName) {
-    const avdHome = environment.getAvdHome();
-    const configFile = path.join(avdHome, `${avdName}.avd`, 'config.ini');
+    const avdPath = environment.getAvdDir(avdName);
+    const configFile = path.join(avdPath, 'config.ini');
     const config = ini.parse(fs.readFileSync(configFile, 'utf-8'));
 
     if (!config['skin.name']) {
diff --git a/detox/src/utils/environment.js b/detox/src/utils/environment.js
index 834ed3e23..f0ed09ebc 100644
--- a/detox/src/utils/environment.js
+++ b/detox/src/utils/environment.js
@@ -2,6 +2,7 @@ const _ = require('lodash');
 const fs = require('fs');
 const os = require('os');
 const path = require('path');
+const ini = require('ini');
 const _which = require('which');
 const exec = require('child-process-promise').exec;
 const appdatapath = require('./appdatapath');
@@ -33,6 +34,20 @@ function getAvdHome() {
   return process.env['ANDROID_AVD_HOME'] || path.join(getEmulatorHome(), 'avd');
 }
 
+function getAvdDir(avdName) {
+  const avdIniPath = path.join(getAvdHome(), `${avdName}.ini`);
+  if (!fs.existsSync(avdIniPath)) {
+    throwMissingAvdINIError(avdName, avdIniPath)
+  }
+
+  const avdIni = ini.parse(fs.readFileSync(avdIniPath, 'utf-8'));
+  if (!fs.existsSync(avdIni.path)) {
+    throwMissingAvdError(avdName, avdIni.path, avdIniPath)
+  }
+
+  return avdIni.path;
+}
+
 function getAndroidEmulatorPath() {
   const sdkRoot = getAndroidSDKPath();
   if (!sdkRoot) {
@@ -95,7 +110,18 @@ function getAdbPath() {
 }
 
 function throwMissingSdkError() {
-    throw new Error(MISSING_SDK_ERROR);
+  throw new Error(MISSING_SDK_ERROR);
+}
+
+function throwMissingAvdINIError(avdName, avdIniPath) {
+  throw new Error(`Failed to find INI file for ${avdName} at path: ${avdIniPath}`);
+}
+
+function throwMissingAvdError(avdName, avdPath, avdIniPath) {
+  throw new Error(
+    `Failed to find AVD ${avdName} directory at path: ${avdPath}\n` +
+    `Please verify "path" property in the INI file: ${avdIniPath}`
+  );
 }
 
 function throwSdkIntegrityError(sdkRoot, relativeExecutablePath) {
@@ -139,6 +165,7 @@ module.exports = {
   getAaptPath,
   getAdbPath,
   getAvdHome,
+  getAvdDir,
   getDetoxVersion,
   getFrameworkPath,
   getAndroidSDKPath,


Test patch:
diff --git a/detox/src/utils/environment.test.js b/detox/src/utils/environment.test.js
index fd70032e4..36d88f836 100644
--- a/detox/src/utils/environment.test.js
+++ b/detox/src/utils/environment.test.js
@@ -59,6 +59,33 @@ describe('Environment', () => {
       });
     });
 
+    describe('getAvdDir', () => {
+      let avdHome;
+
+      beforeEach(async () => {
+        avdHome = process.env['ANDROID_AVD_HOME'] = tempfile();
+        await fs.mkdir(avdHome);
+      });
+
+      afterEach(async () => {
+        await fs.remove(avdHome);
+      });
+
+      it('should throw error if ${avdHome}/${avdName}.ini does not exist', () => {
+        expect(() => Environment.getAvdDir('nonExistent')).toThrow(/Failed.*INI.*at path:/);
+      });
+
+      it('should throw error if path specified in INI file does not exist', () => {
+        fs.writeFileSync(path.join(avdHome, 'MyAVD.ini'), `path=randomPath${Math.random()}`)
+        expect(() => Environment.getAvdDir('MyAVD')).toThrow(/Failed to find.*randomPath0\./);
+      });
+
+      it('should return path specified in INI file if it exists', () => {
+        fs.writeFileSync(path.join(avdHome, 'MyAVD.ini'), `path=${avdHome}`)
+        expect(Environment.getAvdDir('MyAVD')).toBe(avdHome);
+      });
+    });
+
     describe('getAndroidSDKPath', () => {
       it(`should return empty string if $ANDROID_SDK_ROOT and $ANDROID_HOME both are not set`, () => {
         delete process.env.ANDROID_SDK_ROOT;