Skip to content

Commit

Permalink
Update interpreter display (#3)
Browse files Browse the repository at this point in the history
* Add support for multi roots for formatting and linting (#1281)

* update to use latest api

* config changes for multiroot workspace

* linting support with multi roots

* multi root support for formatters

* determine workspace root path

* revert change

* support multiple configs per workspace folder

* modify formatters to use resource specific settings

* modified to settings are resolved using document uri

* 1228 multi root master (#1)

* fix #1280 handle env in shebang (#1290)

* handle shebangs that resolve paths from env

* oops

* make test more specific

* handle promise

* fix #1282 use PYTHONIOENCODING variable (#1291)

* fix #1270 debugger contribution changes (#1288)

* add onDebug activation

* use debug config provider for non workspace debugging

* forgot to save file before commiting a merge

* ability to opt out of telemetry using vscode settings (#1297)

* Fix #1284 debugging parameterized tests (#1299)

* fix #1298 remove vscode.startDebug command (#1300)

* fix #1298 remove vscode.startDebug command

* fix code review comments

* added period as per code review comments

* #1288 installer config and tests (#1302)

* update to use latest api

* config changes for multiroot workspace

* linting support with multi roots

* multi root support for formatters

* determine workspace root path

* revert change

* support multiple configs per workspace folder

* modify formatters to use resource specific settings

* modified installer to pass resource for workspace resolution

* null test in installer

* canges to config settings to support multiroot workspace

* changes to code refactoring to support workspace symbols

* oops

* modified to settings are resolved using document uri

* unit tests for multi root support

* fix unittests for multiroot

* exclude files

* add new line

* config changes for multiroot workspace

* new lines and enabled multi root linter tests

* fix sys variables

* added unit test to resolve ${workspaceRoot} in settings.json

* fixed code review comments

* fixed code review comments

* fix #1276 Pre-commit hooks to ensure code complies with standards (#1277)

* sanitize code

* gulp file to check code hygiene

* fix preLaunchTask in launch.json

* added missing packages

* enabled pre-commit using husky

* enabled a few checks for precommit hook

* fix tslint warnings when running tslint via gulp

* exclude webpack building, else tries to pull in tests as well

* improved checks for commits (strict)

* added new lines

* 1228 multi root workspace symbols (#1307)

* update to use latest api

* config changes for multiroot workspace

* linting support with multi roots

* multi root support for formatters

* determine workspace root path

* revert change

* support multiple configs per workspace folder

* modify formatters to use resource specific settings

* modified installer to pass resource for workspace resolution

* null test in installer

* canges to config settings to support multiroot workspace

* changes to code refactoring to support workspace symbols

* oops

* modified to settings are resolved using document uri

* unit tests for multi root support

* fix unittests for multiroot

* exclude files

* add new line

* config changes for multiroot workspace

* new lines and enabled multi root linter tests

* fix sys variables

* added unit test to resolve ${workspaceRoot} in settings.json

* #1228 workspace symbols with multiroot support

* fix test

* added some data for workspace symbol tests

* data for unit tests

* fixed to add support for multit roots with unit tests

* account for mutiroot files in sub directory

* disable all but multiroot tests

* fixed tests

* include files for tests

* Fixed travis tests for multi root workspace symbols (#1306)

* added logging

* more logging

* yay

* fixed

* more fixes

* fix tests

* removed logging

* enable all tests

* uncommented

* Added brackets around print statements (for p3)

* Fixed travis unit tests (#1308)

* update to use latest api

* config changes for multiroot workspace

* linting support with multi roots

* multi root support for formatters

* determine workspace root path

* revert change

* support multiple configs per workspace folder

* modify formatters to use resource specific settings

* modified installer to pass resource for workspace resolution

* null test in installer

* canges to config settings to support multiroot workspace

* changes to code refactoring to support workspace symbols

* oops

* modified to settings are resolved using document uri

* unit tests for multi root support

* fix unittests for multiroot

* exclude files

* add new line

* config changes for multiroot workspace

* new lines and enabled multi root linter tests

* fix sys variables

* added unit test to resolve ${workspaceRoot} in settings.json

* #1228 workspace symbols with multiroot support

* fix test

* added some data for workspace symbol tests

* data for unit tests

* fixed to add support for multit roots with unit tests

* account for mutiroot files in sub directory

* disable all but multiroot tests

* fixed tests

* fix tests

* test where failing

* properly determine root workspace

* fix pytest unit test

* delete files

* add awaiter

* use a path that works on multiple os

* fixes

* uncomment

* invert

* debug statements

* use default workspace

* reverted unwanted changes

* oops

* test unittests only

* more logging

* partial fixes to unit tests

* run all tests

* changes not to set paths for shebang tests

* remove comments

* update settings only if necessary

* fix test

* include files for tests

* Fixed travis tests for multi root workspace symbols (#1306)

* added logging

* more logging

* yay

* fixed

* more fixes

* fix tests

* removed logging

* enable all tests

* uncommented

* Added brackets around print statements (for p3)

* use resource when getting settings

* fix #1315 unit tests need to wait for extension to activate (#1316)

* fix #1314 allow for simultaneous language features (#1317)

* #1228 support multi roots in language service (#1309)

* update to use latest api

* config changes for multiroot workspace

* linting support with multi roots

* multi root support for formatters

* determine workspace root path

* revert change

* support multiple configs per workspace folder

* modify formatters to use resource specific settings

* modified installer to pass resource for workspace resolution

* null test in installer

* canges to config settings to support multiroot workspace

* changes to code refactoring to support workspace symbols

* oops

* modified to settings are resolved using document uri

* unit tests for multi root support

* fix unittests for multiroot

* exclude files

* add new line

* config changes for multiroot workspace

* new lines and enabled multi root linter tests

* fix sys variables

* added unit test to resolve ${workspaceRoot} in settings.json

* #1228 workspace symbols with multiroot support

* fix test

* added some data for workspace symbol tests

* data for unit tests

* fixed to add support for multit roots with unit tests

* account for mutiroot files in sub directory

* disable all but multiroot tests

* fixed tests

* fix tests

* test where failing

* properly determine root workspace

* fix pytest unit test

* delete files

* add awaiter

* use a path that works on multiple os

* fixes

* uncomment

* invert

* debug statements

* use default workspace

* reverted unwanted changes

* oops

* test unittests only

* more logging

* partial fixes to unit tests

* run all tests

* changes not to set paths for shebang tests

* remove comments

* update settings only if necessary

* fix test

* include files for tests

* Fixed travis tests for multi root workspace symbols (#1306)

* added logging

* more logging

* yay

* fixed

* more fixes

* fix tests

* removed logging

* enable all tests

* uncommented

* Added brackets around print statements (for p3)

* use resource when getting settings

* support multiroot in language services

* add additional tests for #1314 (#1318)

* #1228 run all tests under multiroot (#1322)

* modifications to fix tests to run under multi root setup

* log errors

* fix return type

* fix linter messages

* fix linter errors

* changes to ensure code is formatted correctly

* fixed comments

* delete unwanted file

* hide unwanted folders

* fixes to linters to run on multiroot setup

* udpate settings sequentially

* log the output

* show errors in deleting dir

* removed prospector test, to be completed in #1319

* fixes to tests and sorting provider

* fixed test for interpreter display

* undo commenting of code

* add new line

* fix code review issues

* ensure else is properly formatted

* fix code review comments

* fix #1304 preserve empty lines (#1329)

* #1228 multiroot interpreter display (#1339)

* modifications to fix tests to run under multi root setup

* log errors

* fix return type

* fix linter messages

* fix linter errors

* changes to ensure code is formatted correctly

* fixed comments

* delete unwanted file

* hide unwanted folders

* fixes to linters to run on multiroot setup

* udpate settings sequentially

* log the output

* show errors in deleting dir

* removed prospector test, to be completed in #1319

* fixes to tests and sorting provider

* fixed test for interpreter display

* undo commenting of code

* add new line

* support multi root in interpreter display

* fix linter

* changed package version

* disabled multiroot test

* backwards compatible change

* fix nose tests

* revert change

* enable test but disable it

* multi root support in utils.ts

* fixed #1328

* retries for flaky unit tests

* retry beforeEach

* common retry decorator

* enable telemetry for extension loads

* disable jupyter tests in multiroot tests

* clean up python Path before and after testsclean up python Path before and after tests

* rename test env variable

* dispose cfg settings

* dispose cfg settings

* update comment

* clean up

* rearrange to ensurfe launching ext is first debug option

* bug fix for display name

* resolved code review comment

* Fixed typp

* 1228 multiroot interpreter ui changes (#1345)

* fixes to unit tests and forgotten multiroot

* globally retry all tests 3 times

* refactor changing interpreters

* added tests

* fixed linter

* removed redundant files

* removed unwanted grep

* remove blank line

* fix 948 remove hardcoding of port number (#1353)

* fix #1041 when debugging a test do not cancel it when re-discovering tests (#1354)

* fix 1041 when debugging a test do not cancel it when re-discovering tests

* create enum for creation of cancellation token

* dispose correct cancellationToken

* bug fix - in unit tests

* bug fix - in unit tests

* #1228 multiroot unit test runner (#1357)

* fixes to unit tests and forgotten multiroot

* globally retry all tests 3 times

* refactor changing interpreters

* added tests

* fixed linter

* removed redundant files

* temp changes

* more changes

* lots of refactoring

* adding support for multiroot workspaces

* removed grep

* copy changes for #948 and #1353 into multroot

* replicate solution for #1041 and #1354

* #1041 create enum for creation of cancellation token

* multiroot support for unit tests

* remove empty line (linter warning)

* delete pyc before making changes to py file

* delete pyc file in teardown

* merged multiroot master

* pass uri of workspace when displaing prompt for configuration

* pass uri to commands

* fixed typos based on code review

* prefix path with forward slash, as is done in the extension  unit tests

* include version number into display name
  • Loading branch information
DonJayamanne authored Nov 2, 2017
1 parent 2bc6b2e commit e54947e
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 38 deletions.
14 changes: 13 additions & 1 deletion src/client/interpreter/locators/services/conda.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
import { IS_WINDOWS } from "../../../common/utils";
import { IS_WINDOWS } from '../../../common/utils';

// where to find the Python binary within a conda env.
export const CONDA_RELATIVE_PY_PATH = IS_WINDOWS ? ['python.exe'] : ['bin', 'python'];
// tslint:disable-next-line:variable-name
export const AnacondaCompanyNames = ['Anaconda, Inc.', 'Continuum Analytics, Inc.'];
// tslint:disable-next-line:variable-name
export const AnacondaCompanyName = 'Anaconda, Inc.';
// tslint:disable-next-line:variable-name
export const AnacondaDisplayName = 'Anaconda';
// tslint:disable-next-line:variable-name
export const AnacondaIdentfiers = ['Anaconda', 'Conda', 'Continuum'];

export type CondaInfo = {
envs?: string[];
'sys.version'?: string;
'python_version'?: string;
default_prefix?: string;
};
47 changes: 17 additions & 30 deletions src/client/interpreter/locators/services/condaEnvService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,35 @@ import * as path from 'path';
import { Uri } from 'vscode';
import { VersionUtils } from '../../../common/versionUtils';
import { IInterpreterLocatorService, PythonInterpreter } from '../../contracts';
import { AnacondaCompanyName, AnacondaDisplayName, CONDA_RELATIVE_PY_PATH } from './conda';
import { AnacondaCompanyName, CONDA_RELATIVE_PY_PATH, CondaInfo } from './conda';
import { CondaHelper } from './condaHelper';

type CondaInfo = {
envs?: string[];
'sys.version'?: string;
default_prefix?: string;
};
export class CondaEnvService implements IInterpreterLocatorService {
private readonly condaHelper = new CondaHelper();
constructor(private registryLookupForConda?: IInterpreterLocatorService) {
}
public getInterpreters(resource?: Uri) {
public async getInterpreters(resource?: Uri) {
return this.getSuggestionsFromConda();
}
// tslint:disable-next-line:no-empty
public dispose() { }
public getCondaFile() {
public async getCondaFile() {
if (this.registryLookupForConda) {
return this.registryLookupForConda.getInterpreters()
.then(interpreters => interpreters.filter(this.isCondaEnvironment))
.then(condaInterpreters => this.getLatestVersion(condaInterpreters))
.then(condaInterpreter => {
return condaInterpreter ? path.join(path.dirname(condaInterpreter.path), 'conda.exe') : 'conda';
})
.then(condaPath => {
.then(async condaPath => {
return fs.pathExists(condaPath).then(exists => exists ? condaPath : 'conda');
});
}
return Promise.resolve('conda');
}
public isCondaEnvironment(interpreter: PythonInterpreter) {
return (interpreter.displayName || '').toUpperCase().indexOf('ANACONDA') >= 0 ||
(interpreter.companyDisplayName || '').toUpperCase().indexOf('CONTINUUM') >= 0;
return (interpreter.displayName ? interpreter.displayName : '').toUpperCase().indexOf('ANACONDA') >= 0 ||
(interpreter.companyDisplayName ? interpreter.companyDisplayName : '').toUpperCase().indexOf('CONTINUUM') >= 0;
}
public getLatestVersion(interpreters: PythonInterpreter[]) {
const sortedInterpreters = interpreters.filter(interpreter => interpreter.version && interpreter.version.length > 0);
Expand All @@ -47,8 +44,7 @@ export class CondaEnvService implements IInterpreterLocatorService {
}
}
public async parseCondaInfo(info: CondaInfo) {
// "sys.version": "3.6.1 |Anaconda 4.4.0 (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]".
const displayName = this.getDisplayNameFromVersionInfo(info['sys.version'] || '');
const displayName = this.condaHelper.getDisplayName(info);

// The root of the conda environment is itself a Python interpreter
// envs reported as e.g.: /Users/bob/miniconda3/envs/someEnv.
Expand All @@ -69,47 +65,38 @@ export class CondaEnvService implements IInterpreterLocatorService {
};
return interpreter;
})
.map(env => fs.pathExists(env.path).then(exists => exists ? env : null));
.map(async env => fs.pathExists(env.path).then(exists => exists ? env : null));

return Promise.all(promises)
.then(interpreters => interpreters.filter(interpreter => interpreter !== null && interpreter !== undefined))
// tslint:disable-next-line:no-non-null-assertion
.then(interpreters => interpreters.map(interpreter => interpreter!));
}
private getSuggestionsFromConda(): Promise<PythonInterpreter[]> {
private async getSuggestionsFromConda(): Promise<PythonInterpreter[]> {
return this.getCondaFile()
.then(condaFile => {
.then(async condaFile => {
return new Promise<PythonInterpreter[]>((resolve, reject) => {
// interrogate conda (if it's on the path) to find all environments.
child_process.execFile(condaFile, ['info', '--json'], (_, stdout) => {
if (stdout.length === 0) {
return resolve([]);
resolve([]);
return;
}

try {
const info = JSON.parse(stdout);
// tslint:disable-next-line:prefer-type-cast
const info = JSON.parse(stdout) as CondaInfo;
resolve(this.parseCondaInfo(info));
} catch (e) {
// Failed because either:
// 1. conda is not installed.
// 2. `conda info --json` has changed signature.
// 3. output of `conda info --json` has changed in structure.
// In all cases, we can't offer conda pythonPath suggestions.
return resolve([]);
resolve([]);
}
});
});
});
}
private getDisplayNameFromVersionInfo(versionInfo: string = '') {
if (!versionInfo) {
return AnacondaDisplayName;
}

const versionParts = versionInfo.split('|').map(item => item.trim());
if (versionParts.length > 1 && versionParts[1].indexOf('conda') >= 0) {
return versionParts[1];
}
return AnacondaDisplayName;
}
}
42 changes: 42 additions & 0 deletions src/client/interpreter/locators/services/condaHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { AnacondaDisplayName, AnacondaIdentfiers, CondaInfo } from './conda';

export class CondaHelper {
public getDisplayName(condaInfo: CondaInfo = {}): string {
const pythonVersion = this.getPythonVersion(condaInfo);

// Samples.
// "3.6.1 |Anaconda 4.4.0 (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]".
// "3.6.2 |Anaconda, Inc.| (default, Sep 21 2017, 18:29:43) \n[GCC 4.2.1 Compatible Clang 4.0.1 (tags/RELEASE_401/final)]".
const sysVersion = condaInfo['sys.version'];
if (!sysVersion) {
return pythonVersion ? `Python ${pythonVersion} : ${AnacondaDisplayName}` : AnacondaDisplayName;
}

// Take the first two parts of the sys.version.
const sysVersionParts = sysVersion.split('|').filter((_, index) => index < 2);
if (sysVersionParts.length > 0) {
if (pythonVersion && sysVersionParts[0].startsWith(pythonVersion)) {
sysVersionParts[0] = `Python ${sysVersionParts[0]}`;
} else {
// The first part is not the python version, hence remove this.
sysVersionParts.shift();
}
}

const displayName = sysVersionParts.map(item => item.trim()).join(' : ');
return this.isIdentifiableAsAnaconda(displayName) ? displayName : `${displayName} : ${AnacondaDisplayName}`;
}
private isIdentifiableAsAnaconda(value: string) {
const valueToSearch = value.toLowerCase();
return AnacondaIdentfiers.some(item => valueToSearch.indexOf(item.toLowerCase()) !== -1);
}
private getPythonVersion(condaInfo: CondaInfo): string | undefined {
// Sample.
// 3.6.2.final.0 (hence just take everything untill the third period).
const pythonVersion = condaInfo.python_version;
if (!pythonVersion) {
return undefined;
}
return pythonVersion.split('.').filter((_, index) => index < 3).join('.');
}
}
14 changes: 7 additions & 7 deletions src/test/interpreters/condaEnvService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Uri } from 'vscode';
import { PythonSettings } from '../../client/common/configSettings';
import { IS_WINDOWS } from '../../client/common/utils';
import { PythonInterpreter } from '../../client/interpreter/contracts';
import { AnacondaCompanyName } from '../../client/interpreter/locators/services/conda';
import { AnacondaCompanyName, AnacondaDisplayName } from '../../client/interpreter/locators/services/conda';
import { CondaEnvService } from '../../client/interpreter/locators/services/condaEnvService';
import { initialize, initializeTest } from '../initialize';
import { MockProvider } from './mocks';
Expand Down Expand Up @@ -55,33 +55,33 @@ suite('Interpreters from Conda Environments', () => {

const path1 = path.join(info.envs[0], IS_WINDOWS ? 'python.exe' : 'bin/python');
assert.equal(interpreters[0].path, path1, 'Incorrect path for first env');
assert.equal(interpreters[0].displayName, 'Anaconda (numpy)', 'Incorrect display name for first env');
assert.equal(interpreters[0].displayName, `Anaonda 4.4.0 (64-bit) : ${AnacondaDisplayName} (numpy)`, 'Incorrect display name for first env');
assert.equal(interpreters[0].companyDisplayName, AnacondaCompanyName, 'Incorrect company display name for first env');
});
test('Must use the default display name if sys.version is empty', async () => {
const condaProvider = new CondaEnvService();
const info = {
envs: [path.join(environmentsPath, 'conda', 'envs', 'numpy')],
envs: [path.join(environmentsPath, 'conda', 'envs', 'numpy')]
};
const interpreters = await condaProvider.parseCondaInfo(info);
assert.equal(interpreters.length, 1, 'Incorrect number of entries');

const path1 = path.join(info.envs[0], IS_WINDOWS ? 'python.exe' : 'bin/python');
assert.equal(interpreters[0].path, path1, 'Incorrect path for first env');
assert.equal(interpreters[0].displayName, 'Anaconda (numpy)', 'Incorrect display name for first env');
assert.equal(interpreters[0].displayName, `${AnacondaDisplayName} (numpy)`, 'Incorrect display name for first env');
assert.equal(interpreters[0].companyDisplayName, AnacondaCompanyName, 'Incorrect company display name for first env');
});
test('Must include the default_prefix into the list of interpreters', async () => {
const condaProvider = new CondaEnvService();
const info = {
default_prefix: path.join(environmentsPath, 'conda', 'envs', 'numpy'),
default_prefix: path.join(environmentsPath, 'conda', 'envs', 'numpy')
};
const interpreters = await condaProvider.parseCondaInfo(info);
assert.equal(interpreters.length, 1, 'Incorrect number of entries');

const path1 = path.join(info.default_prefix, IS_WINDOWS ? 'python.exe' : 'bin/python');
assert.equal(interpreters[0].path, path1, 'Incorrect path for first env');
assert.equal(interpreters[0].displayName, 'Anaconda', 'Incorrect display name for first env');
assert.equal(interpreters[0].displayName, AnacondaDisplayName, 'Incorrect display name for first env');
assert.equal(interpreters[0].companyDisplayName, AnacondaCompanyName, 'Incorrect company display name for first env');
});
test('Must exclude interpreters that do not exist on disc', async () => {
Expand Down Expand Up @@ -169,7 +169,7 @@ suite('Interpreters from Conda Environments', () => {
{ displayName: 'One', path: path.join(environmentsPath, 'path1', 'one.exe'), companyDisplayName: 'One 1', version: '1' },
{ displayName: 'Anaconda', path: condaPythonExePath, companyDisplayName: 'Two 2', version: '1.11.0' },
{ displayName: 'Three', path: path.join(environmentsPath, 'path2', 'one.exe'), companyDisplayName: 'Three 3', version: '2.10.1' },
{ displayName: 'Seven', path: path.join(environmentsPath, 'conda', 'envs', 'numpy'), companyDisplayName: 'Continuum Analytics, Inc.' },
{ displayName: 'Seven', path: path.join(environmentsPath, 'conda', 'envs', 'numpy'), companyDisplayName: 'Continuum Analytics, Inc.' }
];
const mockRegistryProvider = new MockProvider(registryInterpreters);
const condaProvider = new CondaEnvService(mockRegistryProvider);
Expand Down
45 changes: 45 additions & 0 deletions src/test/interpreters/condaHelper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import * as assert from 'assert';
import { AnacondaDisplayName, CondaInfo } from '../../client/interpreter/locators/services/conda';
import { CondaHelper } from '../../client/interpreter/locators/services/condaHelper';
import { initialize, initializeTest } from '../initialize';

// tslint:disable-next-line:max-func-body-length
suite('Interpreters display name from Conda Environments', () => {
const condaHelper = new CondaHelper();
suiteSetup(initialize);
setup(initializeTest);
test('Must return default display name for invalid Conda Info', () => {
assert.equal(condaHelper.getDisplayName(), AnacondaDisplayName, 'Incorrect display name');
assert.equal(condaHelper.getDisplayName({}), AnacondaDisplayName, 'Incorrect display name');
});
test('Must return at least Python Version', () => {
const info: CondaInfo = {
python_version: '3.6.1.final.10'
};
const displayName = condaHelper.getDisplayName(info);
assert.equal(displayName, `Python 3.6.1 : ${AnacondaDisplayName}`, 'Incorrect display name');
});
test('Must return info without first part if not a python version', () => {
const info: CondaInfo = {
'sys.version': '3.6.1 |Anaconda 4.4.0 (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]'
};
const displayName = condaHelper.getDisplayName(info);
assert.equal(displayName, 'Anaconda 4.4.0 (64-bit)', 'Incorrect display name');
});
test('Must return info prefixed with word \'Python\'', () => {
const info: CondaInfo = {
python_version: '3.6.1.final.10',
'sys.version': '3.6.1 |Anaconda 4.4.0 (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]'
};
const displayName = condaHelper.getDisplayName(info);
assert.equal(displayName, 'Python 3.6.1 : Anaconda 4.4.0 (64-bit)', 'Incorrect display name');
});
test('Must include Ananconda name if Company name not found', () => {
const info: CondaInfo = {
python_version: '3.6.1.final.10',
'sys.version': '3.6.1 |4.4.0 (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]'
};
const displayName = condaHelper.getDisplayName(info);
assert.equal(displayName, `Python 3.6.1 : 4.4.0 (64-bit) : ${AnacondaDisplayName}`, 'Incorrect display name');
});
});

0 comments on commit e54947e

Please sign in to comment.