Repository: wix/Detox
Pull request #1987 (2020-03-31 14:24:47)

Refactor bloated emulator driver according to sub-concerns, part 2
- [x] This is a small change 
- [ ] This change has been discussed in issue #<?> and the solution has been agreed upon with maintainers.

---

**Description:**

Part 2 of #1984. This time, extracting away emulator-version querying logic. To be continued...


Patch:
diff --git a/detox/src/devices/drivers/android/EmulatorDriver.js b/detox/src/devices/drivers/android/EmulatorDriver.js
index eed09d9e3..6b7171e0f 100644
--- a/detox/src/devices/drivers/android/EmulatorDriver.js
+++ b/detox/src/devices/drivers/android/EmulatorDriver.js
@@ -8,6 +8,7 @@ const DetoxRuntimeError = require('../../../errors/DetoxRuntimeError');
 const DeviceRegistry = require('../../DeviceRegistry');
 const Emulator = require('./tools/Emulator');
 const EmulatorTelnet = require('./tools/EmulatorTelnet');
+const EmulatorVersion = require('./EmulatorVersion');
 const environment = require('../../../utils/environment');
 const retry = require('../../../utils/retry');
 const log = require('../../../utils/logger').child({ __filename });
@@ -19,7 +20,6 @@ const DetoxEmulatorsPortRange = {
 
 const ACQUIRE_DEVICE_EV = 'ACQUIRE_DEVICE';
 const EMU_BIN_STABLE_SKIN_VER = 28;
-const EMU_BIN_VERSION_DETECT_EV = 'EMU_BIN_VERSION_DETECT';
 
 class EmulatorDriver extends AndroidDriver {
   constructor(config) {
@@ -31,7 +31,7 @@ class EmulatorDriver extends AndroidDriver {
     });
     this.pendingBoots = {};
     this._name = 'Unspecified Emulator';
-    this._binaryVersion = undefined;
+    this._emulatorVersion = new EmulatorVersion(this.emulator);
   }
 
   get name() {
@@ -60,28 +60,8 @@ class EmulatorDriver extends AndroidDriver {
     await super.cleanup(adbName, bundleId);
   }
 
-  async binaryVersion() {
-    if (this._binaryVersion) {
-      return this._binaryVersion;
-    }
-
-    const rawOutput = await this.emulator.exec('-version') || '';
-    const matches = rawOutput.match(/Android emulator version ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]*)/);
-    if (!matches || !matches[1]) {
-      log.warn({ event: EMU_BIN_VERSION_DETECT_EV, success: false }, 'Could not detect emulator binary version, got:', rawOutput);
-      return null;
-    }
-
-    const version = matches[1];
-    const [major, minor, patch] = version.split('\.');
-    this._binaryVersion = {
-      major: Number(major),
-      minor: Number(minor),
-      patch: Number(patch),
-    };
-
-    log.debug({ event: EMU_BIN_VERSION_DETECT_EV, success: true }, 'Detected emulator binary version', this._binaryVersion);
-    return this._binaryVersion;
+  /*async*/ binaryVersion() {
+    return this._emulatorVersion.resolve();
   }
 
   async _boot(avdName, adbName) {
diff --git a/detox/src/devices/drivers/android/EmulatorVersion.js b/detox/src/devices/drivers/android/EmulatorVersion.js
new file mode 100644
index 000000000..7320dc745
--- /dev/null
+++ b/detox/src/devices/drivers/android/EmulatorVersion.js
@@ -0,0 +1,40 @@
+const log = require('../../../utils/logger').child({ __filename });
+
+const EMU_BIN_VERSION_DETECT_EV = 'EMU_BIN_VERSION_DETECT';
+
+class EmulatorVersion {
+  constructor(emulator) {
+    this.emulator = emulator;
+    this.version = undefined;
+  }
+
+  async resolve() {
+    if (!this.version) {
+      this.version = await this._resolve();
+    }
+    return this.version;
+  }
+
+  async _resolve() {
+    const rawOutput = await this.emulator.exec('-version') || '';
+    const matches = rawOutput.match(/Android emulator version ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]*)/);
+    if (!matches) {
+      log.warn({ event: EMU_BIN_VERSION_DETECT_EV, success: false }, 'Could not detect emulator binary version, got:', rawOutput);
+      return null;
+    }
+
+    const version = this._parseVersionString(matches[1]);
+    log.debug({ event: EMU_BIN_VERSION_DETECT_EV, success: true }, 'Detected emulator binary version', version);
+    return version;
+  }
+
+  _parseVersionString(versionRaw) {
+    const [major, minor, patch] = versionRaw.split('\.');
+    return {
+      major: Number(major),
+      minor: Number(minor),
+      patch: Number(patch),
+    };
+  }
+}
+module.exports = EmulatorVersion;


Test patch:
diff --git a/detox/src/devices/drivers/android/EmulatorVersion.test.js b/detox/src/devices/drivers/android/EmulatorVersion.test.js
new file mode 100644
index 000000000..e0f6e17f7
--- /dev/null
+++ b/detox/src/devices/drivers/android/EmulatorVersion.test.js
@@ -0,0 +1,80 @@
+describe('Emulator binary version', () => {
+  const versionResult = [
+    'Android emulator version 30.1.2.3 (build_id 6306047) (CL:N/A)',
+    "Copyright (C) 2006-2017 The Android Open Source Project and many others.",
+    "This program is a derivative of the QEMU CPU emulator (www.qemu.org).",
+    "  This software is licensed under the terms of the GNU General Public",
+    "  License version 2, as published by the Free Software Foundation, and",
+    "  may be copied, distributed, and modified under those terms.",
+    "  This program is distributed in the hope that it will be useful,",
+    "  but WITHOUT ANY WARRANTY; without even the implied warranty of",
+    "  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the",
+    "  GNU General Public License for more details.",
+  ].join('\n');
+  const expectedVersion = {
+    major: 30,
+    minor: 1,
+    patch: 2,
+  };
+
+  let emulator;
+  let log;
+  let emulatorVersion;
+  beforeEach(() => {
+    emulator = {
+      exec: jest.fn().mockResolvedValue(versionResult),
+    };
+
+    jest.mock('../../../utils/logger', () => ({
+      child: jest.fn().mockReturnValue({
+        debug: jest.fn(),
+        warn: jest.fn(),
+      }),
+    }));
+    log = require('../../../utils/logger').child();
+
+    const EmulatorVersion = require('./EmulatorVersion');
+    emulatorVersion = new EmulatorVersion(emulator);
+  });
+
+  it('should query the emulator', async () => {
+    await emulatorVersion.resolve();
+    expect(emulator.exec).toHaveBeenCalledWith('-version');
+  });
+
+  it('should extract version from common log', async () => {
+    const version = await emulatorVersion.resolve();
+    expect(version).toEqual(expectedVersion);
+  });
+
+  it('should return null for an empty query result', async () => {
+    emulator.exec.mockResolvedValue(undefined);
+    const version = await emulatorVersion.resolve();
+    expect(version).toEqual(null);
+  });
+
+  it('should return null for a query result that has an invalid syntax', async () => {
+    emulator.exec.mockResolvedValue('Android emulator version \<invalid\> (build_id 6306047) (CL:N/A)');
+    const version = await emulatorVersion.resolve();
+    expect(version).toEqual(null);
+  });
+
+  it('should cache the version', async () => {
+    await emulatorVersion.resolve();
+    const version = await emulatorVersion.resolve();
+
+    expect(emulator.exec).toHaveBeenCalledTimes(1);
+    expect(version).toEqual(expectedVersion);
+  });
+
+  it('should log the version', async () => {
+    await emulatorVersion.resolve();
+    expect(log.debug).toHaveBeenCalledWith({event: 'EMU_BIN_VERSION_DETECT', success: true}, expect.any(String), expectedVersion);
+  });
+
+  it('should log in case of an error', async () => {
+    emulator.exec.mockResolvedValue('mock result');
+    await emulatorVersion.resolve();
+    expect(log.warn).toHaveBeenCalledWith({event: 'EMU_BIN_VERSION_DETECT', success: false}, expect.any(String), 'mock result');
+  });
+});