Skip to content

Commit

Permalink
Modify resolveEnv()
Browse files Browse the repository at this point in the history
  • Loading branch information
Kartik Raj committed Sep 23, 2020
1 parent 471fa69 commit 06d3fb0
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 52 deletions.
30 changes: 18 additions & 12 deletions src/client/pythonEnvironments/collection/environmentsResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ import { Event, EventEmitter } from 'vscode';
import { traceVerbose } from '../../common/logger';
import { areSameEnvironment, PythonEnvInfo } from '../base/info';
import { InterpreterInformation } from '../base/info/interpreter';
import {
ILocator, IPythonEnvsIterator, PythonEnvUpdatedEvent, QueryForEvent,
} from '../base/locator';
import { ILocator, IPythonEnvsIterator, PythonEnvUpdatedEvent, QueryForEvent } from '../base/locator';
import { PythonEnvsChangedEvent } from '../base/watcher';
import { IEnvironmentInfoService } from '../info/environmentInfoService';

Expand All @@ -19,11 +17,19 @@ export class PythonEnvsResolver implements ILocator {

constructor(
private readonly pythonEnvsReducer: ILocator,
private readonly environmentInfoService: IEnvironmentInfoService,
private readonly environmentInfoService: IEnvironmentInfoService
) {}

public resolveEnv(env: string | PythonEnvInfo): Promise<PythonEnvInfo | undefined> {
return this.pythonEnvsReducer.resolveEnv(env);
public async resolveEnv(env: string | PythonEnvInfo): Promise<PythonEnvInfo | undefined> {
const environment = await this.pythonEnvsReducer.resolveEnv(env);
if (!environment) {
return undefined;
}
const interpreterInfo = await this.environmentInfoService.getEnvironmentInfo(environment.executable.filename);
if (!interpreterInfo) {
return undefined;
}
return getResolvedEnv(interpreterInfo, environment);
}

public iterEnvs(query?: QueryForEvent<PythonEnvsChangedEvent>): IPythonEnvsIterator {
Expand All @@ -34,13 +40,13 @@ export class PythonEnvsResolver implements ILocator {
return iterator;
}

private async* iterEnvsIterator(
private async *iterEnvsIterator(
iterator: IPythonEnvsIterator,
didUpdate: EventEmitter<PythonEnvUpdatedEvent | null>,
didUpdate: EventEmitter<PythonEnvUpdatedEvent | null>
): AsyncIterator<PythonEnvInfo, void> {
const state = {
done: false,
pending: 0,
pending: 0
};
const seen: PythonEnvInfo[] = [];

Expand Down Expand Up @@ -84,10 +90,10 @@ export class PythonEnvsResolver implements ILocator {
envIndex: number,
state: { done: boolean; pending: number },
didUpdate: EventEmitter<PythonEnvUpdatedEvent | null>,
seen: PythonEnvInfo[],
seen: PythonEnvInfo[]
) {
const interpreterInfo = await this.environmentInfoService.getEnvironmentInfo(
seen[envIndex].executable.filename,
seen[envIndex].executable.filename
);
if (interpreterInfo && seen[envIndex]) {
const resolvedEnv = getResolvedEnv(interpreterInfo, seen[envIndex]);
Expand All @@ -106,7 +112,7 @@ export class PythonEnvsResolver implements ILocator {
*/
function checkIfFinishedAndNotify(
state: { done: boolean; pending: number },
didUpdate: EventEmitter<PythonEnvUpdatedEvent | null>,
didUpdate: EventEmitter<PythonEnvUpdatedEvent | null>
) {
if (state.done && state.pending === 0) {
didUpdate.fire(null);
Expand Down
148 changes: 108 additions & 40 deletions src/test/pythonEnvironments/collection/environmentsResolver.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,28 @@ import { PythonEnvUpdatedEvent } from '../../../client/pythonEnvironments/base/l
import { PythonEnvsChangedEvent } from '../../../client/pythonEnvironments/base/watcher';
import { PythonEnvsResolver } from '../../../client/pythonEnvironments/collection/environmentsResolver';
import * as ExternalDep from '../../../client/pythonEnvironments/common/externalDependencies';
import {
EnvironmentInfoService,
IEnvironmentInfoService,
} from '../../../client/pythonEnvironments/info/environmentInfoService';
import { EnvironmentInfoService } from '../../../client/pythonEnvironments/info/environmentInfoService';
import { sleep } from '../../core';
import { createEnv, getEnvs, SimpleLocator } from '../base/common';

suite('Environments Resolver', () => {
/**
* Returns the expected environment to be returned by Environment info service
*/
function createExpectedEnvInfo(env: PythonEnvInfo): PythonEnvInfo {
const updatedEnv = cloneDeep(env);
updatedEnv.version = {
...parseVersion('3.8.3-final'),
sysVersion: '3.8.3 (tags/v3.8.3:6f8c832, May 13 2020, 22:37:02) [MSC v.1924 64 bit (AMD64)]',
};
updatedEnv.executable.filename = env.executable.filename;
updatedEnv.executable.sysPrefix = 'path';
updatedEnv.arch = Architecture.x64;
return updatedEnv;
}
suite('iterEnvs()', () => {
let stubShellExec: sinon.SinonStub;
let envService: IEnvironmentInfoService;

setup(() => {
envService = new EnvironmentInfoService();
stubShellExec = ImportMock.mockFunction(
ExternalDep,
'shellExecute',
Expand All @@ -44,23 +52,14 @@ suite('Environments Resolver', () => {
stubShellExec.restore();
});

function createExpectedEnvInfo(env: PythonEnvInfo): PythonEnvInfo {
const updatedEnv = cloneDeep(env);
updatedEnv.version = { ...parseVersion('3.8.3-final'), sysVersion: '3.8.3 (tags/v3.8.3:6f8c832, May 13 2020, 22:37:02) [MSC v.1924 64 bit (AMD64)]' };
updatedEnv.executable.filename = env.executable.filename;
updatedEnv.executable.sysPrefix = 'path';
updatedEnv.arch = Architecture.x64;
return updatedEnv;
}

test('Iterator only yields as-is', async () => {
const env1 = createEnv('env1', '3.5.12b1', PythonEnvKind.Venv, path.join('path', 'to', 'exec1'));
const env2 = createEnv('env2', '3.8.1', PythonEnvKind.Conda, path.join('path', 'to', 'exec2'));
const env3 = createEnv('env3', '2.7', PythonEnvKind.System, path.join('path', 'to', 'exec3'));
const env4 = createEnv('env4', '3.9.0rc2', PythonEnvKind.Unknown, path.join('path', 'to', 'exec2'));
const environmentsToBeIterated = [env1, env2, env3, env4];
const pythonEnvManager = new SimpleLocator(environmentsToBeIterated);
const reducer = new PythonEnvsResolver(pythonEnvManager, envService);
const pythonEnvReducer = new SimpleLocator(environmentsToBeIterated);
const reducer = new PythonEnvsResolver(pythonEnvReducer, new EnvironmentInfoService());

const iterator = reducer.iterEnvs();
const envs = await getEnvs(iterator);
Expand All @@ -73,9 +72,9 @@ suite('Environments Resolver', () => {
const env1 = createEnv('env1', '3.5.12b1', PythonEnvKind.Unknown, path.join('path', 'to', 'exec1'));
const env2 = createEnv('env2', '3.8.1', PythonEnvKind.Unknown, path.join('path', 'to', 'exec2'));
const environmentsToBeIterated = [env1, env2];
const pythonEnvManager = new SimpleLocator(environmentsToBeIterated);
const pythonEnvReducer = new SimpleLocator(environmentsToBeIterated);
const onUpdatedEvents: (PythonEnvUpdatedEvent | null)[] = [];
const reducer = new PythonEnvsResolver(pythonEnvManager, envService);
const reducer = new PythonEnvsResolver(pythonEnvReducer, new EnvironmentInfoService());

const iterator = reducer.iterEnvs(); // Act

Expand Down Expand Up @@ -108,9 +107,9 @@ suite('Environments Resolver', () => {
const updatedEnv = createEnv('env1', '3.8.1', PythonEnvKind.System, path.join('path', 'to', 'exec'));
const environmentsToBeIterated = [env];
const didUpdate = new EventEmitter<PythonEnvUpdatedEvent | null>();
const pythonEnvManager = new SimpleLocator(environmentsToBeIterated, { onUpdated: didUpdate.event });
const pythonEnvReducer = new SimpleLocator(environmentsToBeIterated, { onUpdated: didUpdate.event });
const onUpdatedEvents: (PythonEnvUpdatedEvent | null)[] = [];
const reducer = new PythonEnvsResolver(pythonEnvManager, envService);
const reducer = new PythonEnvsResolver(pythonEnvReducer, new EnvironmentInfoService());

const iterator = reducer.iterEnvs(); // Act

Expand All @@ -134,43 +133,112 @@ suite('Environments Resolver', () => {
// Assert
// The updates can be anything, even the number of updates, but they should lead to the same final state
const { length } = onUpdatedEvents;
assert.deepEqual(onUpdatedEvents[length - 2]?.new, createExpectedEnvInfo(updatedEnv), 'The final update to environment is incorrect');
assert.deepEqual(
onUpdatedEvents[length - 2]?.new,
createExpectedEnvInfo(updatedEnv),
'The final update to environment is incorrect',
);
assert.equal(onUpdatedEvents[length - 1], null, 'Last update should be null');
didUpdate.dispose();
});
});

test('onChanged fires iff onChanged from reducer fires', () => {
const pythonEnvManager = new SimpleLocator([]);
const pythonEnvReducer = new SimpleLocator([]);
const event1: PythonEnvsChangedEvent = {};
const event2: PythonEnvsChangedEvent = { kind: PythonEnvKind.Unknown };
const expected = [event1, event2];
const reducer = new PythonEnvsResolver(pythonEnvManager, new EnvironmentInfoService());
const reducer = new PythonEnvsResolver(pythonEnvReducer, new EnvironmentInfoService());

const events: PythonEnvsChangedEvent[] = [];
reducer.onChanged((e) => events.push(e));

pythonEnvManager.fire(event1);
pythonEnvManager.fire(event2);
pythonEnvReducer.fire(event1);
pythonEnvReducer.fire(event2);

assert.deepEqual(events, expected);
});

test('Calls reducer to resolves environments', async () => {
const env = createEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec'));
const resolvedEnv = createEnv('env1', '3.8.1', PythonEnvKind.Conda, 'resolved/path/to/exec');
const pythonEnvManager = new SimpleLocator([], {
resolve: async (e: PythonEnvInfo) => {
if (e === env) {
return resolvedEnv;
}
return undefined;
},
suite('resolveEnv()', () => {
let stubShellExec: sinon.SinonStub;
setup(() => {
stubShellExec = ImportMock.mockFunction(
ExternalDep,
'shellExecute',
new Promise<ExecutionResult<string>>((resolve) => {
resolve({
stdout:
'{"versionInfo": [3, 8, 3, "final", 0], "sysPrefix": "path", "sysVersion": "3.8.3 (tags/v3.8.3:6f8c832, May 13 2020, 22:37:02) [MSC v.1924 64 bit (AMD64)]", "is64Bit": true}',
});
}),
);
});

teardown(() => {
stubShellExec.restore();
});

test('Calls into reducer to get resolved environment, then calls environnment service to resolve environment further and return it', async () => {
const env = createEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec'));
const resolvedEnvReturnedByReducer = createEnv(
'env1',
'3.8.1',
PythonEnvKind.Conda,
'resolved/path/to/exec',
);
const pythonEnvReducer = new SimpleLocator([], {
resolve: async (e: PythonEnvInfo) => {
if (e === env) {
return resolvedEnvReturnedByReducer;
}
throw new Error('Incorrect environment sent to the reducer');
},
});
const reducer = new PythonEnvsResolver(pythonEnvReducer, new EnvironmentInfoService());

const expected = await reducer.resolveEnv(env);

assert.deepEqual(expected, createExpectedEnvInfo(resolvedEnvReturnedByReducer));
});
const reducer = new PythonEnvsResolver(pythonEnvManager, new EnvironmentInfoService());

const expected = await reducer.resolveEnv(env);
test('If the reducer resolves environment, but fetching interpreter info returns undefined, return undefined', async () => {
stubShellExec.returns(
new Promise<ExecutionResult<string>>((_resolve, reject) => {
reject();
}),
);
const env = createEnv('env1', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec'));
const resolvedEnvReturnedByReducer = createEnv(
'env1',
'3.8.1',
PythonEnvKind.Conda,
'resolved/path/to/exec',
);
const pythonEnvReducer = new SimpleLocator([], {
resolve: async (e: PythonEnvInfo) => {
if (e === env) {
return resolvedEnvReturnedByReducer;
}
throw new Error('Incorrect environment sent to the reducer');
},
});
const reducer = new PythonEnvsResolver(pythonEnvReducer, new EnvironmentInfoService());

const expected = await reducer.resolveEnv(env);

assert.deepEqual(expected, undefined);
});

assert.deepEqual(expected, resolvedEnv);
test("If the reducer isn't able to resolve environment, return undefined", async () => {
const env = createEnv('env', '3.8', PythonEnvKind.Unknown, path.join('path', 'to', 'exec'));
const pythonEnvReducer = new SimpleLocator([], {
resolve: async () => undefined,
});
const reducer = new PythonEnvsResolver(pythonEnvReducer, new EnvironmentInfoService());

const expected = await reducer.resolveEnv(env);

assert.deepEqual(expected, undefined);
});
});
});

0 comments on commit 06d3fb0

Please sign in to comment.