Skip to content
This repository was archived by the owner on Feb 2, 2021. It is now read-only.

Plamen5kov/refactoring #918

Merged
merged 3 commits into from
Mar 28, 2017
Merged

Plamen5kov/refactoring #918

merged 3 commits into from
Mar 28, 2017

Conversation

Plamen5kov
Copy link
Contributor

@Plamen5kov Plamen5kov commented Mar 24, 2017

Moved functionality from tns emulate to tns run command.

Main changes:

  • --device flag starts emulator/simulator if necessary
  • --available-devices lists all available images to start along with all connected devices (shows Device Identifier)
  • can start device by using the Device Identifier for both android and ios emulators/simulators
  • can run application on device using it's: Device Identifier and index.

related issue: NativeScript/nativescript-cli#2349

@justcodebuilduser
Copy link

❤️

@@ -12,6 +12,11 @@ class IosEmulatorServices implements Mobile.IiOSSimulatorService {
return "";
}

public async getRunningEmulatorId(image: string): Promise<string> {
//todo: plamen5kov: fix later if necessary
return "";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should return a Promise here - return Promise.resolve("")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the async decorator does that for me.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can verify this by looking at the .js code, right? Can you please share it so that we all know how it looks?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments)).next());
    });
};

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

async generates a method that returns Promise - you can safely return the "".

@@ -20,6 +20,11 @@ class Wp8EmulatorServices implements Mobile.IEmulatorPlatformServices {
return "";
}

public async getRunningEmulatorId(image: string): Promise<string> {
//todo: plamen5kov: fix later if necessary
return "";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should return a Promise here - return Promise.resolve("")

Could you also remove the 'todo' and instead add a comment that it's an empty stub?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer leaving the todo untill all the tests in master pass.

let result: string[] = [];
if (this.$fs.exists(this.avdDir)) {
let entries = this.$fs.readDirectory(this.avdDir);
result = _.filter(entries, (e: string) => e.match(AndroidEmulatorServices.INI_FILES_MASK) !== null)
.map((e) => e.match(AndroidEmulatorServices.INI_FILES_MASK)[1]);
}
return result;
if (suggestedImage && !_.includes(result, suggestedImage)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest we simplify and refactor this a little bit. It took me a lot of time to understand the flow here.

Why not getAvds to return just the AVDs and then we can filter them out when we have to?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You got a point. Will fix.

let result: string[] = [];
if (this.$fs.exists(this.avdDir)) {
let entries = this.$fs.readDirectory(this.avdDir);
result = _.filter(entries, (e: string) => e.match(AndroidEmulatorServices.INI_FILES_MASK) !== null)
.map((e) => e.match(AndroidEmulatorServices.INI_FILES_MASK)[1]);
}
return result;
if (suggestedImage && !_.includes(result, suggestedImage)) {
return null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we return null, I guess null.map()... would throw an exception below in getBestFit(). One more reason for refactoring

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

let foundDevices = this.getDeviceInstances();

//if no --device is passed and no devices are found, the default emulator is started
if (data && data.platform && !data.deviceId) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not fail with an error in the beginning if !data || !data.platform? we move this check everywhere below and it's something I think this method cannot operate correctly without anyway?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To improve readability I would suggest something like: if(!data.deviceId && _.isEmpty(foundDevices)) { }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not fail with an error in the beginning if !data || !data.platform?

Because there's common code that will use this functionality and won't need platform specified


// are there any running devices
await this.detectCurrentlyAttachedDevices();
let foundDevices = this.getDeviceInstances();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rename this variable to deviceInstances to be consistent with the method - getDeviceInstances


//check if --device(value) is running, if it's not or it's not the same as is specified, start with name from --device(value)
if (data && data.platform && data.deviceId) {
if (!helpers.isNumber(data.deviceId)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if the deviceId is a number? Is it incorrect for deviceId? What's the format of the device Id and do we need to validate this here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this context, we only care if the deviceId is an image that we need to start the emulator from. If it's a number, we don't need to do anything

//check if --device(value) is running, if it's not or it's not the same as is specified, start with name from --device(value)
if (data && data.platform && data.deviceId) {
if (!helpers.isNumber(data.deviceId)) {
let searchedDevice = _.find(this.getDeviceInstances(), (device: Mobile.IDevice) => { return device.deviceInfo.identifier === data.deviceId; });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rename this to just "activeDeviceInstance" then it would be meaningful to have if(!activeDeviceInstance) { start one for me}


// if only emulator flag is passed and no other emulators are running, start default emulator
if (data && data.platform && data.emulator && foundDevices.length) {
let atLeastOneEmulator = _.some(foundDevices, (value) => {return value.isEmulator; });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would be "runningDeviceInstance" I guess?

if (data && data.platform && data.emulator && foundDevices.length) {
let atLeastOneEmulator = _.some(foundDevices, (value) => {return value.isEmulator; });
if (!atLeastOneEmulator) {
return await this.startEmulator(data.platform);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does the --emulator flag support --emulatorId or a like for it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--emulator flag is boolean, if you want to specify the emulator --device flag should be used.

* Returns true if there's a running device with specified identifier.
* @param identifier parameter passed by the user to --device flag
*/
private async hasDevice(identifier: string): Promise<boolean> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per the comment of the method I would make it more self-descriptive as "hasRunningDevice"

@justcodebuilduser
Copy link

💔

* Returns true if there's a running device with specified identifier.
* @param identifier parameter passed by the user to --device flag
*/
private async hasDevice(identifier: string): Promise<boolean> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think any async operations are ran in the method.

emulatorIdentifier = await emulatorService.getRunningEmulatorId(deviceOption);
}

if (await this.hasDevice(emulatorIdentifier)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the await if this.hasDevice becomes synchronous.

@@ -117,7 +117,7 @@ class AndroidEmulatorServices implements Mobile.IAndroidEmulatorServices {
// unlock screen
await this.unlockScreen(emulatorId);
} else {
this.$errors.fail("Could not find an emulator image to run your project.");
this.$errors.fail(`Could not find an emulator image or identifier to run your project. Please run: "tns run <platform> --available-devices"`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure to change this one if tns devices will be used in favor of run --available devices

@@ -217,6 +220,11 @@ export class DevicesService implements Mobile.IDevicesService {
await this.getDeviceDetectionIntervalFuture();
}

/**
* Returns device that maches an identifier.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[minor] maches -> maTches typo

@justcodebuilduser
Copy link

💔

bootstrap.ts Outdated
$injector.requireCommand("device|android", "./commands/device/list-devices");
$injector.requireCommand("device|ios", "./commands/device/list-devices");
$injector.requireCommand(["device|android", "devices|android"], "./commands/device/list-devices");
$injector.requireCommand(["device|ios", "devics|ios"], "./commands/device/list-devices");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: devics|

@@ -65,4 +72,4 @@ class ListiOSDevicesCommand implements ICommand {
}
}

$injector.registerCommand("device|ios", ListiOSDevicesCommand);
$injector.registerCommand(["device|ios", "devics|ios"], ListiOSDevicesCommand);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: devics

let minVersion = this.$emulatorSettingsService.minVersion;

let best = _(this.getAvds())
let avdResults = this.getAvds();
if (suggestedImage && !_.includes(avdResults, suggestedImage)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The whole if/else can be simplified even further to just one filter:

if(suggestedImage) {
avgResults = avgResults.filter(avd => avd === suggestedImage);
}

}

// if only emulator flag is passed and no other emulators are running, start default emulator
if (data && data.platform && data.emulator && deviceInstances.length) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can validate the data && data.platform in the beginning of the method - it's included in all If statements in this method

@justcodebuilduser
Copy link

💔

@Plamen5kov Plamen5kov force-pushed the plamen5kov/refactoring branch from 252c027 to 2fe8654 Compare March 27, 2017 12:57
@justcodebuilduser
Copy link

💔

1 similar comment
@justcodebuilduser
Copy link

💔

@@ -117,7 +117,7 @@ class AndroidEmulatorServices implements Mobile.IAndroidEmulatorServices {
// unlock screen
await this.unlockScreen(emulatorId);
} else {
this.$errors.fail("Could not find an emulator image to run your project.");
this.$errors.fail(`Could not find an emulator image or identifier to run your project. Please run: "tns device <platform> --available-devices"`);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this code is used in different CLIs. In case you want to specify command that the user should execute, you can use something like:

his.$errors.fail(`Could not find an emulator image or identifier to run your project. Please run: "${this.$staticConfig.CLIENT_NAME} device <platform> --available-devices"`);

emulatorIdentifier = await emulatorService.getRunningEmulatorId(deviceOption);
}

if (this.hasRunningDevice(emulatorIdentifier)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this code is correct. In case I have running emulator and device attached, if I use any command with --device <my connected real device identifier>, the current code will still return the emulator instance, which is not what I want.

Copy link
Contributor Author

@Plamen5kov Plamen5kov Mar 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We pass the emulatorIdentifier from `emulatorService.getRunningEmulatorId(). Try it out: if the case is, connected emulator and a connected device and you want to run on the emulator you can do the following:
tns run android --device <emulator_index>
tns run android --device <emulator-5554/6 ...>
tns run android --device <emulator_image>
This will start the app only on the specified emulator.

If it's a device you want:
tns run android --device <device_identifier>
tns run android --device <device_index>

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please excuse me, totally my mistake - emulatorIdentifier will be falsey value in case I've specified the identifier of the real device

public async startEmulatorIfNecessary(data?: Mobile.IDevicesServicesInitializationOptions): Promise<void> {
if (data && data.deviceId && data.emulator) {
this.$errors.failWithoutHelp(`--device and –emulator are incompatible options.
If you are trying to run on specific emulator, use tns run –device <DeviceID>.`);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

`--device and --emulator are incompatible options. If you are trying to run on specific emulator, use "${this.$staticConfig.CLIENT_NAME} run --device <DeviceID>.`


// if only emulator flag is passed and no other emulators are running, start default emulator
if (data.emulator && deviceInstances.length) {
let runningDeviceInstance = _.some(deviceInstances, (value) => {return value.isEmulator; });
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be shorten:

const runningDeviceInstance = _.some(deviceInstances, value => value.isEmulator);

@@ -437,6 +440,8 @@ describe("devicesService", () => {
it("does not fail when androidDeviceDiscovery startLookingForDevices fails", async () => {
(<any>androidDeviceDiscovery).startLookingForDevices = (): Promise<void> => { throw new Error("my error"); };
iOSDeviceDiscovery.emit("deviceFound", iOSDevice);
let hostInfo = testInjector.resolve("hostInfo");
hostInfo.isDarwin = true;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this code added?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The purpose of the test is to check if startLookingForDevices fails, which is in no way compromised by adding hostInfo.isDarwin. It's used to resolve an emulator service, and by that time the test has either failed or not.

import { createTable } from "../helpers";

//todo: plamen5kov: moved most of emulator-service here as a temporary solution untill after 3.0-RC
export class ImageService implements Mobile.IImageService {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm, why is this called ImageService? This way I would think it works with images, for example in App_Resources dir.

Copy link
Contributor Author

@Plamen5kov Plamen5kov Mar 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could rename it to EmulatorImageService if it would be more readable?

@@ -469,6 +474,8 @@ describe("devicesService", () => {
assert.isFalse(devicesService.hasDevices, "Initially devicesService hasDevices must be false.");
iOSDeviceDiscovery.emit("deviceFound", iOSDevice);
androidDeviceDiscovery.emit("deviceFound", androidDevice);
let hostInfo = testInjector.resolve("hostInfo");
hostInfo.isDarwin = true;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here - the test should be platform independent - why should we set the OS to be macOS ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I planed to change this test. Will update it with correct message, in appliance with refactoring.

@Plamen5kov Plamen5kov force-pushed the plamen5kov/refactoring branch from 4950d06 to 24af78c Compare March 28, 2017 05:25
@justcodebuilduser
Copy link

❤️

add comments to devices-service

updated tests after logical changes
* removed startEmulator functionality from initialize, so we dont expect emulator to be started by initialize in any case anymore
* in initialize we first check the platform, because other functionality depends on it
* getDevice method should work even withrout platform specified (at least the test say so)

fixed running emulator by index

Pass the device id to the startEmulator.

removed method we do not want to support

made lint happy

added available-devices option to mobile-cli-lib

added alias for tns device: devices

updates after review
updated tests to check if emulators are started in specific cases
updated getAvds logic and getbestFit to improve readability

updates after review
typo, field and method renamings

fixed typos

simplify code

refactoring after review
@Plamen5kov Plamen5kov force-pushed the plamen5kov/refactoring branch from 24af78c to 363a40e Compare March 28, 2017 05:53
@justcodebuilduser
Copy link

❤️

@justcodebuilduser
Copy link

❤️

@Plamen5kov Plamen5kov merged commit 11415a5 into master Mar 28, 2017
@Plamen5kov Plamen5kov deleted the plamen5kov/refactoring branch March 28, 2017 16:25
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants