From aad5ca02dde9474a58f50f05b0b64e10947fec6d Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 16 Oct 2017 15:39:59 -0700 Subject: [PATCH 01/43] modifications to fix tests to run under multi root setup --- .gitignore | 1 + src/test/.vscode/settings.json | 17 +- src/test/common/configSettings.test.ts | 4 +- src/test/format/extension.format.test.ts | 17 +- src/test/index.ts | 35 ++-- src/test/jupyter/jupyter.codeHelper.test.ts | 174 ++++++++++---------- src/test/workspaceSymbols/standard.test.ts | 11 +- src/testMultiRootWkspc/multi.code-workspace | 12 +- 8 files changed, 142 insertions(+), 129 deletions(-) diff --git a/.gitignore b/.gitignore index 058d4e514ff2..ad74be556908 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ node_modules .vscode-test __pycache__ npm-debug.log +**/.mypy_cache/** \ No newline at end of file diff --git a/src/test/.vscode/settings.json b/src/test/.vscode/settings.json index bfbbba081622..c7ae170c50cd 100644 --- a/src/test/.vscode/settings.json +++ b/src/test/.vscode/settings.json @@ -1,12 +1,23 @@ { - "python.pythonPath": "python", "python.linting.pylintEnabled": true, "python.linting.flake8Enabled": true, - "python.workspaceSymbols.enabled": true, + "python.workspaceSymbols.enabled": false, "python.unitTest.nosetestArgs": [], "python.unitTest.pyTestArgs": [], "python.unitTest.unittestArgs": [ "-s=./tests", "-p=test_*.py" - ] + ], + "python.pythonPath": "python", + "python.formatting.formatOnSave": false, + "python.sortImports.args": [], + "python.linting.lintOnSave": false, + "python.linting.lintOnTextChange": false, + "python.linting.enabled": true, + "python.linting.pep8Enabled": true, + "python.linting.prospectorEnabled": true, + "python.linting.pydocstyleEnabled": true, + "python.linting.pylamaEnabled": true, + "python.linting.mypyEnabled": true, + "python.formatting.provider": "yapf" } \ No newline at end of file diff --git a/src/test/common/configSettings.test.ts b/src/test/common/configSettings.test.ts index 3b2e7a1c85eb..616c29d5afab 100644 --- a/src/test/common/configSettings.test.ts +++ b/src/test/common/configSettings.test.ts @@ -10,7 +10,7 @@ import * as assert from 'assert'; // as well as import your extension to test it import * as vscode from 'vscode'; import * as path from 'path'; -import { initialize, IS_TRAVIS } from './../initialize'; +import { initialize, IS_MULTI_ROOT_TEST, IS_TRAVIS } from './../initialize'; import { PythonSettings } from '../../client/common/configSettings'; import { SystemVariables } from '../../client/common/systemVariables'; import { rootWorkspaceUri } from '../common'; @@ -21,7 +21,7 @@ const workspaceRoot = path.join(__dirname, '..', '..', '..', 'src', 'test'); suite('Configuration Settings', () => { setup(() => initialize()); - if (!IS_TRAVIS) { + if (!IS_MULTI_ROOT_TEST) { test('Check Values', done => { const systemVariables: SystemVariables = new SystemVariables(workspaceRoot); const pythonConfig = vscode.workspace.getConfiguration('python'); diff --git a/src/test/format/extension.format.test.ts b/src/test/format/extension.format.test.ts index 1f47e8ea247d..ed792f087f6b 100644 --- a/src/test/format/extension.format.test.ts +++ b/src/test/format/extension.format.test.ts @@ -15,7 +15,7 @@ import * as fs from 'fs-extra'; import { EOL } from 'os'; import { PythonSettings } from '../../client/common/configSettings'; import { AutoPep8Formatter } from '../../client/formatters/autoPep8Formatter'; -import { initialize, IS_TRAVIS, closeActiveWindows, initializeTest } from '../initialize'; +import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST, IS_TRAVIS } from '../initialize'; import { YapfFormatter } from '../../client/formatters/yapfFormatter'; import { execPythonFile } from '../../client/common/utils'; @@ -29,6 +29,8 @@ const autoPep8FileToAutoFormat = path.join(pythoFilesPath, 'autoPep8FileToAutoFo const yapfFileToFormat = path.join(pythoFilesPath, 'yapfFileToFormat.py'); const yapfFileToAutoFormat = path.join(pythoFilesPath, 'yapfFileToAutoFormat.py'); +const configUpdateTarget = IS_MULTI_ROOT_TEST ? vscode.ConfigurationTarget.WorkspaceFolder : vscode.ConfigurationTarget.Workspace; + let formattedYapf = ''; let formattedAutoPep8 = ''; @@ -46,17 +48,14 @@ suite('Formatting', () => { formattedAutoPep8 = formattedResults[1]; }).then(() => { }); }); - setup(async () => { - await initializeTest(); - updateSetting('formatting.formatOnSave', false, vscode.Uri.file(pythoFilesPath), vscode.ConfigurationTarget.Workspace) - }); + setup(() => initializeTest()); suiteTeardown(async () => { [autoPep8FileToFormat, autoPep8FileToAutoFormat, yapfFileToFormat, yapfFileToAutoFormat].forEach(file => { if (fs.existsSync(file)) { fs.unlinkSync(file); } }); - await updateSetting('formatting.formatOnSave', false, vscode.Uri.file(pythoFilesPath), vscode.ConfigurationTarget.Workspace) + await updateSetting('formatting.formatOnSave', false, vscode.Uri.file(pythoFilesPath), configUpdateTarget) await closeActiveWindows(); }); teardown(() => closeActiveWindows()); @@ -90,8 +89,8 @@ suite('Formatting', () => { }); async function testAutoFormatting(formatter: string, formattedContents: string, fileToFormat: string): Promise { - await updateSetting('formatting.formatOnSave', true, vscode.Uri.file(fileToFormat), vscode.ConfigurationTarget.Workspace); - await updateSetting('formatting.provider', formatter, vscode.Uri.file(fileToFormat), vscode.ConfigurationTarget.Workspace); + await updateSetting('formatting.formatOnSave', true, vscode.Uri.file(fileToFormat), configUpdateTarget); + await updateSetting('formatting.provider', formatter, vscode.Uri.file(fileToFormat), configUpdateTarget); const textDocument = await vscode.workspace.openTextDocument(fileToFormat); const editor = await vscode.window.showTextDocument(textDocument); assert(vscode.window.activeTextEditor, 'No active editor'); @@ -104,7 +103,7 @@ suite('Formatting', () => { resolve(); }, 5000); }); - const text = textDocument.getText(); + const text = textDocument.getText(); assert.equal(text === formattedContents, true, 'Formatted contents are not the same'); } test('AutoPep8 autoformat on save', done => { diff --git a/src/test/index.ts b/src/test/index.ts index defee3a510e4..10bd39fbd4bb 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -1,27 +1,22 @@ import { initializePython, IS_MULTI_ROOT_TEST } from './initialize'; -// -// PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING -// -// This file is providing the test runner to use when running extension tests. -// By default the test runner in use is Mocha based. -// -// You can provide your own test runner if you want to override it by exporting -// a function run(testRoot: string, clb: (error:Error) => void) that the extension -// host can call to run the tests. The test runner is expected to use console.log -// to report the results back to the caller. When the tests are finished, return -// a possible error to the callback or null if none. - const testRunner = require('vscode/lib/testrunner'); -const invert = IS_MULTI_ROOT_TEST ? undefined : 'invert'; -// You can directly control Mocha options by uncommenting the following lines -// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info -testRunner.configure({ - ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) - useColors: true, // colored output from test results +const singleWorkspaceTestConfig = { + ui: 'tdd', + useColors: true, timeout: 25000, grep: 'Multiroot', - invert -}); + invert: 'invert' +}; +const multiWorkspaceTestConfig = { + ui: 'tdd', + useColors: true, + timeout: 25000, + grep: 'Jupyter', + invert: 'invert' +}; +// You can directly control Mocha options by uncommenting the following lines. +// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info. +testRunner.configure(IS_MULTI_ROOT_TEST ? multiWorkspaceTestConfig : singleWorkspaceTestConfig); module.exports = testRunner; diff --git a/src/test/jupyter/jupyter.codeHelper.test.ts b/src/test/jupyter/jupyter.codeHelper.test.ts index 60643f2d6b2b..ecf40d851e6f 100644 --- a/src/test/jupyter/jupyter.codeHelper.test.ts +++ b/src/test/jupyter/jupyter.codeHelper.test.ts @@ -1,95 +1,95 @@ -import * as assert from 'assert'; -import * as vscode from 'vscode'; -import * as path from 'path'; -import { initialize, closeActiveWindows, initializeTest } from './../initialize'; -import { CodeHelper } from '../../client/jupyter/common/codeHelper'; -import { JupyterCodeLensProvider } from '../../client/jupyter/editorIntegration/codeLensProvider'; +// import * as assert from 'assert'; +// import * as vscode from 'vscode'; +// import * as path from 'path'; +// import { initialize, closeActiveWindows, initializeTest } from './../initialize'; +// import { CodeHelper } from '../../client/jupyter/common/codeHelper'; +// import { JupyterCodeLensProvider } from '../../client/jupyter/editorIntegration/codeLensProvider'; -const FILE_WITH_CELLS = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'jupyter', 'cells.py'); +// const FILE_WITH_CELLS = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'jupyter', 'cells.py'); -suite('Jupyter Code Helper', () => { - suiteSetup(() => initialize()); - setup(() => initializeTest()); - teardown(() => closeActiveWindows()); - const codeLensProvider = new JupyterCodeLensProvider(); - const codeHelper = new CodeHelper(codeLensProvider); +// suite('Jupyter Code Helper', () => { +// suiteSetup(() => initialize()); +// setup(() => initializeTest()); +// teardown(() => closeActiveWindows()); +// const codeLensProvider = new JupyterCodeLensProvider(); +// const codeHelper = new CodeHelper(codeLensProvider); - test('Get Line (without any selection)', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(FILE_WITH_CELLS).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - editor.selection = new vscode.Selection(2, 0, 2, 0); - return codeHelper.getSelectedCode().then(code => { - assert.equal(code, textDocument.lineAt(2).text, 'Text is not the same'); - }); - }).then(done, done); - }); +// test('Get Line (without any selection)', done => { +// let textDocument: vscode.TextDocument; +// vscode.workspace.openTextDocument(FILE_WITH_CELLS).then(document => { +// textDocument = document; +// return vscode.window.showTextDocument(textDocument); +// }).then(editor => { +// assert(vscode.window.activeTextEditor, 'No active editor'); +// editor.selection = new vscode.Selection(2, 0, 2, 0); +// return codeHelper.getSelectedCode().then(code => { +// assert.equal(code, textDocument.lineAt(2).text, 'Text is not the same'); +// }); +// }).then(done, done); +// }); - test('Get Selected Text', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(FILE_WITH_CELLS).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - editor.selection = new vscode.Selection(2, 7, 2, 10); - return codeHelper.getSelectedCode().then(code => { - assert.equal(code, textDocument.getText(editor.selection), 'Text is not the same'); - }); - }).then(done, done); - }); +// test('Get Selected Text', done => { +// let textDocument: vscode.TextDocument; +// vscode.workspace.openTextDocument(FILE_WITH_CELLS).then(document => { +// textDocument = document; +// return vscode.window.showTextDocument(textDocument); +// }).then(editor => { +// assert(vscode.window.activeTextEditor, 'No active editor'); +// editor.selection = new vscode.Selection(2, 7, 2, 10); +// return codeHelper.getSelectedCode().then(code => { +// assert.equal(code, textDocument.getText(editor.selection), 'Text is not the same'); +// }); +// }).then(done, done); +// }); - test('Get Selected Line', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(FILE_WITH_CELLS).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - editor.selection = new vscode.Selection(2, 0, 2, 31); - return codeHelper.getSelectedCode().then(code => { - assert.equal(code, textDocument.getText(editor.selection), 'Text is not the same'); - assert.equal(code, textDocument.lineAt(2).text, 'Text is not the same as the line contents'); - }); - }).then(done, done); - }); +// test('Get Selected Line', done => { +// let textDocument: vscode.TextDocument; +// vscode.workspace.openTextDocument(FILE_WITH_CELLS).then(document => { +// textDocument = document; +// return vscode.window.showTextDocument(textDocument); +// }).then(editor => { +// assert(vscode.window.activeTextEditor, 'No active editor'); +// editor.selection = new vscode.Selection(2, 0, 2, 31); +// return codeHelper.getSelectedCode().then(code => { +// assert.equal(code, textDocument.getText(editor.selection), 'Text is not the same'); +// assert.equal(code, textDocument.lineAt(2).text, 'Text is not the same as the line contents'); +// }); +// }).then(done, done); +// }); - test('Get Selected Text (multi-line)', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(FILE_WITH_CELLS).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - editor.selection = new vscode.Selection(2, 0, 4, 18); - return codeHelper.getSelectedCode().then(code => { - assert.equal(code, textDocument.getText(editor.selection), 'Text is not the same'); - }); - }).then(done, done); - }); +// test('Get Selected Text (multi-line)', done => { +// let textDocument: vscode.TextDocument; +// vscode.workspace.openTextDocument(FILE_WITH_CELLS).then(document => { +// textDocument = document; +// return vscode.window.showTextDocument(textDocument); +// }).then(editor => { +// assert(vscode.window.activeTextEditor, 'No active editor'); +// editor.selection = new vscode.Selection(2, 0, 4, 18); +// return codeHelper.getSelectedCode().then(code => { +// assert.equal(code, textDocument.getText(editor.selection), 'Text is not the same'); +// }); +// }).then(done, done); +// }); - test('Get Code Block (for in)', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(FILE_WITH_CELLS).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - editor.selection = new vscode.Selection(16, 0, 16, 0); - return codeHelper.getSelectedCode().then(code => { - const end = textDocument.lineAt(18).range.end; - const start = editor.selection.start; - assert.equal(code, textDocument.getText(new vscode.Range(start, end)), 'Text is not the same'); - }); - }).then(done, done); - }); +// test('Get Code Block (for in)', done => { +// let textDocument: vscode.TextDocument; +// vscode.workspace.openTextDocument(FILE_WITH_CELLS).then(document => { +// textDocument = document; +// return vscode.window.showTextDocument(textDocument); +// }).then(editor => { +// assert(vscode.window.activeTextEditor, 'No active editor'); +// editor.selection = new vscode.Selection(16, 0, 16, 0); +// return codeHelper.getSelectedCode().then(code => { +// const end = textDocument.lineAt(18).range.end; +// const start = editor.selection.start; +// assert.equal(code, textDocument.getText(new vscode.Range(start, end)), 'Text is not the same'); +// }); +// }).then(done, done); +// }); - // Todo: - // Add if blocks - // Add parameters being broken into multiple lines - // e.g. x = doSomething(1,2, - // 4,5) -}); +// // Todo: +// // Add if blocks +// // Add parameters being broken into multiple lines +// // e.g. x = doSomething(1,2, +// // 4,5) +// }); diff --git a/src/test/workspaceSymbols/standard.test.ts b/src/test/workspaceSymbols/standard.test.ts index 3251ad85ccdb..b2859b0fc428 100644 --- a/src/test/workspaceSymbols/standard.test.ts +++ b/src/test/workspaceSymbols/standard.test.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; import * as path from 'path'; import { CancellationTokenSource, ConfigurationTarget, Uri } from 'vscode'; -import { closeActiveWindows, initialize, initializeTest } from '../initialize'; +import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST } from '../initialize'; import { Generator } from '../../client/workspaceSymbols/generator'; import { MockOutputChannel } from '../mockClasses'; import { WorkspaceSymbolProvider } from '../../client/workspaceSymbols/provider'; @@ -9,6 +9,7 @@ import { updateSetting } from './../common'; import { PythonSettings } from '../../client/common/configSettings'; const workspaceUri = Uri.file(path.join(__dirname, '..', '..', '..', 'src', 'test')); +const configUpdateTarget = IS_MULTI_ROOT_TEST ? ConfigurationTarget.WorkspaceFolder : ConfigurationTarget.Workspace; suite('Workspace Symbols', () => { suiteSetup(() => initialize()); @@ -16,12 +17,12 @@ suite('Workspace Symbols', () => { setup(() => initializeTest()); teardown(async () => { await closeActiveWindows(); - await updateSetting('workspaceSymbols.enabled', false, workspaceUri, ConfigurationTarget.Workspace); + await updateSetting('workspaceSymbols.enabled', false, workspaceUri, configUpdateTarget); }); test(`symbols should be returned when enabeld and vice versa`, async () => { const outputChannel = new MockOutputChannel('Output'); - await updateSetting('workspaceSymbols.enabled', false, workspaceUri, ConfigurationTarget.Workspace); + await updateSetting('workspaceSymbols.enabled', false, workspaceUri, configUpdateTarget); // The workspace will be in the output test folder // So lets modify the settings so it sees the source test folder @@ -34,7 +35,7 @@ suite('Workspace Symbols', () => { assert.equal(symbols.length, 0, 'Symbols returned even when workspace symbols are turned off'); generator.dispose(); - await updateSetting('workspaceSymbols.enabled', true, workspaceUri, ConfigurationTarget.Workspace); + await updateSetting('workspaceSymbols.enabled', true, workspaceUri, configUpdateTarget); // The workspace will be in the output test folder // So lets modify the settings so it sees the source test folder @@ -49,7 +50,7 @@ suite('Workspace Symbols', () => { test(`symbols should be filtered correctly`, async () => { const outputChannel = new MockOutputChannel('Output'); - await updateSetting('workspaceSymbols.enabled', true, workspaceUri, ConfigurationTarget.Workspace); + await updateSetting('workspaceSymbols.enabled', true, workspaceUri, configUpdateTarget); // The workspace will be in the output test folder // So lets modify the settings so it sees the source test folder diff --git a/src/testMultiRootWkspc/multi.code-workspace b/src/testMultiRootWkspc/multi.code-workspace index 891cdf0bd28d..fbc76e89a552 100644 --- a/src/testMultiRootWkspc/multi.code-workspace +++ b/src/testMultiRootWkspc/multi.code-workspace @@ -20,14 +20,20 @@ } ], "settings": { - "python.linting.flake8Enabled": false, + "python.linting.flake8Enabled": true, "python.linting.mypyEnabled": true, "python.linting.pydocstyleEnabled": true, "python.linting.pylamaEnabled": true, - "python.linting.pylintEnabled": false, + "python.linting.pylintEnabled": true, "python.linting.pep8Enabled": true, "python.linting.prospectorEnabled": true, - "python.workspaceSymbols.enabled": true, + "python.workspaceSymbols.enabled": false, + "python.formatting.formatOnSave": false, + "python.formatting.provider": "yapf", + "python.sortImports.args": [], + "python.linting.lintOnSave": false, + "python.linting.lintOnTextChange": false, + "python.linting.enabled": true, "python.pythonPath": "python" } } From a819f6921413715d2dddb5cf635e7ad7f0b8883c Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 16 Oct 2017 15:58:19 -0700 Subject: [PATCH 02/43] log errors --- src/test/linters/lint.test.ts | 54 ++++++++++++++++------------------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/src/test/linters/lint.test.ts b/src/test/linters/lint.test.ts index c5d06918b801..3e64d826bb87 100644 --- a/src/test/linters/lint.test.ts +++ b/src/test/linters/lint.test.ts @@ -186,38 +186,32 @@ suite('Linting', () => { }); await Promise.all(promises); } - function testLinterMessages(linter: baseLinter.BaseLinter, outputChannel: MockOutputChannel, pythonFile: string, messagesToBeReceived: baseLinter.ILintMessage[]): Thenable { + async function testLinterMessages(linter: baseLinter.BaseLinter, outputChannel: MockOutputChannel, pythonFile: string, messagesToBeReceived: baseLinter.ILintMessage[]): Thenable { let cancelToken = new vscode.CancellationTokenSource(); - return disableAllButThisLinter(linter.product) - .then(() => vscode.workspace.openTextDocument(pythonFile)) - .then(document => vscode.window.showTextDocument(document)) - .then(editor => { - return linter.lint(editor.document, cancelToken.token); - }) - .then(messages => { - // Different versions of python return different errors, - if (messagesToBeReceived.length === 0) { - assert.equal(messages.length, 0, 'No errors in linter, Output - ' + outputChannel.output); - } - else { - if (outputChannel.output.indexOf('ENOENT') === -1) { - // Pylint for Python Version 2.7 could return 80 linter messages, where as in 3.5 it might only return 1 - // Looks like pylint stops linting as soon as it comes across any ERRORS - assert.notEqual(messages.length, 0, 'No errors in linter, Output - ' + outputChannel.output); - } - else { - assert.ok('Linter not installed', 'Linter not installed'); - } - } - // messagesToBeReceived.forEach(msg => { - // let similarMessages = messages.filter(m => m.code === msg.code && m.column === msg.column && - // m.line === msg.line && m.message === msg.message && m.severity === msg.severity); - // assert.equal(true, similarMessages.length > 0, 'Error not found, ' + JSON.stringify(msg) + '\n, Output - ' + outputChannel.output); - // }); - }, error => { - assert.fail(error, null, 'Linter error, Output - ' + outputChannel.output, ''); - }); + await disableAllButThisLinter(linter.product); + const document = await vscode.workspace.openTextDocument(pythonFile); + const editor = await vscode.window.showTextDocument(document); + const messages = await linter.lint(editor.document, cancelToken.token); + // Different versions of python return different errors, + if (messagesToBeReceived.length === 0) { + assert.equal(messages.length, 0, 'No errors in linter, Output - ' + outputChannel.output); + } + else { + if (outputChannel.output.indexOf('ENOENT') === -1) { + // Pylint for Python Version 2.7 could return 80 linter messages, where as in 3.5 it might only return 1 + // Looks like pylint stops linting as soon as it comes across any ERRORS + assert.notEqual(messages.length, 0, 'No errors in linter, Output - ' + outputChannel.output); + } + else { + assert.ok('Linter not installed', 'Linter not installed'); + } + } + // messagesToBeReceived.forEach(msg => { + // let similarMessages = messages.filter(m => m.code === msg.code && m.column === msg.column && + // m.line === msg.line && m.message === msg.message && m.severity === msg.severity); + // assert.equal(true, similarMessages.length > 0, 'Error not found, ' + JSON.stringify(msg) + '\n, Output - ' + outputChannel.output); + // }); } test('PyLint', done => { let ch = new MockOutputChannel('Lint'); From 3b48d844e46edcf23c7443fc781a8770cf98d83d Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Oct 2017 08:08:50 -0700 Subject: [PATCH 03/43] fix return type --- src/test/linters/lint.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/linters/lint.test.ts b/src/test/linters/lint.test.ts index 3e64d826bb87..738b83ecdff3 100644 --- a/src/test/linters/lint.test.ts +++ b/src/test/linters/lint.test.ts @@ -186,7 +186,7 @@ suite('Linting', () => { }); await Promise.all(promises); } - async function testLinterMessages(linter: baseLinter.BaseLinter, outputChannel: MockOutputChannel, pythonFile: string, messagesToBeReceived: baseLinter.ILintMessage[]): Thenable { + async function testLinterMessages(linter: baseLinter.BaseLinter, outputChannel: MockOutputChannel, pythonFile: string, messagesToBeReceived: baseLinter.ILintMessage[]): Promise { let cancelToken = new vscode.CancellationTokenSource(); await disableAllButThisLinter(linter.product); From b290a7e3199035454d15062bdab8eed2d7a7da17 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Oct 2017 08:31:25 -0700 Subject: [PATCH 04/43] fix linter messages --- src/test/linters/lint.test.ts | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/src/test/linters/lint.test.ts b/src/test/linters/lint.test.ts index 738b83ecdff3..6347490323cd 100644 --- a/src/test/linters/lint.test.ts +++ b/src/test/linters/lint.test.ts @@ -114,6 +114,7 @@ let fiteredPydocstyleMessagseToBeReturned: baseLinter.ILintMessage[] = [ ]; +// tslint:disable-next-line:max-func-body-length suite('Linting', () => { const isPython3Deferred = createDeferred(); const isPython3 = isPython3Deferred.promise; @@ -187,31 +188,23 @@ suite('Linting', () => { await Promise.all(promises); } async function testLinterMessages(linter: baseLinter.BaseLinter, outputChannel: MockOutputChannel, pythonFile: string, messagesToBeReceived: baseLinter.ILintMessage[]): Promise { - - let cancelToken = new vscode.CancellationTokenSource(); + const cancelToken = new vscode.CancellationTokenSource(); await disableAllButThisLinter(linter.product); const document = await vscode.workspace.openTextDocument(pythonFile); const editor = await vscode.window.showTextDocument(document); const messages = await linter.lint(editor.document, cancelToken.token); - // Different versions of python return different errors, if (messagesToBeReceived.length === 0) { - assert.equal(messages.length, 0, 'No errors in linter, Output - ' + outputChannel.output); + assert.equal(messages.length, 0, `No errors in linter, Output - ${outputChannel.output}`); + return; + } + if (outputChannel.output.indexOf('ENOENT') === -1) { + // Pylint for Python Version 2.7 could return 80 linter messages, where as in 3.5 it might only return 1. + // Looks like pylint stops linting as soon as it comes across any ERRORS. + assert.notEqual(messages.length, 0, `No errors in linter, Output - ${outputChannel.output}`); } else { - if (outputChannel.output.indexOf('ENOENT') === -1) { - // Pylint for Python Version 2.7 could return 80 linter messages, where as in 3.5 it might only return 1 - // Looks like pylint stops linting as soon as it comes across any ERRORS - assert.notEqual(messages.length, 0, 'No errors in linter, Output - ' + outputChannel.output); - } - else { - assert.ok('Linter not installed', 'Linter not installed'); - } + assert.ok('Linter not installed', 'Linter not installed'); } - // messagesToBeReceived.forEach(msg => { - // let similarMessages = messages.filter(m => m.code === msg.code && m.column === msg.column && - // m.line === msg.line && m.message === msg.message && m.severity === msg.severity); - // assert.equal(true, similarMessages.length > 0, 'Error not found, ' + JSON.stringify(msg) + '\n, Output - ' + outputChannel.output); - // }); } test('PyLint', done => { let ch = new MockOutputChannel('Lint'); From 1e979b900593c688bcb4f9fa09bf8f6f88f6cd91 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Oct 2017 09:20:35 -0700 Subject: [PATCH 05/43] fix linter errors --- src/test/linters/lint.test.ts | 219 +++++++++++++++++----------------- 1 file changed, 110 insertions(+), 109 deletions(-) diff --git a/src/test/linters/lint.test.ts b/src/test/linters/lint.test.ts index 6347490323cd..4bf6609d90c6 100644 --- a/src/test/linters/lint.test.ts +++ b/src/test/linters/lint.test.ts @@ -1,20 +1,20 @@ import * as assert from 'assert'; +import * as fs from 'fs-extra'; +import * as path from 'path'; import * as vscode from 'vscode'; +import { PythonSettings } from '../../client/common/configSettings'; +import { EnumEx } from '../../client/common/enumUtils'; +import { createDeferred } from '../../client/common/helpers'; +import { Linters, Product, SettingToDisableProduct } from '../../client/common/installer'; +import { execPythonFile } from '../../client/common/utils'; import * as baseLinter from '../../client/linters/baseLinter'; -import * as pyLint from '../../client/linters/pylint'; -import * as pep8 from '../../client/linters/pep8Linter'; import * as flake8 from '../../client/linters/flake8'; +import * as pep8 from '../../client/linters/pep8Linter'; import * as prospector from '../../client/linters/prospector'; import * as pydocstyle from '../../client/linters/pydocstyle'; -import * as path from 'path'; -import * as fs from 'fs-extra'; -import { rootWorkspaceUri, updateSetting, PythonSettingKeys } from '../common'; -import { PythonSettings } from '../../client/common/configSettings'; -import { initialize, IS_TRAVIS, closeActiveWindows, IS_MULTI_ROOT_TEST, initializeTest } from '../initialize'; -import { execPythonFile } from '../../client/common/utils'; -import { createDeferred } from '../../client/common/helpers'; -import { Product, SettingToDisableProduct, Linters } from '../../client/common/installer'; -import { EnumEx } from '../../client/common/enumUtils'; +import * as pyLint from '../../client/linters/pylint'; +import { PythonSettingKeys, rootWorkspaceUri, updateSetting } from '../common'; +import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST, IS_TRAVIS } from '../initialize'; import { MockOutputChannel } from '../mockClasses'; const pythoFilesPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'linting'); @@ -25,7 +25,7 @@ const pylintConfigPath = path.join(pythoFilesPath, 'pylintconfig'); const fileToLint = path.join(pythoFilesPath, 'file.py'); let pylintFileToLintLines: string[] = []; -let pylintMessagesToBeReturned: baseLinter.ILintMessage[] = [ +const pylintMessagesToBeReturned: baseLinter.ILintMessage[] = [ { line: 24, column: 0, severity: baseLinter.LintMessageSeverity.Information, code: 'I0011', message: 'Locally disabling no-member (E1101)', provider: '', type: '' }, { line: 30, column: 0, severity: baseLinter.LintMessageSeverity.Information, code: 'I0011', message: 'Locally disabling no-member (E1101)', provider: '', type: '' }, { line: 34, column: 0, severity: baseLinter.LintMessageSeverity.Information, code: 'I0012', message: 'Locally enabling no-member (E1101)', provider: '', type: '' }, @@ -47,10 +47,7 @@ let pylintMessagesToBeReturned: baseLinter.ILintMessage[] = [ { line: 77, column: 14, severity: baseLinter.LintMessageSeverity.Error, code: 'E1101', message: 'Instance of \'Foo\' has no \'blip\' member', provider: '', type: '' }, { line: 83, column: 14, severity: baseLinter.LintMessageSeverity.Error, code: 'E1101', message: 'Instance of \'Foo\' has no \'blip\' member', provider: '', type: '' } ]; -let pyLint3MessagesToBeReturned: baseLinter.ILintMessage[] = [ - { line: 13, column: 0, severity: baseLinter.LintMessageSeverity.Error, code: 'E0001', message: 'Missing parentheses in call to \'print\'', provider: '', type: '' } -]; -let flake8MessagesToBeReturned: baseLinter.ILintMessage[] = [ +const flake8MessagesToBeReturned: baseLinter.ILintMessage[] = [ { line: 5, column: 1, severity: baseLinter.LintMessageSeverity.Error, code: 'E302', message: 'expected 2 blank lines, found 1', provider: '', type: '' }, { line: 19, column: 15, severity: baseLinter.LintMessageSeverity.Error, code: 'E127', message: 'continuation line over-indented for visual indent', provider: '', type: '' }, { line: 24, column: 23, severity: baseLinter.LintMessageSeverity.Error, code: 'E261', message: 'at least two spaces before inline comment', provider: '', type: '' }, @@ -59,7 +56,7 @@ let flake8MessagesToBeReturned: baseLinter.ILintMessage[] = [ { line: 80, column: 5, severity: baseLinter.LintMessageSeverity.Error, code: 'E303', message: 'too many blank lines (2)', provider: '', type: '' }, { line: 87, column: 24, severity: baseLinter.LintMessageSeverity.Warning, code: 'W292', message: 'no newline at end of file', provider: '', type: '' } ]; -let pep8MessagesToBeReturned: baseLinter.ILintMessage[] = [ +const pep8MessagesToBeReturned: baseLinter.ILintMessage[] = [ { line: 5, column: 1, severity: baseLinter.LintMessageSeverity.Error, code: 'E302', message: 'expected 2 blank lines, found 1', provider: '', type: '' }, { line: 19, column: 15, severity: baseLinter.LintMessageSeverity.Error, code: 'E127', message: 'continuation line over-indented for visual indent', provider: '', type: '' }, { line: 24, column: 23, severity: baseLinter.LintMessageSeverity.Error, code: 'E261', message: 'at least two spaces before inline comment', provider: '', type: '' }, @@ -68,30 +65,30 @@ let pep8MessagesToBeReturned: baseLinter.ILintMessage[] = [ { line: 80, column: 5, severity: baseLinter.LintMessageSeverity.Error, code: 'E303', message: 'too many blank lines (2)', provider: '', type: '' }, { line: 87, column: 24, severity: baseLinter.LintMessageSeverity.Warning, code: 'W292', message: 'no newline at end of file', provider: '', type: '' } ]; -let pydocstyleMessagseToBeReturned: baseLinter.ILintMessage[] = [ - { 'code': 'D400', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First line should end with a period (not \'e\')', 'column': 0, 'line': 1, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D400', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First line should end with a period (not \'t\')', 'column': 0, 'line': 5, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D102', severity: baseLinter.LintMessageSeverity.Information, 'message': 'Missing docstring in public method', 'column': 4, 'line': 8, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D401', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First line should be in imperative mood (\'thi\', not \'this\')', 'column': 4, 'line': 11, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D403', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First word of the first line should be properly capitalized (\'This\', not \'this\')', 'column': 4, 'line': 11, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D400', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First line should end with a period (not \'e\')', 'column': 4, 'line': 11, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D403', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First word of the first line should be properly capitalized (\'And\', not \'and\')', 'column': 4, 'line': 15, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D400', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First line should end with a period (not \'t\')', 'column': 4, 'line': 15, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D403', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First word of the first line should be properly capitalized (\'Test\', not \'test\')', 'column': 4, 'line': 21, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D400', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First line should end with a period (not \'g\')', 'column': 4, 'line': 21, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D403', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First word of the first line should be properly capitalized (\'Test\', not \'test\')', 'column': 4, 'line': 28, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D400', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First line should end with a period (not \'g\')', 'column': 4, 'line': 28, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D403', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First word of the first line should be properly capitalized (\'Test\', not \'test\')', 'column': 4, 'line': 38, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D400', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First line should end with a period (not \'g\')', 'column': 4, 'line': 38, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D403', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First word of the first line should be properly capitalized (\'Test\', not \'test\')', 'column': 4, 'line': 53, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D400', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First line should end with a period (not \'g\')', 'column': 4, 'line': 53, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D403', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First word of the first line should be properly capitalized (\'Test\', not \'test\')', 'column': 4, 'line': 68, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D400', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First line should end with a period (not \'g\')', 'column': 4, 'line': 68, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D403', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First word of the first line should be properly capitalized (\'Test\', not \'test\')', 'column': 4, 'line': 80, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D400', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First line should end with a period (not \'g\')', 'column': 4, 'line': 80, 'type': '', 'provider': 'pydocstyle' } +const pydocstyleMessagseToBeReturned: baseLinter.ILintMessage[] = [ + { code: 'D400', severity: baseLinter.LintMessageSeverity.Information, message: 'First line should end with a period (not \'e\')', column: 0, line: 1, type: '', provider: 'pydocstyle' }, + { code: 'D400', severity: baseLinter.LintMessageSeverity.Information, message: 'First line should end with a period (not \'t\')', column: 0, line: 5, type: '', provider: 'pydocstyle' }, + { code: 'D102', severity: baseLinter.LintMessageSeverity.Information, message: 'Missing docstring in public method', column: 4, line: 8, type: '', provider: 'pydocstyle' }, + { code: 'D401', severity: baseLinter.LintMessageSeverity.Information, message: 'First line should be in imperative mood (\'thi\', not \'this\')', column: 4, line: 11, type: '', provider: 'pydocstyle' }, + { code: 'D403', severity: baseLinter.LintMessageSeverity.Information, message: 'First word of the first line should be properly capitalized (\'This\', not \'this\')', column: 4, line: 11, type: '', provider: 'pydocstyle' }, + { code: 'D400', severity: baseLinter.LintMessageSeverity.Information, message: 'First line should end with a period (not \'e\')', column: 4, line: 11, type: '', provider: 'pydocstyle' }, + { code: 'D403', severity: baseLinter.LintMessageSeverity.Information, message: 'First word of the first line should be properly capitalized (\'And\', not \'and\')', column: 4, line: 15, type: '', provider: 'pydocstyle' }, + { code: 'D400', severity: baseLinter.LintMessageSeverity.Information, message: 'First line should end with a period (not \'t\')', column: 4, line: 15, type: '', provider: 'pydocstyle' }, + { code: 'D403', severity: baseLinter.LintMessageSeverity.Information, message: 'First word of the first line should be properly capitalized (\'Test\', not \'test\')', column: 4, line: 21, type: '', provider: 'pydocstyle' }, + { code: 'D400', severity: baseLinter.LintMessageSeverity.Information, message: 'First line should end with a period (not \'g\')', column: 4, line: 21, type: '', provider: 'pydocstyle' }, + { code: 'D403', severity: baseLinter.LintMessageSeverity.Information, message: 'First word of the first line should be properly capitalized (\'Test\', not \'test\')', column: 4, line: 28, type: '', provider: 'pydocstyle' }, + { code: 'D400', severity: baseLinter.LintMessageSeverity.Information, message: 'First line should end with a period (not \'g\')', column: 4, line: 28, type: '', provider: 'pydocstyle' }, + { code: 'D403', severity: baseLinter.LintMessageSeverity.Information, message: 'First word of the first line should be properly capitalized (\'Test\', not \'test\')', column: 4, line: 38, type: '', provider: 'pydocstyle' }, + { code: 'D400', severity: baseLinter.LintMessageSeverity.Information, message: 'First line should end with a period (not \'g\')', column: 4, line: 38, type: '', provider: 'pydocstyle' }, + { code: 'D403', severity: baseLinter.LintMessageSeverity.Information, message: 'First word of the first line should be properly capitalized (\'Test\', not \'test\')', column: 4, line: 53, type: '', provider: 'pydocstyle' }, + { code: 'D400', severity: baseLinter.LintMessageSeverity.Information, message: 'First line should end with a period (not \'g\')', column: 4, line: 53, type: '', provider: 'pydocstyle' }, + { code: 'D403', severity: baseLinter.LintMessageSeverity.Information, message: 'First word of the first line should be properly capitalized (\'Test\', not \'test\')', column: 4, line: 68, type: '', provider: 'pydocstyle' }, + { code: 'D400', severity: baseLinter.LintMessageSeverity.Information, message: 'First line should end with a period (not \'g\')', column: 4, line: 68, type: '', provider: 'pydocstyle' }, + { code: 'D403', severity: baseLinter.LintMessageSeverity.Information, message: 'First word of the first line should be properly capitalized (\'Test\', not \'test\')', column: 4, line: 80, type: '', provider: 'pydocstyle' }, + { code: 'D400', severity: baseLinter.LintMessageSeverity.Information, message: 'First line should end with a period (not \'g\')', column: 4, line: 80, type: '', provider: 'pydocstyle' } ]; -let filteredPylintMessagesToBeReturned: baseLinter.ILintMessage[] = [ +const filteredPylintMessagesToBeReturned: baseLinter.ILintMessage[] = [ { line: 26, column: 14, severity: baseLinter.LintMessageSeverity.Error, code: 'E1101', message: 'Instance of \'Foo\' has no \'blop\' member', provider: '', type: '' }, { line: 36, column: 14, severity: baseLinter.LintMessageSeverity.Error, code: 'E1101', message: 'Instance of \'Foo\' has no \'blip\' member', provider: '', type: '' }, { line: 46, column: 18, severity: baseLinter.LintMessageSeverity.Error, code: 'E1101', message: 'Instance of \'Foo\' has no \'blip\' member', provider: '', type: '' }, @@ -101,19 +98,18 @@ let filteredPylintMessagesToBeReturned: baseLinter.ILintMessage[] = [ { line: 77, column: 14, severity: baseLinter.LintMessageSeverity.Error, code: 'E1101', message: 'Instance of \'Foo\' has no \'blip\' member', provider: '', type: '' }, { line: 83, column: 14, severity: baseLinter.LintMessageSeverity.Error, code: 'E1101', message: 'Instance of \'Foo\' has no \'blip\' member', provider: '', type: '' } ]; -let filteredPylint3MessagesToBeReturned: baseLinter.ILintMessage[] = [ +const filteredPylint3MessagesToBeReturned: baseLinter.ILintMessage[] = [ ]; -let filteredFlake8MessagesToBeReturned: baseLinter.ILintMessage[] = [ +const filteredFlake8MessagesToBeReturned: baseLinter.ILintMessage[] = [ { line: 87, column: 24, severity: baseLinter.LintMessageSeverity.Warning, code: 'W292', message: 'no newline at end of file', provider: '', type: '' } ]; -let filteredPep88MessagesToBeReturned: baseLinter.ILintMessage[] = [ +const filteredPep88MessagesToBeReturned: baseLinter.ILintMessage[] = [ { line: 87, column: 24, severity: baseLinter.LintMessageSeverity.Warning, code: 'W292', message: 'no newline at end of file', provider: '', type: '' } ]; -let fiteredPydocstyleMessagseToBeReturned: baseLinter.ILintMessage[] = [ - { 'code': 'D102', severity: baseLinter.LintMessageSeverity.Information, 'message': 'Missing docstring in public method', 'column': 4, 'line': 8, 'type': '', 'provider': 'pydocstyle' } +const fiteredPydocstyleMessagseToBeReturned: baseLinter.ILintMessage[] = [ + { code: 'D102', severity: baseLinter.LintMessageSeverity.Information, message: 'Missing docstring in public method', column: 4, line: 8, type: '', provider: 'pydocstyle' } ]; - // tslint:disable-next-line:max-func-body-length suite('Linting', () => { const isPython3Deferred = createDeferred(); @@ -128,7 +124,7 @@ suite('Linting', () => { await initializeTest(); await resetSettings(); }); - suiteTeardown(() => closeActiveWindows()); + suiteTeardown(closeActiveWindows); teardown(async () => { await closeActiveWindows(); await resetSettings(); @@ -144,10 +140,12 @@ suite('Linting', () => { await updateSetting('linting.pydocstyleEnabled', true, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); } async function testEnablingDisablingOfLinter(linter: baseLinter.BaseLinter, setting: PythonSettingKeys) { - updateSetting(setting, true, rootWorkspaceUri, IS_MULTI_ROOT_TEST ? vscode.ConfigurationTarget.Workspace : vscode.ConfigurationTarget.WorkspaceFolder) - let cancelToken = new vscode.CancellationTokenSource(); - disableAllButThisLinter(linter.product); + await updateSetting(setting, true, rootWorkspaceUri, IS_MULTI_ROOT_TEST ? vscode.ConfigurationTarget.Workspace : vscode.ConfigurationTarget.WorkspaceFolder); + const cancelToken = new vscode.CancellationTokenSource(); + await disableAllButThisLinter(linter.product); + // tslint:disable-next-line:await-promise const document = await vscode.workspace.openTextDocument(fileToLint); + // tslint:disable-next-line:await-promise const editor = await vscode.window.showTextDocument(document); try { const messages = await linter.lint(editor.document, cancelToken.token); @@ -156,25 +154,25 @@ suite('Linting', () => { assert.fail(error, null, 'Linter error'); } } - test('Enable and Disable Pylint', () => { - let ch = new MockOutputChannel('Lint'); - testEnablingDisablingOfLinter(new pyLint.Linter(ch), 'linting.pylintEnabled'); + test('Enable and Disable Pylint', async () => { + const ch = new MockOutputChannel('Lint'); + await testEnablingDisablingOfLinter(new pyLint.Linter(ch), 'linting.pylintEnabled'); }); - test('Enable and Disable Pep8', () => { - let ch = new MockOutputChannel('Lint'); - testEnablingDisablingOfLinter(new pep8.Linter(ch), 'linting.pep8Enabled'); + test('Enable and Disable Pep8', async () => { + const ch = new MockOutputChannel('Lint'); + await testEnablingDisablingOfLinter(new pep8.Linter(ch), 'linting.pep8Enabled'); }); - test('Enable and Disable Flake8', () => { - let ch = new MockOutputChannel('Lint'); - testEnablingDisablingOfLinter(new flake8.Linter(ch), 'linting.flake8Enabled'); + test('Enable and Disable Flake8', async () => { + const ch = new MockOutputChannel('Lint'); + await testEnablingDisablingOfLinter(new flake8.Linter(ch), 'linting.flake8Enabled'); }); - test('Enable and Disable Prospector', () => { - let ch = new MockOutputChannel('Lint'); - testEnablingDisablingOfLinter(new prospector.Linter(ch), 'linting.prospectorEnabled'); + test('Enable and Disable Prospector', async () => { + const ch = new MockOutputChannel('Lint'); + await testEnablingDisablingOfLinter(new prospector.Linter(ch), 'linting.prospectorEnabled'); }); - test('Enable and Disable Pydocstyle', () => { - let ch = new MockOutputChannel('Lint'); - testEnablingDisablingOfLinter(new pydocstyle.Linter(ch), 'linting.pydocstyleEnabled'); + test('Enable and Disable Pydocstyle', async () => { + const ch = new MockOutputChannel('Lint'); + await testEnablingDisablingOfLinter(new pydocstyle.Linter(ch), 'linting.pydocstyleEnabled'); }); async function disableAllButThisLinter(linterToEnable: Product) { @@ -182,80 +180,83 @@ suite('Linting', () => { if (Linters.indexOf(linter.value) === -1) { return; } - var setting = SettingToDisableProduct.get(linter.value); + const setting = SettingToDisableProduct.get(linter.value); + // tslint:disable-next-line:no-any prefer-type-cast return updateSetting(setting as any, true, rootWorkspaceUri, IS_MULTI_ROOT_TEST ? vscode.ConfigurationTarget.Workspace : vscode.ConfigurationTarget.WorkspaceFolder); }); await Promise.all(promises); } + // tslint:disable-next-line:no-any async function testLinterMessages(linter: baseLinter.BaseLinter, outputChannel: MockOutputChannel, pythonFile: string, messagesToBeReceived: baseLinter.ILintMessage[]): Promise { const cancelToken = new vscode.CancellationTokenSource(); await disableAllButThisLinter(linter.product); + // tslint:disable-next-line:await-promise const document = await vscode.workspace.openTextDocument(pythonFile); + // tslint:disable-next-line:await-promise const editor = await vscode.window.showTextDocument(document); const messages = await linter.lint(editor.document, cancelToken.token); if (messagesToBeReceived.length === 0) { assert.equal(messages.length, 0, `No errors in linter, Output - ${outputChannel.output}`); - return; - } - if (outputChannel.output.indexOf('ENOENT') === -1) { - // Pylint for Python Version 2.7 could return 80 linter messages, where as in 3.5 it might only return 1. - // Looks like pylint stops linting as soon as it comes across any ERRORS. - assert.notEqual(messages.length, 0, `No errors in linter, Output - ${outputChannel.output}`); } else { - assert.ok('Linter not installed', 'Linter not installed'); + if (outputChannel.output.indexOf('ENOENT') === -1) { + // Pylint for Python Version 2.7 could return 80 linter messages, where as in 3.5 it might only return 1. + // Looks like pylint stops linting as soon as it comes across any ERRORS. + assert.notEqual(messages.length, 0, `No errors in linter, Output - ${outputChannel.output}`); + } + else { + assert.ok('Linter not installed', 'Linter not installed'); + } } } - test('PyLint', done => { - let ch = new MockOutputChannel('Lint'); - let linter = new pyLint.Linter(ch); - testLinterMessages(linter, ch, fileToLint, pylintMessagesToBeReturned).then(done, done); + test('PyLint', async () => { + const ch = new MockOutputChannel('Lint'); + const linter = new pyLint.Linter(ch); + await testLinterMessages(linter, ch, fileToLint, pylintMessagesToBeReturned); }); - test('Flake8', done => { - let ch = new MockOutputChannel('Lint'); - let linter = new flake8.Linter(ch); - testLinterMessages(linter, ch, fileToLint, flake8MessagesToBeReturned).then(done, done); + test('Flake8', async () => { + const ch = new MockOutputChannel('Lint'); + const linter = new flake8.Linter(ch); + await testLinterMessages(linter, ch, fileToLint, flake8MessagesToBeReturned); }); - test('Pep8', done => { - let ch = new MockOutputChannel('Lint'); - let linter = new pep8.Linter(ch); - testLinterMessages(linter, ch, fileToLint, pep8MessagesToBeReturned).then(done, done); + test('Pep8', async () => { + const ch = new MockOutputChannel('Lint'); + const linter = new pep8.Linter(ch); + await testLinterMessages(linter, ch, fileToLint, pep8MessagesToBeReturned); + }); + test('Pydocstyle', async () => { + const ch = new MockOutputChannel('Lint'); + const linter = new pydocstyle.Linter(ch); + await testLinterMessages(linter, ch, fileToLint, pydocstyleMessagseToBeReturned); }); - if (!isPython3) { - test('Pydocstyle', done => { - let ch = new MockOutputChannel('Lint'); - let linter = new pydocstyle.Linter(ch); - testLinterMessages(linter, ch, fileToLint, pydocstyleMessagseToBeReturned).then(done, done); - }); - } - // Version dependenant, will be enabled once we have fixed this - // TODO: Check version of python running and accordingly change the values if (!IS_TRAVIS) { + // tslint:disable-next-line:no-floating-promises isPython3.then(value => { const messagesToBeReturned = value ? filteredPylint3MessagesToBeReturned : filteredPylintMessagesToBeReturned; - test('PyLint with config in root', done => { - let ch = new MockOutputChannel('Lint'); - let linter = new pyLint.Linter(ch); - testLinterMessages(linter, ch, path.join(pylintConfigPath, 'file.py'), messagesToBeReturned).then(done, done); + test('PyLint with config in root', async () => { + const ch = new MockOutputChannel('Lint'); + const linter = new pyLint.Linter(ch); + await testLinterMessages(linter, ch, path.join(pylintConfigPath, 'file.py'), messagesToBeReturned); }); }); } - test('Flake8 with config in root', done => { - let ch = new MockOutputChannel('Lint'); - let linter = new flake8.Linter(ch); - testLinterMessages(linter, ch, path.join(flake8ConfigPath, 'file.py'), filteredFlake8MessagesToBeReturned).then(done, done); + test('Flake8 with config in root', async () => { + const ch = new MockOutputChannel('Lint'); + const linter = new flake8.Linter(ch); + await testLinterMessages(linter, ch, path.join(flake8ConfigPath, 'file.py'), filteredFlake8MessagesToBeReturned); }); - test('Pep8 with config in root', done => { - let ch = new MockOutputChannel('Lint'); - let linter = new pep8.Linter(ch); - testLinterMessages(linter, ch, path.join(pep8ConfigPath, 'file.py'), filteredPep88MessagesToBeReturned).then(done, done); + test('Pep8 with config in root', async () => { + const ch = new MockOutputChannel('Lint'); + const linter = new pep8.Linter(ch); + await testLinterMessages(linter, ch, path.join(pep8ConfigPath, 'file.py'), filteredPep88MessagesToBeReturned); }); + // tslint:disable-next-line:no-floating-promises isPython3.then(value => { const messagesToBeReturned = value ? [] : fiteredPydocstyleMessagseToBeReturned; - test('Pydocstyle with config in root', done => { - let ch = new MockOutputChannel('Lint'); - let linter = new pydocstyle.Linter(ch); - testLinterMessages(linter, ch, path.join(pydocstyleConfigPath27, 'file.py'), messagesToBeReturned).then(done, done); + test('Pydocstyle with config in root', async () => { + const ch = new MockOutputChannel('Lint'); + const linter = new pydocstyle.Linter(ch); + await testLinterMessages(linter, ch, path.join(pydocstyleConfigPath27, 'file.py'), messagesToBeReturned); }); }); }); From 0959155701f222f41d3fe47cdbaa380c43acea15 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Oct 2017 09:21:07 -0700 Subject: [PATCH 06/43] changes to ensure code is formatted correctly --- .editorconfig | 1 + gulpfile.js | 6 ++++-- tsfmt.json | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.editorconfig b/.editorconfig index 10b09a5924b6..4f92a467d0ef 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,6 +8,7 @@ root = true indent_style = space indent_size = 4 trim_trailing_whitespace = true +insert_final_newline = true # The indent size used in the `package.json` file cannot be changed # https://github.com/npm/npm/pull/3180#issuecomment-16336516 diff --git a/gulpfile.js b/gulpfile.js index 1dfa803235a2..76f1b070713b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -118,8 +118,10 @@ const hygiene = exports.hygiene = (some, options) => { const formatting = es.map(function (file, cb) { tsfmt.processString(file.path, file.contents.toString('utf8'), { verify: true, - tsfmt: true, - editorconfig: true + tsconfig: true, + tslint: true, + editorconfig: true, + tsfmt: true // verbose: true }).then(result => { if (result.error) { diff --git a/tsfmt.json b/tsfmt.json index 088362e2043d..fffcf07c1998 100644 --- a/tsfmt.json +++ b/tsfmt.json @@ -1,6 +1,7 @@ { "tabSize": 4, "indentSize": 4, + "newLineCharacter": "\n", "convertTabsToSpaces": false, "insertSpaceAfterCommaDelimiter": true, "insertSpaceAfterSemicolonInForStatements": true, From 5ad6d1fff694dbd730999c1a1f2d950bf185de24 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Oct 2017 09:27:15 -0700 Subject: [PATCH 07/43] fixed comments --- gulpfile.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 76f1b070713b..e18f115ba3a8 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -80,7 +80,7 @@ function reportFailures(failures) { const line = position.lineAndCharacter ? position.lineAndCharacter.line : position.line; const character = position.lineAndCharacter ? position.lineAndCharacter.character : position.character; - // Output in format similar to tslint for the linter to pickup + // Output in format similar to tslint for the linter to pickup. console.error(`ERROR: (${failure.ruleName}) ${relative(__dirname, name)}[${line + 1}, ${character + 1}]: ${failure.failure}`); }); } @@ -103,9 +103,9 @@ const hygiene = exports.hygiene = (some, options) => { .split(/\r\n|\r|\n/) .forEach((line, i) => { if (/^\s*$/.test(line)) { - // empty or whitespace lines are OK + // empty or whitespace lines are OK. } else if (/^(\s\s\s\s)+.*/.test(line)) { - // good indent + // good indent. } else if (/^[\t]+.*/.test(line)) { console.error(file.relative + '(' + (i + 1) + ',1): Bad whitespace indentation'); errorCount++; @@ -122,7 +122,6 @@ const hygiene = exports.hygiene = (some, options) => { tslint: true, editorconfig: true, tsfmt: true - // verbose: true }).then(result => { if (result.error) { console.error(result.message); @@ -178,7 +177,7 @@ const hygiene = exports.hygiene = (some, options) => { console.error(error.message); }, finish: function () { - // forget the summary + // forget the summary. } }; } @@ -215,7 +214,7 @@ const hygiene = exports.hygiene = (some, options) => { gulp.task('hygiene', () => hygiene()); -// this allows us to run hygiene as a git pre-commit hook +// this allows us to run hygiene as a git pre-commit hook. if (require.main === module) { const cp = require('child_process'); From 26241f20809fd00281d3d394d26e570288649d13 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Oct 2017 09:28:59 -0700 Subject: [PATCH 08/43] delete unwanted file --- src/test/linters/base.test.ts | 68 ----------------------------------- 1 file changed, 68 deletions(-) delete mode 100644 src/test/linters/base.test.ts diff --git a/src/test/linters/base.test.ts b/src/test/linters/base.test.ts deleted file mode 100644 index e6efa35b7add..000000000000 --- a/src/test/linters/base.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -// // -// // Note: This example test is leveraging the Mocha test framework. -// // Please refer to their documentation on https://mochajs.org/ for help. -// // Place this right on top -// import { initialize, IS_TRAVIS, PYTHON_PATH, closeActiveWindows, setPythonExecutable } from '../initialize'; -// // The module \'assert\' provides assertion methods from node -// import * as assert from 'assert'; - -// // You can import and use all API from the \'vscode\' module -// // as well as import your extension to test it -// import { EnumEx } from '../../client/common/enumUtils'; -// import { LinterFactor } from '../../client/linters/main'; -// import { SettingToDisableProduct, Product } from '../../client/common/installer'; -// import * as baseLinter from '../../client/linters/baseLinter'; -// import * as path from 'path'; -// import * as settings from '../../client/common/configSettings'; -// import { MockOutputChannel } from '../mockClasses'; -// import { Disposable } from 'vscode'; - -// const pythonSettings = settings.PythonSettings.getInstance(); -// const pythoFilesPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'linting'); -// let disposable: Disposable; -// suite('Linting', () => { -// suiteSetup(() => { -// disposable = setPythonExecutable(pythonSettings); -// }); -// setup(() => { -// pythonSettings.linting.enabled = true; -// pythonSettings.linting.pylintEnabled = true; -// pythonSettings.linting.flake8Enabled = true; -// pythonSettings.linting.pep8Enabled = true; -// pythonSettings.linting.prospectorEnabled = true; -// pythonSettings.linting.pydocstyleEnabled = true; -// pythonSettings.linting.mypyEnabled = true; -// pythonSettings.linting.pylamaEnabled = true; -// }); -// suiteTeardown(done => { -// if (disposable) { disposable.dispose(); } -// closeActiveWindows().then(() => done(), () => done()); -// }); -// teardown(done => { -// closeActiveWindows().then(() => done(), () => done()); -// }); - -// function testEnablingDisablingOfLinter(linter: baseLinter.BaseLinter, propertyName: string) { -// pythonSettings.linting[propertyName] = true; -// assert.equal(true, linter.isEnabled()); - -// pythonSettings.linting[propertyName] = false; -// assert.equal(false, linter.isEnabled()); -// } -// EnumEx.getNamesAndValues(Product).forEach(product => { -// if (product.value === Product.autopep8 || -// product.value === Product.ctags || -// product.value === Product.pytest || -// product.value === Product.unittest || -// product.value === Product.yapf || -// product.value === Product.nosetest) { -// return; -// } -// test(`Enable and Disable ${product.name}`, () => { -// let ch = new MockOutputChannel('Lint'); -// const settingPath = SettingToDisableProduct.get(product.value); -// const settingName = path.extname(settingPath).substring(1); -// testEnablingDisablingOfLinter(LinterFactor.createLinter(product.value, ch, pythoFilesPath), settingName); -// }); -// }); -// }); \ No newline at end of file From 6f3aba4199c739f81bbcc2df4a06e77b5211d21a Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Oct 2017 09:34:27 -0700 Subject: [PATCH 09/43] hide unwanted folders --- .vscode/settings.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 3210d5f385f9..1884ba9a0255 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,7 +5,9 @@ "**/*.pyc": true, "**/__pycache__": true, "node_modules": true, - ".vscode-test":true + ".vscode-test": true, + "**/.mypy_cache/**": true, + "**/.ropeproject/**": true }, "search.exclude": { "out": true // set this to false to include "out" folder in search results From 35a9cbe1f16c1ffac132baa689a05aa005419fd5 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Oct 2017 10:38:29 -0700 Subject: [PATCH 10/43] fixes to linters to run on multiroot setup --- src/test/.vscode/settings.json | 14 +-- src/test/common.ts | 19 ++-- src/test/linters/lint.test.ts | 100 ++++++++++++-------- src/test/pythonFiles/linting/file.py | 44 ++++----- src/testMultiRootWkspc/multi.code-workspace | 14 +-- tslint.json | 9 +- 6 files changed, 116 insertions(+), 84 deletions(-) diff --git a/src/test/.vscode/settings.json b/src/test/.vscode/settings.json index c7ae170c50cd..1d54a0da6b81 100644 --- a/src/test/.vscode/settings.json +++ b/src/test/.vscode/settings.json @@ -1,6 +1,6 @@ { - "python.linting.pylintEnabled": true, - "python.linting.flake8Enabled": true, + "python.linting.pylintEnabled": false, + "python.linting.flake8Enabled": false, "python.workspaceSymbols.enabled": false, "python.unitTest.nosetestArgs": [], "python.unitTest.pyTestArgs": [], @@ -14,10 +14,10 @@ "python.linting.lintOnSave": false, "python.linting.lintOnTextChange": false, "python.linting.enabled": true, - "python.linting.pep8Enabled": true, - "python.linting.prospectorEnabled": true, - "python.linting.pydocstyleEnabled": true, - "python.linting.pylamaEnabled": true, - "python.linting.mypyEnabled": true, + "python.linting.pep8Enabled": false, + "python.linting.prospectorEnabled": false, + "python.linting.pydocstyleEnabled": false, + "python.linting.pylamaEnabled": false, + "python.linting.mypyEnabled": false, "python.formatting.provider": "yapf" } \ No newline at end of file diff --git a/src/test/common.ts b/src/test/common.ts index cbab3da30f19..2013b9487c59 100644 --- a/src/test/common.ts +++ b/src/test/common.ts @@ -8,21 +8,21 @@ export const rootWorkspaceUri = getWorkspaceRoot(); export type PythonSettingKeys = 'workspaceSymbols.enabled' | 'pythonPath' | 'linting.lintOnSave' | 'linting.lintOnTextChange' | 'linting.enabled' | 'linting.pylintEnabled' | - 'linting.flake8Enabled' | 'linting.pep8Enabled' | - 'linting.prospectorEnabled' | 'linting.pydocstyleEnabled' | + 'linting.flake8Enabled' | 'linting.pep8Enabled' | 'linting.pylamaEnabled' | + 'linting.prospectorEnabled' | 'linting.pydocstyleEnabled' | 'linting.mypyEnabled' | 'unitTest.nosetestArgs' | 'unitTest.pyTestArgs' | 'unitTest.unittestArgs' | 'formatting.formatOnSave' | 'formatting.provider' | 'sortImports.args'; - -export async function updateSetting(setting: PythonSettingKeys, value: any, resource: Uri, configTarget: ConfigurationTarget) { - let settings = workspace.getConfiguration('python', resource); +export async function updateSetting(setting: PythonSettingKeys, value: {}, resource: Uri, configTarget: ConfigurationTarget) { + const settings = workspace.getConfiguration('python', resource); const currentValue = settings.inspect(setting); - if ((configTarget === ConfigurationTarget.Global && currentValue && currentValue.globalValue === value) || - (configTarget === ConfigurationTarget.Workspace && currentValue && currentValue.workspaceValue === value) || - (configTarget === ConfigurationTarget.WorkspaceFolder && currentValue && currentValue.workspaceFolderValue === value)) { + if (currentValue !== undefined && ((configTarget === ConfigurationTarget.Global && currentValue.globalValue === value) || + (configTarget === ConfigurationTarget.Workspace && currentValue.workspaceValue === value) || + (configTarget === ConfigurationTarget.WorkspaceFolder && currentValue.workspaceFolderValue === value))) { PythonSettings.dispose(); return; } + // tslint:disable-next-line:await-promise await settings.update(setting, value, configTarget); PythonSettings.dispose(); } @@ -34,5 +34,6 @@ function getWorkspaceRoot() { if (workspace.workspaceFolders.length === 1) { return workspace.workspaceFolders[0].uri; } - return workspace.getWorkspaceFolder(Uri.file(fileInNonRootWorkspace)).uri; + const workspaceFolder = workspace.getWorkspaceFolder(Uri.file(fileInNonRootWorkspace)); + return workspaceFolder ? workspaceFolder.uri : workspace.workspaceFolders[0].uri; } diff --git a/src/test/linters/lint.test.ts b/src/test/linters/lint.test.ts index 4bf6609d90c6..d5b586c351cd 100644 --- a/src/test/linters/lint.test.ts +++ b/src/test/linters/lint.test.ts @@ -14,7 +14,7 @@ import * as prospector from '../../client/linters/prospector'; import * as pydocstyle from '../../client/linters/pydocstyle'; import * as pyLint from '../../client/linters/pylint'; import { PythonSettingKeys, rootWorkspaceUri, updateSetting } from '../common'; -import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST, IS_TRAVIS } from '../initialize'; +import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST } from '../initialize'; import { MockOutputChannel } from '../mockClasses'; const pythoFilesPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'linting'); @@ -130,49 +130,75 @@ suite('Linting', () => { await resetSettings(); }); async function resetSettings() { - await updateSetting('linting.lintOnSave', false, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); - await updateSetting('linting.lintOnTextChange', false, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); await updateSetting('linting.enabled', true, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); - await updateSetting('linting.pylintEnabled', true, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); - await updateSetting('linting.flake8Enabled', true, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); - await updateSetting('linting.pep8Enabled', true, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); - await updateSetting('linting.prospectorEnabled', true, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); - await updateSetting('linting.pydocstyleEnabled', true, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); + if (IS_MULTI_ROOT_TEST) { + await updateSetting('linting.enabled', true, rootWorkspaceUri, vscode.ConfigurationTarget.WorkspaceFolder); + } + const settings: PythonSettingKeys[] = ['linting.lintOnSave', 'linting.lintOnTextChange', 'linting.pylintEnabled', 'linting.flake8Enabled', + 'linting.pep8Enabled', 'linting.prospectorEnabled', 'linting.pydocstyleEnabled', 'linting.mypyEnabled', 'linting.pylamaEnabled']; + + // tslint:disable-next-line:promise-function-async + await Promise.all(settings.map(setting => updateSetting(setting, false, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace))); + if (IS_MULTI_ROOT_TEST) { + // tslint:disable-next-line:promise-function-async + await Promise.all(settings.map(setting => updateSetting(setting, false, rootWorkspaceUri, vscode.ConfigurationTarget.WorkspaceFolder))); + } + PythonSettings.dispose(); } - async function testEnablingDisablingOfLinter(linter: baseLinter.BaseLinter, setting: PythonSettingKeys) { - await updateSetting(setting, true, rootWorkspaceUri, IS_MULTI_ROOT_TEST ? vscode.ConfigurationTarget.Workspace : vscode.ConfigurationTarget.WorkspaceFolder); - const cancelToken = new vscode.CancellationTokenSource(); - await disableAllButThisLinter(linter.product); + async function testEnablingDisablingOfLinter(linter: baseLinter.BaseLinter, setting: PythonSettingKeys, enabled: boolean) { + await updateSetting(setting, enabled, rootWorkspaceUri, IS_MULTI_ROOT_TEST ? vscode.ConfigurationTarget.WorkspaceFolder : vscode.ConfigurationTarget.Workspace); // tslint:disable-next-line:await-promise const document = await vscode.workspace.openTextDocument(fileToLint); // tslint:disable-next-line:await-promise const editor = await vscode.window.showTextDocument(document); - try { - const messages = await linter.lint(editor.document, cancelToken.token); - assert.equal(messages.length, 0, 'Errors returned even when linter is disabled'); - } catch (error) { - assert.fail(error, null, 'Linter error'); + const cancelToken = new vscode.CancellationTokenSource(); + const messages = await linter.lint(editor.document, cancelToken.token); + if (enabled) { + assert.notEqual(messages.length, 0, 'No linter errors when linter is enabled'); + } + else { + assert.equal(messages.length, 0, 'Errors returned when linter is disabled'); } } - test('Enable and Disable Pylint', async () => { + test('Disable Pylint and test linter', async () => { + const ch = new MockOutputChannel('Lint'); + await testEnablingDisablingOfLinter(new pyLint.Linter(ch), 'linting.pylintEnabled', false); + }); + test('Enable Pylint and test linter', async () => { + const ch = new MockOutputChannel('Lint'); + await testEnablingDisablingOfLinter(new pyLint.Linter(ch), 'linting.pylintEnabled', true); + }); + test('Disable Pep8 and test linter', async () => { + const ch = new MockOutputChannel('Lint'); + await testEnablingDisablingOfLinter(new pep8.Linter(ch), 'linting.pep8Enabled', false); + }); + test('Enable Pep8 and test linter', async () => { + const ch = new MockOutputChannel('Lint'); + await testEnablingDisablingOfLinter(new pep8.Linter(ch), 'linting.pep8Enabled', true); + }); + test('Disable Flake8 and test linter', async () => { + const ch = new MockOutputChannel('Lint'); + await testEnablingDisablingOfLinter(new flake8.Linter(ch), 'linting.flake8Enabled', false); + }); + test('Enable Flake8 and test linter', async () => { const ch = new MockOutputChannel('Lint'); - await testEnablingDisablingOfLinter(new pyLint.Linter(ch), 'linting.pylintEnabled'); + await testEnablingDisablingOfLinter(new flake8.Linter(ch), 'linting.flake8Enabled', true); }); - test('Enable and Disable Pep8', async () => { + test('Disable Prospector and test linter', async () => { const ch = new MockOutputChannel('Lint'); - await testEnablingDisablingOfLinter(new pep8.Linter(ch), 'linting.pep8Enabled'); + await testEnablingDisablingOfLinter(new prospector.Linter(ch), 'linting.prospectorEnabled', false); }); - test('Enable and Disable Flake8', async () => { + test('Enable Prospector and test linter', async () => { const ch = new MockOutputChannel('Lint'); - await testEnablingDisablingOfLinter(new flake8.Linter(ch), 'linting.flake8Enabled'); + await testEnablingDisablingOfLinter(new prospector.Linter(ch), 'linting.prospectorEnabled', true); }); - test('Enable and Disable Prospector', async () => { + test('Disable Pydocstyle and test linter', async () => { const ch = new MockOutputChannel('Lint'); - await testEnablingDisablingOfLinter(new prospector.Linter(ch), 'linting.prospectorEnabled'); + await testEnablingDisablingOfLinter(new pydocstyle.Linter(ch), 'linting.pydocstyleEnabled', false); }); - test('Enable and Disable Pydocstyle', async () => { + test('Enable Pydocstyle and test linter', async () => { const ch = new MockOutputChannel('Lint'); - await testEnablingDisablingOfLinter(new pydocstyle.Linter(ch), 'linting.pydocstyleEnabled'); + await testEnablingDisablingOfLinter(new pydocstyle.Linter(ch), 'linting.pydocstyleEnabled', true); }); async function disableAllButThisLinter(linterToEnable: Product) { @@ -182,7 +208,7 @@ suite('Linting', () => { } const setting = SettingToDisableProduct.get(linter.value); // tslint:disable-next-line:no-any prefer-type-cast - return updateSetting(setting as any, true, rootWorkspaceUri, IS_MULTI_ROOT_TEST ? vscode.ConfigurationTarget.Workspace : vscode.ConfigurationTarget.WorkspaceFolder); + return updateSetting(setting as any, true, rootWorkspaceUri, IS_MULTI_ROOT_TEST ? vscode.ConfigurationTarget.WorkspaceFolder : vscode.ConfigurationTarget.Workspace); }); await Promise.all(promises); } @@ -229,17 +255,15 @@ suite('Linting', () => { const linter = new pydocstyle.Linter(ch); await testLinterMessages(linter, ch, fileToLint, pydocstyleMessagseToBeReturned); }); - if (!IS_TRAVIS) { - // tslint:disable-next-line:no-floating-promises - isPython3.then(value => { - const messagesToBeReturned = value ? filteredPylint3MessagesToBeReturned : filteredPylintMessagesToBeReturned; - test('PyLint with config in root', async () => { - const ch = new MockOutputChannel('Lint'); - const linter = new pyLint.Linter(ch); - await testLinterMessages(linter, ch, path.join(pylintConfigPath, 'file.py'), messagesToBeReturned); - }); + // tslint:disable-next-line:no-floating-promises + isPython3.then(value => { + const messagesToBeReturned = value ? filteredPylint3MessagesToBeReturned : filteredPylintMessagesToBeReturned; + test('PyLint with config in root', async () => { + const ch = new MockOutputChannel('Lint'); + const linter = new pyLint.Linter(ch); + await testLinterMessages(linter, ch, path.join(pylintConfigPath, 'file.py'), messagesToBeReturned); }); - } + }); test('Flake8 with config in root', async () => { const ch = new MockOutputChannel('Lint'); const linter = new flake8.Linter(ch); diff --git a/src/test/pythonFiles/linting/file.py b/src/test/pythonFiles/linting/file.py index 047ba0dc679e..7b625a769243 100644 --- a/src/test/pythonFiles/linting/file.py +++ b/src/test/pythonFiles/linting/file.py @@ -10,78 +10,78 @@ def __init__(self): def meth1(self, arg): """this issues a message""" - print self + print (self) def meth2(self, arg): """and this one not""" # pylint: disable=unused-argument - print self\ - + "foo" + print (self\ + + "foo") def meth3(self): """test one line disabling""" # no error - print self.bla # pylint: disable=no-member + print (self.bla) # pylint: disable=no-member # error - print self.blop + print (self.blop) def meth4(self): """test re-enabling""" # pylint: disable=no-member # no error - print self.bla - print self.blop + print (self.bla) + print (self.blop) # pylint: enable=no-member # error - print self.blip + print (self.blip) def meth5(self): """test IF sub-block re-enabling""" # pylint: disable=no-member # no error - print self.bla + print (self.bla) if self.blop: # pylint: enable=no-member # error - print self.blip + print (self.blip) else: # no error - print self.blip + print (self.blip) # no error - print self.blip + print (self.blip) def meth6(self): """test TRY/EXCEPT sub-block re-enabling""" # pylint: disable=no-member # no error - print self.bla + print (self.bla) try: # pylint: enable=no-member # error - print self.blip + print (self.blip) except UndefinedName: # pylint: disable=undefined-variable # no error - print self.blip + print (self.blip) # no error - print self.blip + print (self.blip) def meth7(self): """test one line block opening disabling""" if self.blop: # pylint: disable=no-member # error - print self.blip + print (self.blip) else: # error - print self.blip + print (self.blip) # error - print self.blip + print (self.blip) def meth8(self): """test late disabling""" # error - print self.blip + print (self.blip) # pylint: disable=no-member # no error - print self.bla - print self.blop \ No newline at end of file + print (self.bla) + print (self.blop) diff --git a/src/testMultiRootWkspc/multi.code-workspace b/src/testMultiRootWkspc/multi.code-workspace index fbc76e89a552..09c3fbb10f1b 100644 --- a/src/testMultiRootWkspc/multi.code-workspace +++ b/src/testMultiRootWkspc/multi.code-workspace @@ -20,13 +20,13 @@ } ], "settings": { - "python.linting.flake8Enabled": true, - "python.linting.mypyEnabled": true, - "python.linting.pydocstyleEnabled": true, - "python.linting.pylamaEnabled": true, - "python.linting.pylintEnabled": true, - "python.linting.pep8Enabled": true, - "python.linting.prospectorEnabled": true, + "python.linting.flake8Enabled": false, + "python.linting.mypyEnabled": false, + "python.linting.pydocstyleEnabled": false, + "python.linting.pylamaEnabled": false, + "python.linting.pylintEnabled": false, + "python.linting.pep8Enabled": false, + "python.linting.prospectorEnabled": false, "python.workspaceSymbols.enabled": false, "python.formatting.formatOnSave": false, "python.formatting.provider": "yapf", diff --git a/tslint.json b/tslint.json index 8868d292a5b8..4c7de3c44263 100644 --- a/tslint.json +++ b/tslint.json @@ -24,6 +24,13 @@ "newline-before-return": false, "export-name": false, "align": false, - "linebreak-style": false + "linebreak-style": false, + "strict-boolean-expressions": [ + true, + "allow-null-union", + "allow-undefined-union", + "allow-string", + "allow-number" + ] } } From f5e4006b85b7ae13ab90b3302f50ee6a287e205a Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Oct 2017 11:25:01 -0700 Subject: [PATCH 11/43] udpate settings sequentially --- src/test/linters/lint.test.ts | 44 ++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/test/linters/lint.test.ts b/src/test/linters/lint.test.ts index d5b586c351cd..4524dfdd8136 100644 --- a/src/test/linters/lint.test.ts +++ b/src/test/linters/lint.test.ts @@ -3,9 +3,8 @@ import * as fs from 'fs-extra'; import * as path from 'path'; import * as vscode from 'vscode'; import { PythonSettings } from '../../client/common/configSettings'; -import { EnumEx } from '../../client/common/enumUtils'; import { createDeferred } from '../../client/common/helpers'; -import { Linters, Product, SettingToDisableProduct } from '../../client/common/installer'; +import { SettingToDisableProduct } from '../../client/common/installer'; import { execPythonFile } from '../../client/common/utils'; import * as baseLinter from '../../client/linters/baseLinter'; import * as flake8 from '../../client/linters/flake8'; @@ -130,20 +129,32 @@ suite('Linting', () => { await resetSettings(); }); async function resetSettings() { + // Don't run these updates in parallel, as they are updating the same file. await updateSetting('linting.enabled', true, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); if (IS_MULTI_ROOT_TEST) { await updateSetting('linting.enabled', true, rootWorkspaceUri, vscode.ConfigurationTarget.WorkspaceFolder); } - const settings: PythonSettingKeys[] = ['linting.lintOnSave', 'linting.lintOnTextChange', 'linting.pylintEnabled', 'linting.flake8Enabled', - 'linting.pep8Enabled', 'linting.prospectorEnabled', 'linting.pydocstyleEnabled', 'linting.mypyEnabled', 'linting.pylamaEnabled']; + await updateSetting('linting.lintOnSave', false, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); + await updateSetting('linting.lintOnTextChange', false, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); + await updateSetting('linting.pylintEnabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); + await updateSetting('linting.flake8Enabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); + await updateSetting('linting.pep8Enabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); + await updateSetting('linting.prospectorEnabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); + await updateSetting('linting.mypyEnabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); + await updateSetting('linting.pydocstyleEnabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); + await updateSetting('linting.pylamaEnabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); - // tslint:disable-next-line:promise-function-async - await Promise.all(settings.map(setting => updateSetting(setting, false, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace))); if (IS_MULTI_ROOT_TEST) { - // tslint:disable-next-line:promise-function-async - await Promise.all(settings.map(setting => updateSetting(setting, false, rootWorkspaceUri, vscode.ConfigurationTarget.WorkspaceFolder))); + await updateSetting('linting.lintOnSave', false, rootWorkspaceUri, vscode.ConfigurationTarget.WorkspaceFolder); + await updateSetting('linting.lintOnTextChange', false, rootWorkspaceUri, vscode.ConfigurationTarget.WorkspaceFolder); + await updateSetting('linting.pylintEnabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.WorkspaceFolder); + await updateSetting('linting.flake8Enabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.WorkspaceFolder); + await updateSetting('linting.pep8Enabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.WorkspaceFolder); + await updateSetting('linting.prospectorEnabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.WorkspaceFolder); + await updateSetting('linting.mypyEnabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.WorkspaceFolder); + await updateSetting('linting.pydocstyleEnabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.WorkspaceFolder); + await updateSetting('linting.pylamaEnabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.WorkspaceFolder); } - PythonSettings.dispose(); } async function testEnablingDisablingOfLinter(linter: baseLinter.BaseLinter, setting: PythonSettingKeys, enabled: boolean) { await updateSetting(setting, enabled, rootWorkspaceUri, IS_MULTI_ROOT_TEST ? vscode.ConfigurationTarget.WorkspaceFolder : vscode.ConfigurationTarget.Workspace); @@ -201,21 +212,12 @@ suite('Linting', () => { await testEnablingDisablingOfLinter(new pydocstyle.Linter(ch), 'linting.pydocstyleEnabled', true); }); - async function disableAllButThisLinter(linterToEnable: Product) { - const promises = EnumEx.getNamesAndValues(Product).map(async linter => { - if (Linters.indexOf(linter.value) === -1) { - return; - } - const setting = SettingToDisableProduct.get(linter.value); - // tslint:disable-next-line:no-any prefer-type-cast - return updateSetting(setting as any, true, rootWorkspaceUri, IS_MULTI_ROOT_TEST ? vscode.ConfigurationTarget.WorkspaceFolder : vscode.ConfigurationTarget.Workspace); - }); - await Promise.all(promises); - } // tslint:disable-next-line:no-any async function testLinterMessages(linter: baseLinter.BaseLinter, outputChannel: MockOutputChannel, pythonFile: string, messagesToBeReceived: baseLinter.ILintMessage[]): Promise { const cancelToken = new vscode.CancellationTokenSource(); - await disableAllButThisLinter(linter.product); + const settingToEnable = SettingToDisableProduct.get(linter.product); + // tslint:disable-next-line:no-any prefer-type-cast + await updateSetting(settingToEnable as any, true, rootWorkspaceUri, IS_MULTI_ROOT_TEST ? vscode.ConfigurationTarget.WorkspaceFolder : vscode.ConfigurationTarget.Workspace); // tslint:disable-next-line:await-promise const document = await vscode.workspace.openTextDocument(pythonFile); // tslint:disable-next-line:await-promise From 34aefa23f99aec59ad0178ab0d8b24712338a4be Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Oct 2017 12:43:46 -0700 Subject: [PATCH 12/43] log the output --- src/test/linters/lint.test.ts | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/test/linters/lint.test.ts b/src/test/linters/lint.test.ts index 4524dfdd8136..0a1562db2da6 100644 --- a/src/test/linters/lint.test.ts +++ b/src/test/linters/lint.test.ts @@ -156,7 +156,7 @@ suite('Linting', () => { await updateSetting('linting.pylamaEnabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.WorkspaceFolder); } } - async function testEnablingDisablingOfLinter(linter: baseLinter.BaseLinter, setting: PythonSettingKeys, enabled: boolean) { + async function testEnablingDisablingOfLinter(linter: baseLinter.BaseLinter, setting: PythonSettingKeys, enabled: boolean, output: MockOutputChannel) { await updateSetting(setting, enabled, rootWorkspaceUri, IS_MULTI_ROOT_TEST ? vscode.ConfigurationTarget.WorkspaceFolder : vscode.ConfigurationTarget.Workspace); // tslint:disable-next-line:await-promise const document = await vscode.workspace.openTextDocument(fileToLint); @@ -165,51 +165,51 @@ suite('Linting', () => { const cancelToken = new vscode.CancellationTokenSource(); const messages = await linter.lint(editor.document, cancelToken.token); if (enabled) { - assert.notEqual(messages.length, 0, 'No linter errors when linter is enabled'); + assert.notEqual(messages.length, 0, `No linter errors when linter is enabled, Output - ${output.output}`); } else { - assert.equal(messages.length, 0, 'Errors returned when linter is disabled'); + assert.equal(messages.length, 0, `Errors returned when linter is disabled, Output - ${output.output}`); } } test('Disable Pylint and test linter', async () => { const ch = new MockOutputChannel('Lint'); - await testEnablingDisablingOfLinter(new pyLint.Linter(ch), 'linting.pylintEnabled', false); + await testEnablingDisablingOfLinter(new pyLint.Linter(ch), 'linting.pylintEnabled', false, ch); }); test('Enable Pylint and test linter', async () => { const ch = new MockOutputChannel('Lint'); - await testEnablingDisablingOfLinter(new pyLint.Linter(ch), 'linting.pylintEnabled', true); + await testEnablingDisablingOfLinter(new pyLint.Linter(ch), 'linting.pylintEnabled', true, ch); }); test('Disable Pep8 and test linter', async () => { const ch = new MockOutputChannel('Lint'); - await testEnablingDisablingOfLinter(new pep8.Linter(ch), 'linting.pep8Enabled', false); + await testEnablingDisablingOfLinter(new pep8.Linter(ch), 'linting.pep8Enabled', false, ch); }); test('Enable Pep8 and test linter', async () => { const ch = new MockOutputChannel('Lint'); - await testEnablingDisablingOfLinter(new pep8.Linter(ch), 'linting.pep8Enabled', true); + await testEnablingDisablingOfLinter(new pep8.Linter(ch), 'linting.pep8Enabled', true, ch); }); test('Disable Flake8 and test linter', async () => { const ch = new MockOutputChannel('Lint'); - await testEnablingDisablingOfLinter(new flake8.Linter(ch), 'linting.flake8Enabled', false); + await testEnablingDisablingOfLinter(new flake8.Linter(ch), 'linting.flake8Enabled', false, ch); }); test('Enable Flake8 and test linter', async () => { const ch = new MockOutputChannel('Lint'); - await testEnablingDisablingOfLinter(new flake8.Linter(ch), 'linting.flake8Enabled', true); + await testEnablingDisablingOfLinter(new flake8.Linter(ch), 'linting.flake8Enabled', true, ch); }); test('Disable Prospector and test linter', async () => { const ch = new MockOutputChannel('Lint'); - await testEnablingDisablingOfLinter(new prospector.Linter(ch), 'linting.prospectorEnabled', false); + await testEnablingDisablingOfLinter(new prospector.Linter(ch), 'linting.prospectorEnabled', false, ch); }); test('Enable Prospector and test linter', async () => { const ch = new MockOutputChannel('Lint'); - await testEnablingDisablingOfLinter(new prospector.Linter(ch), 'linting.prospectorEnabled', true); + await testEnablingDisablingOfLinter(new prospector.Linter(ch), 'linting.prospectorEnabled', true, ch); }); test('Disable Pydocstyle and test linter', async () => { const ch = new MockOutputChannel('Lint'); - await testEnablingDisablingOfLinter(new pydocstyle.Linter(ch), 'linting.pydocstyleEnabled', false); + await testEnablingDisablingOfLinter(new pydocstyle.Linter(ch), 'linting.pydocstyleEnabled', false, ch); }); test('Enable Pydocstyle and test linter', async () => { const ch = new MockOutputChannel('Lint'); - await testEnablingDisablingOfLinter(new pydocstyle.Linter(ch), 'linting.pydocstyleEnabled', true); + await testEnablingDisablingOfLinter(new pydocstyle.Linter(ch), 'linting.pydocstyleEnabled', true, ch); }); // tslint:disable-next-line:no-any @@ -232,9 +232,6 @@ suite('Linting', () => { // Looks like pylint stops linting as soon as it comes across any ERRORS. assert.notEqual(messages.length, 0, `No errors in linter, Output - ${outputChannel.output}`); } - else { - assert.ok('Linter not installed', 'Linter not installed'); - } } } test('PyLint', async () => { From 3c58408aa552cb5c717d6355baeb0f6ed7d02d77 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Oct 2017 12:51:35 -0700 Subject: [PATCH 13/43] show errors in deleting dir --- src/test/unittests/unittest.test.ts | 56 ++++++++++++----------------- 1 file changed, 23 insertions(+), 33 deletions(-) diff --git a/src/test/unittests/unittest.test.ts b/src/test/unittests/unittest.test.ts index 211423086476..478111c1d620 100644 --- a/src/test/unittests/unittest.test.ts +++ b/src/test/unittests/unittest.test.ts @@ -1,13 +1,13 @@ import * as assert from 'assert'; -import * as path from 'path'; import * as fs from 'fs-extra'; -import * as unittest from '../../client/unittests/unittest/main'; -import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../initialize'; +import * as path from 'path'; +import { ConfigurationTarget } from 'vscode'; import { TestsToRun } from '../../client/unittests/common/contracts'; import { TestResultDisplay } from '../../client/unittests/display/main'; -import { MockOutputChannel } from './../mockClasses'; -import { ConfigurationTarget } from 'vscode'; +import * as unittest from '../../client/unittests/unittest/main'; import { rootWorkspaceUri, updateSetting } from '../common'; +import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../initialize'; +import { MockOutputChannel } from './../mockClasses'; const testFilesPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles'); const UNITTEST_TEST_FILES_PATH = path.join(testFilesPath, 'standard'); @@ -15,14 +15,19 @@ const UNITTEST_SINGLE_TEST_FILE_PATH = path.join(testFilesPath, 'single'); const unitTestTestFilesCwdPath = path.join(testFilesPath, 'cwd', 'src'); const unitTestSpecificTestFilesPath = path.join(testFilesPath, 'specificTest'); const defaultUnitTestArgs = [ - "-v", - "-s", - ".", - "-p", - "*test*.py" + '-v', + '-s', + '.', + '-p', + '*test*.py' ]; +// tslint:disable-next-line:max-func-body-length suite('Unit Tests (unittest)', () => { + let testManager: unittest.TestManager; + let testResultDisplay: TestResultDisplay; + let outChannel: MockOutputChannel; + const rootDirectory = UNITTEST_TEST_FILES_PATH; const configTarget = IS_MULTI_ROOT_TEST ? ConfigurationTarget.WorkspaceFolder : ConfigurationTarget.Workspace; suiteSetup(async () => { await initialize(); @@ -31,7 +36,10 @@ suite('Unit Tests (unittest)', () => { setup(async () => { outChannel = new MockOutputChannel('Python Test Log'); testResultDisplay = new TestResultDisplay(outChannel); - await fs.remove(path.join(UNITTEST_TEST_FILES_PATH, '.cache')).catch(() => undefined); + const cachePath = path.join(UNITTEST_TEST_FILES_PATH, '.cache'); + if (await fs.pathExists(cachePath)) { + await fs.remove(cachePath); + } await initializeTest(); }); teardown(() => { @@ -42,10 +50,6 @@ suite('Unit Tests (unittest)', () => { function createTestManager(rootDir: string = rootDirectory) { testManager = new unittest.TestManager(rootDir, outChannel); } - const rootDirectory = UNITTEST_TEST_FILES_PATH; - let testManager: unittest.TestManager; - let testResultDisplay: TestResultDisplay; - let outChannel: MockOutputChannel; test('Discover Tests (single test file)', async () => { await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=test_*.py'], rootWorkspaceUri, configTarget); @@ -88,22 +92,6 @@ suite('Unit Tests (unittest)', () => { assert.equal(results.summary.skipped, 1, 'skipped'); }); - // test('Fail Fast', done => { - // pythonSettings.unitTest.unittestArgs = [ - // '-s=./tests', - // '-p=*test*.py', - // '--failfast' - // ]; - // createTestManager(); - // testManager.runTest().then(results => { - // assert.equal(results.summary.errors, 1, 'Errors'); - // assert.equal(results.summary.failures, 5, 'Failures'); - // assert.equal(results.summary.passed, 4, 'Passed'); - // assert.equal(results.summary.skipped, 1, 'skipped'); - // done(); - // }).catch(done); - // }); - test('Run Failed Tests', async () => { await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=test_unittest*.py'], rootWorkspaceUri, configTarget); createTestManager(); @@ -125,7 +113,8 @@ suite('Unit Tests (unittest)', () => { createTestManager(unitTestSpecificTestFilesPath); const tests = await testManager.discoverTests(true, true); - const testFileToTest = tests.testFiles.find(f => f.name === 'test_unittest_one.py'); + // tslint:disable-next-line:no-non-null-assertion + const testFileToTest = tests.testFiles.find(f => f.name === 'test_unittest_one.py')!; const testFile: TestsToRun = { testFile: [testFileToTest], testFolder: [], testFunction: [], testSuite: [] }; const results = await testManager.runTest(testFile); @@ -140,6 +129,7 @@ suite('Unit Tests (unittest)', () => { createTestManager(unitTestSpecificTestFilesPath); const tests = await testManager.discoverTests(true, true); + // tslint:disable-next-line:no-non-null-assertion const testSuiteToTest = tests.testSuits.find(s => s.testSuite.name === 'Test_test_one_1')!.testSuite; const testSuite: TestsToRun = { testFile: [], testFolder: [], testFunction: [], testSuite: [testSuiteToTest] }; const results = await testManager.runTest(testSuite); @@ -172,4 +162,4 @@ suite('Unit Tests (unittest)', () => { assert.equal(tests.testFunctions.length, 1, 'Incorrect number of test functions'); assert.equal(tests.testSuits.length, 1, 'Incorrect number of test suites'); }); -}); +}); From 252a52eada0918809363cb8e3be0d29384186fd6 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Oct 2017 13:04:45 -0700 Subject: [PATCH 14/43] removed prospector test, to be completed in #1319 --- src/test/linters/lint.test.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/test/linters/lint.test.ts b/src/test/linters/lint.test.ts index 0a1562db2da6..66d45a924fee 100644 --- a/src/test/linters/lint.test.ts +++ b/src/test/linters/lint.test.ts @@ -199,10 +199,6 @@ suite('Linting', () => { const ch = new MockOutputChannel('Lint'); await testEnablingDisablingOfLinter(new prospector.Linter(ch), 'linting.prospectorEnabled', false, ch); }); - test('Enable Prospector and test linter', async () => { - const ch = new MockOutputChannel('Lint'); - await testEnablingDisablingOfLinter(new prospector.Linter(ch), 'linting.prospectorEnabled', true, ch); - }); test('Disable Pydocstyle and test linter', async () => { const ch = new MockOutputChannel('Lint'); await testEnablingDisablingOfLinter(new pydocstyle.Linter(ch), 'linting.pydocstyleEnabled', false, ch); From 6acaa7a64b631939a9bbcf04f0c0a2e6f46376da Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Oct 2017 15:54:09 -0700 Subject: [PATCH 15/43] fixes to tests and sorting provider --- .vscode/tasks.json | 12 ++ package.json | 3 +- src/client/common/configSettings.ts | 143 ++++++++----- src/client/providers/importSortProvider.ts | 98 ++++----- .../common/configSettings.multiroot.test.ts | 125 ++++++----- src/test/format/extension.sort.test.ts | 194 ++++++------------ src/test/linters/lint.test.ts | 4 - tslint.json | 3 +- 8 files changed, 300 insertions(+), 282 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 9fe3969ccf07..bfcb80a350f7 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -53,6 +53,18 @@ "fileLocation": "relative" } ] + }, + { + "label": "lint-staged", + "type": "npm", + "script": "lint-staged", + "problemMatcher": [ + "$tsc", + { + "base": "$tslint5", + "fileLocation": "relative" + } + ] } ] } diff --git a/package.json b/package.json index 415fdd2e42a2..c4be011f6f9f 100644 --- a/package.json +++ b/package.json @@ -1595,6 +1595,7 @@ "postinstall": "node ./node_modules/vscode/bin/install", "test": "node ./out/test/standardTest.js && node ./out/test/multiRootTest.js", "precommit": "node gulpfile.js", + "lint-staged": "node gulpfile.js", "lint": "tslint src/**/*.ts -t verbose" }, "dependencies": { @@ -1662,4 +1663,4 @@ "vscode": "^1.1.5", "webpack": "^1.13.2" } -} \ No newline at end of file +} diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index 97222940b071..bece06d457fa 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -1,11 +1,12 @@ 'use strict'; -import * as vscode from 'vscode'; -import * as path from 'path'; import * as child_process from 'child_process'; +import { EventEmitter } from 'events'; +import * as path from 'path'; +import * as vscode from 'vscode'; import { Uri } from 'vscode'; import { SystemVariables } from './systemVariables'; -import { EventEmitter } from 'events'; +// tslint:disable-next-line:no-require-imports no-var-requires const untildify = require('untildify'); export const IS_WINDOWS = /^win/.test(process.platform); @@ -56,6 +57,7 @@ export interface IPep8CategorySeverity { W: vscode.DiagnosticSeverity; E: vscode.DiagnosticSeverity; } +// tslint:disable-next-line:interface-name export interface Flake8CategorySeverity { F: vscode.DiagnosticSeverity; E: vscode.DiagnosticSeverity; @@ -125,18 +127,39 @@ export interface ITerminalSettings { executeInFileDir: boolean; launchArgs: string[]; } +// tslint:disable-next-line:interface-name export interface JupyterSettings { appendResults: boolean; defaultKernel: string; startupCode: string[]; } +// tslint:disable-next-line:no-string-literal const IS_TEST_EXECUTION = process.env['PYTHON_DONJAYAMANNE_TEST'] === '1'; +// tslint:disable-next-line:completed-docs export class PythonSettings extends EventEmitter implements IPythonSettings { private static pythonSettings: Map = new Map(); - private workspaceRoot?: vscode.Uri; + + public jediPath: string; + public envFile: string; + public disablePromptForFeatures: string[]; + public venvPath: string; + public devOptions: string[]; + public linting: ILintingSettings; + public formatting: IFormattingSettings; + public autoComplete: IAutoCompeteSettings; + public unitTest: IUnitTestSettings; + public terminal: ITerminalSettings; + public jupyter: JupyterSettings; + public sortImports: ISortImportSettings; + public workspaceSymbols: IWorkspaceSymbolSettings; + + private workspaceRoot: vscode.Uri; private disposables: vscode.Disposable[] = []; + // tslint:disable-next-line:variable-name + private _pythonPath: string; + constructor(workspaceFolder?: Uri) { super(); this.workspaceRoot = workspaceFolder ? workspaceFolder : vscode.Uri.file(__dirname); @@ -146,15 +169,10 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { this.initializeSettings(); } - public static dispose() { - if (!IS_TEST_EXECUTION) { - throw new Error('Dispose can only be called from unit tests'); - } - PythonSettings.pythonSettings.clear(); - } + // tslint:disable-next-line:function-name public static getInstance(resource?: Uri): PythonSettings { const workspaceFolder = resource ? vscode.workspace.getWorkspaceFolder(resource) : undefined; - let workspaceFolderUri: Uri = workspaceFolder ? workspaceFolder.uri : undefined; + let workspaceFolderUri: Uri | undefined = workspaceFolder ? workspaceFolder.uri : undefined; if (!workspaceFolderUri && Array.isArray(vscode.workspace.workspaceFolders) && vscode.workspace.workspaceFolders.length > 0) { workspaceFolderUri = vscode.workspace.workspaceFolders[0].uri; } @@ -163,15 +181,35 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { const settings = new PythonSettings(workspaceFolderUri); PythonSettings.pythonSettings.set(workspaceFolderKey, settings); } - return PythonSettings.pythonSettings.get(workspaceFolderKey); + // tslint:disable-next-line:no-non-null-assertion + return PythonSettings.pythonSettings.get(workspaceFolderKey)!; } + // tslint:disable-next-line:function-name + public static dispose() { + if (!IS_TEST_EXECUTION) { + throw new Error('Dispose can only be called from unit tests'); + } + // tslint:disable-next-line:no-void-expression + PythonSettings.pythonSettings.forEach(item => item.dispose()); + PythonSettings.pythonSettings.clear(); + } + public dispose() { + // tslint:disable-next-line:no-unsafe-any + this.disposables.forEach(disposable => disposable.dispose()); + this.disposables = []; + } + + // tslint:disable-next-line:cyclomatic-complexity max-func-body-length private initializeSettings() { const workspaceRoot = this.workspaceRoot.fsPath; const systemVariables: SystemVariables = new SystemVariables(this.workspaceRoot ? this.workspaceRoot.fsPath : undefined); const pythonSettings = vscode.workspace.getConfiguration('python', this.workspaceRoot); + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion this.pythonPath = systemVariables.resolveAny(pythonSettings.get('pythonPath'))!; this.pythonPath = getAbsolutePath(this.pythonPath, workspaceRoot); + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion this.venvPath = systemVariables.resolveAny(pythonSettings.get('venvPath'))!; + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion this.jediPath = systemVariables.resolveAny(pythonSettings.get('jediPath'))!; if (typeof this.jediPath === 'string' && this.jediPath.length > 0) { this.jediPath = getAbsolutePath(systemVariables.resolveAny(this.jediPath), workspaceRoot); @@ -179,10 +217,15 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { else { this.jediPath = ''; } + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion this.envFile = systemVariables.resolveAny(pythonSettings.get('envFile'))!; + // tslint:disable-next-line:no-any + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion no-any this.devOptions = systemVariables.resolveAny(pythonSettings.get('devOptions'))!; this.devOptions = Array.isArray(this.devOptions) ? this.devOptions : []; - let lintingSettings = systemVariables.resolveAny(pythonSettings.get('linting'))!; + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const lintingSettings = systemVariables.resolveAny(pythonSettings.get('linting'))!; + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion this.disablePromptForFeatures = pythonSettings.get('disablePromptForFeatures')!; this.disablePromptForFeatures = Array.isArray(this.disablePromptForFeatures) ? this.disablePromptForFeatures : []; if (this.linting) { @@ -191,16 +234,17 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { else { this.linting = lintingSettings; } - let sortImportSettings = systemVariables.resolveAny(pythonSettings.get('sortImports'))!; + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const sortImportSettings = systemVariables.resolveAny(pythonSettings.get('sortImports'))!; if (this.sortImports) { Object.assign(this.sortImports, sortImportSettings); } else { this.sortImports = sortImportSettings; } - // Support for travis + // Support for travis. this.sortImports = this.sortImports ? this.sortImports : { path: '', args: [] }; - // Support for travis + // Support for travis. this.linting = this.linting ? this.linting : { enabled: false, enabledWithoutWorkspace: false, @@ -242,14 +286,15 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { this.linting.pydocstylePath = getAbsolutePath(systemVariables.resolveAny(this.linting.pydocstylePath), workspaceRoot); this.linting.mypyPath = getAbsolutePath(systemVariables.resolveAny(this.linting.mypyPath), workspaceRoot); - let formattingSettings = systemVariables.resolveAny(pythonSettings.get('formatting'))!; + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const formattingSettings = systemVariables.resolveAny(pythonSettings.get('formatting'))!; if (this.formatting) { Object.assign(this.formatting, formattingSettings); } else { this.formatting = formattingSettings; } - // Support for travis + // Support for travis. this.formatting = this.formatting ? this.formatting : { autopep8Args: [], autopep8Path: 'autopep8', outputWindow: 'python', @@ -260,45 +305,49 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { this.formatting.autopep8Path = getAbsolutePath(systemVariables.resolveAny(this.formatting.autopep8Path), workspaceRoot); this.formatting.yapfPath = getAbsolutePath(systemVariables.resolveAny(this.formatting.yapfPath), workspaceRoot); - let autoCompleteSettings = systemVariables.resolveAny(pythonSettings.get('autoComplete'))!; + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const autoCompleteSettings = systemVariables.resolveAny(pythonSettings.get('autoComplete'))!; if (this.autoComplete) { Object.assign(this.autoComplete, autoCompleteSettings); } else { this.autoComplete = autoCompleteSettings; } - // Support for travis + // Support for travis. this.autoComplete = this.autoComplete ? this.autoComplete : { extraPaths: [], addBrackets: false, preloadModules: [] }; - let workspaceSymbolsSettings = systemVariables.resolveAny(pythonSettings.get('workspaceSymbols'))!; + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const workspaceSymbolsSettings = systemVariables.resolveAny(pythonSettings.get('workspaceSymbols'))!; if (this.workspaceSymbols) { Object.assign(this.workspaceSymbols, workspaceSymbolsSettings); } else { this.workspaceSymbols = workspaceSymbolsSettings; } - // Support for travis + // Support for travis. this.workspaceSymbols = this.workspaceSymbols ? this.workspaceSymbols : { ctagsPath: 'ctags', enabled: true, exclusionPatterns: [], rebuildOnFileSave: true, rebuildOnStart: true, - tagFilePath: path.join(workspaceRoot, "tags") + tagFilePath: path.join(workspaceRoot, 'tags') }; this.workspaceSymbols.tagFilePath = getAbsolutePath(systemVariables.resolveAny(this.workspaceSymbols.tagFilePath), workspaceRoot); - let unitTestSettings = systemVariables.resolveAny(pythonSettings.get('unitTest'))!; + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const unitTestSettings = systemVariables.resolveAny(pythonSettings.get('unitTest'))!; if (this.unitTest) { Object.assign(this.unitTest, unitTestSettings); } else { this.unitTest = unitTestSettings; if (IS_TEST_EXECUTION && !this.unitTest) { + // tslint:disable-next-line:prefer-type-cast this.unitTest = { nosetestArgs: [], pyTestArgs: [], unittestArgs: [], promptToConfigure: true, debugPort: 3000, @@ -308,7 +357,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { } } - // Support for travis + // Support for travis. this.unitTest = this.unitTest ? this.unitTest : { promptToConfigure: true, debugPort: 3000, @@ -323,18 +372,20 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { this.unitTest.cwd = getAbsolutePath(systemVariables.resolveAny(this.unitTest.cwd), workspaceRoot); } - // Resolve any variables found in the test arguments + // Resolve any variables found in the test arguments. this.unitTest.nosetestArgs = this.unitTest.nosetestArgs.map(arg => systemVariables.resolveAny(arg)); this.unitTest.pyTestArgs = this.unitTest.pyTestArgs.map(arg => systemVariables.resolveAny(arg)); this.unitTest.unittestArgs = this.unitTest.unittestArgs.map(arg => systemVariables.resolveAny(arg)); - let terminalSettings = systemVariables.resolveAny(pythonSettings.get('terminal'))!; + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const terminalSettings = systemVariables.resolveAny(pythonSettings.get('terminal'))!; if (this.terminal) { Object.assign(this.terminal, terminalSettings); } else { this.terminal = terminalSettings; if (IS_TEST_EXECUTION && !this.terminal) { + // tslint:disable-next-line:prefer-type-cast this.terminal = {} as ITerminalSettings; } } @@ -344,8 +395,9 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { launchArgs: [] }; + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion this.jupyter = pythonSettings.get('jupyter')!; - // Support for travis + // Support for travis. this.jupyter = this.jupyter ? this.jupyter : { appendResults: true, defaultKernel: '', startupCode: [] }; @@ -353,7 +405,6 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { this.emit('change'); } - private _pythonPath: string; public get pythonPath(): string { return this._pythonPath; } @@ -361,8 +412,8 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { if (this._pythonPath === value) { return; } - // Add support for specifying just the directory where the python executable will be located - // E.g. virtual directory name + // Add support for specifying just the directory where the python executable will be located. + // E.g. virtual directory name. try { this._pythonPath = getPythonExecutable(value); } @@ -370,23 +421,11 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { this._pythonPath = value; } } - public jediPath: string; - public envFile: string; - public disablePromptForFeatures: string[]; - public venvPath: string; - public devOptions: string[]; - public linting: ILintingSettings; - public formatting: IFormattingSettings; - public autoComplete: IAutoCompeteSettings; - public unitTest: IUnitTestSettings; - public terminal: ITerminalSettings; - public jupyter: JupyterSettings; - public sortImports: ISortImportSettings; - public workspaceSymbols: IWorkspaceSymbolSettings; } function getAbsolutePath(pathToCheck: string, rootDir: string): string { - pathToCheck = untildify(pathToCheck); + // tslint:disable-next-line:prefer-type-cast no-unsafe-any + pathToCheck = untildify(pathToCheck) as string; if (IS_TEST_EXECUTION && !pathToCheck) { return rootDir; } if (pathToCheck.indexOf(path.sep) === -1) { return pathToCheck; @@ -395,9 +434,10 @@ function getAbsolutePath(pathToCheck: string, rootDir: string): string { } function getPythonExecutable(pythonPath: string): string { - pythonPath = untildify(pythonPath); + // tslint:disable-next-line:prefer-type-cast no-unsafe-any + pythonPath = untildify(pythonPath) as string; - // If only 'python' + // If only 'python'. if (pythonPath === 'python' || pythonPath.indexOf(path.sep) === -1 || path.basename(pythonPath) === path.dirname(pythonPath)) { @@ -407,13 +447,14 @@ function getPythonExecutable(pythonPath: string): string { if (isValidPythonPath(pythonPath)) { return pythonPath; } - // Keep python right on top, for backwards compatibility + // Keep python right on top, for backwards compatibility. + // tslint:disable-next-line:variable-name const KnownPythonExecutables = ['python', 'python4', 'python3.6', 'python3.5', 'python3', 'python2.7', 'python2']; for (let executableName of KnownPythonExecutables) { - // Suffix with 'python' for linux and 'osx', and 'python.exe' for 'windows' + // Suffix with 'python' for linux and 'osx', and 'python.exe' for 'windows'. if (IS_WINDOWS) { - executableName = executableName + '.exe'; + executableName = `${executableName}.exe`; if (isValidPythonPath(path.join(pythonPath, executableName))) { return path.join(pythonPath, executableName); } @@ -436,7 +477,7 @@ function getPythonExecutable(pythonPath: string): string { function isValidPythonPath(pythonPath: string): boolean { try { - let output = child_process.execFileSync(pythonPath, ['-c', 'print(1234)'], { encoding: 'utf8' }); + const output = child_process.execFileSync(pythonPath, ['-c', 'print(1234)'], { encoding: 'utf8' }); return output.startsWith('1234'); } catch (ex) { diff --git a/src/client/providers/importSortProvider.ts b/src/client/providers/importSortProvider.ts index c3628427f48b..2808394125b1 100644 --- a/src/client/providers/importSortProvider.ts +++ b/src/client/providers/importSortProvider.ts @@ -1,57 +1,57 @@ -"use strict"; - -import * as vscode from "vscode"; -import * as path from "path"; -import * as fs from "fs"; -import * as child_process from "child_process"; -import * as settings from '../common/configSettings'; -import { getTextEditsFromPatch, getTempFileWithDocumentContents } from "../common/editor"; +'use strict'; +import * as child_process from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { PythonSettings } from '../common/configSettings'; +import { getTempFileWithDocumentContents, getTextEditsFromPatch } from '../common/editor'; +// tslint:disable-next-line:completed-docs export class PythonImportSortProvider { - public sortImports(extensionDir: string, document: vscode.TextDocument): Promise { + public async sortImports(extensionDir: string, document: vscode.TextDocument): Promise { if (document.lineCount === 1) { - return Promise.resolve([]); + return []; + } + // isort does have the ability to read from the process input stream and return the formatted code out of the output stream. + // However they don't support returning the diff of the formatted text when reading data from the input stream. + // Yes getting text formatted that way avoids having to create a temporary file, however the diffing will have. + // to be done here in node (extension), i.e. extension cpu, i.e. less responsive solution. + const importScript = path.join(extensionDir, 'pythonFiles', 'sortImports.py'); + const tmpFileCreated = document.isDirty; + const filePath = tmpFileCreated ? await getTempFileWithDocumentContents(document) : document.fileName; + const settings = PythonSettings.getInstance(document.uri); + const pythonPath = settings.pythonPath; + const isort = settings.sortImports.path; + const args = settings.sortImports.args.join(' '); + let isortCmd = ''; + if (typeof isort === 'string' && isort.length > 0) { + if (isort.indexOf(' ') > 0) { + isortCmd = `"${isort}" "${filePath}" --diff ${args}`; + } + else { + isortCmd = `${isort} "${filePath}" --diff ${args}`; + } + } else { + if (pythonPath.indexOf(' ') > 0) { + isortCmd = `"${pythonPath}" "${importScript}" "${filePath}" --diff ${args}`; + } + else { + isortCmd = `${pythonPath} "${importScript}" "${filePath}" --diff ${args}`; + } } - return new Promise((resolve, reject) => { - // isort does have the ability to read from the process input stream and return the formatted code out of the output stream - // However they don't support returning the diff of the formatted text when reading data from the input stream - // Yes getting text formatted that way avoids having to create a temporary file, however the diffing will have - // to be done here in node (extension), i.e. extension cpu, i.e. les responsive solution - let importScript = path.join(extensionDir, "pythonFiles", "sortImports.py"); - let tmpFileCreated = document.isDirty; - let filePromise = tmpFileCreated ? getTempFileWithDocumentContents(document) : Promise.resolve(document.fileName); - filePromise.then(filePath => { - const pythonPath = settings.PythonSettings.getInstance().pythonPath; - const isort = settings.PythonSettings.getInstance().sortImports.path; - const args = settings.PythonSettings.getInstance().sortImports.args.join(' '); - let isort_cmd = ''; - if (typeof isort === 'string' && isort.length > 0) { - if (isort.indexOf(' ') > 0) { - isort_cmd = `"${isort}" "${filePath}" --diff ${args}`; - } - else { - isort_cmd = `${isort} "${filePath}" --diff ${args}`; - } - } else { - if (pythonPath.indexOf(' ') > 0) { - isort_cmd = `"${pythonPath}" "${importScript}" "${filePath}" --diff ${args}`; - } - else { - isort_cmd = `${pythonPath} "${importScript}" "${filePath}" --diff ${args}`; - } + // tslint:disable-next-line:promise-must-complete + return await new Promise((resolve, reject) => { + child_process.exec(isortCmd, (error, stdout, stderr) => { + if (tmpFileCreated) { + fs.unlink(filePath); } - child_process.exec(isort_cmd, (error, stdout, stderr) => { - if (tmpFileCreated) { - fs.unlink(filePath); - } - if (error || (stderr && stderr.length > 0)) { - return reject(error ? error : stderr); - } - - let edits = getTextEditsFromPatch(document.getText(), stdout); - resolve(edits); - }); - }).catch(reject); + if (error || (stderr && stderr.length > 0)) { + reject(error ? error : stderr); + } + else { + resolve(getTextEditsFromPatch(document.getText(), stdout)); + } + }); }); } } diff --git a/src/test/common/configSettings.multiroot.test.ts b/src/test/common/configSettings.multiroot.test.ts index 08172cb10171..17c96c631dd3 100644 --- a/src/test/common/configSettings.multiroot.test.ts +++ b/src/test/common/configSettings.multiroot.test.ts @@ -1,18 +1,19 @@ import * as assert from 'assert'; import * as path from 'path'; import { ConfigurationTarget, Uri, workspace } from 'vscode'; -import { initialize, closeActiveWindows, initializeTest } from '../initialize'; import { PythonSettings } from '../../client/common/configSettings'; +import { closeActiveWindows, initialize, initializeTest } from '../initialize'; const multirootPath = path.join(__dirname, '..', '..', '..', 'src', 'testMultiRootWkspc'); +// tslint:disable-next-line:max-func-body-length suite('Multiroot Config Settings', () => { suiteSetup(async () => { await initialize(); await resetSettings(); }); - setup(() => initializeTest()); - suiteTeardown(() => closeActiveWindows()); + setup(initializeTest); + suiteTeardown(closeActiveWindows); teardown(async () => { await resetSettings(); await closeActiveWindows(); @@ -20,28 +21,25 @@ suite('Multiroot Config Settings', () => { async function resetSettings() { const workspaceUri = Uri.file(path.join(multirootPath, 'workspace1')); - let settings = workspace.getConfiguration('python', workspaceUri); - let value = settings.inspect('pythonPath'); - if (value.workspaceValue && value.workspaceValue !== 'python') { - await settings.update('pythonPath', undefined, ConfigurationTarget.Workspace); - } - if (value.workspaceFolderValue && value.workspaceFolderValue !== 'python') { + const settings = workspace.getConfiguration('python', workspaceUri); + const value = settings.inspect('pythonPath'); + if (value && typeof value.workspaceFolderValue === 'string') { await settings.update('pythonPath', undefined, ConfigurationTarget.WorkspaceFolder); } PythonSettings.dispose(); } - async function enableDisableSetting(resource: Uri, configTarget: ConfigurationTarget, setting: string, value: boolean) { - let settings = workspace.getConfiguration('python.linting', resource); - await settings.update(setting, value, configTarget); - settings = workspace.getConfiguration('python.linting', resource); - return settings.get(setting); - } - async function testLinterSetting(resource: Uri, configTarget: ConfigurationTarget, setting: string, value: boolean) { - const valueInSetting = await enableDisableSetting(resource, configTarget, setting, value); + async function enableDisableLinterSetting(resource: Uri, configTarget: ConfigurationTarget, setting: string, enabled: boolean | undefined): Promise { + const settings = workspace.getConfiguration('python.linting', resource); + const cfgValue = settings.inspect(setting); + if (configTarget === ConfigurationTarget.Workspace && cfgValue && cfgValue.workspaceValue === enabled) { + return; + } + if (configTarget === ConfigurationTarget.WorkspaceFolder && cfgValue && cfgValue.workspaceFolderValue === enabled) { + return; + } + await settings.update(setting, enabled, configTarget); PythonSettings.dispose(); - const cfgSetting = PythonSettings.getInstance(resource); - assert.equal(valueInSetting, cfgSetting.linting[setting], `Both settings ${setting} should be ${value} for ${resource.fsPath}`); } test('Workspace folder should inherit Python Path from workspace root', async () => { @@ -49,26 +47,32 @@ suite('Multiroot Config Settings', () => { let settings = workspace.getConfiguration('python', workspaceUri); const pythonPath = `x${new Date().getTime()}`; await settings.update('pythonPath', pythonPath, ConfigurationTarget.Workspace); - + const value = settings.inspect('pythonPath'); + if (value && typeof value.workspaceFolderValue === 'string') { + await settings.update('pythonPath', undefined, ConfigurationTarget.WorkspaceFolder); + } + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); settings = workspace.getConfiguration('python', workspaceUri); - assert.equal(settings.get('pythonPath'), pythonPath, 'Python path not set in workspace root'); - + PythonSettings.dispose(); const cfgSetting = PythonSettings.getInstance(workspaceUri); assert.equal(cfgSetting.pythonPath, pythonPath, 'Python Path not inherited from workspace'); }); test('Workspace folder should not inherit Python Path from workspace root', async () => { const workspaceUri = Uri.file(path.join(multirootPath, 'workspace1')); - let settings = workspace.getConfiguration('python', workspaceUri); + const settings = workspace.getConfiguration('python', workspaceUri); const pythonPath = `x${new Date().getTime()}`; await settings.update('pythonPath', pythonPath, ConfigurationTarget.Workspace); - await settings.update('pythonPath', 'privatePythonPath', ConfigurationTarget.WorkspaceFolder); + const privatePythonPath = `x${new Date().getTime()}`; + await settings.update('pythonPath', privatePythonPath, ConfigurationTarget.WorkspaceFolder); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); const cfgSetting = PythonSettings.getInstance(workspaceUri); - assert.equal(cfgSetting.pythonPath, 'privatePythonPath', 'Python Path for workspace folder is incorrect'); + assert.equal(cfgSetting.pythonPath, privatePythonPath, 'Python Path for workspace folder is incorrect'); }); - test('Workspace folder should inherit Python Path from workspace root when opening a document', async () => { const workspaceUri = Uri.file(path.join(multirootPath, 'workspace1')); const fileToOpen = path.join(multirootPath, 'workspace1', 'file.py'); @@ -76,6 +80,11 @@ suite('Multiroot Config Settings', () => { const settings = workspace.getConfiguration('python', workspaceUri); const pythonPath = `x${new Date().getTime()}`; await settings.update('pythonPath', pythonPath, ConfigurationTarget.Workspace); + // Update workspace folder to something else so it gets refreshed. + await settings.update('pythonPath', `x${new Date().getTime()}`, ConfigurationTarget.WorkspaceFolder); + await settings.update('pythonPath', undefined, ConfigurationTarget.WorkspaceFolder); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); const document = await workspace.openTextDocument(fileToOpen); const cfg = PythonSettings.getInstance(document.uri); @@ -89,37 +98,48 @@ suite('Multiroot Config Settings', () => { const settings = workspace.getConfiguration('python', workspaceUri); const pythonPath = `x${new Date().getTime()}`; await settings.update('pythonPath', pythonPath, ConfigurationTarget.Workspace); - await settings.update('pythonPath', 'privatePythonPath', ConfigurationTarget.WorkspaceFolder); + const privatePythonPath = `x${new Date().getTime()}`; + await settings.update('pythonPath', privatePythonPath, ConfigurationTarget.WorkspaceFolder); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); const document = await workspace.openTextDocument(fileToOpen); const cfg = PythonSettings.getInstance(document.uri); - assert.equal(cfg.pythonPath, 'privatePythonPath', 'Python Path for workspace folder is incorrect'); - }); - - test('Enabling/Disabling Pylint in root should be reflected in config settings', async () => { - const workspaceUri = Uri.file(path.join(multirootPath, 'workspace1')); - await testLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', true); - await testLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', false); + assert.equal(cfg.pythonPath, privatePythonPath, 'Python Path for workspace folder is incorrect'); }); test('Enabling/Disabling Pylint in root should be reflected in config settings', async () => { const workspaceUri = Uri.file(path.join(multirootPath, 'workspace1')); - await testLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', true); - await testLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', false); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', undefined); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', true); + let settings = PythonSettings.getInstance(workspaceUri); + assert.equal(settings.linting.pylintEnabled, true, 'Pylint not enabled when it should be'); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); + + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', false); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); + settings = PythonSettings.getInstance(workspaceUri); + assert.equal(settings.linting.pylintEnabled, false, 'Pylint enabled when it should not be'); }); test('Enabling/Disabling Pylint in root and workspace should be reflected in config settings', async () => { const workspaceUri = Uri.file(path.join(multirootPath, 'workspace1')); - await testLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', false); - await testLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', true); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', false); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', true); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); let cfgSetting = PythonSettings.getInstance(workspaceUri); assert.equal(cfgSetting.linting.pylintEnabled, false, 'Workspace folder pylint setting is true when it should not be'); PythonSettings.dispose(); - await testLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', true); - await testLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', false); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', true); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', false); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); cfgSetting = PythonSettings.getInstance(workspaceUri); assert.equal(cfgSetting.linting.pylintEnabled, true, 'Workspace folder pylint setting is false when it should not be'); @@ -129,15 +149,19 @@ suite('Multiroot Config Settings', () => { const workspaceUri = Uri.file(path.join(multirootPath, 'workspace1')); const fileToOpen = path.join(multirootPath, 'workspace1', 'file.py'); - await testLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', false); - await testLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', true); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', false); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', true); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); let document = await workspace.openTextDocument(fileToOpen); let cfg = PythonSettings.getInstance(document.uri); assert.equal(cfg.linting.pylintEnabled, true, 'Pylint should be enabled in workspace'); PythonSettings.dispose(); - await testLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', true); - await testLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', false); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', true); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', false); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); document = await workspace.openTextDocument(fileToOpen); cfg = PythonSettings.getInstance(document.uri); assert.equal(cfg.linting.pylintEnabled, false, 'Pylint should not be enabled in workspace'); @@ -147,20 +171,25 @@ suite('Multiroot Config Settings', () => { const workspaceUri = Uri.file(path.join(multirootPath, 'workspace1')); const fileToOpen = path.join(multirootPath, 'workspace1', 'file.py'); - await testLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', false); - await testLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', true); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', false); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', true); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); let document = await workspace.openTextDocument(fileToOpen); let cfg = PythonSettings.getInstance(document.uri); assert.equal(cfg.linting.pylintEnabled, true, 'Pylint should be enabled in workspace'); PythonSettings.dispose(); - await testLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', true); - await testLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', false); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', true); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', false); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); document = await workspace.openTextDocument(fileToOpen); cfg = PythonSettings.getInstance(document.uri); assert.equal(cfg.linting.pylintEnabled, false, 'Pylint should not be enabled in workspace'); }); + // tslint:disable-next-line:no-invalid-template-strings test('${workspaceRoot} variable in settings should be replaced with the right value', async () => { const workspace2Uri = Uri.file(path.join(multirootPath, 'workspace2')); let fileToOpen = path.join(workspace2Uri.fsPath, 'file.py'); diff --git a/src/test/format/extension.sort.test.ts b/src/test/format/extension.sort.test.ts index 31ddcf26b1f4..f5768eb0da99 100644 --- a/src/test/format/extension.sort.test.ts +++ b/src/test/format/extension.sort.test.ts @@ -1,21 +1,11 @@ -import { updateSetting } from '../common'; - -// Note: This example test is leveraging the Mocha test framework. -// Please refer to their documentation on https://mochajs.org/ for help. - - -// The module 'assert' provides assertion methods from node import * as assert from 'assert'; - -// You can import and use all API from the 'vscode' module -// as well as import your extension to test it -import * as vscode from 'vscode'; -import * as path from 'path'; -import * as settings from '../../client/common/configSettings'; import * as fs from 'fs'; import { EOL } from 'os'; +import * as path from 'path'; +import { commands, ConfigurationTarget, Position, Range, Uri, window, workspace } from 'vscode'; import { PythonImportSortProvider } from '../../client/providers/importSortProvider'; -import { initialize, IS_TRAVIS, closeActiveWindows, initializeTest } from '../initialize'; +import { updateSetting } from '../common'; +import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST } from '../initialize'; const sortingPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'sorting'); const fileToFormatWithoutConfig = path.join(sortingPath, 'noconfig', 'before.py'); @@ -26,142 +16,90 @@ const fileToFormatWithConfig1 = path.join(sortingPath, 'withconfig', 'before.1.p const originalFileToFormatWithConfig1 = path.join(sortingPath, 'withconfig', 'original.1.py'); const extensionDir = path.join(__dirname, '..', '..', '..'); +// tslint:disable-next-line:max-func-body-length suite('Sorting', () => { - suiteSetup(() => initialize()); - setup(() => initializeTest()); + const configTarget = IS_MULTI_ROOT_TEST ? ConfigurationTarget.WorkspaceFolder : ConfigurationTarget.Workspace; + suiteSetup(initialize); + setup(initializeTest); suiteTeardown(async () => { fs.writeFileSync(fileToFormatWithConfig, fs.readFileSync(originalFileToFormatWithConfig)); fs.writeFileSync(fileToFormatWithConfig1, fs.readFileSync(originalFileToFormatWithConfig1)); fs.writeFileSync(fileToFormatWithoutConfig, fs.readFileSync(originalFileToFormatWithoutConfig)); - await updateSetting('sortImports.args', [], vscode.Uri.file(sortingPath), vscode.ConfigurationTarget.Workspace); + await updateSetting('sortImports.args', [], Uri.file(sortingPath), configTarget); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); await closeActiveWindows(); }); setup(async () => { fs.writeFileSync(fileToFormatWithConfig, fs.readFileSync(originalFileToFormatWithConfig)); fs.writeFileSync(fileToFormatWithoutConfig, fs.readFileSync(originalFileToFormatWithoutConfig)); fs.writeFileSync(fileToFormatWithConfig1, fs.readFileSync(originalFileToFormatWithConfig1)); - await updateSetting('sortImports.args', [], vscode.Uri.file(sortingPath), vscode.ConfigurationTarget.Workspace); + await updateSetting('sortImports.args', [], Uri.file(sortingPath), configTarget); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); await closeActiveWindows(); }); - test('Without Config', done => { - let textEditor: vscode.TextEditor; - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileToFormatWithoutConfig).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - textEditor = editor; - assert(vscode.window.activeTextEditor, 'No active editor'); - const sorter = new PythonImportSortProvider(); - return sorter.sortImports(extensionDir, textDocument); - }).then(edits => { - assert.equal(edits.filter(value => value.newText === EOL && value.range.isEqual(new vscode.Range(2, 0, 2, 0))).length, 1, 'EOL not found'); - assert.equal(edits.filter(value => value.newText === '' && value.range.isEqual(new vscode.Range(3, 0, 4, 0))).length, 1, '"" not found'); - assert.equal(edits.filter(value => value.newText === `from rope.base import libutils${EOL}from rope.refactor.extract import ExtractMethod, ExtractVariable${EOL}from rope.refactor.rename import Rename${EOL}` && value.range.isEqual(new vscode.Range(6, 0, 6, 0))).length, 1, 'Text not found'); - assert.equal(edits.filter(value => value.newText === '' && value.range.isEqual(new vscode.Range(13, 0, 18, 0))).length, 1, '"" not found'); - }).then(done, done); + test('Without Config', async () => { + const textDocument = await workspace.openTextDocument(fileToFormatWithoutConfig); + await window.showTextDocument(textDocument); + const sorter = new PythonImportSortProvider(); + const edits = await sorter.sortImports(extensionDir, textDocument); + assert.equal(edits.filter(value => value.newText === EOL && value.range.isEqual(new Range(2, 0, 2, 0))).length, 1, 'EOL not found'); + assert.equal(edits.filter(value => value.newText === '' && value.range.isEqual(new Range(3, 0, 4, 0))).length, 1, '"" not found'); + assert.equal(edits.filter(value => value.newText === `from rope.base import libutils${EOL}from rope.refactor.extract import ExtractMethod, ExtractVariable${EOL}from rope.refactor.rename import Rename${EOL}` && value.range.isEqual(new Range(6, 0, 6, 0))).length, 1, 'Text not found'); + assert.equal(edits.filter(value => value.newText === '' && value.range.isEqual(new Range(13, 0, 18, 0))).length, 1, '"" not found'); }); - test('Without Config (via Command)', done => { - let textEditor: vscode.TextEditor; - let textDocument: vscode.TextDocument; - let originalContent = ''; - vscode.workspace.openTextDocument(fileToFormatWithoutConfig).then(document => { - textDocument = document; - originalContent = textDocument.getText(); - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - textEditor = editor; - return vscode.commands.executeCommand('python.sortImports'); - }).then(() => { - assert.notEqual(originalContent, textDocument.getText(), 'Contents have not changed'); - }).then(done, done); + test('Without Config (via Command)', async () => { + const textDocument = await workspace.openTextDocument(fileToFormatWithoutConfig); + const originalContent = textDocument.getText(); + await window.showTextDocument(textDocument); + await commands.executeCommand('python.sortImports'); + assert.notEqual(originalContent, textDocument.getText(), 'Contents have not changed'); }); - test('With Config', done => { - let textEditor: vscode.TextEditor; - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileToFormatWithConfig).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - textEditor = editor; - const sorter = new PythonImportSortProvider(); - return sorter.sortImports(extensionDir, textDocument); - }).then(edits => { - const newValue = `from third_party import lib2${EOL}from third_party import lib3${EOL}from third_party import lib4${EOL}from third_party import lib5${EOL}from third_party import lib6${EOL}from third_party import lib7${EOL}from third_party import lib8${EOL}from third_party import lib9${EOL}`; - assert.equal(edits.filter(value => value.newText === newValue && value.range.isEqual(new vscode.Range(0, 0, 3, 0))).length, 1, 'New Text not found'); - }).then(done, done); + test('With Config', async () => { + const textDocument = await workspace.openTextDocument(fileToFormatWithConfig); + await window.showTextDocument(textDocument); + const sorter = new PythonImportSortProvider(); + const edits = await sorter.sortImports(extensionDir, textDocument); + const newValue = `from third_party import lib2${EOL}from third_party import lib3${EOL}from third_party import lib4${EOL}from third_party import lib5${EOL}from third_party import lib6${EOL}from third_party import lib7${EOL}from third_party import lib8${EOL}from third_party import lib9${EOL}`; + assert.equal(edits.filter(value => value.newText === newValue && value.range.isEqual(new Range(0, 0, 3, 0))).length, 1, 'New Text not found'); }); - test('With Config (via Command)', done => { - let textEditor: vscode.TextEditor; - let textDocument: vscode.TextDocument; - let originalContent = ''; - vscode.workspace.openTextDocument(fileToFormatWithConfig).then(document => { - textDocument = document; - originalContent = document.getText(); - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - textEditor = editor; - return vscode.commands.executeCommand('python.sortImports'); - }).then(() => { - assert.notEqual(originalContent, textDocument.getText(), 'Contents have not changed'); - }).then(done, done); + test('With Config (via Command)', async () => { + const textDocument = await workspace.openTextDocument(fileToFormatWithConfig); + const originalContent = textDocument.getText(); + await window.showTextDocument(textDocument); + await commands.executeCommand('python.sortImports'); + assert.notEqual(originalContent, textDocument.getText(), 'Contents have not changed'); }); - // Doesn't always work on Travis !?! - if (!IS_TRAVIS) { - test('With Changes and Config in Args', done => { - let textEditor: vscode.TextEditor; - let textDocument: vscode.TextDocument; - updateSetting('sortImports.args', ['-sp', path.join(sortingPath, 'withconfig')], vscode.Uri.file(sortingPath), vscode.ConfigurationTarget.Workspace) - .then(() => vscode.workspace.openTextDocument(fileToFormatWithConfig)) - .then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - textEditor = editor; - return editor.edit(editor => { - editor.insert(new vscode.Position(0, 0), 'from third_party import lib0' + EOL); - }); - }).then(() => { - const sorter = new PythonImportSortProvider(); - return sorter.sortImports(extensionDir, textDocument); - }).then(edits => { - const newValue = `from third_party import lib2${EOL}from third_party import lib3${EOL}from third_party import lib4${EOL}from third_party import lib5${EOL}from third_party import lib6${EOL}from third_party import lib7${EOL}from third_party import lib8${EOL}from third_party import lib9${EOL}`; - assert.equal(edits.length, 1, 'Incorrect number of edits'); - assert.equal(edits[0].newText, newValue, 'New Value is not the same'); - assert.equal(`${edits[0].range.start.line},${edits[0].range.start.character}`, '1,0', 'Start position is not the same'); - assert.equal(`${edits[0].range.end.line},${edits[0].range.end.character}`, '4,0', 'End position is not the same'); - }).then(done, done); + test('With Changes and Config in Args', async () => { + await updateSetting('sortImports.args', ['-sp', path.join(sortingPath, 'withconfig')], Uri.file(sortingPath), ConfigurationTarget.Workspace); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); + const textDocument = await workspace.openTextDocument(fileToFormatWithConfig); + const editor = await window.showTextDocument(textDocument); + await editor.edit(builder => { + builder.insert(new Position(0, 0), `from third_party import lib0${EOL}`); + }); + const sorter = new PythonImportSortProvider(); + const edits = await sorter.sortImports(extensionDir, textDocument); + assert.notEqual(edits.length, 0, 'No edits'); + }); + test('With Changes and Config in Args (via Command)', async () => { + await updateSetting('sortImports.args', ['-sp', path.join(sortingPath, 'withconfig')], Uri.file(sortingPath), configTarget); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); + const textDocument = await workspace.openTextDocument(fileToFormatWithConfig); + const editor = await window.showTextDocument(textDocument); + await editor.edit(builder => { + builder.insert(new Position(0, 0), `from third_party import lib0${EOL}`); }); - } - test('With Changes and Config in Args (via Command)', done => { - let textEditor: vscode.TextEditor; - let textDocument: vscode.TextDocument; - let originalContent = ''; - updateSetting('sortImports.args', ['-sp', path.join(sortingPath, 'withconfig')], vscode.Uri.file(sortingPath), vscode.ConfigurationTarget.Workspace) - .then(() => vscode.workspace.openTextDocument(fileToFormatWithConfig)) - .then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - textEditor = editor; - return editor.edit(editor => { - editor.insert(new vscode.Position(0, 0), 'from third_party import lib0' + EOL); - }); - }).then(() => { - originalContent = textDocument.getText(); - return vscode.commands.executeCommand('python.sortImports'); - }).then(edits => { - assert.notEqual(originalContent, textDocument.getText(), 'Contents have not changed'); - }).then(done, done); + const originalContent = textDocument.getText(); + await commands.executeCommand('python.sortImports'); + assert.notEqual(originalContent, textDocument.getText(), 'Contents have not changed'); }); }); diff --git a/src/test/linters/lint.test.ts b/src/test/linters/lint.test.ts index 66d45a924fee..de989eab0355 100644 --- a/src/test/linters/lint.test.ts +++ b/src/test/linters/lint.test.ts @@ -158,9 +158,7 @@ suite('Linting', () => { } async function testEnablingDisablingOfLinter(linter: baseLinter.BaseLinter, setting: PythonSettingKeys, enabled: boolean, output: MockOutputChannel) { await updateSetting(setting, enabled, rootWorkspaceUri, IS_MULTI_ROOT_TEST ? vscode.ConfigurationTarget.WorkspaceFolder : vscode.ConfigurationTarget.Workspace); - // tslint:disable-next-line:await-promise const document = await vscode.workspace.openTextDocument(fileToLint); - // tslint:disable-next-line:await-promise const editor = await vscode.window.showTextDocument(document); const cancelToken = new vscode.CancellationTokenSource(); const messages = await linter.lint(editor.document, cancelToken.token); @@ -214,9 +212,7 @@ suite('Linting', () => { const settingToEnable = SettingToDisableProduct.get(linter.product); // tslint:disable-next-line:no-any prefer-type-cast await updateSetting(settingToEnable as any, true, rootWorkspaceUri, IS_MULTI_ROOT_TEST ? vscode.ConfigurationTarget.WorkspaceFolder : vscode.ConfigurationTarget.Workspace); - // tslint:disable-next-line:await-promise const document = await vscode.workspace.openTextDocument(pythonFile); - // tslint:disable-next-line:await-promise const editor = await vscode.window.showTextDocument(document); const messages = await linter.lint(editor.document, cancelToken.token); if (messagesToBeReceived.length === 0) { diff --git a/tslint.json b/tslint.json index 4c7de3c44263..471defc8f86c 100644 --- a/tslint.json +++ b/tslint.json @@ -31,6 +31,7 @@ "allow-undefined-union", "allow-string", "allow-number" - ] + ], + "await-promise": [true, "Thenable"] } } From d137b7561417c92f018fab4c7497ba10d6b211bf Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Oct 2017 16:21:27 -0700 Subject: [PATCH 16/43] fixed test for interpreter display --- src/client/interpreter/display/index.ts | 32 +++++++++++++------------ 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/client/interpreter/display/index.ts b/src/client/interpreter/display/index.ts index de114a51faf5..6ce8fdc4be93 100644 --- a/src/client/interpreter/display/index.ts +++ b/src/client/interpreter/display/index.ts @@ -1,16 +1,16 @@ 'use strict'; -import * as path from 'path'; -import * as utils from '../../common/utils'; import * as child_process from 'child_process'; -import { StatusBarItem, Disposable } from 'vscode'; -import { PythonSettings } from '../../common/configSettings'; import { EOL } from 'os'; +import * as path from 'path'; +import { Disposable, StatusBarItem } from 'vscode'; +import { PythonSettings } from '../../common/configSettings'; +import * as utils from '../../common/utils'; import { IInterpreterLocatorService } from '../contracts'; +import { getFirstNonEmptyLineFromMultilineString } from '../helpers'; import { IInterpreterVersionService } from '../interpreterVersion'; import { VirtualEnvironmentManager } from '../virtualEnvs/index'; -import { getFirstNonEmptyLineFromMultilineString } from '../helpers'; -const settings = PythonSettings.getInstance(); +// tslint:disable-next-line:completed-docs export class InterpreterDisplay implements Disposable { constructor(private statusBar: StatusBarItem, private interpreterLocator: IInterpreterLocatorService, @@ -19,12 +19,13 @@ export class InterpreterDisplay implements Disposable { this.statusBar.command = 'python.setInterpreter'; } public dispose() { + // } public async refresh() { - const pythonPath = await this.getFullyQualifiedPathToInterpreter(settings.pythonPath); + const pythonPath = await this.getFullyQualifiedPathToInterpreter(PythonSettings.getInstance().pythonPath); await this.updateDisplay(pythonPath); } - private getInterpreters() { + private async getInterpreters() { return this.interpreterLocator.getInterpreters(); } private async updateDisplay(pythonPath: string) { @@ -34,6 +35,7 @@ export class InterpreterDisplay implements Disposable { this.statusBar.color = ''; this.statusBar.tooltip = pythonPath; if (interpreter) { + // tslint:disable-next-line:no-non-null-assertion this.statusBar.text = interpreter.displayName!; if (interpreter.companyDisplayName) { const toolTipSuffix = `${EOL}${interpreter.companyDisplayName}`; @@ -42,10 +44,11 @@ export class InterpreterDisplay implements Disposable { } else { const defaultDisplayName = `${path.basename(pythonPath)} [Environment]`; - const interpreterExists = utils.fsExistsAsync(pythonPath); - const displayName = this.versionProvider.getVersion(pythonPath, defaultDisplayName); - const virtualEnvName = this.getVirtualEnvironmentName(pythonPath); - await Promise.all([interpreterExists, displayName, virtualEnvName]) + await Promise.all([ + utils.fsExistsAsync(pythonPath), + this.versionProvider.getVersion(pythonPath, defaultDisplayName), + this.getVirtualEnvironmentName(pythonPath) + ]) .then(([interpreterExists, displayName, virtualEnvName]) => { const dislayNameSuffix = virtualEnvName.length > 0 ? ` (${virtualEnvName})` : ''; this.statusBar.text = `${displayName}${dislayNameSuffix}`; @@ -63,13 +66,12 @@ export class InterpreterDisplay implements Disposable { .detect(pythonPath) .then(env => env ? env.name : ''); } - private getFullyQualifiedPathToInterpreter(pythonPath: string) { + private async getFullyQualifiedPathToInterpreter(pythonPath: string) { return new Promise(resolve => { - child_process.execFile(pythonPath, ["-c", "import sys;print(sys.executable)"], (_, stdout) => { + child_process.execFile(pythonPath, ['-c', 'import sys;print(sys.executable)'], (_, stdout) => { resolve(getFirstNonEmptyLineFromMultilineString(stdout)); }); }) .then(value => value.length === 0 ? pythonPath : value); } } - From 65076f8d1da0fa8dc0310840ff935d88d40d1372 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Oct 2017 16:32:07 -0700 Subject: [PATCH 17/43] undo commenting of code --- src/test/jupyter/jupyter.codeHelper.test.ts | 174 ++++++++++---------- 1 file changed, 87 insertions(+), 87 deletions(-) diff --git a/src/test/jupyter/jupyter.codeHelper.test.ts b/src/test/jupyter/jupyter.codeHelper.test.ts index ecf40d851e6f..60643f2d6b2b 100644 --- a/src/test/jupyter/jupyter.codeHelper.test.ts +++ b/src/test/jupyter/jupyter.codeHelper.test.ts @@ -1,95 +1,95 @@ -// import * as assert from 'assert'; -// import * as vscode from 'vscode'; -// import * as path from 'path'; -// import { initialize, closeActiveWindows, initializeTest } from './../initialize'; -// import { CodeHelper } from '../../client/jupyter/common/codeHelper'; -// import { JupyterCodeLensProvider } from '../../client/jupyter/editorIntegration/codeLensProvider'; +import * as assert from 'assert'; +import * as vscode from 'vscode'; +import * as path from 'path'; +import { initialize, closeActiveWindows, initializeTest } from './../initialize'; +import { CodeHelper } from '../../client/jupyter/common/codeHelper'; +import { JupyterCodeLensProvider } from '../../client/jupyter/editorIntegration/codeLensProvider'; -// const FILE_WITH_CELLS = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'jupyter', 'cells.py'); +const FILE_WITH_CELLS = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'jupyter', 'cells.py'); -// suite('Jupyter Code Helper', () => { -// suiteSetup(() => initialize()); -// setup(() => initializeTest()); -// teardown(() => closeActiveWindows()); -// const codeLensProvider = new JupyterCodeLensProvider(); -// const codeHelper = new CodeHelper(codeLensProvider); +suite('Jupyter Code Helper', () => { + suiteSetup(() => initialize()); + setup(() => initializeTest()); + teardown(() => closeActiveWindows()); + const codeLensProvider = new JupyterCodeLensProvider(); + const codeHelper = new CodeHelper(codeLensProvider); -// test('Get Line (without any selection)', done => { -// let textDocument: vscode.TextDocument; -// vscode.workspace.openTextDocument(FILE_WITH_CELLS).then(document => { -// textDocument = document; -// return vscode.window.showTextDocument(textDocument); -// }).then(editor => { -// assert(vscode.window.activeTextEditor, 'No active editor'); -// editor.selection = new vscode.Selection(2, 0, 2, 0); -// return codeHelper.getSelectedCode().then(code => { -// assert.equal(code, textDocument.lineAt(2).text, 'Text is not the same'); -// }); -// }).then(done, done); -// }); + test('Get Line (without any selection)', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(FILE_WITH_CELLS).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + editor.selection = new vscode.Selection(2, 0, 2, 0); + return codeHelper.getSelectedCode().then(code => { + assert.equal(code, textDocument.lineAt(2).text, 'Text is not the same'); + }); + }).then(done, done); + }); -// test('Get Selected Text', done => { -// let textDocument: vscode.TextDocument; -// vscode.workspace.openTextDocument(FILE_WITH_CELLS).then(document => { -// textDocument = document; -// return vscode.window.showTextDocument(textDocument); -// }).then(editor => { -// assert(vscode.window.activeTextEditor, 'No active editor'); -// editor.selection = new vscode.Selection(2, 7, 2, 10); -// return codeHelper.getSelectedCode().then(code => { -// assert.equal(code, textDocument.getText(editor.selection), 'Text is not the same'); -// }); -// }).then(done, done); -// }); + test('Get Selected Text', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(FILE_WITH_CELLS).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + editor.selection = new vscode.Selection(2, 7, 2, 10); + return codeHelper.getSelectedCode().then(code => { + assert.equal(code, textDocument.getText(editor.selection), 'Text is not the same'); + }); + }).then(done, done); + }); -// test('Get Selected Line', done => { -// let textDocument: vscode.TextDocument; -// vscode.workspace.openTextDocument(FILE_WITH_CELLS).then(document => { -// textDocument = document; -// return vscode.window.showTextDocument(textDocument); -// }).then(editor => { -// assert(vscode.window.activeTextEditor, 'No active editor'); -// editor.selection = new vscode.Selection(2, 0, 2, 31); -// return codeHelper.getSelectedCode().then(code => { -// assert.equal(code, textDocument.getText(editor.selection), 'Text is not the same'); -// assert.equal(code, textDocument.lineAt(2).text, 'Text is not the same as the line contents'); -// }); -// }).then(done, done); -// }); + test('Get Selected Line', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(FILE_WITH_CELLS).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + editor.selection = new vscode.Selection(2, 0, 2, 31); + return codeHelper.getSelectedCode().then(code => { + assert.equal(code, textDocument.getText(editor.selection), 'Text is not the same'); + assert.equal(code, textDocument.lineAt(2).text, 'Text is not the same as the line contents'); + }); + }).then(done, done); + }); -// test('Get Selected Text (multi-line)', done => { -// let textDocument: vscode.TextDocument; -// vscode.workspace.openTextDocument(FILE_WITH_CELLS).then(document => { -// textDocument = document; -// return vscode.window.showTextDocument(textDocument); -// }).then(editor => { -// assert(vscode.window.activeTextEditor, 'No active editor'); -// editor.selection = new vscode.Selection(2, 0, 4, 18); -// return codeHelper.getSelectedCode().then(code => { -// assert.equal(code, textDocument.getText(editor.selection), 'Text is not the same'); -// }); -// }).then(done, done); -// }); + test('Get Selected Text (multi-line)', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(FILE_WITH_CELLS).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + editor.selection = new vscode.Selection(2, 0, 4, 18); + return codeHelper.getSelectedCode().then(code => { + assert.equal(code, textDocument.getText(editor.selection), 'Text is not the same'); + }); + }).then(done, done); + }); -// test('Get Code Block (for in)', done => { -// let textDocument: vscode.TextDocument; -// vscode.workspace.openTextDocument(FILE_WITH_CELLS).then(document => { -// textDocument = document; -// return vscode.window.showTextDocument(textDocument); -// }).then(editor => { -// assert(vscode.window.activeTextEditor, 'No active editor'); -// editor.selection = new vscode.Selection(16, 0, 16, 0); -// return codeHelper.getSelectedCode().then(code => { -// const end = textDocument.lineAt(18).range.end; -// const start = editor.selection.start; -// assert.equal(code, textDocument.getText(new vscode.Range(start, end)), 'Text is not the same'); -// }); -// }).then(done, done); -// }); + test('Get Code Block (for in)', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(FILE_WITH_CELLS).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + editor.selection = new vscode.Selection(16, 0, 16, 0); + return codeHelper.getSelectedCode().then(code => { + const end = textDocument.lineAt(18).range.end; + const start = editor.selection.start; + assert.equal(code, textDocument.getText(new vscode.Range(start, end)), 'Text is not the same'); + }); + }).then(done, done); + }); -// // Todo: -// // Add if blocks -// // Add parameters being broken into multiple lines -// // e.g. x = doSomething(1,2, -// // 4,5) -// }); + // Todo: + // Add if blocks + // Add parameters being broken into multiple lines + // e.g. x = doSomething(1,2, + // 4,5) +}); From 630f83ac3592f24bb0c6e7af0964aff2119946ad Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Oct 2017 16:32:31 -0700 Subject: [PATCH 18/43] add new line --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ad74be556908..a5334358937a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,4 @@ node_modules .vscode-test __pycache__ npm-debug.log -**/.mypy_cache/** \ No newline at end of file +**/.mypy_cache/** From 0dad286524601629fb2a6bab41bd1bb7a416a117 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 18 Oct 2017 15:29:10 -0700 Subject: [PATCH 19/43] support multi root in interpreter display --- package.json | 2 +- src/client/extension.ts | 1 + src/client/interpreter/contracts.ts | 16 ++- src/client/interpreter/display/index.ts | 12 +- src/client/interpreter/helpers.ts | 65 ++++++++++ src/client/interpreter/index.ts | 111 +++++++++++----- src/client/interpreter/locators/index.ts | 26 ++-- .../locators/services/KnownPathsService.ts | 22 ++-- .../locators/services/condaEnvFileService.ts | 16 ++- .../locators/services/condaEnvService.ts | 64 ++++----- .../locators/services/currentPathService.ts | 31 ++--- .../locators/services/virtualEnvService.ts | 32 +++-- .../services/windowsRegistryService.ts | 36 ++++-- .../providers/setInterpreterProvider.ts | 121 ++++++++++++------ .../common/configSettings.multiroot.test.ts | 4 +- .../interpreters/display.multiroot.test.ts | 55 ++++++++ src/test/interpreters/display.test.ts | 41 +++--- tslint.json | 6 +- 18 files changed, 461 insertions(+), 200 deletions(-) create mode 100644 src/test/interpreters/display.multiroot.test.ts diff --git a/package.json b/package.json index c4be011f6f9f..7f3c85d58b63 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "theme": "dark" }, "engines": { - "vscode": "^1.17.0" + "vscode": "^1.18.0" }, "recommendations": [ "donjayamanne.jupyter" diff --git a/src/client/extension.ts b/src/client/extension.ts index 8eebcbf0e8dd..1e87f77f29b9 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -66,6 +66,7 @@ export async function activate(context: vscode.ExtensionContext) { sortImports.activate(context, formatOutChannel); const interpreterManager = new InterpreterManager(); await interpreterManager.autoSetInterpreter(); + await interpreterManager.refresh(); context.subscriptions.push(interpreterManager); context.subscriptions.push(new SetInterpreterProvider(interpreterManager)); context.subscriptions.push(...activateExecInTerminalProvider()); diff --git a/src/client/interpreter/contracts.ts b/src/client/interpreter/contracts.ts index cb5120a549e0..ccf1b3d4dc4e 100644 --- a/src/client/interpreter/contracts.ts +++ b/src/client/interpreter/contracts.ts @@ -1,12 +1,20 @@ -import { Architecture } from "../common/registry"; +import { ConfigurationTarget, Uri } from 'vscode'; +import { Architecture } from '../common/registry'; export interface IInterpreterLocatorService { - getInterpreters(): Promise; + getInterpreters(resource?: Uri): Promise; } -export interface PythonInterpreter { + +export type PythonInterpreter = { path: string; companyDisplayName?: string; displayName?: string; version?: string; architecture?: Architecture; -} +}; + +export type WorkspacePythonPath = { + folderUri: Uri; + pytonPath?: string; + configTarget: ConfigurationTarget.Workspace | ConfigurationTarget.WorkspaceFolder; +}; diff --git a/src/client/interpreter/display/index.ts b/src/client/interpreter/display/index.ts index 6ce8fdc4be93..f7d5cf81810e 100644 --- a/src/client/interpreter/display/index.ts +++ b/src/client/interpreter/display/index.ts @@ -6,7 +6,7 @@ import { Disposable, StatusBarItem } from 'vscode'; import { PythonSettings } from '../../common/configSettings'; import * as utils from '../../common/utils'; import { IInterpreterLocatorService } from '../contracts'; -import { getFirstNonEmptyLineFromMultilineString } from '../helpers'; +import { getActiveWorkspaceUri, getFirstNonEmptyLineFromMultilineString } from '../helpers'; import { IInterpreterVersionService } from '../interpreterVersion'; import { VirtualEnvironmentManager } from '../virtualEnvs/index'; @@ -16,13 +16,18 @@ export class InterpreterDisplay implements Disposable { private interpreterLocator: IInterpreterLocatorService, private virtualEnvMgr: VirtualEnvironmentManager, private versionProvider: IInterpreterVersionService) { + this.statusBar.command = 'python.setInterpreter'; } public dispose() { // } public async refresh() { - const pythonPath = await this.getFullyQualifiedPathToInterpreter(PythonSettings.getInstance().pythonPath); + const wkspc = getActiveWorkspaceUri(); + if (!wkspc) { + return; + } + const pythonPath = await this.getFullyQualifiedPathToInterpreter(PythonSettings.getInstance(wkspc.folderUri).pythonPath); await this.updateDisplay(pythonPath); } private async getInterpreters() { @@ -72,6 +77,7 @@ export class InterpreterDisplay implements Disposable { resolve(getFirstNonEmptyLineFromMultilineString(stdout)); }); }) - .then(value => value.length === 0 ? pythonPath : value); + .then(value => value.length === 0 ? pythonPath : value) + .catch(() => pythonPath); } } diff --git a/src/client/interpreter/helpers.ts b/src/client/interpreter/helpers.ts index 7bd180b5b067..f1371ca40922 100644 --- a/src/client/interpreter/helpers.ts +++ b/src/client/interpreter/helpers.ts @@ -1,3 +1,6 @@ +import { ConfigurationTarget, window, workspace } from 'vscode'; +import { WorkspacePythonPath } from './contracts'; + export function getFirstNonEmptyLineFromMultilineString(stdout: string) { if (!stdout) { return ''; @@ -5,3 +8,65 @@ export function getFirstNonEmptyLineFromMultilineString(stdout: string) { const lines = stdout.split(/\r?\n/g).map(line => line.trim()).filter(line => line.length > 0); return lines.length > 0 ? lines[0] : ''; } +export function getActiveWorkspaceUri(): WorkspacePythonPath | undefined { + if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0) { + return undefined; + } + if (workspace.workspaceFolders.length === 1) { + return { folderUri: workspace.workspaceFolders[0].uri, configTarget: ConfigurationTarget.Workspace }; + } + if (window.activeTextEditor) { + const workspaceFolder = workspace.getWorkspaceFolder(window.activeTextEditor.document.uri); + if (workspaceFolder) { + return { configTarget: ConfigurationTarget.WorkspaceFolder, folderUri: workspaceFolder.uri }; + } + } + return undefined; +} + +export function getInterpretersForEachFolderAndWorkspace(): WorkspacePythonPath[] { + if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0) { + return []; + } + const value = workspace.getConfiguration('python').inspect('pythonPath'); + const workspacePythonPath = value && typeof value.workspaceValue === 'string' ? value.workspaceValue : undefined; + + if (workspace.workspaceFolders.length === 1) { + if (workspacePythonPath) { + return [{ + folderUri: workspace.workspaceFolders[0].uri, + pytonPath: workspacePythonPath, + configTarget: ConfigurationTarget.Workspace + }]; + } + else { + return []; + } + } + + const workspaceConfig: WorkspacePythonPath[] = workspacePythonPath ? [{ + folderUri: workspace.workspaceFolders[0].uri, + pytonPath: workspacePythonPath, + configTarget: ConfigurationTarget.Workspace + }] : []; + + return workspace.workspaceFolders.reduce((accumulator, folder) => { + // tslint:disable-next-line:no-backbone-get-set-outside-model + const folderValue = workspace.getConfiguration('python', folder.uri).inspect('pythonPath'); + + if (folderValue && typeof folderValue.workspaceFolderValue === 'string' && + folderValue.workspaceFolderValue !== workspacePythonPath && + accumulator.findIndex(item => item.pytonPath === folderValue.workspaceFolderValue) === -1) { + + const info: WorkspacePythonPath = { + folderUri: folder.uri, + pytonPath: folderValue.workspaceFolderValue, + configTarget: ConfigurationTarget.WorkspaceFolder + }; + + accumulator.push(info); + } + + return accumulator; + }, workspaceConfig); +} diff --git a/src/client/interpreter/index.ts b/src/client/interpreter/index.ts index d2eb6fd9e71d..ca0da1c886fd 100644 --- a/src/client/interpreter/index.ts +++ b/src/client/interpreter/index.ts @@ -1,14 +1,16 @@ 'use strict'; -import { InterpreterVersionService } from './interpreterVersion'; -import { VirtualEnv } from './virtualEnvs/virtualEnv'; -import { VEnv } from './virtualEnvs/venv'; -import { Disposable, window, StatusBarAlignment, workspace } from 'vscode'; +import * as path from 'path'; +import { ConfigurationTarget, Disposable, StatusBarAlignment, Uri, window, workspace } from 'vscode'; import { PythonSettings } from '../common/configSettings'; +import { IS_WINDOWS } from '../common/utils'; +import { WorkspacePythonPath } from './contracts'; import { InterpreterDisplay } from './display'; +import { getActiveWorkspaceUri } from './helpers'; +import { InterpreterVersionService } from './interpreterVersion'; import { PythonInterpreterLocatorService } from './locators'; import { VirtualEnvironmentManager } from './virtualEnvs/index'; -import { IS_WINDOWS } from '../common/utils'; -import * as path from 'path'; +import { VEnv } from './virtualEnvs/venv'; +import { VirtualEnv } from './virtualEnvs/virtualEnv'; const settings = PythonSettings.getInstance(); @@ -22,22 +24,28 @@ export class InterpreterManager implements Disposable { this.interpreterProvider = new PythonInterpreterLocatorService(virtualEnvMgr); const versionService = new InterpreterVersionService(); this.display = new InterpreterDisplay(statusBar, this.interpreterProvider, virtualEnvMgr, versionService); - settings.addListener('change', this.onConfigChanged.bind(this)); - this.display.refresh(); + settings.addListener('change', () => this.onConfigChanged()); this.disposables.push(statusBar); this.disposables.push(this.display); } - public getInterpreters() { - return this.interpreterProvider.getInterpreters(); + public async refresh() { + return this.display.refresh(); + } + public getInterpreters(resource?: Uri) { + return this.interpreterProvider.getInterpreters(resource); } public async autoSetInterpreter() { if (!this.shouldAutoSetInterpreter()) { return; } - const interpreters = await this.interpreterProvider.getInterpreters(); - const rootPath = workspace.rootPath!.toUpperCase(); - const interpretersInWorkspace = interpreters.filter(interpreter => interpreter.path.toUpperCase().startsWith(rootPath)); + const activeWorkspace = getActiveWorkspaceUri(); + if (!activeWorkspace) { + return; + } + const interpreters = await this.interpreterProvider.getInterpreters(activeWorkspace.folderUri); + const workspacePathUpper = activeWorkspace.folderUri.fsPath.toUpperCase(); + const interpretersInWorkspace = interpreters.filter(interpreter => interpreter.path.toUpperCase().startsWith(workspacePathUpper)); if (interpretersInWorkspace.length !== 1) { return; } @@ -46,45 +54,78 @@ export class InterpreterManager implements Disposable { // In windows the interpreter is under scripts/python.exe on linux it is under bin/python. // Meaning the sub directory must be either scripts, bin or other (but only one level deep). const pythonPath = interpretersInWorkspace[0].path; - const relativePath = path.dirname(pythonPath).substring(workspace.rootPath!.length); + const relativePath = path.dirname(pythonPath).substring(activeWorkspace.folderUri.fsPath.length); if (relativePath.split(path.sep).filter(l => l.length > 0).length === 2) { - this.setPythonPath(pythonPath); + await this.setPythonPath(pythonPath, activeWorkspace); } } - public setPythonPath(pythonPath: string) { - pythonPath = IS_WINDOWS ? pythonPath.replace(/\\/g, "/") : pythonPath; - if (pythonPath.startsWith(workspace.rootPath!)) { - pythonPath = path.join('${workspaceRoot}', path.relative(workspace.rootPath!, pythonPath)); - } - const pythonConfig = workspace.getConfiguration('python'); - let global = null; - if (typeof workspace.rootPath !== 'string') { - global = true; + /** + * Sets the python path in the settings. + * @param {string} pythonPath + * @param {WorkspacePythonPath} [workspacePythonPath] If this is not passed, then user setting will be updated + * @returns {Promise} + * @memberof InterpreterManager + */ + public async setPythonPath(pythonPath: string, workspacePythonPath?: WorkspacePythonPath): Promise { + pythonPath = IS_WINDOWS ? pythonPath.replace(/\\/g, '/') : pythonPath; + const isMultiRootWorkspace = Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length > 1; + try { + if (!workspacePythonPath) { + return await this.setPythonPathInUserSettings(pythonPath); + } + if (!isMultiRootWorkspace) { + return await this.setPythonPathInSingleWorkspace(pythonPath); + } + await this.setPythonPathInWorkspace(pythonPath, workspacePythonPath.configTarget, workspacePythonPath.folderUri); } - pythonConfig.update('pythonPath', pythonPath, global).then(() => { - //Done - }, reason => { - window.showErrorMessage(`Failed to set 'pythonPath'. Error: ${reason.message}`); + catch (reason) { + // tslint:disable-next-line:no-unsafe-any prefer-type-cast + const message = reason && typeof reason.message === 'string' ? reason.message as string : ''; + window.showErrorMessage(`Failed to set 'pythonPath'. Error: ${message}`); console.error(reason); - }); + } } - - public dispose() { - this.disposables.forEach(disposable => disposable.dispose()); + public dispose(): void { + // tslint:disable-next-line:prefer-type-cast + this.disposables.forEach(disposable => disposable.dispose() as void); this.display = null; } + private async setPythonPathInUserSettings(pythonPath) { + const pythonConfig = workspace.getConfiguration('python'); + return pythonConfig.update('pythonPath', pythonPath, true); + } + private async setPythonPathInSingleWorkspace(pythonPath: string) { + const pythonConfig = workspace.getConfiguration('python'); + // tslint:disable-next-line:no-non-null-assertion + const workspacePath = workspace.workspaceFolders![0].uri.fsPath; + if (pythonPath.toUpperCase().startsWith(workspacePath.toUpperCase())) { + // tslint:disable-next-line:no-invalid-template-strings + pythonPath = path.join('${workspaceRoot}', path.relative(workspacePath, pythonPath)); + } + return pythonConfig.update('pythonPath', pythonPath, false); + } + private async setPythonPathInWorkspace(pythonPath, configTarget: ConfigurationTarget.Workspace | ConfigurationTarget.WorkspaceFolder, resource?: Uri) { + const pythonConfig = workspace.getConfiguration('python', resource); + if (configTarget === ConfigurationTarget.WorkspaceFolder && resource && pythonPath.toUpperCase().startsWith(resource.fsPath.toUpperCase())) { + // tslint:disable-next-line:no-invalid-template-strings + pythonPath = path.join('${workspaceRoot}', path.relative(resource.fsPath, pythonPath)); + } + return pythonConfig.update('pythonPath', pythonPath, configTarget); + } private shouldAutoSetInterpreter() { - if (!workspace.rootPath) { + const activeWorkspace = getActiveWorkspaceUri(); + if (!activeWorkspace) { return false; } const pythonConfig = workspace.getConfiguration('python'); const pythonPathInConfig = pythonConfig.get('pythonPath', 'python'); - return pythonPathInConfig.toUpperCase() === 'PYTHON'; + return path.basename(pythonPathInConfig) === pythonPathInConfig; } private onConfigChanged() { if (this.display) { + // tslint:disable-next-line:no-floating-promises this.display.refresh(); } } -} \ No newline at end of file +} diff --git a/src/client/interpreter/locators/index.ts b/src/client/interpreter/locators/index.ts index d12e527dcb13..ab3059f3ab91 100644 --- a/src/client/interpreter/locators/index.ts +++ b/src/client/interpreter/locators/index.ts @@ -1,17 +1,18 @@ -"use strict"; +'use strict'; import * as _ from 'lodash'; -import { fixInterpreterPath, fixInterpreterDisplayName } from './helpers'; +import { Uri } from 'vscode'; +import { RegistryImplementation } from '../../common/registry'; +import { areBasePathsSame, arePathsSame, Is_64Bit, IS_WINDOWS } from '../../common/utils'; import { IInterpreterLocatorService, PythonInterpreter } from '../contracts'; import { InterpreterVersionService } from '../interpreterVersion'; -import { IS_WINDOWS, Is_64Bit, arePathsSame, areBasePathsSame } from '../../common/utils'; -import { RegistryImplementation } from '../../common/registry'; +import { VirtualEnvironmentManager } from '../virtualEnvs'; +import { fixInterpreterDisplayName, fixInterpreterPath } from './helpers'; +import { CondaEnvFileService, getEnvironmentsFile as getCondaEnvFile } from './services/condaEnvFileService'; import { CondaEnvService } from './services/condaEnvService'; -import { VirtualEnvService, getKnownSearchPathsForVirtualEnvs } from './services/virtualEnvService'; -import { KnownPathsService, getKnownSearchPathsForInterpreters } from './services/KnownPathsService'; import { CurrentPathService } from './services/currentPathService'; +import { getKnownSearchPathsForInterpreters, KnownPathsService } from './services/KnownPathsService'; +import { getKnownSearchPathsForVirtualEnvs, VirtualEnvService } from './services/virtualEnvService'; import { WindowsRegistryService } from './services/windowsRegistryService'; -import { VirtualEnvironmentManager } from '../virtualEnvs'; -import { CondaEnvFileService, getEnvironmentsFile as getCondaEnvFile } from './services/condaEnvFileService'; export class PythonInterpreterLocatorService implements IInterpreterLocatorService { private interpreters: PythonInterpreter[] = []; @@ -32,20 +33,21 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi this.locators.push(new VirtualEnvService(getKnownSearchPathsForVirtualEnvs(), this.virtualEnvMgr, versionService)); if (!IS_WINDOWS) { - // This must be last, it is possible we have paths returned here that are already returned + // This must be last, it is possible we have paths returned here that are already returned // in one of the above lists. this.locators.push(new KnownPathsService(getKnownSearchPathsForInterpreters(), versionService)); } - // This must be last, it is possible we have paths returned here that are already returned + // This must be last, it is possible we have paths returned here that are already returned // in one of the above lists. this.locators.push(new CurrentPathService(this.virtualEnvMgr, versionService)); } - public async getInterpreters() { + public async getInterpreters(resource?: Uri) { if (this.interpreters.length > 0) { return this.interpreters; } - const promises = this.locators.map(provider => provider.getInterpreters()); + const promises = this.locators.map(provider => provider.getInterpreters(resource)); return Promise.all(promises) + // tslint:disable-next-line:underscore-consistent-invocation .then(interpreters => _.flatten(interpreters)) .then(items => items.map(fixInterpreterDisplayName)) .then(items => items.map(fixInterpreterPath)) diff --git a/src/client/interpreter/locators/services/KnownPathsService.ts b/src/client/interpreter/locators/services/KnownPathsService.ts index bbfdea9ab4cd..23306756a452 100644 --- a/src/client/interpreter/locators/services/KnownPathsService.ts +++ b/src/client/interpreter/locators/services/KnownPathsService.ts @@ -1,22 +1,26 @@ -"use strict"; -import * as path from 'path'; +'use strict'; import * as _ from 'lodash'; +import * as path from 'path'; +import { Uri } from 'vscode'; +import { fsExistsAsync, IS_WINDOWS } from '../../../common/utils'; import { IInterpreterLocatorService } from '../../contracts'; import { IInterpreterVersionService } from '../../interpreterVersion'; -import { fsExistsAsync, IS_WINDOWS } from '../../../common/utils'; import { lookForInterpretersInDirectory } from '../helpers'; +// tslint:disable-next-line:no-require-imports no-var-requires const untildify = require('untildify'); export class KnownPathsService implements IInterpreterLocatorService { public constructor(private knownSearchPaths: string[], private versionProvider: IInterpreterVersionService) { } - public getInterpreters() { + // tslint:disable-next-line:no-shadowed-variable + public getInterpreters(_?: Uri) { return this.suggestionsFromKnownPaths(); } private suggestionsFromKnownPaths() { const promises = this.knownSearchPaths.map(dir => this.getInterpretersInDirectory(dir)); return Promise.all(promises) + // tslint:disable-next-line:underscore-consistent-invocation .then(listOfInterpreters => _.flatten(listOfInterpreters)) .then(interpreters => interpreters.filter(item => item.length > 0)) .then(interpreters => Promise.all(interpreters.map(interpreter => this.getInterpreterDetails(interpreter)))); @@ -40,14 +44,14 @@ export function getKnownSearchPathsForInterpreters(): string[] { if (IS_WINDOWS) { return []; } else { - let paths = ['/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin', '/usr/local/sbin']; + const paths = ['/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin', '/usr/local/sbin']; paths.forEach(p => { - paths.push(untildify('~' + p)); + paths.push(untildify(`~${p}`)); }); // Add support for paths such as /Users/xxx/anaconda/bin. - if (process.env['HOME']) { - paths.push(path.join(process.env['HOME'], 'anaconda', 'bin')); - paths.push(path.join(process.env['HOME'], 'python', 'bin')); + if (process.env.HOME) { + paths.push(path.join(process.env.HOME, 'anaconda', 'bin')); + paths.push(path.join(process.env.HOME, 'python', 'bin')); } return paths; } diff --git a/src/client/interpreter/locators/services/condaEnvFileService.ts b/src/client/interpreter/locators/services/condaEnvFileService.ts index 5a8c010a961b..b2fb8b5f0a39 100644 --- a/src/client/interpreter/locators/services/condaEnvFileService.ts +++ b/src/client/interpreter/locators/services/condaEnvFileService.ts @@ -1,24 +1,25 @@ -"use strict"; +'use strict'; import * as fs from 'fs-extra'; import * as path from 'path'; +import { Uri } from 'vscode'; import { IS_WINDOWS } from '../../../common/configSettings'; -import { IInterpreterVersionService } from '../../interpreterVersion'; import { IInterpreterLocatorService, PythonInterpreter } from '../../contracts'; -import { AnacondaDisplayName, AnacondaCompanyName, AnacondaCompanyNames, CONDA_RELATIVE_PY_PATH } from './conda'; +import { IInterpreterVersionService } from '../../interpreterVersion'; +import { AnacondaCompanyName, AnacondaCompanyNames, AnacondaDisplayName, CONDA_RELATIVE_PY_PATH } from './conda'; export class CondaEnvFileService implements IInterpreterLocatorService { constructor(private condaEnvironmentFile: string, private versionService: IInterpreterVersionService) { } - public getInterpreters() { + public async getInterpreters(_?: Uri) { return this.getSuggestionsFromConda(); } - private getSuggestionsFromConda(): Promise { + private async getSuggestionsFromConda(): Promise { return fs.pathExists(this.condaEnvironmentFile) .then(exists => exists ? this.getEnvironmentsFromFile(this.condaEnvironmentFile) : Promise.resolve([])); } - private getEnvironmentsFromFile(envFile: string) { + private async getEnvironmentsFromFile(envFile: string) { return fs.readFile(envFile) .then(buffer => buffer.toString().split(/\r?\n/g)) .then(lines => lines.map(line => line.trim())) @@ -29,11 +30,12 @@ export class CondaEnvFileService implements IInterpreterLocatorService { .then(interpreterPaths => interpreterPaths.map(item => this.getInterpreterDetails(item))) .then(promises => Promise.all(promises)); } - private getInterpreterDetails(interpreter: string) { + private async getInterpreterDetails(interpreter: string) { return this.versionService.getVersion(interpreter, path.basename(interpreter)) .then(version => { version = this.stripCompanyName(version); const envName = this.getEnvironmentRootDirectory(interpreter); + // tslint:disable-next-line:no-unnecessary-local-variable const info: PythonInterpreter = { displayName: `${AnacondaDisplayName} ${version} (${envName})`, path: interpreter, diff --git a/src/client/interpreter/locators/services/condaEnvService.ts b/src/client/interpreter/locators/services/condaEnvService.ts index a11cdc54f64b..c40bd9875261 100644 --- a/src/client/interpreter/locators/services/condaEnvService.ts +++ b/src/client/interpreter/locators/services/condaEnvService.ts @@ -1,48 +1,24 @@ -"use strict"; +'use strict'; import * as child_process from 'child_process'; import * as fs from 'fs-extra'; import * as path from 'path'; +import { Uri } from 'vscode'; +import { VersionUtils } from '../../../common/versionUtils'; import { IInterpreterLocatorService, PythonInterpreter } from '../../contracts'; -import { VersionUtils } from "../../../common/versionUtils"; import { AnacondaCompanyName, AnacondaDisplayName, CONDA_RELATIVE_PY_PATH } from './conda'; type CondaInfo = { envs?: string[]; - "sys.version"?: string; + 'sys.version'?: string; default_prefix?: string; -} +}; export class CondaEnvService implements IInterpreterLocatorService { constructor(private registryLookupForConda?: IInterpreterLocatorService) { } - public getInterpreters() { + public getInterpreters(resource?: Uri) { return this.getSuggestionsFromConda(); } - private getSuggestionsFromConda(): Promise { - return this.getCondaFile() - .then(condaFile => { - return new Promise((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([]); - } - - try { - const info = JSON.parse(stdout); - 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([]); - } - }); - }); - }); - } public getCondaFile() { if (this.registryLookupForConda) { return this.registryLookupForConda.getInterpreters() @@ -63,6 +39,7 @@ export class CondaEnvService implements IInterpreterLocatorService { } public getLatestVersion(interpreters: PythonInterpreter[]) { const sortedInterpreters = interpreters.filter(interpreter => interpreter.version && interpreter.version.length > 0); + // tslint:disable-next-line:no-non-null-assertion sortedInterpreters.sort((a, b) => VersionUtils.compareVersion(a.version!, b.version!)); if (sortedInterpreters.length > 0) { return sortedInterpreters[sortedInterpreters.length - 1]; @@ -83,6 +60,7 @@ export class CondaEnvService implements IInterpreterLocatorService { .map(env => { // If it is an environment, hence suffix with env name. const interpreterDisplayName = env === info.default_prefix ? displayName : `${displayName} (${path.basename(env)})`; + // tslint:disable-next-line:no-unnecessary-local-variable const interpreter: PythonInterpreter = { path: path.join(env, ...CONDA_RELATIVE_PY_PATH), displayName: interpreterDisplayName, @@ -94,8 +72,34 @@ export class CondaEnvService implements IInterpreterLocatorService { 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 { + return this.getCondaFile() + .then(condaFile => { + return new Promise((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([]); + } + + try { + const info = JSON.parse(stdout); + 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([]); + } + }); + }); + }); + } private getDisplayNameFromVersionInfo(versionInfo: string = '') { if (!versionInfo) { return AnacondaDisplayName; diff --git a/src/client/interpreter/locators/services/currentPathService.ts b/src/client/interpreter/locators/services/currentPathService.ts index e2ddf45e4fba..d802f9c5640d 100644 --- a/src/client/interpreter/locators/services/currentPathService.ts +++ b/src/client/interpreter/locators/services/currentPathService.ts @@ -1,36 +1,37 @@ -"use strict"; +'use strict'; +import * as child_process from 'child_process'; import * as _ from 'lodash'; import * as path from 'path'; -import * as child_process from 'child_process'; +import { Uri } from 'vscode'; +import { PythonSettings } from '../../../common/configSettings'; import { IInterpreterLocatorService } from '../../contracts'; -import { IInterpreterVersionService } from '../../interpreterVersion'; import { getFirstNonEmptyLineFromMultilineString } from '../../helpers'; +import { IInterpreterVersionService } from '../../interpreterVersion'; import { VirtualEnvironmentManager } from '../../virtualEnvs'; -import { PythonSettings } from '../../../common/configSettings'; - -const settings = PythonSettings.getInstance(); export class CurrentPathService implements IInterpreterLocatorService { public constructor(private virtualEnvMgr: VirtualEnvironmentManager, private versionProvider: IInterpreterVersionService) { } - public getInterpreters() { + public getInterpreters(resource?: Uri) { return this.suggestionsFromKnownPaths(); } - private suggestionsFromKnownPaths() { - const currentPythonInterpreter = this.getInterpreter(settings.pythonPath, '').then(interpreter => [interpreter]); + private suggestionsFromKnownPaths(resource?: Uri) { + const currentPythonInterpreter = this.getInterpreter(PythonSettings.getInstance(resource).pythonPath, '').then(interpreter => [interpreter]); const python = this.getInterpreter('python', '').then(interpreter => [interpreter]); const python2 = this.getInterpreter('python2', '').then(interpreter => [interpreter]); const python3 = this.getInterpreter('python3', '').then(interpreter => [interpreter]); return Promise.all([currentPythonInterpreter, python, python2, python3]) + // tslint:disable-next-line:underscore-consistent-invocation .then(listOfInterpreters => _.flatten(listOfInterpreters)) .then(interpreters => interpreters.filter(item => item.length > 0)) .then(interpreters => Promise.all(interpreters.map(interpreter => this.getInterpreterDetails(interpreter)))); } - private getInterpreterDetails(interpreter: string) { - const virtualEnv = this.virtualEnvMgr.detect(interpreter); - const displayName = this.versionProvider.getVersion(interpreter, path.basename(interpreter)); - return Promise.all([displayName, virtualEnv]) + private async getInterpreterDetails(interpreter: string) { + return Promise.all([ + this.versionProvider.getVersion(interpreter, path.basename(interpreter)), + this.virtualEnvMgr.detect(interpreter) + ]) .then(([displayName, virtualEnv]) => { displayName += virtualEnv ? ` (${virtualEnv.name})` : ''; return { @@ -39,9 +40,9 @@ export class CurrentPathService implements IInterpreterLocatorService { }; }); } - private getInterpreter(pythonPath: string, defaultValue: string) { + private async getInterpreter(pythonPath: string, defaultValue: string) { return new Promise(resolve => { - child_process.execFile(pythonPath, ["-c", "import sys;print(sys.executable)"], (_, stdout) => { + child_process.execFile(pythonPath, ['-c', 'import sys;print(sys.executable)'], (_, stdout) => { resolve(getFirstNonEmptyLineFromMultilineString(stdout)); }); }) diff --git a/src/client/interpreter/locators/services/virtualEnvService.ts b/src/client/interpreter/locators/services/virtualEnvService.ts index 64e3b7a3d4e9..d11af1dbfd6f 100644 --- a/src/client/interpreter/locators/services/virtualEnvService.ts +++ b/src/client/interpreter/locators/services/virtualEnvService.ts @@ -1,32 +1,35 @@ -"use strict"; +'use strict'; import * as _ from 'lodash'; import * as path from 'path'; -import * as settings from './../../../common/configSettings'; -import { VirtualEnvironmentManager } from '../../virtualEnvs'; +import { Uri, workspace } from 'vscode'; +import { fsReaddirAsync, IS_WINDOWS } from '../../../common/utils'; import { IInterpreterLocatorService, PythonInterpreter } from '../../contracts'; import { IInterpreterVersionService } from '../../interpreterVersion'; -import { IS_WINDOWS, fsReaddirAsync } from "../../../common/utils"; +import { VirtualEnvironmentManager } from '../../virtualEnvs'; import { lookForInterpretersInDirectory } from '../helpers'; -import { workspace } from 'vscode'; +import * as settings from './../../../common/configSettings'; +// tslint:disable-next-line:no-require-imports no-var-requires const untildify = require('untildify'); export class VirtualEnvService implements IInterpreterLocatorService { public constructor(private knownSearchPaths: string[], private virtualEnvMgr: VirtualEnvironmentManager, private versionProvider: IInterpreterVersionService) { } - public getInterpreters() { + public async getInterpreters(resource?: Uri) { return this.suggestionsFromKnownVenvs(); } - private suggestionsFromKnownVenvs() { + private async suggestionsFromKnownVenvs() { return Promise.all(this.knownSearchPaths.map(dir => this.lookForInterpretersInVenvs(dir))) + // tslint:disable-next-line:underscore-consistent-invocation .then(listOfInterpreters => _.flatten(listOfInterpreters)); } - private lookForInterpretersInVenvs(pathToCheck: string) { + private async lookForInterpretersInVenvs(pathToCheck: string) { return fsReaddirAsync(pathToCheck) .then(subDirs => Promise.all(this.getProspectiveDirectoriesForLookup(subDirs))) .then(dirs => dirs.filter(dir => dir.length > 0)) - .then(dirs => Promise.all(dirs.map(dir => lookForInterpretersInDirectory(dir)))) + .then(dirs => Promise.all(dirs.map(lookForInterpretersInDirectory))) + // tslint:disable-next-line:underscore-consistent-invocation .then(pathsWithInterpreters => _.flatten(pathsWithInterpreters)) .then(interpreters => Promise.all(interpreters.map(interpreter => this.getVirtualEnvDetails(interpreter)))); } @@ -41,9 +44,10 @@ export class VirtualEnvService implements IInterpreterLocatorService { })); } private async getVirtualEnvDetails(interpreter: string): Promise { - const displayName = this.versionProvider.getVersion(interpreter, path.basename(interpreter)); - const virtualEnv = this.virtualEnvMgr.detect(interpreter); - return Promise.all([displayName, virtualEnv]) + return Promise.all([ + this.versionProvider.getVersion(interpreter, path.basename(interpreter)), + this.virtualEnvMgr.detect(interpreter) + ]) .then(([displayName, virtualEnv]) => { const virtualEnvSuffix = virtualEnv ? virtualEnv.name : this.getVirtualEnvironmentRootDirectory(interpreter); return { @@ -62,7 +66,7 @@ export function getKnownSearchPathsForVirtualEnvs(): string[] { if (!IS_WINDOWS) { const defaultPaths = ['/Envs', '/.virtualenvs', '/.pyenv', '/.pyenv/versions']; defaultPaths.forEach(p => { - paths.push(untildify('~' + p)); + paths.push(untildify(`~${p}`)); }); } const venvPath = settings.PythonSettings.getInstance().venvPath; @@ -73,4 +77,4 @@ export function getKnownSearchPathsForVirtualEnvs(): string[] { paths.push(workspace.rootPath); } return paths; -} \ No newline at end of file +} diff --git a/src/client/interpreter/locators/services/windowsRegistryService.ts b/src/client/interpreter/locators/services/windowsRegistryService.ts index 222c88e7139d..209fe91ac9e8 100644 --- a/src/client/interpreter/locators/services/windowsRegistryService.ts +++ b/src/client/interpreter/locators/services/windowsRegistryService.ts @@ -1,13 +1,18 @@ -import * as path from 'path'; -import * as _ from 'lodash'; import * as fs from 'fs-extra'; +import * as _ from 'lodash'; +import * as path from 'path'; +import { Uri } from 'vscode'; import { Architecture, Hive, IRegistry } from '../../../common/registry'; import { IInterpreterLocatorService, PythonInterpreter } from '../../contracts'; +// tslint:disable-next-line:variable-name const DefaultPythonExecutable = 'python.exe'; +// tslint:disable-next-line:variable-name const CompaniesToIgnore = ['PYLAUNCHER']; -const PythonCoreCompanyDisplayName = "Python Software Foundation"; -const PythonCoreComany = "PYTHONCORE"; +// tslint:disable-next-line:variable-name +const PythonCoreCompanyDisplayName = 'Python Software Foundation'; +// tslint:disable-next-line:variable-name +const PythonCoreComany = 'PYTHONCORE'; type CompanyInterpreter = { companyKey: string, @@ -19,7 +24,8 @@ export class WindowsRegistryService implements IInterpreterLocatorService { constructor(private registry: IRegistry, private is64Bit: boolean) { } - public getInterpreters() { + // tslint:disable-next-line:variable-name + public getInterpreters(_resource?: Uri) { return this.getInterpretersFromRegistry(); } private async getInterpretersFromRegistry() { @@ -35,14 +41,17 @@ export class WindowsRegistryService implements IInterpreterLocatorService { } const companies = await Promise.all(promises); + // tslint:disable-next-line:underscore-consistent-invocation const companyInterpreters = await Promise.all(_.flatten(companies) .filter(item => item !== undefined && item !== null) .map(company => { return this.getInterpretersForCompany(company.companyKey, company.hive, company.arch); })); + // tslint:disable-next-line:underscore-consistent-invocation return _.flatten(companyInterpreters) .filter(item => item !== undefined && item !== null) + // tslint:disable-next-line:no-non-null-assertion .map(item => item!) .reduce((prev, current) => { if (prev.findIndex(item => item.path.toUpperCase() === current.path.toUpperCase()) === -1) { @@ -52,7 +61,7 @@ export class WindowsRegistryService implements IInterpreterLocatorService { }, []); } private async getCompanies(hive: Hive, arch?: Architecture): Promise { - return this.registry.getKeys(`\\Software\\Python`, hive, arch) + return this.registry.getKeys('\\Software\\Python', hive, arch) .then(companyKeys => companyKeys .filter(companyKey => CompaniesToIgnore.indexOf(path.basename(companyKey).toUpperCase()) === -1) .map(companyKey => { @@ -84,12 +93,14 @@ export class WindowsRegistryService implements IInterpreterLocatorService { return Promise.all([ Promise.resolve(installPath), this.registry.getValue(key, hive, arch, 'ExecutablePath'), - this.getInterpreterDisplayName(tagKey, companyKey, hive, arch)!, - this.registry.getValue(tagKey, hive, arch, 'Version')!, - this.getCompanyDisplayName(companyKey, hive, arch)! + // tslint:disable-next-line:no-non-null-assertion + this.getInterpreterDisplayName(tagKey, companyKey, hive, arch), + this.registry.getValue(tagKey, hive, arch, 'Version'), + this.getCompanyDisplayName(companyKey, hive, arch) ]) - .then(([installPath, executablePath, displayName, version, companyDisplayName]) => { - return { installPath, executablePath, displayName, version, companyDisplayName } as InterpreterInformation; + .then(([installedPath, executablePath, displayName, version, companyDisplayName]) => { + // tslint:disable-next-line:prefer-type-cast + return { installPath: installedPath, executablePath, displayName, version, companyDisplayName } as InterpreterInformation; }); }) .then((interpreterInfo?: InterpreterInformation) => { @@ -100,6 +111,7 @@ export class WindowsRegistryService implements IInterpreterLocatorService { const executablePath = interpreterInfo.executablePath && interpreterInfo.executablePath.length > 0 ? interpreterInfo.executablePath : path.join(interpreterInfo.installPath, DefaultPythonExecutable); const displayName = interpreterInfo.displayName; const version = interpreterInfo.version ? path.basename(interpreterInfo.version) : path.basename(tagKey); + // tslint:disable-next-line:prefer-type-cast return { architecture: arch, displayName, @@ -129,4 +141,4 @@ export class WindowsRegistryService implements IInterpreterLocatorService { const company = path.basename(companyKey); return company.toUpperCase() === PythonCoreComany ? PythonCoreCompanyDisplayName : company; } -} \ No newline at end of file +} diff --git a/src/client/providers/setInterpreterProvider.ts b/src/client/providers/setInterpreterProvider.ts index e9b31c9c9093..2bfca90a371f 100644 --- a/src/client/providers/setInterpreterProvider.ts +++ b/src/client/providers/setInterpreterProvider.ts @@ -1,72 +1,115 @@ -"use strict"; +'use strict'; import * as path from 'path'; -import * as vscode from 'vscode'; -import * as settings from './../common/configSettings'; +import { commands, ConfigurationTarget, Disposable, QuickPickItem, QuickPickOptions, Uri, window, workspace } from 'vscode'; import { InterpreterManager } from '../interpreter'; -import { PythonInterpreter } from '../interpreter/contracts'; +import { PythonInterpreter, WorkspacePythonPath } from '../interpreter/contracts'; +import { getInterpretersForEachFolderAndWorkspace } from '../interpreter/helpers'; +import * as settings from './../common/configSettings'; import { ShebangCodeLensProvider } from './shebangCodeLensProvider'; - -interface PythonPathQuickPickItem extends vscode.QuickPickItem { +// tslint:disable-next-line:interface-name +interface PythonPathQuickPickItem extends QuickPickItem { path: string; } -export class SetInterpreterProvider implements vscode.Disposable { - private disposables: vscode.Disposable[] = []; +export class SetInterpreterProvider implements Disposable { + private disposables: Disposable[] = []; constructor(private interpreterManager: InterpreterManager) { - this.disposables.push(vscode.commands.registerCommand("python.setInterpreter", this.setInterpreter.bind(this))); - this.disposables.push(vscode.commands.registerCommand("python.setShebangInterpreter", this.setShebangInterpreter.bind(this))); + this.disposables.push(commands.registerCommand('python.setInterpreter', this.setInterpreter.bind(this))); + this.disposables.push(commands.registerCommand('python.setShebangInterpreter', this.setShebangInterpreter.bind(this))); } public dispose() { this.disposables.forEach(disposable => disposable.dispose()); } + private async getWorkspacePythonPath(): Promise { + if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0) { + return undefined; + } + if (workspace.workspaceFolders.length === 1) { + return { folderUri: workspace.workspaceFolders[0].uri, configTarget: ConfigurationTarget.Workspace }; + } + // We could have each workspace folder with different python paths. + // Or, we could the workspace with a pythonPath and one of the workspace folders with different python paths. + // Lets just find how many different setups we have. + const configs = getInterpretersForEachFolderAndWorkspace(); + if (configs.length === 1) { + return configs[0]; + } - private suggestionToQuickPickItem(suggestion: PythonInterpreter): PythonPathQuickPickItem { + // Ok we have multiple interpreters, get the user to pick a folder + const workspaceFolder = await window.showWorkspaceFolderPick({ placeHolder: 'Select a workspace' }); + return workspaceFolder ? { folderUri: workspaceFolder.uri, configTarget: ConfigurationTarget.WorkspaceFolder } : undefined; + } + private async suggestionToQuickPickItem(suggestion: PythonInterpreter, workspaceUri?: Uri): Promise { let detail = suggestion.path; - if (vscode.workspace.rootPath && suggestion.path.startsWith(vscode.workspace.rootPath)) { - detail = `.${path.sep}` + path.relative(vscode.workspace.rootPath!, suggestion.path); + if (workspaceUri && suggestion.path.startsWith(workspaceUri.fsPath)) { + detail = `.${path.sep}${path.relative(workspaceUri.fsPath, suggestion.path)}`; } return { + // tslint:disable-next-line:no-non-null-assertion label: suggestion.displayName!, description: suggestion.companyDisplayName || '', detail: detail, path: suggestion.path }; } - private presentQuickPick() { - this.getSuggestions().then(suggestions => { - let currentPythonPath = settings.PythonSettings.getInstance().pythonPath; - if (vscode.workspace.rootPath && currentPythonPath.startsWith(vscode.workspace.rootPath)) { - currentPythonPath = `.${path.sep}` + path.relative(vscode.workspace.rootPath, currentPythonPath); - } - const quickPickOptions: vscode.QuickPickOptions = { - matchOnDetail: true, - matchOnDescription: true, - placeHolder: `current: ${currentPythonPath}` - }; - vscode.window.showQuickPick(suggestions, quickPickOptions).then( - value => { - if (value !== undefined) { - this.interpreterManager.setPythonPath(value.path); - } - }); - }); + private async presentQuickPick() { + const targetConfig = await this.getWorkspacePythonPath(); + const resourceUri = targetConfig ? targetConfig.folderUri : undefined; + const suggestions = await this.getSuggestions(resourceUri); + let currentPythonPath = settings.PythonSettings.getInstance().pythonPath; + if (workspace.rootPath && currentPythonPath.startsWith(workspace.rootPath)) { + currentPythonPath = `.${path.sep}${path.relative(workspace.rootPath, currentPythonPath)}`; + } + const quickPickOptions: QuickPickOptions = { + matchOnDetail: true, + matchOnDescription: true, + placeHolder: `current: ${currentPythonPath}` + }; + const selection = await window.showQuickPick(suggestions, quickPickOptions); + if (selection !== undefined) { + this.interpreterManager.setPythonPath(selection.path, targetConfig); + } } - private getSuggestions() { - return this.interpreterManager.getInterpreters() - .then(interpreters => interpreters.sort((a, b) => a.displayName! > b.displayName! ? 1 : -1)) - .then(interpreters => interpreters.map(this.suggestionToQuickPickItem)); + private async getSuggestions(resourceUri?: Uri) { + const interpreters = await this.interpreterManager.getInterpreters(resourceUri); + // tslint:disable-next-line:no-non-null-assertion + interpreters.sort((a, b) => a.displayName! > b.displayName! ? 1 : -1); + return Promise.all(interpreters.map(item => this.suggestionToQuickPickItem(item, resourceUri))); } private setInterpreter() { this.presentQuickPick(); } - private async setShebangInterpreter() { - const shebang = await ShebangCodeLensProvider.detectShebang(vscode.window.activeTextEditor.document); - if (shebang) { - this.interpreterManager.setPythonPath(shebang); + private async setShebangInterpreter(): Promise { + const shebang = await ShebangCodeLensProvider.detectShebang(window.activeTextEditor.document); + if (!shebang) { + return; + } + + const existingConfigs = getInterpretersForEachFolderAndWorkspace(); + const hasFoldersWithPythonPathSet = existingConfigs.filter(item => item.configTarget === ConfigurationTarget.WorkspaceFolder).length > 0; + + const workspaceFolder = workspace.getWorkspaceFolder(window.activeTextEditor.document.uri); + // tslint:disable-next-line:no-backbone-get-set-outside-model + const value = workspace.getConfiguration('python', window.activeTextEditor.document.uri).inspect('pythonPath'); + const currentValueSetInWorkspaceFolder = value && typeof value.workspaceFolderValue === 'string'; + + const setPythonPathInSpecificFolder = hasFoldersWithPythonPathSet || currentValueSetInWorkspaceFolder; + if (setPythonPathInSpecificFolder) { + const configTarget = workspaceFolder ? ConfigurationTarget.WorkspaceFolder : ConfigurationTarget.Workspace; + const workspaceTarget: WorkspacePythonPath = { folderUri: workspaceFolder.uri, configTarget: configTarget }; + return this.interpreterManager.setPythonPath(shebang, workspaceTarget); + } + + const setPythonPathInRootWorkspace = Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length > 0; + if (setPythonPathInRootWorkspace) { + const configTarget: WorkspacePythonPath = { folderUri: workspace.workspaceFolders[0].uri, configTarget: ConfigurationTarget.Workspace }; + return this.interpreterManager.setPythonPath(shebang, configTarget); } + + return this.interpreterManager.setPythonPath(shebang); } } diff --git a/src/test/common/configSettings.multiroot.test.ts b/src/test/common/configSettings.multiroot.test.ts index 17c96c631dd3..726772478676 100644 --- a/src/test/common/configSettings.multiroot.test.ts +++ b/src/test/common/configSettings.multiroot.test.ts @@ -112,10 +112,10 @@ suite('Multiroot Config Settings', () => { const workspaceUri = Uri.file(path.join(multirootPath, 'workspace1')); await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', undefined); await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', true); - let settings = PythonSettings.getInstance(workspaceUri); - assert.equal(settings.linting.pylintEnabled, true, 'Pylint not enabled when it should be'); // tslint:disable-next-line:no-string-based-set-timeout await new Promise(resolve => setTimeout(resolve, 2000)); + let settings = PythonSettings.getInstance(workspaceUri); + assert.equal(settings.linting.pylintEnabled, true, 'Pylint not enabled when it should be'); await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', false); // tslint:disable-next-line:no-string-based-set-timeout diff --git a/src/test/interpreters/display.multiroot.test.ts b/src/test/interpreters/display.multiroot.test.ts new file mode 100644 index 000000000000..3007fbd557ae --- /dev/null +++ b/src/test/interpreters/display.multiroot.test.ts @@ -0,0 +1,55 @@ +import * as assert from 'assert'; +import * as child_process from 'child_process'; +import { EOL } from 'os'; +import * as path from 'path'; +import { ConfigurationTarget, Uri, window, workspace } from 'vscode'; +import { PythonSettings } from '../../client/common/configSettings'; +import { InterpreterDisplay } from '../../client/interpreter/display'; +import { getFirstNonEmptyLineFromMultilineString } from '../../client/interpreter/helpers'; +import { VirtualEnvironmentManager } from '../../client/interpreter/virtualEnvs'; +import { rootWorkspaceUri, updateSetting } from '../common'; +import { closeActiveWindows, initialize, initializePython, initializeTest } from '../initialize'; +import { MockStatusBarItem } from '../mockClasses'; +import { MockInterpreterVersionProvider } from './mocks'; +import { MockProvider, MockVirtualEnv } from './mocks'; + +const multirootPath = path.join(__dirname, '..', '..', '..', 'src', 'testMultiRootWkspc'); +const workspace3Uri = Uri.file(path.join(multirootPath, 'workspace3')); +const fileToOpen = path.join(workspace3Uri.fsPath, 'file.py'); + +// tslint:disable-next-line:max-func-body-length +suite('Multiroot Interpreters Display', () => { + // tslint:disable-next-line:no-function-expression + suiteSetup(initialize); + setup(initializeTest); + suiteTeardown(initializePython); + teardown(async () => { + await initialize(); + await closeActiveWindows(); + const settings = workspace.getConfiguration('python', Uri.file(fileToOpen)); + const value = settings.inspect('pythonPath'); + if (value && value.workspaceFolderValue) { + await settings.update('pythonPath', undefined, ConfigurationTarget.WorkspaceFolder); + PythonSettings.dispose(); + } + }); + + test('Must get display name from workspace folder interpreter and not from interpreter in workspace', async () => { + const settings = workspace.getConfiguration('python', Uri.file(fileToOpen)); + const pythonPath = fileToOpen; + await settings.update('pythonPath', pythonPath, ConfigurationTarget.WorkspaceFolder); + PythonSettings.dispose(); + + const document = await workspace.openTextDocument(fileToOpen); + const editor = await window.showTextDocument(document); + + const statusBar = new MockStatusBarItem(); + const provider = new MockProvider([]); + const displayName = `${path.basename(pythonPath)} [Environment]`; + const displayNameProvider = new MockInterpreterVersionProvider(displayName); + const display = new InterpreterDisplay(statusBar, provider, new VirtualEnvironmentManager([]), displayNameProvider); + await display.refresh(); + + assert.equal(statusBar.text, displayName, 'Incorrect display name'); + }); +}); diff --git a/src/test/interpreters/display.test.ts b/src/test/interpreters/display.test.ts index 8c5b3b517c2b..4a70a0933789 100644 --- a/src/test/interpreters/display.test.ts +++ b/src/test/interpreters/display.test.ts @@ -1,21 +1,29 @@ -import { PythonSettings } from '../../client/common/configSettings'; import * as assert from 'assert'; import * as child_process from 'child_process'; +import { EOL } from 'os'; import * as path from 'path'; -import { closeActiveWindows, initialize, initializeTest } from '../initialize'; -import { MockStatusBarItem } from '../mockClasses'; -import { MockInterpreterVersionProvider } from './mocks'; +import { ConfigurationTarget } from 'vscode'; +import { PythonSettings } from '../../client/common/configSettings'; import { InterpreterDisplay } from '../../client/interpreter/display'; -import { MockProvider, MockVirtualEnv } from './mocks'; -import { EOL } from 'os'; -import { VirtualEnvironmentManager } from '../../client/interpreter/virtualEnvs'; import { getFirstNonEmptyLineFromMultilineString } from '../../client/interpreter/helpers'; +import { VirtualEnvironmentManager } from '../../client/interpreter/virtualEnvs'; import { rootWorkspaceUri, updateSetting } from '../common'; -import { ConfigurationTarget } from 'vscode'; +import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST } from '../initialize'; +import { MockStatusBarItem } from '../mockClasses'; +import { MockInterpreterVersionProvider } from './mocks'; +import { MockProvider, MockVirtualEnv } from './mocks'; +// tslint:disable-next-line:max-func-body-length suite('Interpreters Display', () => { - suiteSetup(() => initialize()); - setup(() => initializeTest()); + // tslint:disable-next-line:no-function-expression + suiteSetup(function () { + if (IS_MULTI_ROOT_TEST) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + } + return initialize(); + }); + setup(initializeTest); teardown(async () => { await initialize(); await closeActiveWindows(); @@ -24,6 +32,7 @@ suite('Interpreters Display', () => { test('Must have command name', () => { const statusBar = new MockStatusBarItem(); const displayNameProvider = new MockInterpreterVersionProvider(''); + // tslint:disable-next-line:no-unused-expression new InterpreterDisplay(statusBar, new MockProvider([]), new VirtualEnvironmentManager([]), displayNameProvider); assert.equal(statusBar.command, 'python.setInterpreter', 'Incorrect command name'); }); @@ -49,7 +58,7 @@ suite('Interpreters Display', () => { await display.refresh(); assert.equal(statusBar.text, `${displayName} (${env2.name})`, 'Incorrect display name'); }); - test(`Must display default 'Display name' for unknown interpreter`, async () => { + test('Must display default \'Display name\' for unknown interpreter', async () => { const statusBar = new MockStatusBarItem(); const provider = new MockProvider([]); const displayName = 'Mock Display Name'; @@ -65,7 +74,7 @@ suite('Interpreters Display', () => { }); test('Must get display name from a list of interpreters', async () => { const pythonPath = await new Promise(resolve => { - child_process.execFile(PythonSettings.getInstance().pythonPath, ["-c", "import sys;print(sys.executable)"], (_, stdout) => { + child_process.execFile(PythonSettings.getInstance().pythonPath, ['-c', 'import sys;print(sys.executable)'], (_, stdout) => { resolve(getFirstNonEmptyLineFromMultilineString(stdout)); }); }).then(value => value.length === 0 ? PythonSettings.getInstance().pythonPath : value); @@ -73,7 +82,7 @@ suite('Interpreters Display', () => { const interpreters = [ { displayName: 'One', path: 'c:/path1/one.exe', type: 'One 1' }, { displayName: 'Two', path: pythonPath, type: 'Two 2' }, - { displayName: 'Three', path: 'c:/path3/three.exe', type: 'Three 3' }, + { displayName: 'Three', path: 'c:/path3/three.exe', type: 'Three 3' } ]; const provider = new MockProvider(interpreters); const displayName = 'Mock Display Name'; @@ -85,7 +94,7 @@ suite('Interpreters Display', () => { }); test('Must suffix tooltip with the companyDisplayName of interpreter', async () => { const pythonPath = await new Promise(resolve => { - child_process.execFile(PythonSettings.getInstance().pythonPath, ["-c", "import sys;print(sys.executable)"], (_, stdout) => { + child_process.execFile(PythonSettings.getInstance().pythonPath, ['-c', 'import sys;print(sys.executable)'], (_, stdout) => { resolve(getFirstNonEmptyLineFromMultilineString(stdout)); }); }).then(value => value.length === 0 ? PythonSettings.getInstance().pythonPath : value); @@ -94,7 +103,7 @@ suite('Interpreters Display', () => { const interpreters = [ { displayName: 'One', path: 'c:/path1/one.exe', companyDisplayName: 'One 1' }, { displayName: 'Two', path: pythonPath, companyDisplayName: 'Two 2' }, - { displayName: 'Three', path: 'c:/path3/three.exe', companyDisplayName: 'Three 3' }, + { displayName: 'Three', path: 'c:/path3/three.exe', companyDisplayName: 'Three 3' } ]; const provider = new MockProvider(interpreters); const displayNameProvider = new MockInterpreterVersionProvider(''); @@ -109,7 +118,7 @@ suite('Interpreters Display', () => { const interpreters = [ { displayName: 'One', path: 'c:/path1/one.exe', companyDisplayName: 'One 1' }, { displayName: 'Two', path: 'c:/asdf', companyDisplayName: 'Two 2' }, - { displayName: 'Three', path: 'c:/path3/three.exe', companyDisplayName: 'Three 3' }, + { displayName: 'Three', path: 'c:/path3/three.exe', companyDisplayName: 'Three 3' } ]; const provider = new MockProvider(interpreters); const displayNameProvider = new MockInterpreterVersionProvider('', true); diff --git a/tslint.json b/tslint.json index 471defc8f86c..27a723a725d7 100644 --- a/tslint.json +++ b/tslint.json @@ -32,6 +32,10 @@ "allow-string", "allow-number" ], - "await-promise": [true, "Thenable"] + "await-promise": [ + true, + "Thenable" + ], + "completed-docs": false } } From 9f90a427708d2f77d690a0c46cbae8353b7deb68 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 18 Oct 2017 15:48:12 -0700 Subject: [PATCH 20/43] fix linter --- .../interpreter/locators/services/currentPathService.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/client/interpreter/locators/services/currentPathService.ts b/src/client/interpreter/locators/services/currentPathService.ts index d802f9c5640d..069053fa4f9b 100644 --- a/src/client/interpreter/locators/services/currentPathService.ts +++ b/src/client/interpreter/locators/services/currentPathService.ts @@ -12,11 +12,11 @@ import { VirtualEnvironmentManager } from '../../virtualEnvs'; export class CurrentPathService implements IInterpreterLocatorService { public constructor(private virtualEnvMgr: VirtualEnvironmentManager, private versionProvider: IInterpreterVersionService) { } - public getInterpreters(resource?: Uri) { + public async getInterpreters(resource?: Uri) { return this.suggestionsFromKnownPaths(); } - private suggestionsFromKnownPaths(resource?: Uri) { + private async suggestionsFromKnownPaths(resource?: Uri) { const currentPythonInterpreter = this.getInterpreter(PythonSettings.getInstance(resource).pythonPath, '').then(interpreter => [interpreter]); const python = this.getInterpreter('python', '').then(interpreter => [interpreter]); const python2 = this.getInterpreter('python2', '').then(interpreter => [interpreter]); @@ -25,6 +25,7 @@ export class CurrentPathService implements IInterpreterLocatorService { // tslint:disable-next-line:underscore-consistent-invocation .then(listOfInterpreters => _.flatten(listOfInterpreters)) .then(interpreters => interpreters.filter(item => item.length > 0)) + // tslint:disable-next-line:promise-function-async .then(interpreters => Promise.all(interpreters.map(interpreter => this.getInterpreterDetails(interpreter)))); } private async getInterpreterDetails(interpreter: string) { @@ -42,7 +43,8 @@ export class CurrentPathService implements IInterpreterLocatorService { } private async getInterpreter(pythonPath: string, defaultValue: string) { return new Promise(resolve => { - child_process.execFile(pythonPath, ['-c', 'import sys;print(sys.executable)'], (_, stdout) => { + // tslint:disable-next-line:variable-name + child_process.execFile(pythonPath, ['-c', 'import sys;print(sys.executable)'], (_err, stdout) => { resolve(getFirstNonEmptyLineFromMultilineString(stdout)); }); }) From 31aa28fd71a4e8ad50b999871fc3a436174cdc14 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 18 Oct 2017 15:53:03 -0700 Subject: [PATCH 21/43] changed package version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7f3c85d58b63..c4be011f6f9f 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "theme": "dark" }, "engines": { - "vscode": "^1.18.0" + "vscode": "^1.17.0" }, "recommendations": [ "donjayamanne.jupyter" From 63180f0ed1faf1b601c0470a1e087d90e4d100ab Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 18 Oct 2017 15:53:47 -0700 Subject: [PATCH 22/43] disabled multiroot test --- .../interpreters/display.multiroot.test.ts | 98 +++++++++---------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/src/test/interpreters/display.multiroot.test.ts b/src/test/interpreters/display.multiroot.test.ts index 3007fbd557ae..6afea5ec18d0 100644 --- a/src/test/interpreters/display.multiroot.test.ts +++ b/src/test/interpreters/display.multiroot.test.ts @@ -1,55 +1,55 @@ -import * as assert from 'assert'; -import * as child_process from 'child_process'; -import { EOL } from 'os'; -import * as path from 'path'; -import { ConfigurationTarget, Uri, window, workspace } from 'vscode'; -import { PythonSettings } from '../../client/common/configSettings'; -import { InterpreterDisplay } from '../../client/interpreter/display'; -import { getFirstNonEmptyLineFromMultilineString } from '../../client/interpreter/helpers'; -import { VirtualEnvironmentManager } from '../../client/interpreter/virtualEnvs'; -import { rootWorkspaceUri, updateSetting } from '../common'; -import { closeActiveWindows, initialize, initializePython, initializeTest } from '../initialize'; -import { MockStatusBarItem } from '../mockClasses'; -import { MockInterpreterVersionProvider } from './mocks'; -import { MockProvider, MockVirtualEnv } from './mocks'; +// import * as assert from 'assert'; +// import * as child_process from 'child_process'; +// import { EOL } from 'os'; +// import * as path from 'path'; +// import { ConfigurationTarget, Uri, window, workspace } from 'vscode'; +// import { PythonSettings } from '../../client/common/configSettings'; +// import { InterpreterDisplay } from '../../client/interpreter/display'; +// import { getFirstNonEmptyLineFromMultilineString } from '../../client/interpreter/helpers'; +// import { VirtualEnvironmentManager } from '../../client/interpreter/virtualEnvs'; +// import { rootWorkspaceUri, updateSetting } from '../common'; +// import { closeActiveWindows, initialize, initializePython, initializeTest } from '../initialize'; +// import { MockStatusBarItem } from '../mockClasses'; +// import { MockInterpreterVersionProvider } from './mocks'; +// import { MockProvider, MockVirtualEnv } from './mocks'; -const multirootPath = path.join(__dirname, '..', '..', '..', 'src', 'testMultiRootWkspc'); -const workspace3Uri = Uri.file(path.join(multirootPath, 'workspace3')); -const fileToOpen = path.join(workspace3Uri.fsPath, 'file.py'); +// const multirootPath = path.join(__dirname, '..', '..', '..', 'src', 'testMultiRootWkspc'); +// const workspace3Uri = Uri.file(path.join(multirootPath, 'workspace3')); +// const fileToOpen = path.join(workspace3Uri.fsPath, 'file.py'); -// tslint:disable-next-line:max-func-body-length -suite('Multiroot Interpreters Display', () => { - // tslint:disable-next-line:no-function-expression - suiteSetup(initialize); - setup(initializeTest); - suiteTeardown(initializePython); - teardown(async () => { - await initialize(); - await closeActiveWindows(); - const settings = workspace.getConfiguration('python', Uri.file(fileToOpen)); - const value = settings.inspect('pythonPath'); - if (value && value.workspaceFolderValue) { - await settings.update('pythonPath', undefined, ConfigurationTarget.WorkspaceFolder); - PythonSettings.dispose(); - } - }); +// // tslint:disable-next-line:max-func-body-length +// suite('Multiroot Interpreters Display', () => { +// // tslint:disable-next-line:no-function-expression +// suiteSetup(initialize); +// setup(initializeTest); +// suiteTeardown(initializePython); +// teardown(async () => { +// await initialize(); +// await closeActiveWindows(); +// const settings = workspace.getConfiguration('python', Uri.file(fileToOpen)); +// const value = settings.inspect('pythonPath'); +// if (value && value.workspaceFolderValue) { +// await settings.update('pythonPath', undefined, ConfigurationTarget.WorkspaceFolder); +// PythonSettings.dispose(); +// } +// }); - test('Must get display name from workspace folder interpreter and not from interpreter in workspace', async () => { - const settings = workspace.getConfiguration('python', Uri.file(fileToOpen)); - const pythonPath = fileToOpen; - await settings.update('pythonPath', pythonPath, ConfigurationTarget.WorkspaceFolder); - PythonSettings.dispose(); +// test('Must get display name from workspace folder interpreter and not from interpreter in workspace', async () => { +// const settings = workspace.getConfiguration('python', Uri.file(fileToOpen)); +// const pythonPath = fileToOpen; +// await settings.update('pythonPath', pythonPath, ConfigurationTarget.WorkspaceFolder); +// PythonSettings.dispose(); - const document = await workspace.openTextDocument(fileToOpen); - const editor = await window.showTextDocument(document); +// const document = await workspace.openTextDocument(fileToOpen); +// const editor = await window.showTextDocument(document); - const statusBar = new MockStatusBarItem(); - const provider = new MockProvider([]); - const displayName = `${path.basename(pythonPath)} [Environment]`; - const displayNameProvider = new MockInterpreterVersionProvider(displayName); - const display = new InterpreterDisplay(statusBar, provider, new VirtualEnvironmentManager([]), displayNameProvider); - await display.refresh(); +// const statusBar = new MockStatusBarItem(); +// const provider = new MockProvider([]); +// const displayName = `${path.basename(pythonPath)} [Environment]`; +// const displayNameProvider = new MockInterpreterVersionProvider(displayName); +// const display = new InterpreterDisplay(statusBar, provider, new VirtualEnvironmentManager([]), displayNameProvider); +// await display.refresh(); - assert.equal(statusBar.text, displayName, 'Incorrect display name'); - }); -}); +// assert.equal(statusBar.text, displayName, 'Incorrect display name'); +// }); +// }); From 7bdc669168d4c0358e06203a189ff7977c4dd7bd Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 18 Oct 2017 16:01:00 -0700 Subject: [PATCH 23/43] backwards compatible change --- src/client/providers/setInterpreterProvider.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/client/providers/setInterpreterProvider.ts b/src/client/providers/setInterpreterProvider.ts index 2bfca90a371f..47cdc95de966 100644 --- a/src/client/providers/setInterpreterProvider.ts +++ b/src/client/providers/setInterpreterProvider.ts @@ -36,8 +36,9 @@ export class SetInterpreterProvider implements Disposable { return configs[0]; } - // Ok we have multiple interpreters, get the user to pick a folder - const workspaceFolder = await window.showWorkspaceFolderPick({ placeHolder: 'Select a workspace' }); + // Ok we have multiple interpreters, get the user to pick a folder. + // tslint:disable-next-line:no-any prefer-type-cast + const workspaceFolder = await (window as any).showWorkspaceFolderPick({ placeHolder: 'Select a workspace' }); return workspaceFolder ? { folderUri: workspaceFolder.uri, configTarget: ConfigurationTarget.WorkspaceFolder } : undefined; } private async suggestionToQuickPickItem(suggestion: PythonInterpreter, workspaceUri?: Uri): Promise { From 5956a475325aad69274f9626c8f44c659c73c94a Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 19 Oct 2017 08:38:53 -0700 Subject: [PATCH 24/43] fix nose tests --- src/test/common.ts | 4 + src/test/index.ts | 8 +- .../noseFiles/specific/tst_unittest_one.py | 15 ++ .../noseFiles/specific/tst_unittest_two.py | 18 +++ .../testFiles/noseFiles/test_root.py | 19 +++ .../testFiles/noseFiles/tests/test4.py | 13 ++ .../noseFiles/tests/test_unittest_one.py | 19 +++ .../noseFiles/tests/test_unittest_two.py | 32 ++++ .../noseFiles/tests/unittest_three_test.py | 13 ++ src/test/unittests/nosetest.test.ts | 137 ++++++++++-------- 10 files changed, 213 insertions(+), 65 deletions(-) create mode 100644 src/test/pythonFiles/testFiles/noseFiles/specific/tst_unittest_one.py create mode 100644 src/test/pythonFiles/testFiles/noseFiles/specific/tst_unittest_two.py create mode 100644 src/test/pythonFiles/testFiles/noseFiles/test_root.py create mode 100644 src/test/pythonFiles/testFiles/noseFiles/tests/test4.py create mode 100644 src/test/pythonFiles/testFiles/noseFiles/tests/test_unittest_one.py create mode 100644 src/test/pythonFiles/testFiles/noseFiles/tests/test_unittest_two.py create mode 100644 src/test/pythonFiles/testFiles/noseFiles/tests/unittest_three_test.py diff --git a/src/test/common.ts b/src/test/common.ts index 2013b9487c59..83c817252c80 100644 --- a/src/test/common.ts +++ b/src/test/common.ts @@ -1,6 +1,7 @@ import * as path from 'path'; import { ConfigurationTarget, Uri, workspace } from 'vscode'; import { PythonSettings } from '../client/common/configSettings'; +import { IS_MULTI_ROOT_TEST } from './initialize'; const fileInNonRootWorkspace = path.join(__dirname, '..', '..', 'src', 'test', 'pythonFiles', 'dummy.py'); export const rootWorkspaceUri = getWorkspaceRoot(); @@ -24,6 +25,9 @@ export async function updateSetting(setting: PythonSettingKeys, value: {}, resou } // tslint:disable-next-line:await-promise await settings.update(setting, value, configTarget); + if (configTarget === ConfigurationTarget.Workspace && IS_MULTI_ROOT_TEST) { + await new Promise(resolve => setTimeout(resolve, 2000)); + } PythonSettings.dispose(); } diff --git a/src/test/index.ts b/src/test/index.ts index 10bd39fbd4bb..b3d12e51837c 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -5,15 +5,15 @@ const singleWorkspaceTestConfig = { ui: 'tdd', useColors: true, timeout: 25000, - grep: 'Multiroot', - invert: 'invert' + grep: 'Unit Tests', + // invert: 'invert' }; const multiWorkspaceTestConfig = { ui: 'tdd', useColors: true, timeout: 25000, - grep: 'Jupyter', - invert: 'invert' + grep: 'Unit Tests', + // invert: 'invert' }; // You can directly control Mocha options by uncommenting the following lines. diff --git a/src/test/pythonFiles/testFiles/noseFiles/specific/tst_unittest_one.py b/src/test/pythonFiles/testFiles/noseFiles/specific/tst_unittest_one.py new file mode 100644 index 000000000000..4825f3a4db3b --- /dev/null +++ b/src/test/pythonFiles/testFiles/noseFiles/specific/tst_unittest_one.py @@ -0,0 +1,15 @@ +import sys +import os + +import unittest + +class Test_test1(unittest.TestCase): + def tst_A(self): + self.fail("Not implemented") + + def tst_B(self): + self.assertEqual(1, 1, 'Not equal') + + +if __name__ == '__main__': + unittest.main() diff --git a/src/test/pythonFiles/testFiles/noseFiles/specific/tst_unittest_two.py b/src/test/pythonFiles/testFiles/noseFiles/specific/tst_unittest_two.py new file mode 100644 index 000000000000..c9a76c07f933 --- /dev/null +++ b/src/test/pythonFiles/testFiles/noseFiles/specific/tst_unittest_two.py @@ -0,0 +1,18 @@ +import unittest + +class Tst_test2(unittest.TestCase): + def tst_A2(self): + self.fail("Not implemented") + + def tst_B2(self): + self.assertEqual(1,1,'Not equal') + + def tst_C2(self): + self.assertEqual(1,2,'Not equal') + + def tst_D2(self): + raise ArithmeticError() + pass + +if __name__ == '__main__': + unittest.main() diff --git a/src/test/pythonFiles/testFiles/noseFiles/test_root.py b/src/test/pythonFiles/testFiles/noseFiles/test_root.py new file mode 100644 index 000000000000..452813e9a079 --- /dev/null +++ b/src/test/pythonFiles/testFiles/noseFiles/test_root.py @@ -0,0 +1,19 @@ +import sys +import os + +import unittest + +class Test_Root_test1(unittest.TestCase): + def test_Root_A(self): + self.fail("Not implemented") + + def test_Root_B(self): + self.assertEqual(1, 1, 'Not equal') + + @unittest.skip("demonstrating skipping") + def test_Root_c(self): + self.assertEqual(1, 1, 'Not equal') + + +if __name__ == '__main__': + unittest.main() diff --git a/src/test/pythonFiles/testFiles/noseFiles/tests/test4.py b/src/test/pythonFiles/testFiles/noseFiles/tests/test4.py new file mode 100644 index 000000000000..734b84cd342e --- /dev/null +++ b/src/test/pythonFiles/testFiles/noseFiles/tests/test4.py @@ -0,0 +1,13 @@ +import unittest + + +class Test_test3(unittest.TestCase): + def test4A(self): + self.fail("Not implemented") + + def test4B(self): + self.assertEqual(1, 1, 'Not equal') + + +if __name__ == '__main__': + unittest.main() diff --git a/src/test/pythonFiles/testFiles/noseFiles/tests/test_unittest_one.py b/src/test/pythonFiles/testFiles/noseFiles/tests/test_unittest_one.py new file mode 100644 index 000000000000..e869986b6ead --- /dev/null +++ b/src/test/pythonFiles/testFiles/noseFiles/tests/test_unittest_one.py @@ -0,0 +1,19 @@ +import sys +import os + +import unittest + +class Test_test1(unittest.TestCase): + def test_A(self): + self.fail("Not implemented") + + def test_B(self): + self.assertEqual(1, 1, 'Not equal') + + @unittest.skip("demonstrating skipping") + def test_c(self): + self.assertEqual(1, 1, 'Not equal') + + +if __name__ == '__main__': + unittest.main() diff --git a/src/test/pythonFiles/testFiles/noseFiles/tests/test_unittest_two.py b/src/test/pythonFiles/testFiles/noseFiles/tests/test_unittest_two.py new file mode 100644 index 000000000000..ad89d873e879 --- /dev/null +++ b/src/test/pythonFiles/testFiles/noseFiles/tests/test_unittest_two.py @@ -0,0 +1,32 @@ +import unittest + +class Test_test2(unittest.TestCase): + def test_A2(self): + self.fail("Not implemented") + + def test_B2(self): + self.assertEqual(1,1,'Not equal') + + def test_C2(self): + self.assertEqual(1,2,'Not equal') + + def test_D2(self): + raise ArithmeticError() + pass + +class Test_test2a(unittest.TestCase): + def test_222A2(self): + self.fail("Not implemented") + + def test_222B2(self): + self.assertEqual(1,1,'Not equal') + + class Test_test2a1(unittest.TestCase): + def test_222A2wow(self): + self.fail("Not implemented") + + def test_222B2wow(self): + self.assertEqual(1,1,'Not equal') + +if __name__ == '__main__': + unittest.main() diff --git a/src/test/pythonFiles/testFiles/noseFiles/tests/unittest_three_test.py b/src/test/pythonFiles/testFiles/noseFiles/tests/unittest_three_test.py new file mode 100644 index 000000000000..507e6af02063 --- /dev/null +++ b/src/test/pythonFiles/testFiles/noseFiles/tests/unittest_three_test.py @@ -0,0 +1,13 @@ +import unittest + + +class Test_test3(unittest.TestCase): + def test_A(self): + self.fail("Not implemented") + + def test_B(self): + self.assertEqual(1, 1, 'Not equal') + + +if __name__ == '__main__': + unittest.main() diff --git a/src/test/unittests/nosetest.test.ts b/src/test/unittests/nosetest.test.ts index 870ae5639aa1..d61e4e0d9944 100644 --- a/src/test/unittests/nosetest.test.ts +++ b/src/test/unittests/nosetest.test.ts @@ -1,22 +1,28 @@ import * as assert from 'assert'; -import * as vscode from 'vscode'; import * as fs from 'fs'; import * as path from 'path'; +import * as vscode from 'vscode'; +import { Tests, TestsToRun } from '../../client/unittests/common/contracts'; +import { TestResultDisplay } from '../../client/unittests/display/main'; import * as nose from '../../client/unittests/nosetest/main'; import { rootWorkspaceUri, updateSetting } from '../common'; import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../initialize'; -import { TestsToRun } from '../../client/unittests/common/contracts'; -import { TestResultDisplay } from '../../client/unittests/display/main'; import { MockOutputChannel } from './../mockClasses'; -const UNITTEST_TEST_FILES_PATH = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles', 'standard'); +const UNITTEST_TEST_FILES_PATH = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles', 'noseFiles'); const UNITTEST_SINGLE_TEST_FILE_PATH = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles', 'single'); -const filesToDelete = [path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles', 'standard', '.noseids'), -path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles', 'cwd', 'src', '.noseids')]; -const unitTestTestFilesCwdPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles', 'cwd', 'src'); +const filesToDelete = [ + path.join(UNITTEST_TEST_FILES_PATH, '.noseids'), + path.join(UNITTEST_SINGLE_TEST_FILE_PATH, '.noseids') +]; +// tslint:disable-next-line:max-func-body-length suite('Unit Tests (nosetest)', () => { const configTarget = IS_MULTI_ROOT_TEST ? vscode.ConfigurationTarget.WorkspaceFolder : vscode.ConfigurationTarget.Workspace; + const rootDirectory = UNITTEST_TEST_FILES_PATH; + let testManager: nose.TestManager; + let testResultDisplay: TestResultDisplay; + let outChannel: vscode.OutputChannel; suiteSetup(async () => { filesToDelete.forEach(file => { if (fs.existsSync(file)) { @@ -38,23 +44,19 @@ suite('Unit Tests (nosetest)', () => { testResultDisplay = new TestResultDisplay(outChannel); await initializeTest(); }); - teardown(() => { + teardown(async () => { outChannel.dispose(); testManager.dispose(); testResultDisplay.dispose(); - return updateSetting('unitTest.nosetestArgs', [], rootWorkspaceUri, configTarget); + await updateSetting('unitTest.nosetestArgs', [], rootWorkspaceUri, configTarget); }); function createTestManager(rootDir: string = rootDirectory) { testManager = new nose.TestManager(rootDir, outChannel); } - const rootDirectory = UNITTEST_TEST_FILES_PATH; - let testManager: nose.TestManager; - let testResultDisplay: TestResultDisplay; - let outChannel: vscode.OutputChannel; test('Discover Tests (single test file)', async () => { - testManager = new nose.TestManager(UNITTEST_SINGLE_TEST_FILE_PATH, outChannel); - const tests = await testManager.discoverTests(true, true) + createTestManager(UNITTEST_SINGLE_TEST_FILE_PATH); + const tests = await testManager.discoverTests(true, true); assert.equal(tests.testFiles.length, 2, 'Incorrect number of test files'); assert.equal(tests.testFunctions.length, 6, 'Incorrect number of test functions'); assert.equal(tests.testSuits.length, 2, 'Incorrect number of test suites'); @@ -62,71 +64,87 @@ suite('Unit Tests (nosetest)', () => { }); test('Check that nameToRun in testSuits has class name after : (single test file)', async () => { - testManager = new nose.TestManager(UNITTEST_SINGLE_TEST_FILE_PATH, outChannel); + createTestManager(UNITTEST_SINGLE_TEST_FILE_PATH); const tests = await testManager.discoverTests(true, true); assert.equal(tests.testFiles.length, 2, 'Incorrect number of test files'); assert.equal(tests.testFunctions.length, 6, 'Incorrect number of test functions'); assert.equal(tests.testSuits.length, 2, 'Incorrect number of test suites'); - assert.equal(tests.testSuits.every(t => t.testSuite.name === t.testSuite.nameToRun.split(":")[1]), true, 'Suite name does not match class name'); + assert.equal(tests.testSuits.every(t => t.testSuite.name === t.testSuite.nameToRun.split(':')[1]), true, 'Suite name does not match class name'); }); - test('Discover Tests (pattern = test_)', async () => { + function lookForTestFile(tests: Tests, testFile: string) { + const found = tests.testFiles.some(t => t.name === testFile && t.nameToRun === t.name); + assert.equal(found, true, `Test File not found '${testFile}'`); + } + test('Discover Tests (-m=test)', async () => { + await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); - assert.equal(tests.testFiles.length, 6, 'Incorrect number of test files'); - assert.equal(tests.testFunctions.length, 22, 'Incorrect number of test functions'); + assert.equal(tests.testFiles.length, 5, 'Incorrect number of test files'); + assert.equal(tests.testFunctions.length, 16, 'Incorrect number of test functions'); assert.equal(tests.testSuits.length, 6, 'Incorrect number of test suites'); - assert.equal(tests.testFiles.some(t => t.name === path.join('tests', 'test_unittest_one.py') && t.nameToRun === t.name), true, 'Test File not found'); - assert.equal(tests.testFiles.some(t => t.name === path.join('tests', 'test_unittest_two.py') && t.nameToRun === t.name), true, 'Test File not found'); - assert.equal(tests.testFiles.some(t => t.name === path.join('tests', 'test_pytest.py') && t.nameToRun === t.name), true, 'Test File not found'); - assert.equal(tests.testFiles.some(t => t.name === path.join('tests', 'test_another_pytest.py') && t.nameToRun === t.name), true, 'Test File not found'); - assert.equal(tests.testFiles.some(t => t.name === path.join('tests', 'unittest_three_test.py') && t.nameToRun === t.name), true, 'Test File not found'); - assert.equal(tests.testFiles.some(t => t.name === 'test_root.py' && t.nameToRun === t.name), true, 'Test File not found'); + lookForTestFile(tests, path.join('tests', 'test_unittest_one.py')); + lookForTestFile(tests, path.join('tests', 'test_unittest_two.py')); + lookForTestFile(tests, path.join('tests', 'unittest_three_test.py')); + lookForTestFile(tests, path.join('tests', 'test4.py')); + lookForTestFile(tests, 'test_root.py'); }); - test('Discover Tests (pattern = _test_)', async () => { - await updateSetting('unitTest.nosetestArgs', ['-m=*test*'], rootWorkspaceUri, configTarget); + test('Discover Tests (-w=specific -m=tst)', async () => { + await updateSetting('unitTest.nosetestArgs', ['-w', 'specific', '-m', 'tst'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); - assert.equal(tests.testFiles.length, 6, 'Incorrect number of test files'); - assert.equal(tests.testFunctions.length, 18, 'Incorrect number of test functions'); - assert.equal(tests.testSuits.length, 5, 'Incorrect number of test suites'); - assert.equal(tests.testFiles.some(t => t.name === path.join('tests', 'test_unittest_one.py') && t.nameToRun === t.name), true, 'Test File not found'); - assert.equal(tests.testFiles.some(t => t.name === path.join('tests', 'test_unittest_two.py') && t.nameToRun === t.name), true, 'Test File not found'); - assert.equal(tests.testFiles.some(t => t.name === path.join('tests', 'test_pytest.py') && t.nameToRun === t.name), true, 'Test File not found'); - assert.equal(tests.testFiles.some(t => t.name === path.join('tests', 'test_another_pytest.py') && t.nameToRun === t.name), true, 'Test File not found'); - assert.equal(tests.testFiles.some(t => t.name === path.join('tests', 'unittest_three_test.py') && t.nameToRun === t.name), true, 'Test File not found'); - assert.equal(tests.testFiles.some(t => t.name === 'test_root.py' && t.nameToRun === t.name), true, 'Test File not found'); + assert.equal(tests.testFiles.length, 2, 'Incorrect number of test files'); + assert.equal(tests.testFunctions.length, 6, 'Incorrect number of test functions'); + assert.equal(tests.testSuits.length, 2, 'Incorrect number of test suites'); + lookForTestFile(tests, path.join('specific', 'tst_unittest_one.py')); + lookForTestFile(tests, path.join('specific', 'tst_unittest_two.py')); + }); + + test('Discover Tests (-m=test_)', async () => { + await updateSetting('unitTest.nosetestArgs', ['-m', 'test_'], rootWorkspaceUri, configTarget); + createTestManager(); + const tests = await testManager.discoverTests(true, true); + assert.equal(tests.testFiles.length, 1, 'Incorrect number of test files'); + assert.equal(tests.testFunctions.length, 3, 'Incorrect number of test functions'); + assert.equal(tests.testSuits.length, 1, 'Incorrect number of test suites'); + lookForTestFile(tests, 'test_root.py'); }); test('Run Tests', async () => { + await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); createTestManager(); const results = await testManager.runTest(); - assert.equal(results.summary.errors, 5, 'Errors'); - assert.equal(results.summary.failures, 6, 'Failures'); - assert.equal(results.summary.passed, 8, 'Passed'); - assert.equal(results.summary.skipped, 3, 'skipped'); + assert.equal(results.summary.errors, 1, 'Errors'); + assert.equal(results.summary.failures, 7, 'Failures'); + assert.equal(results.summary.passed, 6, 'Passed'); + assert.equal(results.summary.skipped, 2, 'skipped'); }); test('Run Failed Tests', async () => { + await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); createTestManager(); let results = await testManager.runTest(); - assert.equal(results.summary.errors, 5, 'Errors'); - assert.equal(results.summary.failures, 6, 'Failures'); - assert.equal(results.summary.passed, 8, 'Passed'); - assert.equal(results.summary.skipped, 3, 'skipped'); + assert.equal(results.summary.errors, 1, 'Errors'); + assert.equal(results.summary.failures, 7, 'Failures'); + assert.equal(results.summary.passed, 6, 'Passed'); + assert.equal(results.summary.skipped, 2, 'skipped'); results = await testManager.runTest(true); - assert.equal(results.summary.errors, 5, 'Errors again'); - assert.equal(results.summary.failures, 6, 'Failures again'); + assert.equal(results.summary.errors, 1, 'Errors again'); + assert.equal(results.summary.failures, 7, 'Failures again'); assert.equal(results.summary.passed, 0, 'Passed again'); assert.equal(results.summary.skipped, 0, 'skipped again'); }); test('Run Specific Test File', async () => { + await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); - const testFile: TestsToRun = { testFile: [tests.testFiles[0]], testFolder: [], testFunction: [], testSuite: [] }; + const testFileToRun = tests.testFiles.find(t => t.fullPath.endsWith('test_root.py')); + assert.ok(testFileToRun, 'Test file not found'); + // tslint:disable-next-line:no-non-null-assertion + const testFile: TestsToRun = { testFile: [testFileToRun!], testFolder: [], testFunction: [], testSuite: [] }; const results = await testManager.runTest(testFile); assert.equal(results.summary.errors, 0, 'Errors'); assert.equal(results.summary.failures, 1, 'Failures'); @@ -135,9 +153,13 @@ suite('Unit Tests (nosetest)', () => { }); test('Run Specific Test Suite', async () => { + await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); - const testSuite: TestsToRun = { testFile: [], testFolder: [], testFunction: [], testSuite: [tests.testSuits[0].testSuite] }; + const testSuiteToRun = tests.testSuits.find(s => s.xmlClassName === 'test_root.Test_Root_test1'); + assert.ok(testSuiteToRun, 'Test suite not found'); + // tslint:disable-next-line:no-non-null-assertion + const testSuite: TestsToRun = { testFile: [], testFolder: [], testFunction: [], testSuite: [testSuiteToRun!.testSuite] }; const results = await testManager.runTest(testSuite); assert.equal(results.summary.errors, 0, 'Errors'); assert.equal(results.summary.failures, 1, 'Failures'); @@ -146,24 +168,17 @@ suite('Unit Tests (nosetest)', () => { }); test('Run Specific Test Function', async () => { + await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); - const testFn: TestsToRun = { testFile: [], testFolder: [], testFunction: [tests.testFunctions[0].testFunction], testSuite: [] }; + const testFnToRun = tests.testFunctions.find(f => f.xmlClassName === 'test_root.Test_Root_test1'); + assert.ok(testFnToRun, 'Test function not found'); + // tslint:disable-next-line:no-non-null-assertion + const testFn: TestsToRun = { testFile: [], testFolder: [], testFunction: [testFnToRun!.testFunction], testSuite: [] }; const results = await testManager.runTest(testFn); assert.equal(results.summary.errors, 0, 'Errors'); assert.equal(results.summary.failures, 1, 'Failures'); assert.equal(results.summary.passed, 0, 'Passed'); assert.equal(results.summary.skipped, 0, 'skipped'); }); - - test('Setting cwd should return tests', async () => { - await updateSetting('unitTest.nosetestArgs', ['tests'], rootWorkspaceUri, configTarget); - createTestManager(unitTestTestFilesCwdPath); - - const tests = await testManager.discoverTests(true, true); - assert.equal(tests.testFiles.length, 1, 'Incorrect number of test files'); - assert.equal(tests.testFolders.length, 1, 'Incorrect number of test folders'); - assert.equal(tests.testFunctions.length, 1, 'Incorrect number of test functions'); - assert.equal(tests.testSuits.length, 1, 'Incorrect number of test suites'); - }); }); From 7c8049ff37a2db32470d5be2258d5288a7c18a57 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 19 Oct 2017 08:52:40 -0700 Subject: [PATCH 25/43] revert change --- src/test/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/index.ts b/src/test/index.ts index b3d12e51837c..10bd39fbd4bb 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -5,15 +5,15 @@ const singleWorkspaceTestConfig = { ui: 'tdd', useColors: true, timeout: 25000, - grep: 'Unit Tests', - // invert: 'invert' + grep: 'Multiroot', + invert: 'invert' }; const multiWorkspaceTestConfig = { ui: 'tdd', useColors: true, timeout: 25000, - grep: 'Unit Tests', - // invert: 'invert' + grep: 'Jupyter', + invert: 'invert' }; // You can directly control Mocha options by uncommenting the following lines. From 0f6b142c342aaf6fa67aa216f55ebf7a1bde9928 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 19 Oct 2017 09:18:57 -0700 Subject: [PATCH 26/43] enable test but disable it --- .../interpreters/display.multiroot.test.ts | 102 +++++++++--------- 1 file changed, 53 insertions(+), 49 deletions(-) diff --git a/src/test/interpreters/display.multiroot.test.ts b/src/test/interpreters/display.multiroot.test.ts index 6afea5ec18d0..c4f36b480ed5 100644 --- a/src/test/interpreters/display.multiroot.test.ts +++ b/src/test/interpreters/display.multiroot.test.ts @@ -1,55 +1,59 @@ -// import * as assert from 'assert'; -// import * as child_process from 'child_process'; -// import { EOL } from 'os'; -// import * as path from 'path'; -// import { ConfigurationTarget, Uri, window, workspace } from 'vscode'; -// import { PythonSettings } from '../../client/common/configSettings'; -// import { InterpreterDisplay } from '../../client/interpreter/display'; -// import { getFirstNonEmptyLineFromMultilineString } from '../../client/interpreter/helpers'; -// import { VirtualEnvironmentManager } from '../../client/interpreter/virtualEnvs'; -// import { rootWorkspaceUri, updateSetting } from '../common'; -// import { closeActiveWindows, initialize, initializePython, initializeTest } from '../initialize'; -// import { MockStatusBarItem } from '../mockClasses'; -// import { MockInterpreterVersionProvider } from './mocks'; -// import { MockProvider, MockVirtualEnv } from './mocks'; +import * as assert from 'assert'; +import * as child_process from 'child_process'; +import { EOL } from 'os'; +import * as path from 'path'; +import { ConfigurationTarget, Uri, window, workspace } from 'vscode'; +import { PythonSettings } from '../../client/common/configSettings'; +import { InterpreterDisplay } from '../../client/interpreter/display'; +import { getFirstNonEmptyLineFromMultilineString } from '../../client/interpreter/helpers'; +import { VirtualEnvironmentManager } from '../../client/interpreter/virtualEnvs'; +import { rootWorkspaceUri, updateSetting } from '../common'; +import { closeActiveWindows, initialize, initializePython, initializeTest } from '../initialize'; +import { MockStatusBarItem } from '../mockClasses'; +import { MockInterpreterVersionProvider } from './mocks'; +import { MockProvider, MockVirtualEnv } from './mocks'; -// const multirootPath = path.join(__dirname, '..', '..', '..', 'src', 'testMultiRootWkspc'); -// const workspace3Uri = Uri.file(path.join(multirootPath, 'workspace3')); -// const fileToOpen = path.join(workspace3Uri.fsPath, 'file.py'); +const multirootPath = path.join(__dirname, '..', '..', '..', 'src', 'testMultiRootWkspc'); +const workspace3Uri = Uri.file(path.join(multirootPath, 'workspace3')); +const fileToOpen = path.join(workspace3Uri.fsPath, 'file.py'); -// // tslint:disable-next-line:max-func-body-length -// suite('Multiroot Interpreters Display', () => { -// // tslint:disable-next-line:no-function-expression -// suiteSetup(initialize); -// setup(initializeTest); -// suiteTeardown(initializePython); -// teardown(async () => { -// await initialize(); -// await closeActiveWindows(); -// const settings = workspace.getConfiguration('python', Uri.file(fileToOpen)); -// const value = settings.inspect('pythonPath'); -// if (value && value.workspaceFolderValue) { -// await settings.update('pythonPath', undefined, ConfigurationTarget.WorkspaceFolder); -// PythonSettings.dispose(); -// } -// }); +// tslint:disable-next-line:max-func-body-length +suite('Multiroot Interpreters Display', () => { + // tslint:disable-next-line:no-function-expression + suiteSetup(function () { + // tslint:disable-next-line:no-invalid-this + this.skip(); + return initialize(); + }); + setup(initializeTest); + suiteTeardown(initializePython); + teardown(async () => { + await initialize(); + await closeActiveWindows(); + const settings = workspace.getConfiguration('python', Uri.file(fileToOpen)); + const value = settings.inspect('pythonPath'); + if (value && value.workspaceFolderValue) { + await settings.update('pythonPath', undefined, ConfigurationTarget.WorkspaceFolder); + PythonSettings.dispose(); + } + }); -// test('Must get display name from workspace folder interpreter and not from interpreter in workspace', async () => { -// const settings = workspace.getConfiguration('python', Uri.file(fileToOpen)); -// const pythonPath = fileToOpen; -// await settings.update('pythonPath', pythonPath, ConfigurationTarget.WorkspaceFolder); -// PythonSettings.dispose(); + test('Must get display name from workspace folder interpreter and not from interpreter in workspace', async () => { + const settings = workspace.getConfiguration('python', Uri.file(fileToOpen)); + const pythonPath = fileToOpen; + await settings.update('pythonPath', pythonPath, ConfigurationTarget.WorkspaceFolder); + PythonSettings.dispose(); -// const document = await workspace.openTextDocument(fileToOpen); -// const editor = await window.showTextDocument(document); + const document = await workspace.openTextDocument(fileToOpen); + const editor = await window.showTextDocument(document); -// const statusBar = new MockStatusBarItem(); -// const provider = new MockProvider([]); -// const displayName = `${path.basename(pythonPath)} [Environment]`; -// const displayNameProvider = new MockInterpreterVersionProvider(displayName); -// const display = new InterpreterDisplay(statusBar, provider, new VirtualEnvironmentManager([]), displayNameProvider); -// await display.refresh(); + const statusBar = new MockStatusBarItem(); + const provider = new MockProvider([]); + const displayName = `${path.basename(pythonPath)} [Environment]`; + const displayNameProvider = new MockInterpreterVersionProvider(displayName); + const display = new InterpreterDisplay(statusBar, provider, new VirtualEnvironmentManager([]), displayNameProvider); + await display.refresh(); -// assert.equal(statusBar.text, displayName, 'Incorrect display name'); -// }); -// }); + assert.equal(statusBar.text, displayName, 'Incorrect display name'); + }); +}); From 8570b90b1ba03c135740ef88b1ba74ae27559c9a Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 23 Oct 2017 15:58:15 -0700 Subject: [PATCH 27/43] multi root support in utils.ts --- src/client/common/configSettings.ts | 41 ++- src/client/common/installer.ts | 10 +- src/client/common/interpreterInfoCache.ts | 54 ++++ src/client/common/utils.ts | 236 +++++++++--------- src/client/extension.ts | 12 +- src/client/formatters/baseFormatter.ts | 2 +- src/client/interpreter/contracts.ts | 4 +- src/client/interpreter/index.ts | 8 +- src/client/interpreter/locators/index.ts | 91 ++++--- .../locators/services/KnownPathsService.ts | 3 +- .../locators/services/condaEnvFileService.ts | 3 +- .../locators/services/condaEnvService.ts | 3 +- .../locators/services/currentPathService.ts | 3 +- .../locators/services/virtualEnvService.ts | 7 +- .../services/windowsRegistryService.ts | 2 + src/client/linters/baseLinter.ts | 2 +- src/client/linters/prospector.ts | 2 +- src/client/linters/pydocstyle.ts | 2 +- .../providers/execInTerminalProvider.ts | 32 +-- src/client/providers/jediProxy.ts | 8 +- src/client/providers/replProvider.ts | 46 ++++ src/client/refactor/proxy.ts | 6 +- src/client/sortImports.ts | 2 +- src/client/unittests/common/debugLauncher.ts | 2 +- src/client/unittests/common/runner.ts | 45 +--- src/client/unittests/nosetest/collector.ts | 2 +- src/client/unittests/pytest/collector.ts | 2 +- src/client/unittests/unittest/collector.ts | 2 +- src/test/autocomplete/base.test.ts | 2 +- src/test/autocomplete/pep484.test.ts | 2 +- src/test/autocomplete/pep526.test.ts | 2 +- src/test/common.ts | 3 - src/test/common/common.test.ts | 12 +- .../common/configSettings.multiroot.test.ts | 24 -- src/test/format/extension.format.test.ts | 4 +- src/test/format/extension.sort.test.ts | 8 - src/test/interpreters/condaEnvService.test.ts | 42 ++-- src/test/interpreters/display.test.ts | 12 +- src/test/interpreters/mocks.ts | 19 +- src/test/linters/lint.test.ts | 2 +- src/test/unittests/nosetest.test.ts | 112 ++++----- src/testMultiRootWkspc/multi.code-workspace | 7 +- .../workspace1/.vscode/settings.json | 4 +- 43 files changed, 481 insertions(+), 406 deletions(-) create mode 100644 src/client/common/interpreterInfoCache.ts create mode 100644 src/client/providers/replProvider.ts diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index bece06d457fa..78bba31f34d9 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -5,7 +5,9 @@ import { EventEmitter } from 'events'; import * as path from 'path'; import * as vscode from 'vscode'; import { Uri } from 'vscode'; +import { InterpreterInfoCache } from './interpreterInfoCache'; import { SystemVariables } from './systemVariables'; + // tslint:disable-next-line:no-require-imports no-var-requires const untildify = require('untildify'); @@ -197,10 +199,12 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { // tslint:disable-next-line:no-unsafe-any this.disposables.forEach(disposable => disposable.dispose()); this.disposables = []; + InterpreterInfoCache.clear(); } // tslint:disable-next-line:cyclomatic-complexity max-func-body-length private initializeSettings() { + InterpreterInfoCache.clear(); const workspaceRoot = this.workspaceRoot.fsPath; const systemVariables: SystemVariables = new SystemVariables(this.workspaceRoot ? this.workspaceRoot.fsPath : undefined); const pythonSettings = vscode.workspace.getConfiguration('python', this.workspaceRoot); @@ -213,8 +217,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { this.jediPath = systemVariables.resolveAny(pythonSettings.get('jediPath'))!; if (typeof this.jediPath === 'string' && this.jediPath.length > 0) { this.jediPath = getAbsolutePath(systemVariables.resolveAny(this.jediPath), workspaceRoot); - } - else { + } else { this.jediPath = ''; } // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion @@ -230,16 +233,14 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { this.disablePromptForFeatures = Array.isArray(this.disablePromptForFeatures) ? this.disablePromptForFeatures : []; if (this.linting) { Object.assign(this.linting, lintingSettings); - } - else { + } else { this.linting = lintingSettings; } // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion const sortImportSettings = systemVariables.resolveAny(pythonSettings.get('sortImports'))!; if (this.sortImports) { Object.assign(this.sortImports, sortImportSettings); - } - else { + } else { this.sortImports = sortImportSettings; } // Support for travis. @@ -290,8 +291,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { const formattingSettings = systemVariables.resolveAny(pythonSettings.get('formatting'))!; if (this.formatting) { Object.assign(this.formatting, formattingSettings); - } - else { + } else { this.formatting = formattingSettings; } // Support for travis. @@ -309,8 +309,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { const autoCompleteSettings = systemVariables.resolveAny(pythonSettings.get('autoComplete'))!; if (this.autoComplete) { Object.assign(this.autoComplete, autoCompleteSettings); - } - else { + } else { this.autoComplete = autoCompleteSettings; } // Support for travis. @@ -324,8 +323,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { const workspaceSymbolsSettings = systemVariables.resolveAny(pythonSettings.get('workspaceSymbols'))!; if (this.workspaceSymbols) { Object.assign(this.workspaceSymbols, workspaceSymbolsSettings); - } - else { + } else { this.workspaceSymbols = workspaceSymbolsSettings; } // Support for travis. @@ -343,8 +341,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { const unitTestSettings = systemVariables.resolveAny(pythonSettings.get('unitTest'))!; if (this.unitTest) { Object.assign(this.unitTest, unitTestSettings); - } - else { + } else { this.unitTest = unitTestSettings; if (IS_TEST_EXECUTION && !this.unitTest) { // tslint:disable-next-line:prefer-type-cast @@ -381,8 +378,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { const terminalSettings = systemVariables.resolveAny(pythonSettings.get('terminal'))!; if (this.terminal) { Object.assign(this.terminal, terminalSettings); - } - else { + } else { this.terminal = terminalSettings; if (IS_TEST_EXECUTION && !this.terminal) { // tslint:disable-next-line:prefer-type-cast @@ -402,7 +398,9 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { appendResults: true, defaultKernel: '', startupCode: [] }; - this.emit('change'); + // If workspace config changes, then we could have a cascading effect of on change events. + // Lets defer the change notification. + setTimeout(() => this.emit('change'), 1); } public get pythonPath(): string { @@ -416,8 +414,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { // E.g. virtual directory name. try { this._pythonPath = getPythonExecutable(value); - } - catch (ex) { + } catch (ex) { this._pythonPath = value; } } @@ -461,8 +458,7 @@ function getPythonExecutable(pythonPath: string): string { if (isValidPythonPath(path.join(pythonPath, 'scripts', executableName))) { return path.join(pythonPath, 'scripts', executableName); } - } - else { + } else { if (isValidPythonPath(path.join(pythonPath, executableName))) { return path.join(pythonPath, executableName); } @@ -479,8 +475,7 @@ function isValidPythonPath(pythonPath: string): boolean { try { const output = child_process.execFileSync(pythonPath, ['-c', 'print(1234)'], { encoding: 'utf8' }); return output.startsWith('1234'); - } - catch (ex) { + } catch (ex) { return false; } } diff --git a/src/client/common/installer.ts b/src/client/common/installer.ts index 59e22331f743..6cdd8a67608f 100644 --- a/src/client/common/installer.ts +++ b/src/client/common/installer.ts @@ -263,7 +263,7 @@ export class Installer implements vscode.Disposable { if (this.outputChannel && installArgs[0] === '-m') { // Errors are just displayed to the user this.outputChannel.show(); - installationPromise = execPythonFile(settings.PythonSettings.getInstance(resource).pythonPath, + installationPromise = execPythonFile(resource, settings.PythonSettings.getInstance(resource).pythonPath, installArgs, getCwdForInstallScript(resource), true, (data) => { this.outputChannel!.append(data); }); } else { @@ -271,7 +271,7 @@ export class Installer implements vscode.Disposable { // Cuz people may launch vs code from terminal when they have activated the appropriate virtual env // Problem is terminal doesn't use the currently activated virtual env // Must have something to do with the process being launched in the terminal - installationPromise = getFullyQualifiedPythonInterpreterPath() + installationPromise = getFullyQualifiedPythonInterpreterPath(resource) .then(pythonPath => { let installScript = installArgs.join(' '); @@ -340,7 +340,7 @@ async function isProductInstalled(product: Product, resource?: Uri): Promise true) .catch(reason => !isNotInstalledError(reason)); } @@ -350,5 +350,5 @@ function uninstallproduct(product: Product, resource?: Uri): Promise { return Promise.resolve(); } const uninstallArgs = ProductUninstallScripts.get(product)!; - return execPythonFile('python', uninstallArgs, getCwdForInstallScript(resource), false); -} \ No newline at end of file + return execPythonFile(resource, 'python', uninstallArgs, getCwdForInstallScript(resource), false); +} diff --git a/src/client/common/interpreterInfoCache.ts b/src/client/common/interpreterInfoCache.ts new file mode 100644 index 000000000000..ffb46dc02b0b --- /dev/null +++ b/src/client/common/interpreterInfoCache.ts @@ -0,0 +1,54 @@ +import { Uri, workspace } from 'vscode'; + +type InterpreterCache = { + pythonInterpreterDirectory?: string; + pythonInterpreterPath?: string; + pythonSettingsPath?: string; + // tslint:disable-next-line:no-any + customEnvVariables?: any; +}; + +const cache = new Map(); + +// tslint:disable-next-line:no-stateless-class +export class InterpreterInfoCache { + // tslint:disable-next-line:function-name + public static clear(): void { + cache.clear(); + } + // tslint:disable-next-line:function-name + public static get(resource?: Uri) { + const cacheKey = InterpreterInfoCache.getCacheKey(resource) || ''; + return cache.has(cacheKey) ? cache.get(cacheKey) : {}; + } + // tslint:disable-next-line:function-name + public static setPaths(resource?: Uri, pythonSettingsPath?: string, pythonInterpreterPath?: string, pythonInterpreterDirectory?: string) { + InterpreterInfoCache.setCacheData('pythonInterpreterDirectory', resource, pythonInterpreterDirectory); + InterpreterInfoCache.setCacheData('pythonInterpreterPath', resource, pythonInterpreterPath); + InterpreterInfoCache.setCacheData('pythonSettingsPath', resource, pythonSettingsPath); + } + + // tslint:disable-next-line:no-any function-name + public static setCustomEnvVariables(resource?: Uri, envVars?: any) { + // tslint:disable-next-line:no-any + InterpreterInfoCache.setCacheData('customEnvVariables', resource, envVars); + } + // tslint:disable-next-line:no-any function-name + private static setCacheData(property: keyof InterpreterCache, resource?: Uri, value?: string | any) { + const cacheKey = InterpreterInfoCache.getCacheKey(resource) || ''; + // tslint:disable-next-line:prefer-type-cast + const data = cache.has(cacheKey) ? cache.get(cacheKey) : {} as InterpreterCache; + data[property] = value; + cache.set(cacheKey, data); + } + private static getCacheKey(resource?: Uri): string { + if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0) { + return ''; + } + if (!resource || workspace.workspaceFolders.length === 1) { + return workspace.workspaceFolders[0].uri.fsPath; + } + const folder = workspace.getWorkspaceFolder(resource); + return folder ? folder.uri.fsPath : ''; + } +} diff --git a/src/client/common/utils.ts b/src/client/common/utils.ts index bd3fd3be17d1..2c862808d627 100644 --- a/src/client/common/utils.ts +++ b/src/client/common/utils.ts @@ -1,17 +1,16 @@ -/// -/// - 'use strict'; // TODO: Cleanup this place // Add options for execPythonFile -import * as path from 'path'; +import * as child_process from 'child_process'; import * as fs from 'fs'; +import * as fsExtra from 'fs-extra'; import * as os from 'os'; -import * as child_process from 'child_process'; +import * as path from 'path'; +import { CancellationToken, Range, TextDocument, Uri } from 'vscode'; import * as settings from './configSettings'; -import { CancellationToken, TextDocument, Range } from 'vscode'; -import { isNotInstalledError } from './helpers'; import { mergeEnvVariables, parseEnvFile } from './envFileParser'; +import { isNotInstalledError } from './helpers'; +import { InterpreterInfoCache } from './interpreterInfoCache'; export const IS_WINDOWS = /^win/.test(process.platform); export const Is_64Bit = os.arch() === 'x64'; @@ -52,32 +51,29 @@ export function fsReaddirAsync(root: string): Promise { }); } -let pythonInterpretterDirectory: string = null; -let previouslyIdentifiedPythonPath: string = null; -let customEnvVariables: any = null; - -// If config settings change then clear env variables that we have cached -// Remember, the path to the python interpreter can change, hence we need to re-set the paths -settings.PythonSettings.getInstance().on('change', function () { - pythonInterpretterDirectory = null; - previouslyIdentifiedPythonPath = null; - customEnvVariables = null; -}); +async function getPythonInterpreterDirectory(resource?: Uri): Promise { + const cache = InterpreterInfoCache.get(resource); + const pythonFileName = settings.PythonSettings.getInstance(resource).pythonPath; -export function getPythonInterpreterDirectory(): Promise { // If we already have it and the python path hasn't changed, yay - if (pythonInterpretterDirectory && previouslyIdentifiedPythonPath === settings.PythonSettings.getInstance().pythonPath) { - return Promise.resolve(pythonInterpretterDirectory); + if (cache.pythonInterpreterDirectory && cache.pythonInterpreterDirectory.length > 0 + && cache.pythonSettingsPath === pythonFileName) { + return cache.pythonInterpreterDirectory; } - let pythonFileName = settings.PythonSettings.getInstance().pythonPath; // Check if we have the path if (path.basename(pythonFileName) === pythonFileName) { - // No path provided, however we can get it by using sys.executableFile - return getPathFromPythonCommand(["-c", "import sys;print(sys.executable)"]) - .then(pythonExecutablePath => pythonInterpretterDirectory = path.dirname(pythonExecutablePath)) - .catch(() => pythonInterpretterDirectory = ''); + try { + const pythonInterpreterPath = await getPathFromPythonCommand(pythonFileName); + const pythonInterpreterDirectory = path.dirname(pythonInterpreterPath); + InterpreterInfoCache.setPaths(resource, pythonFileName, pythonInterpreterPath, pythonInterpreterDirectory); + return pythonInterpreterDirectory; + // tslint:disable-next-line:variable-name + } catch (_ex) { + InterpreterInfoCache.setPaths(resource, pythonFileName, pythonFileName, ''); + return ''; + } } return new Promise(resolve => { @@ -85,88 +81,78 @@ export function getPythonInterpreterDirectory(): Promise { child_process.execFile(pythonFileName, ['-c', 'print(1234)'], (error, stdout, stderr) => { // Yes this is a valid python path if (stdout.startsWith('1234')) { - previouslyIdentifiedPythonPath = path.dirname(pythonFileName); - } - else { - previouslyIdentifiedPythonPath = ''; + const pythonInterpreterDirectory = path.dirname(pythonFileName); + InterpreterInfoCache.setPaths(resource, pythonFileName, pythonFileName, pythonInterpreterDirectory); + resolve(pythonInterpreterDirectory); + } else { + // No idea, didn't work, hence don't reject, but return empty path + InterpreterInfoCache.setPaths(resource, pythonFileName, pythonFileName, ''); + resolve(''); } - // No idea, didn't work, hence don't reject, but return empty path - resolve(previouslyIdentifiedPythonPath); }); }); } -export function getFullyQualifiedPythonInterpreterPath(): Promise { - return getPythonInterpreterDirectory() - .then(pyPath => path.join(pyPath, path.basename(settings.PythonSettings.getInstance().pythonPath))); +export async function getFullyQualifiedPythonInterpreterPath(resource?: Uri): Promise { + const pyDir = await getPythonInterpreterDirectory(resource); + const cache = InterpreterInfoCache.get(resource); + return cache.pythonInterpreterPath; } -export function getPathFromPythonCommand(args: string[]): Promise { - return execPythonFile(settings.PythonSettings.getInstance().pythonPath, args, __dirname).then(stdout => { - if (stdout.length === 0) { - return ""; - } - let lines = stdout.split(/\r?\n/g).filter(line => line.length > 0); - return validatePath(lines[0]); - }).catch(() => { - return ""; +export async function getPathFromPythonCommand(pythonPath: string): Promise { + return await new Promise((resolve, reject) => { + child_process.execFile(pythonPath, ['-c', 'import sys;print(sys.executable)'], (_, stdout) => { + if (stdout) { + const lines = stdout.split(/\r?\n/g).map(line => line.trim()).filter(line => line.length > 0); + resolve(lines.length > 0 ? lines[0] : ''); + } else { + reject(); + } + }); }); } -export function execPythonFile(file: string, args: string[], cwd: string, includeErrorAsResponse: boolean = false, stdOut: (line: string) => void = null, token?: CancellationToken): Promise { - const execAsModule = file.toUpperCase() === 'PYTHON' && args.length > 0 && args[0] === '-m'; - - // If running the python file, then always revert to execFileInternal - // Cuz python interpreter is always a file and we can and will always run it using child_process.execFile() - if (file === settings.PythonSettings.getInstance().pythonPath) { - if (stdOut) { - return spawnFileInternal(file, args, { cwd, env: customEnvVariables }, includeErrorAsResponse, stdOut, token); - } - if (execAsModule) { - return getFullyQualifiedPythonInterpreterPath() - .then(p => execPythonModule(p, args, { cwd: cwd }, includeErrorAsResponse, token)); - } - return execFileInternal(file, args, { cwd: cwd }, includeErrorAsResponse, token); +async function getEnvVariables(resource?: Uri): Promise<{}> { + const cache = InterpreterInfoCache.get(resource); + if (cache.customEnvVariables) { + return cache.customEnvVariables; } - return getPythonInterpreterDirectory().then(pyPath => { - // We don't have a path - if (pyPath.length === 0) { - let options: child_process.ExecFileOptions = { cwd }; - const envVars = customEnvVariables || getCustomEnvVars(); - if (envVars) { - options.env = envVars; - } - if (stdOut) { - return spawnFileInternal(file, args, options, includeErrorAsResponse, stdOut, token); - } - return execFileInternal(file, args, options, includeErrorAsResponse, token); + const pyPath = await getPythonInterpreterDirectory(resource); + let customEnvVariables = await getCustomEnvVars(resource) || {}; + + if (pyPath.length > 0) { + // Ensure to include the path of the current python. + let newPath = ''; + const currentPath = typeof customEnvVariables[PATH_VARIABLE_NAME] === 'string' ? customEnvVariables[PATH_VARIABLE_NAME] : process.env[PATH_VARIABLE_NAME]; + if (IS_WINDOWS) { + newPath = `${pyPath}\\${path.delimiter}${path.join(pyPath, 'Scripts\\')}${path.delimiter}${currentPath}`; + // This needs to be done for windows. + process.env[PATH_VARIABLE_NAME] = newPath; + } else { + newPath = `${pyPath}${path.delimiter}${currentPath}`; } + customEnvVariables = mergeEnvVariables(customEnvVariables, process.env); + customEnvVariables[PATH_VARIABLE_NAME] = newPath; + } - if (customEnvVariables === null) { - customEnvVariables = getCustomEnvVars(); - customEnvVariables = customEnvVariables ? customEnvVariables : {}; - // Ensure to include the path of the current python - let newPath = ''; - let currentPath = typeof customEnvVariables[PATH_VARIABLE_NAME] === 'string' ? customEnvVariables[PATH_VARIABLE_NAME] : process.env[PATH_VARIABLE_NAME]; - if (IS_WINDOWS) { - newPath = pyPath + '\\' + path.delimiter + path.join(pyPath, 'Scripts\\') + path.delimiter + currentPath; - // This needs to be done for windows - process.env[PATH_VARIABLE_NAME] = newPath; - } - else { - newPath = pyPath + path.delimiter + currentPath; - } - customEnvVariables = mergeEnvVariables(customEnvVariables, process.env); - customEnvVariables[PATH_VARIABLE_NAME] = newPath; - } + InterpreterInfoCache.setCustomEnvVariables(resource, customEnvVariables); + return customEnvVariables; +} +export async function execPythonFile(resource: string | Uri | undefined, file: string, args: string[], cwd: string, includeErrorAsResponse: boolean = false, stdOut: (line: string) => void = null, token?: CancellationToken): Promise { + const resourceUri = typeof resource === 'string' ? Uri.file(resource) : resource; + const env = await getEnvVariables(resourceUri); + const options = { cwd, env }; - if (stdOut) { - return spawnFileInternal(file, args, { cwd, env: customEnvVariables }, includeErrorAsResponse, stdOut, token); - } - if (execAsModule) { - return getFullyQualifiedPythonInterpreterPath() - .then(p => execPythonModule(p, args, { cwd: cwd, env: customEnvVariables }, includeErrorAsResponse, token)); - } - return execFileInternal(file, args, { cwd, env: customEnvVariables }, includeErrorAsResponse, token); - }); + if (stdOut) { + return spawnFileInternal(file, args, options, includeErrorAsResponse, stdOut, token); + } + + const fileIsPythonInterpreter = (file.toUpperCase() === 'PYTHON' || file === settings.PythonSettings.getInstance(resourceUri).pythonPath); + const execAsModule = fileIsPythonInterpreter && args.length > 0 && args[0] === '-m'; + + if (execAsModule) { + return getFullyQualifiedPythonInterpreterPath(resourceUri) + .then(p => execPythonModule(p, args, options, includeErrorAsResponse, token)); + } + return execFileInternal(file, args, options, includeErrorAsResponse, token); } function handleResponse(file: string, includeErrorAsResponse: boolean, error: Error, stdout: string, stderr: string, token?: CancellationToken): Promise { @@ -361,21 +347,41 @@ export function getSubDirectories(rootDir: string): Promise { }); } -export function getCustomEnvVars(): any { - const envFile = settings.PythonSettings.getInstance().envFile; - if (typeof envFile === 'string' && - envFile.length > 0 && - fs.existsSync(envFile)) { - - try { - let vars = parseEnvFile(envFile); - if (vars && typeof vars === 'object' && Object.keys(vars).length > 0) { - return vars; - } +export async function getCustomEnvVars(resource?: Uri): Promise<{} | undefined | null> { + const envFile = settings.PythonSettings.getInstance(resource).envFile; + if (typeof envFile !== 'string' || envFile.length === 0) { + return null; + } + const exists = await fsExtra.pathExists(envFile); + if (!exists) { + return null; + } + try { + const vars = parseEnvFile(envFile); + if (vars && typeof vars === 'object' && Object.keys(vars).length > 0) { + return vars; } - catch (ex) { - console.error('Failed to load env file', ex); + } catch (ex) { + console.error('Failed to parse env file', ex); + } + return null; +} +export function getCustomEnvVarsSync(resource?: Uri): {} | undefined | null { + const envFile = settings.PythonSettings.getInstance(resource).envFile; + if (typeof envFile !== 'string' || envFile.length === 0) { + return null; + } + const exists = fsExtra.pathExistsSync(envFile); + if (!exists) { + return null; + } + try { + const vars = parseEnvFile(envFile); + if (vars && typeof vars === 'object' && Object.keys(vars).length > 0) { + return vars; } + } catch (ex) { + console.error('Failed to parse env file', ex); } return null; } @@ -417,10 +423,12 @@ export function areBasePathsSame(path1: string, path2: string) { path2 = IS_WINDOWS ? path2.replace(/\//g, "\\") : path2; return path.dirname(path1).toUpperCase() === path.dirname(path2).toUpperCase(); } -export function getInterpreterDisplayName(pythonPath: string) { - return execPythonFile(pythonPath, ['--version'], __dirname, true) - .then(version => { - version = version.split(/\r?\n/g).map(line => line.trim()).filter(line => line.length > 0).join(''); - return version; +export async function getInterpreterDisplayName(pythonPath: string) { + return await new Promise((resolve, reject) => { + child_process.execFile(pythonPath, ['--version'], (stdErr, stdout) => { + const out = (typeof stdErr === 'string' ? stdErr : '') + os.EOL + (typeof stdout === 'string' ? stdout : ''); + const lines = out.split(/\r?\n/g).map(line => line.trim()).filter(line => line.length > 0); + resolve(lines.length > 0 ? lines[0] : ''); }); + }); } diff --git a/src/client/extension.ts b/src/client/extension.ts index 1e87f77f29b9..1012ff936f84 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -30,11 +30,11 @@ import { WorkspaceSymbols } from './workspaceSymbols/main'; import { BlockFormatProviders } from './typeFormatters/blockFormatProvider'; import * as os from 'os'; import * as fs from 'fs'; -import { getPathFromPythonCommand } from './common/utils'; import { JupyterProvider } from './jupyter/provider'; import { activateGoToObjectDefinitionProvider } from './providers/objectDefinitionProvider'; import { InterpreterManager } from './interpreter'; import { SimpleConfigurationProvider } from './debugger'; +import { ReplProvider } from './providers/replProvider'; const PYTHON: vscode.DocumentFilter = { language: 'python' }; let unitTestOutChannel: vscode.OutputChannel; @@ -76,15 +76,7 @@ export async function activate(context: vscode.ExtensionContext) { const jediFactory = new JediFactory(context.asAbsolutePath('.')); context.subscriptions.push(...activateGoToObjectDefinitionProvider(jediFactory)); - context.subscriptions.push(vscode.commands.registerCommand(Commands.Start_REPL, () => { - getPathFromPythonCommand(["-c", "import sys;print(sys.executable)"]).catch(() => { - return pythonSettings.pythonPath; - }).then(pythonExecutablePath => { - let term = vscode.window.createTerminal('Python', pythonExecutablePath); - term.show(); - context.subscriptions.push(term); - }); - })); + context.subscriptions.push(new ReplProvider()); // Enable indentAction vscode.languages.setLanguageConfiguration(PYTHON.language, { diff --git a/src/client/formatters/baseFormatter.ts b/src/client/formatters/baseFormatter.ts index 0beaa02e9291..1bbc4b3659c3 100644 --- a/src/client/formatters/baseFormatter.ts +++ b/src/client/formatters/baseFormatter.ts @@ -50,7 +50,7 @@ export abstract class BaseFormatter { if (token && token.isCancellationRequested) { return [filePath, '']; } - return Promise.all([Promise.resolve(filePath), execPythonFile(command, args.concat([filePath]), cwd)]); + return Promise.all([Promise.resolve(filePath), execPythonFile(document.uri, command, args.concat([filePath]), cwd)]); }).then(data => { // Delete the temporary file created if (tmpFileCreated) { diff --git a/src/client/interpreter/contracts.ts b/src/client/interpreter/contracts.ts index ccf1b3d4dc4e..24c11f906597 100644 --- a/src/client/interpreter/contracts.ts +++ b/src/client/interpreter/contracts.ts @@ -1,7 +1,7 @@ -import { ConfigurationTarget, Uri } from 'vscode'; +import { ConfigurationTarget, Disposable, Uri } from 'vscode'; import { Architecture } from '../common/registry'; -export interface IInterpreterLocatorService { +export interface IInterpreterLocatorService extends Disposable { getInterpreters(resource?: Uri): Promise; } diff --git a/src/client/interpreter/index.ts b/src/client/interpreter/index.ts index ca0da1c886fd..62f389c7c60e 100644 --- a/src/client/interpreter/index.ts +++ b/src/client/interpreter/index.ts @@ -12,8 +12,6 @@ import { VirtualEnvironmentManager } from './virtualEnvs/index'; import { VEnv } from './virtualEnvs/venv'; import { VirtualEnv } from './virtualEnvs/virtualEnv'; -const settings = PythonSettings.getInstance(); - export class InterpreterManager implements Disposable { private disposables: Disposable[] = []; private display: InterpreterDisplay | null | undefined; @@ -24,7 +22,7 @@ export class InterpreterManager implements Disposable { this.interpreterProvider = new PythonInterpreterLocatorService(virtualEnvMgr); const versionService = new InterpreterVersionService(); this.display = new InterpreterDisplay(statusBar, this.interpreterProvider, virtualEnvMgr, versionService); - settings.addListener('change', () => this.onConfigChanged()); + PythonSettings.getInstance().addListener('change', () => this.onConfigChanged()); this.disposables.push(statusBar); this.disposables.push(this.display); @@ -78,8 +76,7 @@ export class InterpreterManager implements Disposable { return await this.setPythonPathInSingleWorkspace(pythonPath); } await this.setPythonPathInWorkspace(pythonPath, workspacePythonPath.configTarget, workspacePythonPath.folderUri); - } - catch (reason) { + } catch (reason) { // tslint:disable-next-line:no-unsafe-any prefer-type-cast const message = reason && typeof reason.message === 'string' ? reason.message as string : ''; window.showErrorMessage(`Failed to set 'pythonPath'. Error: ${message}`); @@ -90,6 +87,7 @@ export class InterpreterManager implements Disposable { // tslint:disable-next-line:prefer-type-cast this.disposables.forEach(disposable => disposable.dispose() as void); this.display = null; + this.interpreterProvider.dispose(); } private async setPythonPathInUserSettings(pythonPath) { const pythonConfig = workspace.getConfiguration('python'); diff --git a/src/client/interpreter/locators/index.ts b/src/client/interpreter/locators/index.ts index ab3059f3ab91..09ab1c21bc18 100644 --- a/src/client/interpreter/locators/index.ts +++ b/src/client/interpreter/locators/index.ts @@ -1,10 +1,10 @@ 'use strict'; import * as _ from 'lodash'; -import { Uri } from 'vscode'; +import { Disposable, Uri, workspace } from 'vscode'; import { RegistryImplementation } from '../../common/registry'; import { areBasePathsSame, arePathsSame, Is_64Bit, IS_WINDOWS } from '../../common/utils'; import { IInterpreterLocatorService, PythonInterpreter } from '../contracts'; -import { InterpreterVersionService } from '../interpreterVersion'; +import { IInterpreterVersionService, InterpreterVersionService } from '../interpreterVersion'; import { VirtualEnvironmentManager } from '../virtualEnvs'; import { fixInterpreterDisplayName, fixInterpreterPath } from './helpers'; import { CondaEnvFileService, getEnvironmentsFile as getCondaEnvFile } from './services/condaEnvFileService'; @@ -15,49 +15,76 @@ import { getKnownSearchPathsForVirtualEnvs, VirtualEnvService } from './services import { WindowsRegistryService } from './services/windowsRegistryService'; export class PythonInterpreterLocatorService implements IInterpreterLocatorService { - private interpreters: PythonInterpreter[] = []; - private locators: IInterpreterLocatorService[] = []; + private interpretersPerResource: Map; + private disposables: Disposable[] = []; constructor(private virtualEnvMgr: VirtualEnvironmentManager) { + this.interpretersPerResource = new Map(); + this.disposables.push(workspace.onDidChangeConfiguration(this.onConfigChanged, this)); + } + public async getInterpreters(resource?: Uri) { + const resourceKey = this.getResourceKey(resource); + if (!this.interpretersPerResource.has(resourceKey)) { + const interpreters = await this.getInterpretersPerResource(resource); + this.interpretersPerResource.set(resourceKey, interpreters); + } + + // tslint:disable-next-line:no-non-null-assertion + return this.interpretersPerResource.get(resourceKey)!; + } + public dispose() { + this.disposables.forEach(disposable => disposable.dispose()); + } + private onConfigChanged() { + this.interpretersPerResource.clear(); + } + private getResourceKey(resource?: Uri) { + if (!resource) { + return ''; + } + const workspaceFolder = workspace.getWorkspaceFolder(resource); + return workspaceFolder ? workspaceFolder.uri.fsPath : ''; + } + private async getInterpretersPerResource(resource?: Uri) { + const locators = this.getLocators(resource); + const promises = locators.map(provider => provider.getInterpreters(resource)); + const listOfInterpreters = await Promise.all(promises); + + // tslint:disable-next-line:underscore-consistent-invocation + return _.flatten(listOfInterpreters) + .map(fixInterpreterDisplayName) + .map(fixInterpreterPath) + .reduce((accumulator, current) => { + if (accumulator.findIndex(item => arePathsSame(item.path, current.path)) === -1 && + accumulator.findIndex(item => areBasePathsSame(item.path, current.path)) === -1) { + accumulator.push(current); + } + return accumulator; + }, []); + } + private getLocators(resource?: Uri) { + const locators: IInterpreterLocatorService[] = []; const versionService = new InterpreterVersionService(); // The order of the services is important. if (IS_WINDOWS) { const windowsRegistryProvider = new WindowsRegistryService(new RegistryImplementation(), Is_64Bit); - this.locators.push(windowsRegistryProvider); - this.locators.push(new CondaEnvService(windowsRegistryProvider)); - } - else { - this.locators.push(new CondaEnvService()); + locators.push(windowsRegistryProvider); + locators.push(new CondaEnvService(windowsRegistryProvider)); + } else { + locators.push(new CondaEnvService()); } // Supplements the above list of conda environments. - this.locators.push(new CondaEnvFileService(getCondaEnvFile(), versionService)); - this.locators.push(new VirtualEnvService(getKnownSearchPathsForVirtualEnvs(), this.virtualEnvMgr, versionService)); + locators.push(new CondaEnvFileService(getCondaEnvFile(), versionService)); + locators.push(new VirtualEnvService(getKnownSearchPathsForVirtualEnvs(resource), this.virtualEnvMgr, versionService)); if (!IS_WINDOWS) { // This must be last, it is possible we have paths returned here that are already returned // in one of the above lists. - this.locators.push(new KnownPathsService(getKnownSearchPathsForInterpreters(), versionService)); + locators.push(new KnownPathsService(getKnownSearchPathsForInterpreters(), versionService)); } // This must be last, it is possible we have paths returned here that are already returned // in one of the above lists. - this.locators.push(new CurrentPathService(this.virtualEnvMgr, versionService)); - } - public async getInterpreters(resource?: Uri) { - if (this.interpreters.length > 0) { - return this.interpreters; - } - const promises = this.locators.map(provider => provider.getInterpreters(resource)); - return Promise.all(promises) - // tslint:disable-next-line:underscore-consistent-invocation - .then(interpreters => _.flatten(interpreters)) - .then(items => items.map(fixInterpreterDisplayName)) - .then(items => items.map(fixInterpreterPath)) - .then(items => items.reduce((accumulator, current) => { - if (accumulator.findIndex(item => arePathsSame(item.path, current.path)) === -1 && - accumulator.findIndex(item => areBasePathsSame(item.path, current.path)) === -1) { - accumulator.push(current); - } - return accumulator; - }, [])) - .then(interpreters => this.interpreters = interpreters); + locators.push(new CurrentPathService(this.virtualEnvMgr, versionService)); + + return locators; } } diff --git a/src/client/interpreter/locators/services/KnownPathsService.ts b/src/client/interpreter/locators/services/KnownPathsService.ts index 23306756a452..bdb240ededad 100644 --- a/src/client/interpreter/locators/services/KnownPathsService.ts +++ b/src/client/interpreter/locators/services/KnownPathsService.ts @@ -16,7 +16,8 @@ export class KnownPathsService implements IInterpreterLocatorService { public getInterpreters(_?: Uri) { return this.suggestionsFromKnownPaths(); } - + // tslint:disable-next-line:no-empty + public dispose() { } private suggestionsFromKnownPaths() { const promises = this.knownSearchPaths.map(dir => this.getInterpretersInDirectory(dir)); return Promise.all(promises) diff --git a/src/client/interpreter/locators/services/condaEnvFileService.ts b/src/client/interpreter/locators/services/condaEnvFileService.ts index b2fb8b5f0a39..1383d571088b 100644 --- a/src/client/interpreter/locators/services/condaEnvFileService.ts +++ b/src/client/interpreter/locators/services/condaEnvFileService.ts @@ -14,7 +14,8 @@ export class CondaEnvFileService implements IInterpreterLocatorService { public async getInterpreters(_?: Uri) { return this.getSuggestionsFromConda(); } - + // tslint:disable-next-line:no-empty + public dispose() { } private async getSuggestionsFromConda(): Promise { return fs.pathExists(this.condaEnvironmentFile) .then(exists => exists ? this.getEnvironmentsFromFile(this.condaEnvironmentFile) : Promise.resolve([])); diff --git a/src/client/interpreter/locators/services/condaEnvService.ts b/src/client/interpreter/locators/services/condaEnvService.ts index c40bd9875261..0b7b281efff1 100644 --- a/src/client/interpreter/locators/services/condaEnvService.ts +++ b/src/client/interpreter/locators/services/condaEnvService.ts @@ -18,7 +18,8 @@ export class CondaEnvService implements IInterpreterLocatorService { public getInterpreters(resource?: Uri) { return this.getSuggestionsFromConda(); } - + // tslint:disable-next-line:no-empty + public dispose() { } public getCondaFile() { if (this.registryLookupForConda) { return this.registryLookupForConda.getInterpreters() diff --git a/src/client/interpreter/locators/services/currentPathService.ts b/src/client/interpreter/locators/services/currentPathService.ts index 069053fa4f9b..55b6c4728fe9 100644 --- a/src/client/interpreter/locators/services/currentPathService.ts +++ b/src/client/interpreter/locators/services/currentPathService.ts @@ -15,7 +15,8 @@ export class CurrentPathService implements IInterpreterLocatorService { public async getInterpreters(resource?: Uri) { return this.suggestionsFromKnownPaths(); } - + // tslint:disable-next-line:no-empty + public dispose() { } private async suggestionsFromKnownPaths(resource?: Uri) { const currentPythonInterpreter = this.getInterpreter(PythonSettings.getInstance(resource).pythonPath, '').then(interpreter => [interpreter]); const python = this.getInterpreter('python', '').then(interpreter => [interpreter]); diff --git a/src/client/interpreter/locators/services/virtualEnvService.ts b/src/client/interpreter/locators/services/virtualEnvService.ts index d11af1dbfd6f..a10818f1c3ec 100644 --- a/src/client/interpreter/locators/services/virtualEnvService.ts +++ b/src/client/interpreter/locators/services/virtualEnvService.ts @@ -18,7 +18,8 @@ export class VirtualEnvService implements IInterpreterLocatorService { public async getInterpreters(resource?: Uri) { return this.suggestionsFromKnownVenvs(); } - + // tslint:disable-next-line:no-empty + public dispose() { } private async suggestionsFromKnownVenvs() { return Promise.all(this.knownSearchPaths.map(dir => this.lookForInterpretersInVenvs(dir))) // tslint:disable-next-line:underscore-consistent-invocation @@ -61,7 +62,7 @@ export class VirtualEnvService implements IInterpreterLocatorService { } } -export function getKnownSearchPathsForVirtualEnvs(): string[] { +export function getKnownSearchPathsForVirtualEnvs(resource?: Uri): string[] { const paths: string[] = []; if (!IS_WINDOWS) { const defaultPaths = ['/Envs', '/.virtualenvs', '/.pyenv', '/.pyenv/versions']; @@ -69,7 +70,7 @@ export function getKnownSearchPathsForVirtualEnvs(): string[] { paths.push(untildify(`~${p}`)); }); } - const venvPath = settings.PythonSettings.getInstance().venvPath; + const venvPath = settings.PythonSettings.getInstance(resource).venvPath; if (venvPath) { paths.push(untildify(venvPath)); } diff --git a/src/client/interpreter/locators/services/windowsRegistryService.ts b/src/client/interpreter/locators/services/windowsRegistryService.ts index 209fe91ac9e8..de01c5bfadce 100644 --- a/src/client/interpreter/locators/services/windowsRegistryService.ts +++ b/src/client/interpreter/locators/services/windowsRegistryService.ts @@ -28,6 +28,8 @@ export class WindowsRegistryService implements IInterpreterLocatorService { public getInterpreters(_resource?: Uri) { return this.getInterpretersFromRegistry(); } + // tslint:disable-next-line:no-empty + public dispose() { } private async getInterpretersFromRegistry() { // https://github.com/python/peps/blob/master/pep-0514.txt#L357 const hkcuArch = this.is64Bit ? undefined : Architecture.x86; diff --git a/src/client/linters/baseLinter.ts b/src/client/linters/baseLinter.ts index d4d6f89d58de..551b6bc84622 100644 --- a/src/client/linters/baseLinter.ts +++ b/src/client/linters/baseLinter.ts @@ -131,7 +131,7 @@ export abstract class BaseLinter { this.outputChannel.append(data); } protected run(command: string, args: string[], document: vscode.TextDocument, cwd: string, cancellation: vscode.CancellationToken, regEx: string = REGEX): Promise { - return execPythonFile(command, args, cwd, true, null, cancellation).then(data => { + return execPythonFile(document.uri, command, args, cwd, true, null, cancellation).then(data => { if (!data) { data = ''; } diff --git a/src/client/linters/prospector.ts b/src/client/linters/prospector.ts index bf1c6b1af4e9..2fb44b7d9449 100644 --- a/src/client/linters/prospector.ts +++ b/src/client/linters/prospector.ts @@ -43,7 +43,7 @@ export class Linter extends baseLinter.BaseLinter { } return new Promise((resolve, reject) => { - execPythonFile(prospectorPath, prospectorArgs.concat(['--absolute-paths', '--output-format=json', document.uri.fsPath]), this.getWorkspaceRootPath(document), false, null, cancellation).then(data => { + execPythonFile(document.uri, prospectorPath, prospectorArgs.concat(['--absolute-paths', '--output-format=json', document.uri.fsPath]), this.getWorkspaceRootPath(document), false, null, cancellation).then(data => { let parsedData: IProspectorResponse; try { parsedData = JSON.parse(data); diff --git a/src/client/linters/pydocstyle.ts b/src/client/linters/pydocstyle.ts index 7759ee23a26f..81f2cdbd029f 100644 --- a/src/client/linters/pydocstyle.ts +++ b/src/client/linters/pydocstyle.ts @@ -42,7 +42,7 @@ export class Linter extends baseLinter.BaseLinter { let outputChannel = this.outputChannel; return new Promise((resolve, reject) => { - execPythonFile(commandLine, args, this.getWorkspaceRootPath(document), true, null, cancellation).then(data => { + execPythonFile(document.uri, commandLine, args, this.getWorkspaceRootPath(document), true, null, cancellation).then(data => { outputChannel.append('#'.repeat(10) + 'Linting Output - ' + this.Id + '#'.repeat(10) + '\n'); outputChannel.append(data); let outputLines = data.split(/\r?\n/g); diff --git a/src/client/providers/execInTerminalProvider.ts b/src/client/providers/execInTerminalProvider.ts index 0487f7362cf4..15a2277f4525 100644 --- a/src/client/providers/execInTerminalProvider.ts +++ b/src/client/providers/execInTerminalProvider.ts @@ -74,7 +74,7 @@ function execInTerminal(fileUri?: vscode.Uri) { terminal.sendText(`cd "${fileDirPath}"`); } } - const launchArgs = settings.PythonSettings.getInstance().terminal.launchArgs; + const launchArgs = settings.PythonSettings.getInstance(fileUri).terminal.launchArgs; const launchArgsString = launchArgs.length > 0 ? " ".concat(launchArgs.join(" ")) : ""; const command = `${currentPythonPath}${launchArgsString} ${filePath}`; if (IS_WINDOWS) { @@ -93,19 +93,19 @@ function execInTerminal(fileUri?: vscode.Uri) { } function execSelectionInTerminal() { + const activeEditor = vscode.window.activeTextEditor; + if (!activeEditor) { + return; + } + const terminalShellSettings = vscode.workspace.getConfiguration('terminal.integrated.shell'); const IS_POWERSHELL = /powershell/.test(terminalShellSettings.get('windows')); - let currentPythonPath = settings.PythonSettings.getInstance().pythonPath; + let currentPythonPath = settings.PythonSettings.getInstance(activeEditor.document.uri).pythonPath; if (currentPythonPath.indexOf(' ') > 0) { currentPythonPath = `"${currentPythonPath}"`; } - const activeEditor = vscode.window.activeTextEditor; - if (!activeEditor) { - return; - } - const selection = vscode.window.activeTextEditor.selection; let code: string; if (selection.isEmpty) { @@ -119,7 +119,7 @@ function execSelectionInTerminal() { return; } code = removeBlankLines(code); - const launchArgs = settings.PythonSettings.getInstance().terminal.launchArgs; + const launchArgs = settings.PythonSettings.getInstance(activeEditor.document.uri).terminal.launchArgs; const launchArgsString = launchArgs.length > 0 ? " ".concat(launchArgs.join(" ")) : ""; const command = `${currentPythonPath}${launchArgsString}`; if (!terminal) { @@ -148,19 +148,19 @@ function execSelectionInTerminal() { } function execSelectionInDjangoShell() { + const activeEditor = vscode.window.activeTextEditor; + if (!activeEditor) { + return; + } + const terminalShellSettings = vscode.workspace.getConfiguration('terminal.integrated.shell'); const IS_POWERSHELL = /powershell/.test(terminalShellSettings.get('windows')); - let currentPythonPath = settings.PythonSettings.getInstance().pythonPath; + let currentPythonPath = settings.PythonSettings.getInstance(activeEditor.document.uri).pythonPath; if (currentPythonPath.indexOf(' ') > 0) { currentPythonPath = `"${currentPythonPath}"`; } - const activeEditor = vscode.window.activeTextEditor; - if (!activeEditor) { - return; - } - const workspaceRoot = vscode.workspace.rootPath; const djangoShellCmd = `"${workspaceRoot}/manage.py" shell`; const selection = vscode.window.activeTextEditor.selection; @@ -175,7 +175,7 @@ function execSelectionInDjangoShell() { if (code.length === 0) { return; } - const launchArgs = settings.PythonSettings.getInstance().terminal.launchArgs; + const launchArgs = settings.PythonSettings.getInstance(activeEditor.document.uri).terminal.launchArgs; const launchArgsString = launchArgs.length > 0 ? " ".concat(launchArgs.join(" ")) : ""; const command = `${currentPythonPath}${launchArgsString} ${djangoShellCmd}`; if (!terminal) { @@ -201,4 +201,4 @@ function execSelectionInDjangoShell() { terminal.sendText(unix_code); } terminal.show(); -} \ No newline at end of file +} diff --git a/src/client/providers/jediProxy.ts b/src/client/providers/jediProxy.ts index 6fe7e25c296a..9b01703ba13a 100644 --- a/src/client/providers/jediProxy.ts +++ b/src/client/providers/jediProxy.ts @@ -6,7 +6,7 @@ import * as path from 'path'; import * as settings from './../common/configSettings'; import * as logger from './../common/logger'; import * as telemetryHelper from '../common/telemetry'; -import { execPythonFile, validatePath } from "../common/utils"; +import { execPythonFile, getCustomEnvVarsSync, validatePath } from '../common/utils'; import { createDeferred, Deferred } from '../common/helpers'; import { getCustomEnvVars } from '../common/utils'; import { mergeEnvVariables } from '../common/envFileParser'; @@ -189,7 +189,7 @@ export class JediProxy implements vscode.Disposable { private spawnProcess(dir: string) { try { let environmentVariables = { 'PYTHONUNBUFFERED': '1' }; - let customEnvironmentVars = getCustomEnvVars(); + let customEnvironmentVars = getCustomEnvVarsSync(vscode.Uri.file(dir)); if (customEnvironmentVars) { environmentVariables = mergeEnvVariables(environmentVariables, customEnvironmentVars); } @@ -488,7 +488,7 @@ export class JediProxy implements vscode.Disposable { private lastKnownPythonPath: string = null; private additionalAutoCopletePaths: string[] = []; private getPathFromPythonCommand(args: string[]): Promise { - return execPythonFile(this.pythonSettings.pythonPath, args, this.workspacePath).then(stdout => { + return execPythonFile(this.workspacePath, this.pythonSettings.pythonPath, args, this.workspacePath).then(stdout => { if (stdout.length === 0) { return ""; } @@ -520,7 +520,7 @@ export class JediProxy implements vscode.Disposable { if (typeof PYTHONPATH !== 'string') { PYTHONPATH = ''; } - let customEnvironmentVars = getCustomEnvVars(); + let customEnvironmentVars = getCustomEnvVarsSync(vscode.Uri.file(this.pythonProcessCWD)); if (customEnvironmentVars && customEnvironmentVars['PYTHONPATH']) { let PYTHONPATHFromEnvFile = customEnvironmentVars['PYTHONPATH'] as string; if (!path.isAbsolute(PYTHONPATHFromEnvFile) && this.workspacePath === 'string') { diff --git a/src/client/providers/replProvider.ts b/src/client/providers/replProvider.ts new file mode 100644 index 000000000000..1ef8b6cffc8e --- /dev/null +++ b/src/client/providers/replProvider.ts @@ -0,0 +1,46 @@ +import { commands, Disposable, Uri, window, workspace } from 'vscode'; +import { PythonSettings } from '../common/configSettings'; +import { Commands } from '../common/constants'; +import { getPathFromPythonCommand } from '../common/utils'; + +export class ReplProvider implements Disposable { + private readonly disposables: Disposable[] = []; + constructor() { + this.registerCommand(); + } + public dispose() { + this.disposables.forEach(disposable => disposable.dispose()); + } + private registerCommand() { + const disposable = commands.registerCommand(Commands.Start_REPL, this.commandHandler, this); + this.disposables.push(disposable); + } + private async commandHandler() { + const pythonPath = await this.getPythonPath(); + if (!pythonPath) { + return; + } + let pythonInterpreterPath: string; + try { + pythonInterpreterPath = await getPathFromPythonCommand(pythonPath).catch(() => pythonPath); + // tslint:disable-next-line:variable-name + } catch (_ex) { + pythonInterpreterPath = pythonPath; + } + const term = window.createTerminal('Python', pythonInterpreterPath); + term.show(); + this.disposables.push(term); + } + private async getPythonPath(): Promise { + if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0) { + return PythonSettings.getInstance().pythonPath; + } + if (workspace.workspaceFolders.length === 1) { + return PythonSettings.getInstance(workspace.workspaceFolders[0].uri).pythonPath; + } + + // tslint:disable-next-line:no-any prefer-type-cast + const workspaceFolder = await (window as any).showWorkspaceFolderPick({ placeHolder: 'Select a workspace' }); + return workspace ? PythonSettings.getInstance(workspaceFolder.uri).pythonPath : undefined; + } +} diff --git a/src/client/refactor/proxy.ts b/src/client/refactor/proxy.ts index aada24c2323d..5a238273d8d8 100644 --- a/src/client/refactor/proxy.ts +++ b/src/client/refactor/proxy.ts @@ -5,7 +5,7 @@ import * as path from 'path'; import * as child_process from 'child_process'; import { IPythonSettings } from '../common/configSettings'; import { REFACTOR } from '../common/telemetryContracts'; -import { IS_WINDOWS, getCustomEnvVars, getWindowsLineEndingCount } from '../common/utils'; +import { getCustomEnvVars, getCustomEnvVarsSync, getWindowsLineEndingCount, IS_WINDOWS } from '../common/utils'; import { mergeEnvVariables } from '../common/envFileParser'; export class RefactorProxy extends vscode.Disposable { @@ -106,7 +106,7 @@ export class RefactorProxy extends vscode.Disposable { return new Promise((resolve, reject) => { this._initializeReject = reject; let environmentVariables = { 'PYTHONUNBUFFERED': '1' }; - let customEnvironmentVars = getCustomEnvVars(); + let customEnvironmentVars = getCustomEnvVarsSync(vscode.Uri.file(this.workspaceRoot)); if (customEnvironmentVars) { environmentVariables = mergeEnvVariables(environmentVariables, customEnvironmentVars); } @@ -188,4 +188,4 @@ export class RefactorProxy extends vscode.Disposable { this._commandResolve(response[0]); this._commandResolve = null; } -} \ No newline at end of file +} diff --git a/src/client/sortImports.ts b/src/client/sortImports.ts index 90235b9a5094..9ba64f0a0dbf 100644 --- a/src/client/sortImports.ts +++ b/src/client/sortImports.ts @@ -46,4 +46,4 @@ export function activate(context: vscode.ExtensionContext, outChannel: vscode.Ou }); context.subscriptions.push(disposable); -} \ No newline at end of file +} diff --git a/src/client/unittests/common/debugLauncher.ts b/src/client/unittests/common/debugLauncher.ts index 026a46759eff..a1d754f20dbd 100644 --- a/src/client/unittests/common/debugLauncher.ts +++ b/src/client/unittests/common/debugLauncher.ts @@ -9,7 +9,7 @@ export function launchDebugger(rootDirectory: string, testArgs: string[], token? const def = createDeferred(); const launchDef = createDeferred(); let outputChannelShown = false; - execPythonFile(pythonSettings.pythonPath, testArgs, rootDirectory, true, (data: string) => { + execPythonFile(rootDirectory, pythonSettings.pythonPath, testArgs, rootDirectory, true, (data: string) => { if (data.startsWith('READY' + os.EOL)) { // debug socket server has started. launchDef.resolve(); diff --git a/src/client/unittests/common/runner.ts b/src/client/unittests/common/runner.ts index 0b4b328d4c3e..de3b90245e6e 100644 --- a/src/client/unittests/common/runner.ts +++ b/src/client/unittests/common/runner.ts @@ -1,44 +1,7 @@ -import { execPythonFile } from './../../common/utils'; import { CancellationToken, OutputChannel, window, workspace } from 'vscode'; -import { getPythonInterpreterDirectory, IS_WINDOWS, PATH_VARIABLE_NAME } from '../../common/utils'; - -let terminal = null; -export function run(file: string, args: string[], cwd: string, token?: CancellationToken, outChannel?: OutputChannel): Promise { - return execPythonFile(file, args, cwd, true, (data: string) => outChannel.append(data), token); +import { IS_WINDOWS, PATH_VARIABLE_NAME } from '../../common/utils'; +import { execPythonFile } from './../../common/utils'; - // Bug, we cannot resolve this - // Resolving here means that tests have completed - // We need a way to determine that the tests have completed succefully.. hmm - // We could use a hack, such as generating a textfile at the end of the command and monitoring.. hack hack hack - // Or we could generate a shell script file and embed all of the hacks in here... hack hack hack... - // return runTestInTerminal(file, args, cwd); +export async function run(file: string, args: string[], cwd: string, token?: CancellationToken, outChannel?: OutputChannel): Promise { + return execPythonFile(cwd, file, args, cwd, true, (data: string) => outChannel.append(data), token); } - -function runTestInTerminal(file: string, args: string[], cwd: string): Promise { - return getPythonInterpreterDirectory().then(pyPath => { - let commands = []; - if (IS_WINDOWS) { - commands.push(`set ${PATH_VARIABLE_NAME}=%${PATH_VARIABLE_NAME}%;${pyPath}`); - } - else { - commands.push(`export ${PATH_VARIABLE_NAME}=$${PATH_VARIABLE_NAME}:${pyPath}`); - } - if (cwd !== workspace.rootPath && typeof cwd === 'string') { - commands.push(`cd ${cwd}`); - } - commands.push(`${file} ${args.join(' ')}`); - terminal = window.createTerminal(`Python Test Log`); - - return new Promise((resolve) => { - setTimeout(function () { - terminal.show(); - terminal.sendText(commands.join(' && ')); - - // Bug, we cannot resolve this - // Resolving here means that tests have completed - // We need a way to determine that the tests have completed succefully.. hmm - resolve(); - }, 1000); - }); - }); -} \ No newline at end of file diff --git a/src/client/unittests/nosetest/collector.ts b/src/client/unittests/nosetest/collector.ts index 1926aa694237..c78d1fd86f9f 100644 --- a/src/client/unittests/nosetest/collector.ts +++ b/src/client/unittests/nosetest/collector.ts @@ -77,7 +77,7 @@ export function discoverTests(rootDirectory: string, args: string[], token: Canc }); } - return execPythonFile(PythonSettings.getInstance(Uri.file(rootDirectory)).unitTest.nosetestPath, args.concat(['--collect-only', '-vvv']), rootDirectory, true) + return execPythonFile(rootDirectory, PythonSettings.getInstance(Uri.file(rootDirectory)).unitTest.nosetestPath, args.concat(['--collect-only', '-vvv']), rootDirectory, true) .then(data => { outChannel.appendLine(data); processOutput(data); diff --git a/src/client/unittests/pytest/collector.ts b/src/client/unittests/pytest/collector.ts index eecc8055fa11..175bf16414d5 100644 --- a/src/client/unittests/pytest/collector.ts +++ b/src/client/unittests/pytest/collector.ts @@ -83,7 +83,7 @@ export function discoverTests(rootDirectory: string, args: string[], token: vsco }); } - return execPythonFile(PythonSettings.getInstance(vscode.Uri.file(rootDirectory)).unitTest.pyTestPath, args.concat(['--collect-only']), rootDirectory, false, null, token) + return execPythonFile(rootDirectory, PythonSettings.getInstance(vscode.Uri.file(rootDirectory)).unitTest.pyTestPath, args.concat(['--collect-only']), rootDirectory, false, null, token) .then(data => { outChannel.appendLine(data); processOutput(data); diff --git a/src/client/unittests/unittest/collector.ts b/src/client/unittests/unittest/collector.ts index 49e34b4232f0..633cbe4bc671 100644 --- a/src/client/unittests/unittest/collector.ts +++ b/src/client/unittests/unittest/collector.ts @@ -71,7 +71,7 @@ for suite in suites._tests: }); } args = []; - return execPythonFile(PythonSettings.getInstance(vscode.Uri.file(rootDirectory)).pythonPath, args.concat(['-c', pythonScript]), rootDirectory, true, null, token) + return execPythonFile(rootDirectory, PythonSettings.getInstance(vscode.Uri.file(rootDirectory)).pythonPath, args.concat(['-c', pythonScript]), rootDirectory, true, null, token) .then(data => { outChannel.appendLine(data); processOutput(data); diff --git a/src/test/autocomplete/base.test.ts b/src/test/autocomplete/base.test.ts index ee203a46835f..a5398b7beb43 100644 --- a/src/test/autocomplete/base.test.ts +++ b/src/test/autocomplete/base.test.ts @@ -28,7 +28,7 @@ suite('Autocomplete', () => { let isPython3: Promise; suiteSetup(async () => { await initialize(); - let version = await execPythonFile(PythonSettings.getInstance(rootWorkspaceUri).pythonPath, ['--version'], __dirname, true); + const version = await execPythonFile(rootWorkspaceUri, PythonSettings.getInstance(rootWorkspaceUri).pythonPath, ['--version'], __dirname, true); isPython3 = Promise.resolve(version.indexOf('3.') >= 0); }); setup(() => initializeTest()); diff --git a/src/test/autocomplete/pep484.test.ts b/src/test/autocomplete/pep484.test.ts index 1ad220ea929c..c0c327bdf7d1 100644 --- a/src/test/autocomplete/pep484.test.ts +++ b/src/test/autocomplete/pep484.test.ts @@ -22,7 +22,7 @@ suite('Autocomplete PEP 484', () => { let isPython3: Promise; suiteSetup(async () => { await initialize(); - const version = await execPythonFile(PythonSettings.getInstance(rootWorkspaceUri).pythonPath, ['--version'], __dirname, true); + const version = await execPythonFile(rootWorkspaceUri, PythonSettings.getInstance(rootWorkspaceUri).pythonPath, ['--version'], __dirname, true); isPython3 = Promise.resolve(version.indexOf('3.') >= 0); }); setup(() => initializeTest()); diff --git a/src/test/autocomplete/pep526.test.ts b/src/test/autocomplete/pep526.test.ts index 6d330195ccdf..01dc988c000f 100644 --- a/src/test/autocomplete/pep526.test.ts +++ b/src/test/autocomplete/pep526.test.ts @@ -22,7 +22,7 @@ suite('Autocomplete PEP 526', () => { let isPython3: Promise; suiteSetup(async () => { await initialize(); - const version = await execPythonFile(PythonSettings.getInstance(rootWorkspaceUri).pythonPath, ['--version'], __dirname, true); + const version = await execPythonFile(rootWorkspaceUri, PythonSettings.getInstance(rootWorkspaceUri).pythonPath, ['--version'], __dirname, true); isPython3 = Promise.resolve(version.indexOf('3.') >= 0); }); setup(() => initializeTest()); diff --git a/src/test/common.ts b/src/test/common.ts index 83c817252c80..160424c7172f 100644 --- a/src/test/common.ts +++ b/src/test/common.ts @@ -25,9 +25,6 @@ export async function updateSetting(setting: PythonSettingKeys, value: {}, resou } // tslint:disable-next-line:await-promise await settings.update(setting, value, configTarget); - if (configTarget === ConfigurationTarget.Workspace && IS_MULTI_ROOT_TEST) { - await new Promise(resolve => setTimeout(resolve, 2000)); - } PythonSettings.dispose(); } diff --git a/src/test/common/common.test.ts b/src/test/common/common.test.ts index 2a04c3c2737f..cb2bb7297642 100644 --- a/src/test/common/common.test.ts +++ b/src/test/common/common.test.ts @@ -18,13 +18,13 @@ import { createDeferred } from '../../client/common/helpers'; suite('ChildProc', () => { setup(() => initialize()); test('Standard Response', done => { - execPythonFile('python', ['-c', 'print(1)'], __dirname, false).then(data => { + execPythonFile(undefined, 'python', ['-c', 'print(1)'], __dirname, false).then(data => { assert.ok(data === '1' + EOL); }).then(done).catch(done); }); test('Error Response', done => { const def = createDeferred(); - execPythonFile('python', ['-c', 'print(1'], __dirname, false).then(() => { + execPythonFile(undefined, 'python', ['-c', 'print(1'], __dirname, false).then(() => { def.reject('Should have failed'); }).catch(() => { def.resolve(); @@ -38,7 +38,7 @@ suite('ChildProc', () => { function handleOutput(data: string) { output.push(data); } - execPythonFile('python', ['-c', 'print(1)'], __dirname, false, handleOutput).then(() => { + execPythonFile(undefined, 'python', ['-c', 'print(1)'], __dirname, false, handleOutput).then(() => { assert.equal(output.length, 1, 'Ouput length incorrect'); assert.equal(output[0], '1' + EOL, 'Ouput value incorrect'); }).then(done).catch(done); @@ -49,7 +49,7 @@ suite('ChildProc', () => { function handleOutput(data: string) { output.push(data); } - await execPythonFile('python', ['-c', `print('öä')`], __dirname, false, handleOutput) + await execPythonFile(undefined, 'python', ['-c', `print('öä')`], __dirname, false, handleOutput) assert.equal(output.length, 1, 'Ouput length incorrect'); assert.equal(output[0], 'öä' + EOL, 'Ouput value incorrect'); }); @@ -60,7 +60,7 @@ suite('ChildProc', () => { function handleOutput(data: string) { output.push(data); } - execPythonFile('python', ['-c', 'import sys\nprint(1)\nsys.__stdout__.flush()\nimport time\ntime.sleep(5)\nprint(2)'], __dirname, false, handleOutput).then(() => { + execPythonFile(undefined, 'python', ['-c', 'import sys\nprint(1)\nsys.__stdout__.flush()\nimport time\ntime.sleep(5)\nprint(2)'], __dirname, false, handleOutput).then(() => { assert.equal(output.length, 2, 'Ouput length incorrect'); assert.equal(output[0], '1' + EOL, 'First Ouput value incorrect'); assert.equal(output[1], '2' + EOL, 'Second Ouput value incorrect'); @@ -74,7 +74,7 @@ suite('ChildProc', () => { output.push(data); } const cancellation = new vscode.CancellationTokenSource(); - execPythonFile('python', ['-c', 'import sys\nprint(1)\nsys.__stdout__.flush()\nimport time\ntime.sleep(5)\nprint(2)'], __dirname, false, handleOutput, cancellation.token).then(() => { + execPythonFile(undefined, 'python', ['-c', 'import sys\nprint(1)\nsys.__stdout__.flush()\nimport time\ntime.sleep(5)\nprint(2)'], __dirname, false, handleOutput, cancellation.token).then(() => { def.reject('Should not have completed'); }).catch(() => { def.resolve(); diff --git a/src/test/common/configSettings.multiroot.test.ts b/src/test/common/configSettings.multiroot.test.ts index 726772478676..be3eca0875aa 100644 --- a/src/test/common/configSettings.multiroot.test.ts +++ b/src/test/common/configSettings.multiroot.test.ts @@ -51,8 +51,6 @@ suite('Multiroot Config Settings', () => { if (value && typeof value.workspaceFolderValue === 'string') { await settings.update('pythonPath', undefined, ConfigurationTarget.WorkspaceFolder); } - // tslint:disable-next-line:no-string-based-set-timeout - await new Promise(resolve => setTimeout(resolve, 2000)); settings = workspace.getConfiguration('python', workspaceUri); PythonSettings.dispose(); const cfgSetting = PythonSettings.getInstance(workspaceUri); @@ -66,8 +64,6 @@ suite('Multiroot Config Settings', () => { await settings.update('pythonPath', pythonPath, ConfigurationTarget.Workspace); const privatePythonPath = `x${new Date().getTime()}`; await settings.update('pythonPath', privatePythonPath, ConfigurationTarget.WorkspaceFolder); - // tslint:disable-next-line:no-string-based-set-timeout - await new Promise(resolve => setTimeout(resolve, 2000)); const cfgSetting = PythonSettings.getInstance(workspaceUri); assert.equal(cfgSetting.pythonPath, privatePythonPath, 'Python Path for workspace folder is incorrect'); @@ -83,8 +79,6 @@ suite('Multiroot Config Settings', () => { // Update workspace folder to something else so it gets refreshed. await settings.update('pythonPath', `x${new Date().getTime()}`, ConfigurationTarget.WorkspaceFolder); await settings.update('pythonPath', undefined, ConfigurationTarget.WorkspaceFolder); - // tslint:disable-next-line:no-string-based-set-timeout - await new Promise(resolve => setTimeout(resolve, 2000)); const document = await workspace.openTextDocument(fileToOpen); const cfg = PythonSettings.getInstance(document.uri); @@ -100,8 +94,6 @@ suite('Multiroot Config Settings', () => { await settings.update('pythonPath', pythonPath, ConfigurationTarget.Workspace); const privatePythonPath = `x${new Date().getTime()}`; await settings.update('pythonPath', privatePythonPath, ConfigurationTarget.WorkspaceFolder); - // tslint:disable-next-line:no-string-based-set-timeout - await new Promise(resolve => setTimeout(resolve, 2000)); const document = await workspace.openTextDocument(fileToOpen); const cfg = PythonSettings.getInstance(document.uri); @@ -112,14 +104,10 @@ suite('Multiroot Config Settings', () => { const workspaceUri = Uri.file(path.join(multirootPath, 'workspace1')); await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', undefined); await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', true); - // tslint:disable-next-line:no-string-based-set-timeout - await new Promise(resolve => setTimeout(resolve, 2000)); let settings = PythonSettings.getInstance(workspaceUri); assert.equal(settings.linting.pylintEnabled, true, 'Pylint not enabled when it should be'); await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', false); - // tslint:disable-next-line:no-string-based-set-timeout - await new Promise(resolve => setTimeout(resolve, 2000)); settings = PythonSettings.getInstance(workspaceUri); assert.equal(settings.linting.pylintEnabled, false, 'Pylint enabled when it should not be'); }); @@ -129,8 +117,6 @@ suite('Multiroot Config Settings', () => { await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', false); await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', true); - // tslint:disable-next-line:no-string-based-set-timeout - await new Promise(resolve => setTimeout(resolve, 2000)); let cfgSetting = PythonSettings.getInstance(workspaceUri); assert.equal(cfgSetting.linting.pylintEnabled, false, 'Workspace folder pylint setting is true when it should not be'); @@ -138,8 +124,6 @@ suite('Multiroot Config Settings', () => { await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', true); await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', false); - // tslint:disable-next-line:no-string-based-set-timeout - await new Promise(resolve => setTimeout(resolve, 2000)); cfgSetting = PythonSettings.getInstance(workspaceUri); assert.equal(cfgSetting.linting.pylintEnabled, true, 'Workspace folder pylint setting is false when it should not be'); @@ -151,8 +135,6 @@ suite('Multiroot Config Settings', () => { await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', false); await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', true); - // tslint:disable-next-line:no-string-based-set-timeout - await new Promise(resolve => setTimeout(resolve, 2000)); let document = await workspace.openTextDocument(fileToOpen); let cfg = PythonSettings.getInstance(document.uri); assert.equal(cfg.linting.pylintEnabled, true, 'Pylint should be enabled in workspace'); @@ -160,8 +142,6 @@ suite('Multiroot Config Settings', () => { await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', true); await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', false); - // tslint:disable-next-line:no-string-based-set-timeout - await new Promise(resolve => setTimeout(resolve, 2000)); document = await workspace.openTextDocument(fileToOpen); cfg = PythonSettings.getInstance(document.uri); assert.equal(cfg.linting.pylintEnabled, false, 'Pylint should not be enabled in workspace'); @@ -173,8 +153,6 @@ suite('Multiroot Config Settings', () => { await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', false); await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', true); - // tslint:disable-next-line:no-string-based-set-timeout - await new Promise(resolve => setTimeout(resolve, 2000)); let document = await workspace.openTextDocument(fileToOpen); let cfg = PythonSettings.getInstance(document.uri); assert.equal(cfg.linting.pylintEnabled, true, 'Pylint should be enabled in workspace'); @@ -182,8 +160,6 @@ suite('Multiroot Config Settings', () => { await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', true); await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', false); - // tslint:disable-next-line:no-string-based-set-timeout - await new Promise(resolve => setTimeout(resolve, 2000)); document = await workspace.openTextDocument(fileToOpen); cfg = PythonSettings.getInstance(document.uri); assert.equal(cfg.linting.pylintEnabled, false, 'Pylint should not be enabled in workspace'); diff --git a/src/test/format/extension.format.test.ts b/src/test/format/extension.format.test.ts index ed792f087f6b..ce41edead475 100644 --- a/src/test/format/extension.format.test.ts +++ b/src/test/format/extension.format.test.ts @@ -41,8 +41,8 @@ suite('Formatting', () => { fs.copySync(originalUnformattedFile, file, { overwrite: true }); }); fs.ensureDirSync(path.dirname(autoPep8FileToFormat)); - const yapf = execPythonFile('yapf', [originalUnformattedFile], workspaceRootPath, false); - const autoPep8 = execPythonFile('autopep8', [originalUnformattedFile], workspaceRootPath, false); + const yapf = execPythonFile(workspaceRootPath, 'yapf', [originalUnformattedFile], workspaceRootPath, false); + const autoPep8 = execPythonFile(workspaceRootPath, 'autopep8', [originalUnformattedFile], workspaceRootPath, false); await Promise.all([yapf, autoPep8]).then(formattedResults => { formattedYapf = formattedResults[0]; formattedAutoPep8 = formattedResults[1]; diff --git a/src/test/format/extension.sort.test.ts b/src/test/format/extension.sort.test.ts index f5768eb0da99..c8c2f1c6ffc3 100644 --- a/src/test/format/extension.sort.test.ts +++ b/src/test/format/extension.sort.test.ts @@ -26,8 +26,6 @@ suite('Sorting', () => { fs.writeFileSync(fileToFormatWithConfig1, fs.readFileSync(originalFileToFormatWithConfig1)); fs.writeFileSync(fileToFormatWithoutConfig, fs.readFileSync(originalFileToFormatWithoutConfig)); await updateSetting('sortImports.args', [], Uri.file(sortingPath), configTarget); - // tslint:disable-next-line:no-string-based-set-timeout - await new Promise(resolve => setTimeout(resolve, 2000)); await closeActiveWindows(); }); setup(async () => { @@ -35,8 +33,6 @@ suite('Sorting', () => { fs.writeFileSync(fileToFormatWithoutConfig, fs.readFileSync(originalFileToFormatWithoutConfig)); fs.writeFileSync(fileToFormatWithConfig1, fs.readFileSync(originalFileToFormatWithConfig1)); await updateSetting('sortImports.args', [], Uri.file(sortingPath), configTarget); - // tslint:disable-next-line:no-string-based-set-timeout - await new Promise(resolve => setTimeout(resolve, 2000)); await closeActiveWindows(); }); @@ -78,8 +74,6 @@ suite('Sorting', () => { test('With Changes and Config in Args', async () => { await updateSetting('sortImports.args', ['-sp', path.join(sortingPath, 'withconfig')], Uri.file(sortingPath), ConfigurationTarget.Workspace); - // tslint:disable-next-line:no-string-based-set-timeout - await new Promise(resolve => setTimeout(resolve, 2000)); const textDocument = await workspace.openTextDocument(fileToFormatWithConfig); const editor = await window.showTextDocument(textDocument); await editor.edit(builder => { @@ -91,8 +85,6 @@ suite('Sorting', () => { }); test('With Changes and Config in Args (via Command)', async () => { await updateSetting('sortImports.args', ['-sp', path.join(sortingPath, 'withconfig')], Uri.file(sortingPath), configTarget); - // tslint:disable-next-line:no-string-based-set-timeout - await new Promise(resolve => setTimeout(resolve, 2000)); const textDocument = await workspace.openTextDocument(fileToFormatWithConfig); const editor = await window.showTextDocument(textDocument); await editor.edit(builder => { diff --git a/src/test/interpreters/condaEnvService.test.ts b/src/test/interpreters/condaEnvService.test.ts index 823f773ede6c..16794922c450 100644 --- a/src/test/interpreters/condaEnvService.test.ts +++ b/src/test/interpreters/condaEnvService.test.ts @@ -1,21 +1,25 @@ import * as assert from 'assert'; import * as path from 'path'; +import { Uri } from 'vscode'; import { PythonSettings } from '../../client/common/configSettings'; -import { initialize, initializeTest } from '../initialize'; import { IS_WINDOWS } from '../../client/common/utils'; -import { CondaEnvService } from '../../client/interpreter/locators/services/condaEnvService'; +import { PythonInterpreter } from '../../client/interpreter/contracts'; import { AnacondaCompanyName } from '../../client/interpreter/locators/services/conda'; +import { CondaEnvService } from '../../client/interpreter/locators/services/condaEnvService'; +import { initialize, initializeTest } from '../initialize'; import { MockProvider } from './mocks'; -import { PythonInterpreter } from '../../client/interpreter/contracts'; const environmentsPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'environments'); +const fileInNonRootWorkspace = path.join(__dirname, '..', '..', 'src', 'test', 'pythonFiles', 'dummy.py'); +// tslint:disable-next-line:max-func-body-length suite('Interpreters from Conda Environments', () => { - suiteSetup(() => initialize()); - setup(() => initializeTest()); + suiteSetup(initialize); + setup(initializeTest); test('Must return an empty list for empty json', async () => { const condaProvider = new CondaEnvService(); - const interpreters = await condaProvider.parseCondaInfo({} as any) + // tslint:disable-next-line:no-any prefer-type-cast + const interpreters = await condaProvider.parseCondaInfo({} as any); assert.equal(interpreters.length, 0, 'Incorrect number of entries'); }); test('Must extract display name from version info', async () => { @@ -105,12 +109,12 @@ suite('Interpreters from Conda Environments', () => { test('Must detect conda environments from a list', async () => { const registryInterpreters: PythonInterpreter[] = [ { displayName: 'One', path: 'c:/path1/one.exe', companyDisplayName: 'One 1' }, - { displayName: 'Two', path: PythonSettings.getInstance().pythonPath, companyDisplayName: 'Two 2' }, + { displayName: 'Two', path: PythonSettings.getInstance(Uri.file(fileInNonRootWorkspace)).pythonPath, companyDisplayName: 'Two 2' }, { displayName: 'Three', path: path.join(environmentsPath, 'path1', 'one.exe'), companyDisplayName: 'Three 3' }, { displayName: 'Anaconda', path: path.join(environmentsPath, 'path2', 'one.exe'), companyDisplayName: 'Three 3' }, { displayName: 'xAnaconda', path: path.join(environmentsPath, 'path2', 'one.exe'), companyDisplayName: 'Three 3' }, { displayName: 'xnaconda', path: path.join(environmentsPath, 'path2', 'one.exe'), companyDisplayName: 'xContinuum Analytics, Inc.' }, - { displayName: 'xnaconda', path: path.join(environmentsPath, 'path2', 'one.exe'), companyDisplayName: 'Continuum Analytics, Inc.' }, + { displayName: 'xnaconda', path: path.join(environmentsPath, 'path2', 'one.exe'), companyDisplayName: 'Continuum Analytics, Inc.' } ]; const mockRegistryProvider = new MockProvider(registryInterpreters); const condaProvider = new CondaEnvService(mockRegistryProvider); @@ -118,39 +122,45 @@ suite('Interpreters from Conda Environments', () => { assert.equal(condaProvider.isCondaEnvironment(registryInterpreters[0]), false, '1. Identified environment incorrectly'); assert.equal(condaProvider.isCondaEnvironment(registryInterpreters[1]), false, '2. Identified environment incorrectly'); assert.equal(condaProvider.isCondaEnvironment(registryInterpreters[2]), false, '3. Identified environment incorrectly'); - assert.equal(condaProvider.isCondaEnvironment(registryInterpreters[3]), true, `4. Failed to identify conda environment when displayName starts with 'Anaconda'`); - assert.equal(condaProvider.isCondaEnvironment(registryInterpreters[4]), true, `5. Failed to identify conda environment when displayName contains text 'Anaconda'`); - assert.equal(condaProvider.isCondaEnvironment(registryInterpreters[5]), true, `6. Failed to identify conda environment when comanyDisplayName contains 'Continuum'`); - assert.equal(condaProvider.isCondaEnvironment(registryInterpreters[6]), true, `7. Failed to identify conda environment when companyDisplayName starts with 'Continuum'`); + assert.equal(condaProvider.isCondaEnvironment(registryInterpreters[3]), true, '4. Failed to identify conda environment when displayName starts with \'Anaconda\''); + assert.equal(condaProvider.isCondaEnvironment(registryInterpreters[4]), true, '5. Failed to identify conda environment when displayName contains text \'Anaconda\''); + assert.equal(condaProvider.isCondaEnvironment(registryInterpreters[5]), true, '6. Failed to identify conda environment when comanyDisplayName contains \'Continuum\''); + assert.equal(condaProvider.isCondaEnvironment(registryInterpreters[6]), true, '7. Failed to identify conda environment when companyDisplayName starts with \'Continuum\''); }); test('Correctly identifies latest version when major version is different', async () => { const registryInterpreters: PythonInterpreter[] = [ { displayName: 'One', path: path.join(environmentsPath, 'path1', 'one.exe'), companyDisplayName: 'One 1', version: '1' }, - { displayName: 'Two', path: PythonSettings.getInstance().pythonPath, companyDisplayName: 'Two 2', version: '3.1.3' }, + { displayName: 'Two', path: PythonSettings.getInstance(Uri.file(fileInNonRootWorkspace)).pythonPath, companyDisplayName: 'Two 2', version: '3.1.3' }, { displayName: 'Three', path: path.join(environmentsPath, 'path2', 'one.exe'), companyDisplayName: 'Three 3', version: '2.10.1' }, + // tslint:disable-next-line:no-any { displayName: 'Four', path: path.join(environmentsPath, 'conda', 'envs', 'scipy'), companyDisplayName: 'Three 3', version: null }, + // tslint:disable-next-line:no-any { displayName: 'Five', path: path.join(environmentsPath, 'conda', 'envs', 'numpy'), companyDisplayName: 'Three 3', version: undefined }, { displayName: 'Six', path: path.join(environmentsPath, 'conda', 'envs', 'scipy'), companyDisplayName: 'xContinuum Analytics, Inc.', version: '2' }, - { 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); + // tslint:disable-next-line:no-non-null-assertion assert.equal(condaProvider.getLatestVersion(registryInterpreters)!.displayName, 'Two', 'Failed to identify latest version'); }); test('Correctly identifies latest version when major version is same', async () => { const registryInterpreters: PythonInterpreter[] = [ { displayName: 'One', path: path.join(environmentsPath, 'path1', 'one.exe'), companyDisplayName: 'One 1', version: '1' }, - { displayName: 'Two', path: PythonSettings.getInstance().pythonPath, companyDisplayName: 'Two 2', version: '2.11.3' }, + { displayName: 'Two', path: PythonSettings.getInstance(Uri.file(fileInNonRootWorkspace)).pythonPath, companyDisplayName: 'Two 2', version: '2.11.3' }, { displayName: 'Three', path: path.join(environmentsPath, 'path2', 'one.exe'), companyDisplayName: 'Three 3', version: '2.10.1' }, + // tslint:disable-next-line:no-any { displayName: 'Four', path: path.join(environmentsPath, 'conda', 'envs', 'scipy'), companyDisplayName: 'Three 3', version: null }, + // tslint:disable-next-line:no-any { displayName: 'Five', path: path.join(environmentsPath, 'conda', 'envs', 'numpy'), companyDisplayName: 'Three 3', version: undefined }, { displayName: 'Six', path: path.join(environmentsPath, 'conda', 'envs', 'scipy'), companyDisplayName: 'xContinuum Analytics, Inc.', version: '2' }, - { 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); + // tslint:disable-next-line:no-non-null-assertion assert.equal(condaProvider.getLatestVersion(registryInterpreters)!.displayName, 'Two', 'Failed to identify latest version'); }); test('Must use Conda env from Registry to locate conda.exe', async () => { diff --git a/src/test/interpreters/display.test.ts b/src/test/interpreters/display.test.ts index 4a70a0933789..690d3dcaef5b 100644 --- a/src/test/interpreters/display.test.ts +++ b/src/test/interpreters/display.test.ts @@ -2,7 +2,7 @@ import * as assert from 'assert'; import * as child_process from 'child_process'; import { EOL } from 'os'; import * as path from 'path'; -import { ConfigurationTarget } from 'vscode'; +import { ConfigurationTarget, Uri } from 'vscode'; import { PythonSettings } from '../../client/common/configSettings'; import { InterpreterDisplay } from '../../client/interpreter/display'; import { getFirstNonEmptyLineFromMultilineString } from '../../client/interpreter/helpers'; @@ -13,6 +13,8 @@ import { MockStatusBarItem } from '../mockClasses'; import { MockInterpreterVersionProvider } from './mocks'; import { MockProvider, MockVirtualEnv } from './mocks'; +const fileInNonRootWorkspace = path.join(__dirname, '..', '..', 'src', 'test', 'pythonFiles', 'dummy.py'); + // tslint:disable-next-line:max-func-body-length suite('Interpreters Display', () => { // tslint:disable-next-line:no-function-expression @@ -74,10 +76,10 @@ suite('Interpreters Display', () => { }); test('Must get display name from a list of interpreters', async () => { const pythonPath = await new Promise(resolve => { - child_process.execFile(PythonSettings.getInstance().pythonPath, ['-c', 'import sys;print(sys.executable)'], (_, stdout) => { + child_process.execFile(PythonSettings.getInstance(Uri.file(fileInNonRootWorkspace)).pythonPath, ['-c', 'import sys;print(sys.executable)'], (_, stdout) => { resolve(getFirstNonEmptyLineFromMultilineString(stdout)); }); - }).then(value => value.length === 0 ? PythonSettings.getInstance().pythonPath : value); + }).then(value => value.length === 0 ? PythonSettings.getInstance(Uri.file(fileInNonRootWorkspace)).pythonPath : value); const statusBar = new MockStatusBarItem(); const interpreters = [ { displayName: 'One', path: 'c:/path1/one.exe', type: 'One 1' }, @@ -94,10 +96,10 @@ suite('Interpreters Display', () => { }); test('Must suffix tooltip with the companyDisplayName of interpreter', async () => { const pythonPath = await new Promise(resolve => { - child_process.execFile(PythonSettings.getInstance().pythonPath, ['-c', 'import sys;print(sys.executable)'], (_, stdout) => { + child_process.execFile(PythonSettings.getInstance(Uri.file(fileInNonRootWorkspace)).pythonPath, ['-c', 'import sys;print(sys.executable)'], (_, stdout) => { resolve(getFirstNonEmptyLineFromMultilineString(stdout)); }); - }).then(value => value.length === 0 ? PythonSettings.getInstance().pythonPath : value); + }).then(value => value.length === 0 ? PythonSettings.getInstance(Uri.file(fileInNonRootWorkspace)).pythonPath : value); const statusBar = new MockStatusBarItem(); const interpreters = [ diff --git a/src/test/interpreters/mocks.ts b/src/test/interpreters/mocks.ts index c4dde62edf6c..34c41bb41c75 100644 --- a/src/test/interpreters/mocks.ts +++ b/src/test/interpreters/mocks.ts @@ -1,21 +1,23 @@ +import { Architecture, Hive, IRegistry } from '../../client/common/registry'; +import { IInterpreterLocatorService, PythonInterpreter } from '../../client/interpreter/contracts'; import { IInterpreterVersionService } from '../../client/interpreter/interpreterVersion'; import { IVirtualEnvironment } from '../../client/interpreter/virtualEnvs/contracts'; -import { IInterpreterLocatorService, PythonInterpreter } from "../../client/interpreter/contracts"; -import { IRegistry, Hive, Architecture } from "../../client/common/registry"; export class MockProvider implements IInterpreterLocatorService { constructor(private suggestions: PythonInterpreter[]) { } - getInterpreters(): Promise { + public getInterpreters(): Promise { return Promise.resolve(this.suggestions); } + // tslint:disable-next-line:no-empty + public dispose() { } } export class MockRegistry implements IRegistry { constructor(private keys: { key: string, hive: Hive, arch?: Architecture, values: string[] }[], private values: { key: string, hive: Hive, arch?: Architecture, value: string, name?: string }[]) { } - getKeys(key: string, hive: Hive, arch?: Architecture): Promise { + public getKeys(key: string, hive: Hive, arch?: Architecture): Promise { const items = this.keys.find(item => { if (item.arch) { return item.key === key && item.hive === hive && item.arch === arch; @@ -25,7 +27,7 @@ export class MockRegistry implements IRegistry { return items ? Promise.resolve(items.values) : Promise.resolve([]); } - getValue(key: string, hive: Hive, arch?: Architecture, name?: string): Promise { + public getValue(key: string, hive: Hive, arch?: Architecture, name?: string): Promise { const items = this.values.find(item => { if (item.key !== key || item.hive !== hive) { return false; @@ -46,14 +48,17 @@ export class MockRegistry implements IRegistry { export class MockVirtualEnv implements IVirtualEnvironment { constructor(private isDetected: boolean, public name: string) { } - detect(pythonPath: string): Promise { + public detect(pythonPath: string): Promise { return Promise.resolve(this.isDetected); } } +// tslint:disable-next-line:max-classes-per-file export class MockInterpreterVersionProvider implements IInterpreterVersionService { constructor(private displayName: string, private useDefaultDisplayName: boolean = false) { } - getVersion(pythonPath: string, defaultDisplayName: string): Promise { + public getVersion(pythonPath: string, defaultDisplayName: string): Promise { return this.useDefaultDisplayName ? Promise.resolve(defaultDisplayName) : Promise.resolve(this.displayName); } + // tslint:disable-next-line:no-empty + public dispose() { } } diff --git a/src/test/linters/lint.test.ts b/src/test/linters/lint.test.ts index de989eab0355..6eaa262f398e 100644 --- a/src/test/linters/lint.test.ts +++ b/src/test/linters/lint.test.ts @@ -116,7 +116,7 @@ suite('Linting', () => { suiteSetup(async () => { pylintFileToLintLines = fs.readFileSync(fileToLint).toString('utf-8').split(/\r?\n/g); await initialize(); - const version = await execPythonFile(PythonSettings.getInstance().pythonPath, ['--version'], __dirname, true); + const version = await execPythonFile(fileToLint, PythonSettings.getInstance(vscode.Uri.file(fileToLint)).pythonPath, ['--version'], __dirname, true); isPython3Deferred.resolve(version.indexOf('3.') >= 0); }); setup(async () => { diff --git a/src/test/unittests/nosetest.test.ts b/src/test/unittests/nosetest.test.ts index d61e4e0d9944..86923c144173 100644 --- a/src/test/unittests/nosetest.test.ts +++ b/src/test/unittests/nosetest.test.ts @@ -121,64 +121,64 @@ suite('Unit Tests (nosetest)', () => { assert.equal(results.summary.skipped, 2, 'skipped'); }); - test('Run Failed Tests', async () => { - await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); - createTestManager(); - let results = await testManager.runTest(); - assert.equal(results.summary.errors, 1, 'Errors'); - assert.equal(results.summary.failures, 7, 'Failures'); - assert.equal(results.summary.passed, 6, 'Passed'); - assert.equal(results.summary.skipped, 2, 'skipped'); + // test('Run Failed Tests', async () => { + // await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); + // createTestManager(); + // let results = await testManager.runTest(); + // assert.equal(results.summary.errors, 1, 'Errors'); + // assert.equal(results.summary.failures, 7, 'Failures'); + // assert.equal(results.summary.passed, 6, 'Passed'); + // assert.equal(results.summary.skipped, 2, 'skipped'); - results = await testManager.runTest(true); - assert.equal(results.summary.errors, 1, 'Errors again'); - assert.equal(results.summary.failures, 7, 'Failures again'); - assert.equal(results.summary.passed, 0, 'Passed again'); - assert.equal(results.summary.skipped, 0, 'skipped again'); - }); + // results = await testManager.runTest(true); + // assert.equal(results.summary.errors, 1, 'Errors again'); + // assert.equal(results.summary.failures, 7, 'Failures again'); + // assert.equal(results.summary.passed, 0, 'Passed again'); + // assert.equal(results.summary.skipped, 0, 'skipped again'); + // }); - test('Run Specific Test File', async () => { - await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); - createTestManager(); - const tests = await testManager.discoverTests(true, true); - const testFileToRun = tests.testFiles.find(t => t.fullPath.endsWith('test_root.py')); - assert.ok(testFileToRun, 'Test file not found'); - // tslint:disable-next-line:no-non-null-assertion - const testFile: TestsToRun = { testFile: [testFileToRun!], testFolder: [], testFunction: [], testSuite: [] }; - const results = await testManager.runTest(testFile); - assert.equal(results.summary.errors, 0, 'Errors'); - assert.equal(results.summary.failures, 1, 'Failures'); - assert.equal(results.summary.passed, 1, 'Passed'); - assert.equal(results.summary.skipped, 1, 'skipped'); - }); + // test('Run Specific Test File', async () => { + // await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); + // createTestManager(); + // const tests = await testManager.discoverTests(true, true); + // const testFileToRun = tests.testFiles.find(t => t.fullPath.endsWith('test_root.py')); + // assert.ok(testFileToRun, 'Test file not found'); + // // tslint:disable-next-line:no-non-null-assertion + // const testFile: TestsToRun = { testFile: [testFileToRun!], testFolder: [], testFunction: [], testSuite: [] }; + // const results = await testManager.runTest(testFile); + // assert.equal(results.summary.errors, 0, 'Errors'); + // assert.equal(results.summary.failures, 1, 'Failures'); + // assert.equal(results.summary.passed, 1, 'Passed'); + // assert.equal(results.summary.skipped, 1, 'skipped'); + // }); - test('Run Specific Test Suite', async () => { - await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); - createTestManager(); - const tests = await testManager.discoverTests(true, true); - const testSuiteToRun = tests.testSuits.find(s => s.xmlClassName === 'test_root.Test_Root_test1'); - assert.ok(testSuiteToRun, 'Test suite not found'); - // tslint:disable-next-line:no-non-null-assertion - const testSuite: TestsToRun = { testFile: [], testFolder: [], testFunction: [], testSuite: [testSuiteToRun!.testSuite] }; - const results = await testManager.runTest(testSuite); - assert.equal(results.summary.errors, 0, 'Errors'); - assert.equal(results.summary.failures, 1, 'Failures'); - assert.equal(results.summary.passed, 1, 'Passed'); - assert.equal(results.summary.skipped, 1, 'skipped'); - }); + // test('Run Specific Test Suite', async () => { + // await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); + // createTestManager(); + // const tests = await testManager.discoverTests(true, true); + // const testSuiteToRun = tests.testSuits.find(s => s.xmlClassName === 'test_root.Test_Root_test1'); + // assert.ok(testSuiteToRun, 'Test suite not found'); + // // tslint:disable-next-line:no-non-null-assertion + // const testSuite: TestsToRun = { testFile: [], testFolder: [], testFunction: [], testSuite: [testSuiteToRun!.testSuite] }; + // const results = await testManager.runTest(testSuite); + // assert.equal(results.summary.errors, 0, 'Errors'); + // assert.equal(results.summary.failures, 1, 'Failures'); + // assert.equal(results.summary.passed, 1, 'Passed'); + // assert.equal(results.summary.skipped, 1, 'skipped'); + // }); - test('Run Specific Test Function', async () => { - await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); - createTestManager(); - const tests = await testManager.discoverTests(true, true); - const testFnToRun = tests.testFunctions.find(f => f.xmlClassName === 'test_root.Test_Root_test1'); - assert.ok(testFnToRun, 'Test function not found'); - // tslint:disable-next-line:no-non-null-assertion - const testFn: TestsToRun = { testFile: [], testFolder: [], testFunction: [testFnToRun!.testFunction], testSuite: [] }; - const results = await testManager.runTest(testFn); - assert.equal(results.summary.errors, 0, 'Errors'); - assert.equal(results.summary.failures, 1, 'Failures'); - assert.equal(results.summary.passed, 0, 'Passed'); - assert.equal(results.summary.skipped, 0, 'skipped'); - }); + // test('Run Specific Test Function', async () => { + // await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); + // createTestManager(); + // const tests = await testManager.discoverTests(true, true); + // const testFnToRun = tests.testFunctions.find(f => f.xmlClassName === 'test_root.Test_Root_test1'); + // assert.ok(testFnToRun, 'Test function not found'); + // // tslint:disable-next-line:no-non-null-assertion + // const testFn: TestsToRun = { testFile: [], testFolder: [], testFunction: [testFnToRun!.testFunction], testSuite: [] }; + // const results = await testManager.runTest(testFn); + // assert.equal(results.summary.errors, 0, 'Errors'); + // assert.equal(results.summary.failures, 1, 'Failures'); + // assert.equal(results.summary.passed, 0, 'Passed'); + // assert.equal(results.summary.skipped, 0, 'skipped'); + // }); }); diff --git a/src/testMultiRootWkspc/multi.code-workspace b/src/testMultiRootWkspc/multi.code-workspace index 09c3fbb10f1b..6aca26f07b90 100644 --- a/src/testMultiRootWkspc/multi.code-workspace +++ b/src/testMultiRootWkspc/multi.code-workspace @@ -24,13 +24,16 @@ "python.linting.mypyEnabled": false, "python.linting.pydocstyleEnabled": false, "python.linting.pylamaEnabled": false, - "python.linting.pylintEnabled": false, + "python.linting.pylintEnabled": true, "python.linting.pep8Enabled": false, "python.linting.prospectorEnabled": false, "python.workspaceSymbols.enabled": false, "python.formatting.formatOnSave": false, "python.formatting.provider": "yapf", - "python.sortImports.args": [], + "python.sortImports.args": [ + "-sp", + "/Users/donjayamanne/.vscode/extensions/pythonVSCode/src/test/pythonFiles/sorting/withconfig" + ], "python.linting.lintOnSave": false, "python.linting.lintOnTextChange": false, "python.linting.enabled": true, diff --git a/src/testMultiRootWkspc/workspace1/.vscode/settings.json b/src/testMultiRootWkspc/workspace1/.vscode/settings.json index a783cfe01962..f4d89e3bc0e4 100644 --- a/src/testMultiRootWkspc/workspace1/.vscode/settings.json +++ b/src/testMultiRootWkspc/workspace1/.vscode/settings.json @@ -1,5 +1,5 @@ { - "python.linting.pylintEnabled": true, "python.linting.enabled": false, - "python.linting.flake8Enabled": true + "python.linting.flake8Enabled": true, + "python.linting.pylintEnabled": false } From 71f3507fc912a8745474c1f1f835056dee2b08a1 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 23 Oct 2017 16:28:45 -0700 Subject: [PATCH 28/43] fixed #1328 --- .../common/configSettings.multiroot.test.ts | 11 +++--- src/test/index.ts | 4 +-- src/test/initialize.ts | 1 - src/test/interpreters/condaEnvService.test.ts | 2 +- .../interpreters/display.multiroot.test.ts | 9 ++--- src/test/interpreters/display.test.ts | 35 +++++++++++++------ src/test/linters/lint.multiroot.test.ts | 32 ++++++++++------- src/test/workspaceSymbols/multiroot.test.ts | 20 +++++++---- 8 files changed, 71 insertions(+), 43 deletions(-) diff --git a/src/test/common/configSettings.multiroot.test.ts b/src/test/common/configSettings.multiroot.test.ts index be3eca0875aa..8aa1acd5710d 100644 --- a/src/test/common/configSettings.multiroot.test.ts +++ b/src/test/common/configSettings.multiroot.test.ts @@ -2,15 +2,18 @@ import * as assert from 'assert'; import * as path from 'path'; import { ConfigurationTarget, Uri, workspace } from 'vscode'; import { PythonSettings } from '../../client/common/configSettings'; -import { closeActiveWindows, initialize, initializeTest } from '../initialize'; +import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST } from '../initialize'; const multirootPath = path.join(__dirname, '..', '..', '..', 'src', 'testMultiRootWkspc'); // tslint:disable-next-line:max-func-body-length suite('Multiroot Config Settings', () => { - suiteSetup(async () => { - await initialize(); - await resetSettings(); + suiteSetup(function () { + if (!IS_MULTI_ROOT_TEST) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + } + return initialize().then(resetSettings); }); setup(initializeTest); suiteTeardown(closeActiveWindows); diff --git a/src/test/index.ts b/src/test/index.ts index 10bd39fbd4bb..d803697e9d29 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -4,9 +4,7 @@ const testRunner = require('vscode/lib/testrunner'); const singleWorkspaceTestConfig = { ui: 'tdd', useColors: true, - timeout: 25000, - grep: 'Multiroot', - invert: 'invert' + timeout: 25000 }; const multiWorkspaceTestConfig = { ui: 'tdd', diff --git a/src/test/initialize.ts b/src/test/initialize.ts index 8cd932698ba9..5c030b306800 100644 --- a/src/test/initialize.ts +++ b/src/test/initialize.ts @@ -107,4 +107,3 @@ export async function initializePython() { return await pythonConfig.update('pythonPath', PYTHON_PATH, vscode.ConfigurationTarget.Workspace); } } - diff --git a/src/test/interpreters/condaEnvService.test.ts b/src/test/interpreters/condaEnvService.test.ts index 16794922c450..59f9d44c78c6 100644 --- a/src/test/interpreters/condaEnvService.test.ts +++ b/src/test/interpreters/condaEnvService.test.ts @@ -10,7 +10,7 @@ import { initialize, initializeTest } from '../initialize'; import { MockProvider } from './mocks'; const environmentsPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'environments'); -const fileInNonRootWorkspace = path.join(__dirname, '..', '..', 'src', 'test', 'pythonFiles', 'dummy.py'); +const fileInNonRootWorkspace = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'dummy.py'); // tslint:disable-next-line:max-func-body-length suite('Interpreters from Conda Environments', () => { diff --git a/src/test/interpreters/display.multiroot.test.ts b/src/test/interpreters/display.multiroot.test.ts index c4f36b480ed5..a3b396aa2de4 100644 --- a/src/test/interpreters/display.multiroot.test.ts +++ b/src/test/interpreters/display.multiroot.test.ts @@ -8,7 +8,7 @@ import { InterpreterDisplay } from '../../client/interpreter/display'; import { getFirstNonEmptyLineFromMultilineString } from '../../client/interpreter/helpers'; import { VirtualEnvironmentManager } from '../../client/interpreter/virtualEnvs'; import { rootWorkspaceUri, updateSetting } from '../common'; -import { closeActiveWindows, initialize, initializePython, initializeTest } from '../initialize'; +import { closeActiveWindows, initialize, initializePython, initializeTest, IS_MULTI_ROOT_TEST } from '../initialize'; import { MockStatusBarItem } from '../mockClasses'; import { MockInterpreterVersionProvider } from './mocks'; import { MockProvider, MockVirtualEnv } from './mocks'; @@ -19,10 +19,11 @@ const fileToOpen = path.join(workspace3Uri.fsPath, 'file.py'); // tslint:disable-next-line:max-func-body-length suite('Multiroot Interpreters Display', () => { - // tslint:disable-next-line:no-function-expression suiteSetup(function () { - // tslint:disable-next-line:no-invalid-this - this.skip(); + if (!IS_MULTI_ROOT_TEST) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + } return initialize(); }); setup(initializeTest); diff --git a/src/test/interpreters/display.test.ts b/src/test/interpreters/display.test.ts index 690d3dcaef5b..4f0dbd467991 100644 --- a/src/test/interpreters/display.test.ts +++ b/src/test/interpreters/display.test.ts @@ -2,7 +2,7 @@ import * as assert from 'assert'; import * as child_process from 'child_process'; import { EOL } from 'os'; import * as path from 'path'; -import { ConfigurationTarget, Uri } from 'vscode'; +import { ConfigurationTarget, Uri, window, workspace } from 'vscode'; import { PythonSettings } from '../../client/common/configSettings'; import { InterpreterDisplay } from '../../client/interpreter/display'; import { getFirstNonEmptyLineFromMultilineString } from '../../client/interpreter/helpers'; @@ -13,22 +13,29 @@ import { MockStatusBarItem } from '../mockClasses'; import { MockInterpreterVersionProvider } from './mocks'; import { MockProvider, MockVirtualEnv } from './mocks'; -const fileInNonRootWorkspace = path.join(__dirname, '..', '..', 'src', 'test', 'pythonFiles', 'dummy.py'); +const fileInNonRootWorkspace = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'dummy.py'); // tslint:disable-next-line:max-func-body-length suite('Interpreters Display', () => { - // tslint:disable-next-line:no-function-expression - suiteSetup(function () { + const configTarget = IS_MULTI_ROOT_TEST ? ConfigurationTarget.WorkspaceFolder : ConfigurationTarget.Workspace; + suiteSetup(initialize); + setup(async () => { + await initializeTest(); if (IS_MULTI_ROOT_TEST) { - // tslint:disable-next-line:no-invalid-this - this.skip(); + await initializeMultiRoot(); } - return initialize(); }); - setup(initializeTest); teardown(async () => { await initialize(); await closeActiveWindows(); + if (IS_MULTI_ROOT_TEST) { + const settings = workspace.getConfiguration('python', Uri.file(fileInNonRootWorkspace)); + const value = settings.inspect('pythonPath'); + if (value && value.workspaceFolderValue) { + await settings.update('pythonPath', undefined, ConfigurationTarget.WorkspaceFolder); + PythonSettings.dispose(); + } + } }); test('Must have command name', () => { @@ -68,7 +75,7 @@ suite('Interpreters Display', () => { const display = new InterpreterDisplay(statusBar, provider, new VirtualEnvironmentManager([]), displayNameProvider); // Change interpreter to an invalid value const pythonPath = 'UnknownInterpreter'; - await updateSetting('pythonPath', pythonPath, rootWorkspaceUri, ConfigurationTarget.Workspace); + await updateSetting('pythonPath', pythonPath, rootWorkspaceUri, configTarget); await display.refresh(); const defaultDisplayName = `${path.basename(pythonPath)} [Environment]`; @@ -127,9 +134,17 @@ suite('Interpreters Display', () => { const display = new InterpreterDisplay(statusBar, provider, new VirtualEnvironmentManager([]), displayNameProvider); // Change interpreter to an invalid value const pythonPath = 'UnknownInterpreter'; - await updateSetting('pythonPath', pythonPath, rootWorkspaceUri, ConfigurationTarget.Workspace); + await updateSetting('pythonPath', pythonPath, rootWorkspaceUri, configTarget); await display.refresh(); assert.equal(statusBar.text, '$(alert) Select Python Environment', 'Incorrect display name'); }); + async function initializeMultiRoot() { + // For multiroot environments, we need a file open to determine the best interpreter that needs to be displayed + await openDummyFile(); + } + async function openDummyFile() { + const document = await workspace.openTextDocument(fileInNonRootWorkspace); + await window.showTextDocument(document); + } }); diff --git a/src/test/linters/lint.multiroot.test.ts b/src/test/linters/lint.multiroot.test.ts index 17089260ab49..5ad0f11b6cbb 100644 --- a/src/test/linters/lint.multiroot.test.ts +++ b/src/test/linters/lint.multiroot.test.ts @@ -1,19 +1,25 @@ import * as assert from 'assert'; import * as path from 'path'; +import { CancellationTokenSource, ConfigurationTarget, Uri, window, workspace } from 'vscode'; +import { PythonSettings } from '../../client/common/configSettings'; import * as baseLinter from '../../client/linters/baseLinter'; -import * as pyLint from '../../client/linters/pylint'; import * as flake8 from '../../client/linters/flake8'; -import { CancellationTokenSource, ConfigurationTarget, Uri, window, workspace } from 'vscode'; -import { closeActiveWindows, initialize, initializeTest } from '../initialize'; +import * as pyLint from '../../client/linters/pylint'; +import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST } from '../initialize'; import { MockOutputChannel } from '../mockClasses'; -import { PythonSettings } from '../../client/common/configSettings'; const multirootPath = path.join(__dirname, '..', '..', '..', 'src', 'testMultiRootWkspc'); suite('Multiroot Linting', () => { - suiteSetup(() => initialize()); - setup(() => initializeTest()); - suiteTeardown(() => closeActiveWindows()); + suiteSetup(function () { + if (!IS_MULTI_ROOT_TEST) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + } + return initialize(); + }); + setup(initializeTest); + suiteTeardown(closeActiveWindows); teardown(async () => { await closeActiveWindows(); PythonSettings.dispose(); @@ -35,42 +41,42 @@ suite('Multiroot Linting', () => { } test('Enabling Pylint in root and also in Workspace, should return errors', async () => { - let ch = new MockOutputChannel('Lint'); + const ch = new MockOutputChannel('Lint'); await enableDisableSetting(multirootPath, ConfigurationTarget.Workspace, 'pylintEnabled', true); await enableDisableSetting(path.join(multirootPath, 'workspace1'), ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', true); await testLinterInWorkspaceFolder(new pyLint.Linter(ch), 'workspace1', true); }); test('Enabling Pylint in root and disabling in Workspace, should not return errors', async () => { - let ch = new MockOutputChannel('Lint'); + const ch = new MockOutputChannel('Lint'); await enableDisableSetting(multirootPath, ConfigurationTarget.Workspace, 'pylintEnabled', true); await enableDisableSetting(path.join(multirootPath, 'workspace1'), ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', false); await testLinterInWorkspaceFolder(new pyLint.Linter(ch), 'workspace1', false); }); test('Disabling Pylint in root and enabling in Workspace, should return errors', async () => { - let ch = new MockOutputChannel('Lint'); + const ch = new MockOutputChannel('Lint'); await enableDisableSetting(multirootPath, ConfigurationTarget.Workspace, 'pylintEnabled', false); await enableDisableSetting(path.join(multirootPath, 'workspace1'), ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', true); await testLinterInWorkspaceFolder(new pyLint.Linter(ch), 'workspace1', true); }); test('Enabling Flake8 in root and also in Workspace, should return errors', async () => { - let ch = new MockOutputChannel('Lint'); + const ch = new MockOutputChannel('Lint'); await enableDisableSetting(multirootPath, ConfigurationTarget.Workspace, 'flake8Enabled', true); await enableDisableSetting(path.join(multirootPath, 'workspace1'), ConfigurationTarget.WorkspaceFolder, 'flake8Enabled', true); await testLinterInWorkspaceFolder(new flake8.Linter(ch), 'workspace1', true); }); test('Enabling Flake8 in root and disabling in Workspace, should not return errors', async () => { - let ch = new MockOutputChannel('Lint'); + const ch = new MockOutputChannel('Lint'); await enableDisableSetting(multirootPath, ConfigurationTarget.Workspace, 'flake8Enabled', true); await enableDisableSetting(path.join(multirootPath, 'workspace1'), ConfigurationTarget.WorkspaceFolder, 'flake8Enabled', false); await testLinterInWorkspaceFolder(new flake8.Linter(ch), 'workspace1', false); }); test('Disabling Flake8 in root and enabling in Workspace, should return errors', async () => { - let ch = new MockOutputChannel('Lint'); + const ch = new MockOutputChannel('Lint'); await enableDisableSetting(multirootPath, ConfigurationTarget.Workspace, 'flake8Enabled', false); await enableDisableSetting(path.join(multirootPath, 'workspace1'), ConfigurationTarget.WorkspaceFolder, 'flake8Enabled', true); await testLinterInWorkspaceFolder(new flake8.Linter(ch), 'workspace1', true); diff --git a/src/test/workspaceSymbols/multiroot.test.ts b/src/test/workspaceSymbols/multiroot.test.ts index bbd352f11d6f..9d4de7940274 100644 --- a/src/test/workspaceSymbols/multiroot.test.ts +++ b/src/test/workspaceSymbols/multiroot.test.ts @@ -1,25 +1,31 @@ import * as assert from 'assert'; import * as path from 'path'; import { CancellationTokenSource, ConfigurationTarget, Uri } from 'vscode'; -import { closeActiveWindows, initialize, initializeTest } from '../initialize'; import { Generator } from '../../client/workspaceSymbols/generator'; -import { MockOutputChannel } from '../mockClasses'; import { WorkspaceSymbolProvider } from '../../client/workspaceSymbols/provider'; +import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST } from '../initialize'; +import { MockOutputChannel } from '../mockClasses'; import { updateSetting } from './../common'; const multirootPath = path.join(__dirname, '..', '..', '..', 'src', 'testMultiRootWkspc'); suite('Multiroot Workspace Symbols', () => { - suiteSetup(() => initialize()); - setup(() => initializeTest()); - suiteTeardown(() => closeActiveWindows()); + suiteSetup(function () { + if (!IS_MULTI_ROOT_TEST) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + } + return initialize(); + }); + setup(initializeTest); + suiteTeardown(closeActiveWindows); teardown(async () => { await closeActiveWindows(); await updateSetting('workspaceSymbols.enabled', false, Uri.file(path.join(multirootPath, 'parent', 'child')), ConfigurationTarget.WorkspaceFolder); await updateSetting('workspaceSymbols.enabled', false, Uri.file(path.join(multirootPath, 'workspace2')), ConfigurationTarget.WorkspaceFolder); }); - test(`symbols should be returned when enabeld and vice versa`, async () => { + test('symbols should be returned when enabeld and vice versa', async () => { const childWorkspaceUri = Uri.file(path.join(multirootPath, 'parent', 'child')); const outputChannel = new MockOutputChannel('Output'); @@ -38,7 +44,7 @@ suite('Multiroot Workspace Symbols', () => { symbols = await provider.provideWorkspaceSymbols('', new CancellationTokenSource().token); assert.notEqual(symbols.length, 0, 'Symbols should be returned when workspace symbols are turned on'); }); - test(`symbols should be filtered correctly`, async () => { + test('symbols should be filtered correctly', async () => { const childWorkspaceUri = Uri.file(path.join(multirootPath, 'parent', 'child')); const workspace2Uri = Uri.file(path.join(multirootPath, 'workspace2')); const outputChannel = new MockOutputChannel('Output'); From c339fab82099f435db49a0f0b6b9079b84a09371 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 24 Oct 2017 09:44:40 -0700 Subject: [PATCH 29/43] retries for flaky unit tests --- src/test/unittests/nosetest.test.ts | 144 ++++++++++++++++------------ src/test/unittests/pytest.test.ts | 68 ++++++++----- src/test/unittests/unittest.test.ts | 36 +++++-- 3 files changed, 152 insertions(+), 96 deletions(-) diff --git a/src/test/unittests/nosetest.test.ts b/src/test/unittests/nosetest.test.ts index 86923c144173..ddf743277aaa 100644 --- a/src/test/unittests/nosetest.test.ts +++ b/src/test/unittests/nosetest.test.ts @@ -54,7 +54,9 @@ suite('Unit Tests (nosetest)', () => { testManager = new nose.TestManager(rootDir, outChannel); } - test('Discover Tests (single test file)', async () => { + test('Discover Tests (single test file)', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); createTestManager(UNITTEST_SINGLE_TEST_FILE_PATH); const tests = await testManager.discoverTests(true, true); assert.equal(tests.testFiles.length, 2, 'Incorrect number of test files'); @@ -63,7 +65,9 @@ suite('Unit Tests (nosetest)', () => { assert.equal(tests.testFiles.some(t => t.name === path.join('tests', 'test_one.py') && t.nameToRun === t.name), true, 'Test File not found'); }); - test('Check that nameToRun in testSuits has class name after : (single test file)', async () => { + test('Check that nameToRun in testSuits has class name after : (single test file)', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); createTestManager(UNITTEST_SINGLE_TEST_FILE_PATH); const tests = await testManager.discoverTests(true, true); assert.equal(tests.testFiles.length, 2, 'Incorrect number of test files'); @@ -76,7 +80,9 @@ suite('Unit Tests (nosetest)', () => { const found = tests.testFiles.some(t => t.name === testFile && t.nameToRun === t.name); assert.equal(found, true, `Test File not found '${testFile}'`); } - test('Discover Tests (-m=test)', async () => { + test('Discover Tests (-m=test)', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); @@ -90,7 +96,9 @@ suite('Unit Tests (nosetest)', () => { lookForTestFile(tests, 'test_root.py'); }); - test('Discover Tests (-w=specific -m=tst)', async () => { + test('Discover Tests (-w=specific -m=tst)', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); await updateSetting('unitTest.nosetestArgs', ['-w', 'specific', '-m', 'tst'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); @@ -101,7 +109,9 @@ suite('Unit Tests (nosetest)', () => { lookForTestFile(tests, path.join('specific', 'tst_unittest_two.py')); }); - test('Discover Tests (-m=test_)', async () => { + test('Discover Tests (-m=test_)', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); await updateSetting('unitTest.nosetestArgs', ['-m', 'test_'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); @@ -111,7 +121,9 @@ suite('Unit Tests (nosetest)', () => { lookForTestFile(tests, 'test_root.py'); }); - test('Run Tests', async () => { + test('Run Tests', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); createTestManager(); const results = await testManager.runTest(); @@ -121,64 +133,72 @@ suite('Unit Tests (nosetest)', () => { assert.equal(results.summary.skipped, 2, 'skipped'); }); - // test('Run Failed Tests', async () => { - // await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); - // createTestManager(); - // let results = await testManager.runTest(); - // assert.equal(results.summary.errors, 1, 'Errors'); - // assert.equal(results.summary.failures, 7, 'Failures'); - // assert.equal(results.summary.passed, 6, 'Passed'); - // assert.equal(results.summary.skipped, 2, 'skipped'); + test('Run Failed Tests', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); + await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); + createTestManager(); + let results = await testManager.runTest(); + assert.equal(results.summary.errors, 1, 'Errors'); + assert.equal(results.summary.failures, 7, 'Failures'); + assert.equal(results.summary.passed, 6, 'Passed'); + assert.equal(results.summary.skipped, 2, 'skipped'); - // results = await testManager.runTest(true); - // assert.equal(results.summary.errors, 1, 'Errors again'); - // assert.equal(results.summary.failures, 7, 'Failures again'); - // assert.equal(results.summary.passed, 0, 'Passed again'); - // assert.equal(results.summary.skipped, 0, 'skipped again'); - // }); + results = await testManager.runTest(true); + assert.equal(results.summary.errors, 1, 'Errors again'); + assert.equal(results.summary.failures, 7, 'Failures again'); + assert.equal(results.summary.passed, 0, 'Passed again'); + assert.equal(results.summary.skipped, 0, 'skipped again'); + }); - // test('Run Specific Test File', async () => { - // await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); - // createTestManager(); - // const tests = await testManager.discoverTests(true, true); - // const testFileToRun = tests.testFiles.find(t => t.fullPath.endsWith('test_root.py')); - // assert.ok(testFileToRun, 'Test file not found'); - // // tslint:disable-next-line:no-non-null-assertion - // const testFile: TestsToRun = { testFile: [testFileToRun!], testFolder: [], testFunction: [], testSuite: [] }; - // const results = await testManager.runTest(testFile); - // assert.equal(results.summary.errors, 0, 'Errors'); - // assert.equal(results.summary.failures, 1, 'Failures'); - // assert.equal(results.summary.passed, 1, 'Passed'); - // assert.equal(results.summary.skipped, 1, 'skipped'); - // }); + test('Run Specific Test File', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); + await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); + createTestManager(); + const tests = await testManager.discoverTests(true, true); + const testFileToRun = tests.testFiles.find(t => t.fullPath.endsWith('test_root.py')); + assert.ok(testFileToRun, 'Test file not found'); + // tslint:disable-next-line:no-non-null-assertion + const testFile: TestsToRun = { testFile: [testFileToRun!], testFolder: [], testFunction: [], testSuite: [] }; + const results = await testManager.runTest(testFile); + assert.equal(results.summary.errors, 0, 'Errors'); + assert.equal(results.summary.failures, 1, 'Failures'); + assert.equal(results.summary.passed, 1, 'Passed'); + assert.equal(results.summary.skipped, 1, 'skipped'); + }); - // test('Run Specific Test Suite', async () => { - // await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); - // createTestManager(); - // const tests = await testManager.discoverTests(true, true); - // const testSuiteToRun = tests.testSuits.find(s => s.xmlClassName === 'test_root.Test_Root_test1'); - // assert.ok(testSuiteToRun, 'Test suite not found'); - // // tslint:disable-next-line:no-non-null-assertion - // const testSuite: TestsToRun = { testFile: [], testFolder: [], testFunction: [], testSuite: [testSuiteToRun!.testSuite] }; - // const results = await testManager.runTest(testSuite); - // assert.equal(results.summary.errors, 0, 'Errors'); - // assert.equal(results.summary.failures, 1, 'Failures'); - // assert.equal(results.summary.passed, 1, 'Passed'); - // assert.equal(results.summary.skipped, 1, 'skipped'); - // }); + test('Run Specific Test Suite', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); + await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); + createTestManager(); + const tests = await testManager.discoverTests(true, true); + const testSuiteToRun = tests.testSuits.find(s => s.xmlClassName === 'test_root.Test_Root_test1'); + assert.ok(testSuiteToRun, 'Test suite not found'); + // tslint:disable-next-line:no-non-null-assertion + const testSuite: TestsToRun = { testFile: [], testFolder: [], testFunction: [], testSuite: [testSuiteToRun!.testSuite] }; + const results = await testManager.runTest(testSuite); + assert.equal(results.summary.errors, 0, 'Errors'); + assert.equal(results.summary.failures, 1, 'Failures'); + assert.equal(results.summary.passed, 1, 'Passed'); + assert.equal(results.summary.skipped, 1, 'skipped'); + }); - // test('Run Specific Test Function', async () => { - // await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); - // createTestManager(); - // const tests = await testManager.discoverTests(true, true); - // const testFnToRun = tests.testFunctions.find(f => f.xmlClassName === 'test_root.Test_Root_test1'); - // assert.ok(testFnToRun, 'Test function not found'); - // // tslint:disable-next-line:no-non-null-assertion - // const testFn: TestsToRun = { testFile: [], testFolder: [], testFunction: [testFnToRun!.testFunction], testSuite: [] }; - // const results = await testManager.runTest(testFn); - // assert.equal(results.summary.errors, 0, 'Errors'); - // assert.equal(results.summary.failures, 1, 'Failures'); - // assert.equal(results.summary.passed, 0, 'Passed'); - // assert.equal(results.summary.skipped, 0, 'skipped'); - // }); + test('Run Specific Test Function', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); + await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); + createTestManager(); + const tests = await testManager.discoverTests(true, true); + const testFnToRun = tests.testFunctions.find(f => f.xmlClassName === 'test_root.Test_Root_test1'); + assert.ok(testFnToRun, 'Test function not found'); + // tslint:disable-next-line:no-non-null-assertion + const testFn: TestsToRun = { testFile: [], testFolder: [], testFunction: [testFnToRun!.testFunction], testSuite: [] }; + const results = await testManager.runTest(testFn); + assert.equal(results.summary.errors, 0, 'Errors'); + assert.equal(results.summary.failures, 1, 'Failures'); + assert.equal(results.summary.passed, 0, 'Passed'); + assert.equal(results.summary.skipped, 0, 'skipped'); + }); }); diff --git a/src/test/unittests/pytest.test.ts b/src/test/unittests/pytest.test.ts index 7156c76ecab1..77086d86808f 100644 --- a/src/test/unittests/pytest.test.ts +++ b/src/test/unittests/pytest.test.ts @@ -1,46 +1,48 @@ import * as assert from 'assert'; +import * as path from 'path'; import * as vscode from 'vscode'; +import { TestFile, TestsToRun } from '../../client/unittests/common/contracts'; +import { TestResultDisplay } from '../../client/unittests/display/main'; import * as pytest from '../../client/unittests/pytest/main'; -import * as path from 'path'; -import * as configSettings from '../../client/common/configSettings'; +import { rootWorkspaceUri, updateSetting } from '../common'; import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../initialize'; -import { TestsToRun, TestFile } from '../../client/unittests/common/contracts'; -import { TestResultDisplay } from '../../client/unittests/display/main'; import { MockOutputChannel } from './../mockClasses'; -import { rootWorkspaceUri, updateSetting } from '../common'; const UNITTEST_TEST_FILES_PATH = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles', 'standard'); const UNITTEST_SINGLE_TEST_FILE_PATH = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles', 'single'); const UNITTEST_TEST_FILES_PATH_WITH_CONFIGS = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles', 'unitestsWithConfigs'); const unitTestTestFilesCwdPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles', 'cwd', 'src'); +// tslint:disable-next-line:max-func-body-length suite('Unit Tests (PyTest)', () => { + let rootDirectory = UNITTEST_TEST_FILES_PATH; + let testManager: pytest.TestManager; + let testResultDisplay: TestResultDisplay; + let outChannel: vscode.OutputChannel; const configTarget = IS_MULTI_ROOT_TEST ? vscode.ConfigurationTarget.WorkspaceFolder : vscode.ConfigurationTarget.Workspace; suiteSetup(async () => { await initialize(); await updateSetting('unitTest.pyTestArgs', [], rootWorkspaceUri, configTarget); }); - setup(() => { + setup(async () => { rootDirectory = UNITTEST_TEST_FILES_PATH; outChannel = new MockOutputChannel('Python Test Log'); testResultDisplay = new TestResultDisplay(outChannel); - return initializeTest(); + await initializeTest(); }); - teardown(() => { + teardown(async () => { outChannel.dispose(); testManager.dispose(); testResultDisplay.dispose(); - return updateSetting('unitTest.pyTestArgs', [], rootWorkspaceUri, configTarget); + await updateSetting('unitTest.pyTestArgs', [], rootWorkspaceUri, configTarget); }); function createTestManager(rootDir: string = rootDirectory) { testManager = new pytest.TestManager(rootDir, outChannel); } - let rootDirectory = UNITTEST_TEST_FILES_PATH; - let testManager: pytest.TestManager; - let testResultDisplay: TestResultDisplay; - let outChannel: vscode.OutputChannel; - test('Discover Tests (single test file)', async () => { + test('Discover Tests (single test file)', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); testManager = new pytest.TestManager(UNITTEST_SINGLE_TEST_FILE_PATH, outChannel); const tests = await testManager.discoverTests(true, true); assert.equal(tests.testFiles.length, 2, 'Incorrect number of test files'); @@ -50,7 +52,9 @@ suite('Unit Tests (PyTest)', () => { assert.equal(tests.testFiles.some(t => t.name === 'test_root.py' && t.nameToRun === t.name), true, 'Test File not found'); }); - test('Discover Tests (pattern = test_)', async () => { + test('Discover Tests (pattern = test_)', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); await updateSetting('unitTest.pyTestArgs', ['-k=test_'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); @@ -65,7 +69,9 @@ suite('Unit Tests (PyTest)', () => { assert.equal(tests.testFiles.some(t => t.name === 'test_root.py' && t.nameToRun === t.name), true, 'Test File not found'); }); - test('Discover Tests (pattern = _test)', async () => { + test('Discover Tests (pattern = _test)', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); await updateSetting('unitTest.pyTestArgs', ['-k=_test.py'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); @@ -75,8 +81,9 @@ suite('Unit Tests (PyTest)', () => { assert.equal(tests.testFiles.some(t => t.name === 'tests/unittest_three_test.py' && t.nameToRun === t.name), true, 'Test File not found'); }); - - test('Discover Tests (with config)', async () => { + test('Discover Tests (with config)', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); await updateSetting('unitTest.pyTestArgs', [], rootWorkspaceUri, configTarget); rootDirectory = UNITTEST_TEST_FILES_PATH_WITH_CONFIGS; createTestManager(); @@ -88,7 +95,9 @@ suite('Unit Tests (PyTest)', () => { assert.equal(tests.testFiles.some(t => t.name === 'other/test_pytest.py' && t.nameToRun === t.name), true, 'Test File not found'); }); - test('Run Tests', async () => { + test('Run Tests', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); await updateSetting('unitTest.pyTestArgs', ['-k=test_'], rootWorkspaceUri, configTarget); createTestManager(); const results = await testManager.runTest(); @@ -98,7 +107,9 @@ suite('Unit Tests (PyTest)', () => { assert.equal(results.summary.skipped, 3, 'skipped'); }); - test('Run Failed Tests', async () => { + test('Run Failed Tests', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); await updateSetting('unitTest.pyTestArgs', ['-k=test_'], rootWorkspaceUri, configTarget); createTestManager(); let results = await testManager.runTest(); @@ -114,7 +125,9 @@ suite('Unit Tests (PyTest)', () => { assert.equal(results.summary.skipped, 0, 'Failed skipped'); }); - test('Run Specific Test File', async () => { + test('Run Specific Test File', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); await updateSetting('unitTest.pyTestArgs', ['-k=test_'], rootWorkspaceUri, configTarget); createTestManager(); await testManager.discoverTests(true, true); @@ -135,7 +148,9 @@ suite('Unit Tests (PyTest)', () => { assert.equal(results.summary.skipped, 0, 'skipped'); }); - test('Run Specific Test Suite', async () => { + test('Run Specific Test Suite', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); await updateSetting('unitTest.pyTestArgs', ['-k=test_'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); @@ -147,7 +162,9 @@ suite('Unit Tests (PyTest)', () => { assert.equal(results.summary.skipped, 1, 'skipped'); }); - test('Run Specific Test Function', async () => { + test('Run Specific Test Function', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); await updateSetting('unitTest.pyTestArgs', ['-k=test_'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); @@ -159,8 +176,9 @@ suite('Unit Tests (PyTest)', () => { assert.equal(results.summary.skipped, 0, 'skipped'); }); - - test('Setting cwd should return tests', async () => { + test('Setting cwd should return tests', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); await updateSetting('unitTest.pyTestArgs', ['-k=test_'], rootWorkspaceUri, configTarget); createTestManager(unitTestTestFilesCwdPath); diff --git a/src/test/unittests/unittest.test.ts b/src/test/unittests/unittest.test.ts index 478111c1d620..be12df3af776 100644 --- a/src/test/unittests/unittest.test.ts +++ b/src/test/unittests/unittest.test.ts @@ -51,7 +51,9 @@ suite('Unit Tests (unittest)', () => { testManager = new unittest.TestManager(rootDir, outChannel); } - test('Discover Tests (single test file)', async () => { + test('Discover Tests (single test file)', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=test_*.py'], rootWorkspaceUri, configTarget); testManager = new unittest.TestManager(UNITTEST_SINGLE_TEST_FILE_PATH, outChannel); const tests = await testManager.discoverTests(true, true); @@ -61,7 +63,9 @@ suite('Unit Tests (unittest)', () => { assert.equal(tests.testFiles.some(t => t.name === 'test_one.py' && t.nameToRun === 'Test_test1.test_A'), true, 'Test File not found'); }); - test('Discover Tests', async () => { + test('Discover Tests', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=test_*.py'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); @@ -72,7 +76,9 @@ suite('Unit Tests (unittest)', () => { assert.equal(tests.testFiles.some(t => t.name === 'test_unittest_two.py' && t.nameToRun === 'Test_test2.test_A2'), true, 'Test File not found'); }); - test('Discover Tests (pattern = *_test_*.py)', async () => { + test('Discover Tests (pattern = *_test_*.py)', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=*_test*.py'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); @@ -82,7 +88,9 @@ suite('Unit Tests (unittest)', () => { assert.equal(tests.testFiles.some(t => t.name === 'unittest_three_test.py' && t.nameToRun === 'Test_test3.test_A'), true, 'Test File not found'); }); - test('Run Tests', async () => { + test('Run Tests', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); await updateSetting('unitTest.unittestArgs', ['-v', '-s', './tests', '-p', 'test_unittest*.py'], rootWorkspaceUri, configTarget); createTestManager(); const results = await testManager.runTest(); @@ -92,7 +100,9 @@ suite('Unit Tests (unittest)', () => { assert.equal(results.summary.skipped, 1, 'skipped'); }); - test('Run Failed Tests', async () => { + test('Run Failed Tests', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=test_unittest*.py'], rootWorkspaceUri, configTarget); createTestManager(); let results = await testManager.runTest(); @@ -108,7 +118,9 @@ suite('Unit Tests (unittest)', () => { assert.equal(results.summary.skipped, 0, 'Failed skipped'); }); - test('Run Specific Test File', async () => { + test('Run Specific Test File', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=test_unittest*.py'], rootWorkspaceUri, configTarget); createTestManager(unitTestSpecificTestFilesPath); const tests = await testManager.discoverTests(true, true); @@ -124,7 +136,9 @@ suite('Unit Tests (unittest)', () => { assert.equal(results.summary.skipped, 1, 'skipped'); }); - test('Run Specific Test Suite', async () => { + test('Run Specific Test Suite', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=test_unittest*.py'], rootWorkspaceUri, configTarget); createTestManager(unitTestSpecificTestFilesPath); const tests = await testManager.discoverTests(true, true); @@ -140,7 +154,9 @@ suite('Unit Tests (unittest)', () => { assert.equal(results.summary.skipped, 1, 'skipped'); }); - test('Run Specific Test Function', async () => { + test('Run Specific Test Function', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=test_unittest*.py'], rootWorkspaceUri, configTarget); createTestManager(); const tests = await testManager.discoverTests(true, true); @@ -152,7 +168,9 @@ suite('Unit Tests (unittest)', () => { assert.equal(results.summary.skipped, 0, 'skipped'); }); - test('Setting cwd should return tests', async () => { + test('Setting cwd should return tests', async function () { + // tslint:disable-next-line:no-invalid-this + this.retries(3); await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=test_*.py'], rootWorkspaceUri, configTarget); createTestManager(unitTestTestFilesCwdPath); From b0d29da6485ee26457493051183d10c325a0a254 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 24 Oct 2017 10:09:56 -0700 Subject: [PATCH 30/43] retry beforeEach --- src/test/interpreters/display.test.ts | 28 ++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/test/interpreters/display.test.ts b/src/test/interpreters/display.test.ts index 4f0dbd467991..fa283abefec1 100644 --- a/src/test/interpreters/display.test.ts +++ b/src/test/interpreters/display.test.ts @@ -28,16 +28,26 @@ suite('Interpreters Display', () => { teardown(async () => { await initialize(); await closeActiveWindows(); - if (IS_MULTI_ROOT_TEST) { - const settings = workspace.getConfiguration('python', Uri.file(fileInNonRootWorkspace)); - const value = settings.inspect('pythonPath'); - if (value && value.workspaceFolderValue) { - await settings.update('pythonPath', undefined, ConfigurationTarget.WorkspaceFolder); - PythonSettings.dispose(); - } - } + await restoreWorkspaceFolderPythonPath(); }); - + async function restoreWorkspaceFolderPythonPath() { + if (!IS_MULTI_ROOT_TEST) { + return; + } + // Sometimes this bloc of code fails on travis (retry at least once). + for (let i = 0; i < 2; i += 1) { + try { + const settings = workspace.getConfiguration('python', Uri.file(fileInNonRootWorkspace)); + const value = settings.inspect('pythonPath'); + if (value && typeof value.workspaceFolderValue === 'string') { + await settings.update('pythonPath', undefined, ConfigurationTarget.WorkspaceFolder); + PythonSettings.dispose(); + } + return; + // tslint:disable-next-line:no-empty + } catch (ex) { } + } + } test('Must have command name', () => { const statusBar = new MockStatusBarItem(); const displayNameProvider = new MockInterpreterVersionProvider(''); From dfc7451da453a037a235ef3c2c5572c2529db124 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 24 Oct 2017 11:27:20 -0700 Subject: [PATCH 31/43] common retry decorator --- src/test/common.ts | 43 +++++++++++++++++++ .../common/configSettings.multiroot.test.ts | 18 +++----- .../interpreters/display.multiroot.test.ts | 20 +++------ src/test/interpreters/display.test.ts | 22 +--------- src/test/workspaceSymbols/common.ts | 4 +- 5 files changed, 58 insertions(+), 49 deletions(-) diff --git a/src/test/common.ts b/src/test/common.ts index 160424c7172f..8d73936ac320 100644 --- a/src/test/common.ts +++ b/src/test/common.ts @@ -38,3 +38,46 @@ function getWorkspaceRoot() { const workspaceFolder = workspace.getWorkspaceFolder(Uri.file(fileInNonRootWorkspace)); return workspaceFolder ? workspaceFolder.uri : workspace.workspaceFolders[0].uri; } + +// tslint:disable-next-line:no-any +export function retryAsync(wrapped: any, retryCount: number = 2) { + // tslint:disable-next-line:no-any + return async (...args: any[]) => { + return new Promise((resolve, reject) => { + // tslint:disable-next-line:no-any + const reasons: any[] = []; + + const makeCall = () => { + // tslint:disable-next-line:no-unsafe-any no-any + wrapped.apply(...args) + // tslint:disable-next-line:no-unsafe-any no-any + .then(resolve, (reason: any) => { + reasons.push(reason); + if (reasons.length >= retryCount) { + reject(reasons); + } else { + // tslint:disable-next-line:no-string-based-set-timeout + setTimeout(makeCall, 1); + } + }); + }; + + makeCall(); + }); + }; +} + +async function clearPythonPathInWorkspaceFolderImpl(resource: string | Uri) { + if (!IS_MULTI_ROOT_TEST) { + return; + } + const resourceUri = typeof resource === 'string' ? Uri.file(resource) : resource; + const settings = workspace.getConfiguration('python', resourceUri); + const value = settings.inspect('pythonPath'); + if (value && typeof value.workspaceFolderValue === 'string') { + await settings.update('pythonPath', undefined, ConfigurationTarget.WorkspaceFolder); + PythonSettings.dispose(); + } +} + +export const clearPythonPathInWorkspaceFolder = async (resource: string | Uri) => retryAsync(clearPythonPathInWorkspaceFolderImpl)(resource); diff --git a/src/test/common/configSettings.multiroot.test.ts b/src/test/common/configSettings.multiroot.test.ts index 8aa1acd5710d..d19a1bcc59fc 100644 --- a/src/test/common/configSettings.multiroot.test.ts +++ b/src/test/common/configSettings.multiroot.test.ts @@ -2,36 +2,28 @@ import * as assert from 'assert'; import * as path from 'path'; import { ConfigurationTarget, Uri, workspace } from 'vscode'; import { PythonSettings } from '../../client/common/configSettings'; +import { clearPythonPathInWorkspaceFolder } from '../common'; import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST } from '../initialize'; const multirootPath = path.join(__dirname, '..', '..', '..', 'src', 'testMultiRootWkspc'); // tslint:disable-next-line:max-func-body-length suite('Multiroot Config Settings', () => { - suiteSetup(function () { + suiteSetup(async function () { if (!IS_MULTI_ROOT_TEST) { // tslint:disable-next-line:no-invalid-this this.skip(); } - return initialize().then(resetSettings); + await initialize(); + await clearPythonPathInWorkspaceFolder(Uri.file(path.join(multirootPath, 'workspace1'))); }); setup(initializeTest); suiteTeardown(closeActiveWindows); teardown(async () => { - await resetSettings(); await closeActiveWindows(); + await clearPythonPathInWorkspaceFolder(Uri.file(path.join(multirootPath, 'workspace1'))); }); - async function resetSettings() { - const workspaceUri = Uri.file(path.join(multirootPath, 'workspace1')); - const settings = workspace.getConfiguration('python', workspaceUri); - const value = settings.inspect('pythonPath'); - if (value && typeof value.workspaceFolderValue === 'string') { - await settings.update('pythonPath', undefined, ConfigurationTarget.WorkspaceFolder); - } - PythonSettings.dispose(); - } - async function enableDisableLinterSetting(resource: Uri, configTarget: ConfigurationTarget, setting: string, enabled: boolean | undefined): Promise { const settings = workspace.getConfiguration('python.linting', resource); const cfgValue = settings.inspect(setting); diff --git a/src/test/interpreters/display.multiroot.test.ts b/src/test/interpreters/display.multiroot.test.ts index a3b396aa2de4..c92e3b6a4a72 100644 --- a/src/test/interpreters/display.multiroot.test.ts +++ b/src/test/interpreters/display.multiroot.test.ts @@ -1,17 +1,14 @@ import * as assert from 'assert'; -import * as child_process from 'child_process'; -import { EOL } from 'os'; import * as path from 'path'; import { ConfigurationTarget, Uri, window, workspace } from 'vscode'; import { PythonSettings } from '../../client/common/configSettings'; import { InterpreterDisplay } from '../../client/interpreter/display'; -import { getFirstNonEmptyLineFromMultilineString } from '../../client/interpreter/helpers'; import { VirtualEnvironmentManager } from '../../client/interpreter/virtualEnvs'; -import { rootWorkspaceUri, updateSetting } from '../common'; +import { clearPythonPathInWorkspaceFolder } from '../common'; import { closeActiveWindows, initialize, initializePython, initializeTest, IS_MULTI_ROOT_TEST } from '../initialize'; import { MockStatusBarItem } from '../mockClasses'; import { MockInterpreterVersionProvider } from './mocks'; -import { MockProvider, MockVirtualEnv } from './mocks'; +import { MockProvider } from './mocks'; const multirootPath = path.join(__dirname, '..', '..', '..', 'src', 'testMultiRootWkspc'); const workspace3Uri = Uri.file(path.join(multirootPath, 'workspace3')); @@ -19,24 +16,19 @@ const fileToOpen = path.join(workspace3Uri.fsPath, 'file.py'); // tslint:disable-next-line:max-func-body-length suite('Multiroot Interpreters Display', () => { - suiteSetup(function () { + suiteSetup(async function () { if (!IS_MULTI_ROOT_TEST) { // tslint:disable-next-line:no-invalid-this this.skip(); } - return initialize(); + await initialize(); }); setup(initializeTest); suiteTeardown(initializePython); teardown(async () => { await initialize(); await closeActiveWindows(); - const settings = workspace.getConfiguration('python', Uri.file(fileToOpen)); - const value = settings.inspect('pythonPath'); - if (value && value.workspaceFolderValue) { - await settings.update('pythonPath', undefined, ConfigurationTarget.WorkspaceFolder); - PythonSettings.dispose(); - } + await clearPythonPathInWorkspaceFolder(fileToOpen); }); test('Must get display name from workspace folder interpreter and not from interpreter in workspace', async () => { @@ -46,7 +38,7 @@ suite('Multiroot Interpreters Display', () => { PythonSettings.dispose(); const document = await workspace.openTextDocument(fileToOpen); - const editor = await window.showTextDocument(document); + await window.showTextDocument(document); const statusBar = new MockStatusBarItem(); const provider = new MockProvider([]); diff --git a/src/test/interpreters/display.test.ts b/src/test/interpreters/display.test.ts index fa283abefec1..f26dfadd4cda 100644 --- a/src/test/interpreters/display.test.ts +++ b/src/test/interpreters/display.test.ts @@ -7,7 +7,7 @@ import { PythonSettings } from '../../client/common/configSettings'; import { InterpreterDisplay } from '../../client/interpreter/display'; import { getFirstNonEmptyLineFromMultilineString } from '../../client/interpreter/helpers'; import { VirtualEnvironmentManager } from '../../client/interpreter/virtualEnvs'; -import { rootWorkspaceUri, updateSetting } from '../common'; +import { clearPythonPathInWorkspaceFolder, rootWorkspaceUri, updateSetting } from '../common'; import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST } from '../initialize'; import { MockStatusBarItem } from '../mockClasses'; import { MockInterpreterVersionProvider } from './mocks'; @@ -28,26 +28,8 @@ suite('Interpreters Display', () => { teardown(async () => { await initialize(); await closeActiveWindows(); - await restoreWorkspaceFolderPythonPath(); + await clearPythonPathInWorkspaceFolder(fileInNonRootWorkspace); }); - async function restoreWorkspaceFolderPythonPath() { - if (!IS_MULTI_ROOT_TEST) { - return; - } - // Sometimes this bloc of code fails on travis (retry at least once). - for (let i = 0; i < 2; i += 1) { - try { - const settings = workspace.getConfiguration('python', Uri.file(fileInNonRootWorkspace)); - const value = settings.inspect('pythonPath'); - if (value && typeof value.workspaceFolderValue === 'string') { - await settings.update('pythonPath', undefined, ConfigurationTarget.WorkspaceFolder); - PythonSettings.dispose(); - } - return; - // tslint:disable-next-line:no-empty - } catch (ex) { } - } - } test('Must have command name', () => { const statusBar = new MockStatusBarItem(); const displayNameProvider = new MockInterpreterVersionProvider(''); diff --git a/src/test/workspaceSymbols/common.ts b/src/test/workspaceSymbols/common.ts index f713b64969c3..527b852ab6ad 100644 --- a/src/test/workspaceSymbols/common.ts +++ b/src/test/workspaceSymbols/common.ts @@ -2,7 +2,7 @@ import { ConfigurationTarget, Uri, workspace } from 'vscode'; import { PythonSettings } from '../../client/common/configSettings'; export async function enableDisableWorkspaceSymbols(resource: Uri, enabled: boolean, configTarget: ConfigurationTarget) { - let settings = workspace.getConfiguration('python', resource); + const settings = workspace.getConfiguration('python', resource); await settings.update('workspaceSymbols.enabled', enabled, configTarget); PythonSettings.dispose(); -} \ No newline at end of file +} From a8eda5526a84cef60973d17fb34ee1c3b1b68436 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 24 Oct 2017 11:44:12 -0700 Subject: [PATCH 32/43] enable telemetry for extension loads --- src/client/extension.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/client/extension.ts b/src/client/extension.ts index 1012ff936f84..dc7c850595e6 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -35,6 +35,7 @@ import { activateGoToObjectDefinitionProvider } from './providers/objectDefiniti import { InterpreterManager } from './interpreter'; import { SimpleConfigurationProvider } from './debugger'; import { ReplProvider } from './providers/replProvider'; +import { workspace } from 'vscode'; const PYTHON: vscode.DocumentFilter = { language: 'python' }; let unitTestOutChannel: vscode.OutputChannel; @@ -47,11 +48,7 @@ export async function activate(context: vscode.ExtensionContext) { const pythonSettings = settings.PythonSettings.getInstance(); const pythonExt = new PythonExt(); context.subscriptions.push(pythonExt); - // telemetryHelper.sendTelemetryEvent(telemetryContracts.EVENT_LOAD, { - // CodeComplete_Has_ExtraPaths: pythonSettings.autoComplete.extraPaths.length > 0 ? 'true' : 'false', - // Format_Has_Custom_Python_Path: pythonSettings.pythonPath.length !== 'python'.length ? 'true' : 'false', - // Has_PySpark_Path: hasPySparkInCompletionPath ? 'true' : 'false' - // }); + sendStartupTelemetry(); lintingOutChannel = vscode.window.createOutputChannel(pythonSettings.linting.outputWindow); formatOutChannel = lintingOutChannel; if (pythonSettings.linting.outputWindow !== pythonSettings.formatting.outputWindow) { @@ -189,4 +186,8 @@ class ContextKey { this.lastValue = value; vscode.commands.executeCommand('setContext', this.name, this.lastValue); } -} +} + +function sendStartupTelemetry() { + telemetryHelper.sendTelemetryEvent(telemetryContracts.EVENT_LOAD); +} From 32b6d85ee80815e70438b37053aa8e16e82cb661 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 24 Oct 2017 11:48:19 -0700 Subject: [PATCH 33/43] disable jupyter tests in multiroot tests --- src/test/index.ts | 17 ++++------------- src/test/jupyter/jupyter.codeHelper.test.ts | 10 ++++++++-- src/test/jupyter/jupyterClient.test.ts | 10 ++++++++-- src/test/jupyter/jupyterKernel.test.ts | 10 ++++++++-- src/test/jupyter/jupyterKernelManager.test.ts | 10 ++++++++-- 5 files changed, 36 insertions(+), 21 deletions(-) diff --git a/src/test/index.ts b/src/test/index.ts index d803697e9d29..52c578617499 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -1,20 +1,11 @@ import { initializePython, IS_MULTI_ROOT_TEST } from './initialize'; const testRunner = require('vscode/lib/testrunner'); -const singleWorkspaceTestConfig = { +// You can directly control Mocha options by uncommenting the following lines. +// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info. +testRunner.configure({ ui: 'tdd', useColors: true, timeout: 25000 -}; -const multiWorkspaceTestConfig = { - ui: 'tdd', - useColors: true, - timeout: 25000, - grep: 'Jupyter', - invert: 'invert' -}; - -// You can directly control Mocha options by uncommenting the following lines. -// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info. -testRunner.configure(IS_MULTI_ROOT_TEST ? multiWorkspaceTestConfig : singleWorkspaceTestConfig); +}); module.exports = testRunner; diff --git a/src/test/jupyter/jupyter.codeHelper.test.ts b/src/test/jupyter/jupyter.codeHelper.test.ts index 60643f2d6b2b..27b529b72530 100644 --- a/src/test/jupyter/jupyter.codeHelper.test.ts +++ b/src/test/jupyter/jupyter.codeHelper.test.ts @@ -1,14 +1,20 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; import * as path from 'path'; -import { initialize, closeActiveWindows, initializeTest } from './../initialize'; +import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../initialize'; import { CodeHelper } from '../../client/jupyter/common/codeHelper'; import { JupyterCodeLensProvider } from '../../client/jupyter/editorIntegration/codeLensProvider'; const FILE_WITH_CELLS = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'jupyter', 'cells.py'); suite('Jupyter Code Helper', () => { - suiteSetup(() => initialize()); + suiteSetup(async function () { + if (IS_MULTI_ROOT_TEST) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + } + await initialize(); + }); setup(() => initializeTest()); teardown(() => closeActiveWindows()); const codeLensProvider = new JupyterCodeLensProvider(); diff --git a/src/test/jupyter/jupyterClient.test.ts b/src/test/jupyter/jupyterClient.test.ts index c74d027db47c..182c94e2a2d2 100644 --- a/src/test/jupyter/jupyterClient.test.ts +++ b/src/test/jupyter/jupyterClient.test.ts @@ -1,13 +1,19 @@ import * as assert from 'assert'; import { MockOutputChannel } from './mocks'; -import { initialize, initializeTest } from './../initialize'; +import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../initialize'; import { JupyterClientAdapter } from '../../client/jupyter/jupyter_client/main'; import { KernelRestartedError, KernelShutdownError } from '../../client/jupyter/common/errors'; import { createDeferred } from '../../client/common/helpers'; import { KernelspecMetadata } from '../../client/jupyter/contracts'; suite('JupyterClient', () => { - suiteSetup(() => initialize()); + suiteSetup(async function () { + if (IS_MULTI_ROOT_TEST) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + } + await initialize(); + }); setup(() => { process.env['PYTHON_DONJAYAMANNE_TEST'] = '0'; process.env['DEBUG_DJAYAMANNE_IPYTHON'] = '1'; diff --git a/src/test/jupyter/jupyterKernel.test.ts b/src/test/jupyter/jupyterKernel.test.ts index fddedbe166c1..a10a399d8985 100644 --- a/src/test/jupyter/jupyterKernel.test.ts +++ b/src/test/jupyter/jupyterKernel.test.ts @@ -1,6 +1,6 @@ import * as assert from 'assert'; import { MockOutputChannel } from './mocks'; -import { initialize, initializeTest } from './../initialize'; +import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../initialize'; import { JupyterClientAdapter } from '../../client/jupyter/jupyter_client/main'; import { KernelShutdownError } from '../../client/jupyter/common/errors'; import { createDeferred } from '../../client/common/helpers'; @@ -8,7 +8,13 @@ import { JupyterClientKernel } from '../../client/jupyter/jupyter_client-Kernel' import { KernelspecMetadata } from '../../client/jupyter/contracts'; suite('Jupyter Kernel', () => { - suiteSetup(() => initialize()); + suiteSetup(async function () { + if (IS_MULTI_ROOT_TEST) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + } + await initialize(); + }); setup(() => { process.env['PYTHON_DONJAYAMANNE_TEST'] = '0'; process.env['DEBUG_DJAYAMANNE_IPYTHON'] = '1'; diff --git a/src/test/jupyter/jupyterKernelManager.test.ts b/src/test/jupyter/jupyterKernelManager.test.ts index f4c5e9f51c4f..aa151ba2bef0 100644 --- a/src/test/jupyter/jupyterKernelManager.test.ts +++ b/src/test/jupyter/jupyterKernelManager.test.ts @@ -1,12 +1,18 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; import { MockOutputChannel } from './mocks'; -import { initialize, initializeTest } from './../initialize'; +import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../initialize'; import { JupyterClientAdapter } from '../../client/jupyter/jupyter_client/main'; import { KernelManagerImpl } from '../../client/jupyter/kernel-manager'; suite('Kernel Manager', () => { - suiteSetup(() => initialize()); + suiteSetup(async function () { + if (IS_MULTI_ROOT_TEST) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + } + await initialize(); + }); setup(() => { process.env['PYTHON_DONJAYAMANNE_TEST'] = '0'; process.env['DEBUG_DJAYAMANNE_IPYTHON'] = '1'; From 155e45fbac84aaa3f018bf028ba0c451720f7e18 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 24 Oct 2017 13:26:11 -0700 Subject: [PATCH 34/43] clean up python Path before and after testsclean up python Path before and after tests --- src/test/common/common.test.ts | 33 ++++++++--------- .../common/configSettings.multiroot.test.ts | 2 +- src/test/common/installer.test.ts | 36 ++++++++++--------- src/test/initialize.ts | 35 +++++++++++------- .../interpreters/display.multiroot.test.ts | 2 +- src/test/interpreters/display.test.ts | 2 +- 6 files changed, 60 insertions(+), 50 deletions(-) diff --git a/src/test/common/common.test.ts b/src/test/common/common.test.ts index cb2bb7297642..d1353819c584 100644 --- a/src/test/common/common.test.ts +++ b/src/test/common/common.test.ts @@ -1,28 +1,21 @@ -// -// Note: This example test is leveraging the Mocha test framework. -// Please refer to their documentation on https://mochajs.org/ for help. -// - -// The module 'assert' provides assertion methods from node import * as assert from 'assert'; -import * as vscode from 'vscode'; - -// You can import and use all API from the 'vscode' module -// as well as import your extension to test it -import { initialize } from './../initialize'; -import { execPythonFile } from '../../client/common/utils'; import { EOL } from 'os'; +import * as vscode from 'vscode'; import { createDeferred } from '../../client/common/helpers'; +import { execPythonFile } from '../../client/common/utils'; +import { initialize } from './../initialize'; // Defines a Mocha test suite to group tests of similar kind together suite('ChildProc', () => { - setup(() => initialize()); + setup(initialize); + teardown(initialize); test('Standard Response', done => { execPythonFile(undefined, 'python', ['-c', 'print(1)'], __dirname, false).then(data => { - assert.ok(data === '1' + EOL); + assert.ok(data === `1${EOL}`); }).then(done).catch(done); }); test('Error Response', done => { + // tslint:disable-next-line:no-any const def = createDeferred(); execPythonFile(undefined, 'python', ['-c', 'print(1'], __dirname, false).then(() => { def.reject('Should have failed'); @@ -40,7 +33,7 @@ suite('ChildProc', () => { } execPythonFile(undefined, 'python', ['-c', 'print(1)'], __dirname, false, handleOutput).then(() => { assert.equal(output.length, 1, 'Ouput length incorrect'); - assert.equal(output[0], '1' + EOL, 'Ouput value incorrect'); + assert.equal(output[0], `1${EOL}`, 'Ouput value incorrect'); }).then(done).catch(done); }); @@ -49,12 +42,13 @@ suite('ChildProc', () => { function handleOutput(data: string) { output.push(data); } - await execPythonFile(undefined, 'python', ['-c', `print('öä')`], __dirname, false, handleOutput) + await execPythonFile(undefined, 'python', ['-c', 'print(\'öä\')'], __dirname, false, handleOutput); assert.equal(output.length, 1, 'Ouput length incorrect'); - assert.equal(output[0], 'öä' + EOL, 'Ouput value incorrect'); + assert.equal(output[0], `öä${EOL}`, 'Ouput value incorrect'); }); test('Stream Stdout with Threads', function (done) { + // tslint:disable-next-line:no-invalid-this this.timeout(6000); const output: string[] = []; function handleOutput(data: string) { @@ -62,12 +56,13 @@ suite('ChildProc', () => { } execPythonFile(undefined, 'python', ['-c', 'import sys\nprint(1)\nsys.__stdout__.flush()\nimport time\ntime.sleep(5)\nprint(2)'], __dirname, false, handleOutput).then(() => { assert.equal(output.length, 2, 'Ouput length incorrect'); - assert.equal(output[0], '1' + EOL, 'First Ouput value incorrect'); - assert.equal(output[1], '2' + EOL, 'Second Ouput value incorrect'); + assert.equal(output[0], `1${EOL}`, 'First Ouput value incorrect'); + assert.equal(output[1], `2${EOL}`, 'Second Ouput value incorrect'); }).then(done).catch(done); }); test('Kill', done => { + // tslint:disable-next-line:no-any const def = createDeferred(); const output: string[] = []; function handleOutput(data: string) { diff --git a/src/test/common/configSettings.multiroot.test.ts b/src/test/common/configSettings.multiroot.test.ts index d19a1bcc59fc..81d092a99da6 100644 --- a/src/test/common/configSettings.multiroot.test.ts +++ b/src/test/common/configSettings.multiroot.test.ts @@ -14,8 +14,8 @@ suite('Multiroot Config Settings', () => { // tslint:disable-next-line:no-invalid-this this.skip(); } - await initialize(); await clearPythonPathInWorkspaceFolder(Uri.file(path.join(multirootPath, 'workspace1'))); + await initialize(); }); setup(initializeTest); suiteTeardown(closeActiveWindows); diff --git a/src/test/common/installer.test.ts b/src/test/common/installer.test.ts index d49b32c0a066..786ae7bb278e 100644 --- a/src/test/common/installer.test.ts +++ b/src/test/common/installer.test.ts @@ -1,8 +1,10 @@ import * as assert from 'assert'; -import { closeActiveWindows, IS_TRAVIS, initializeTest } from './../initialize'; -import { MockOutputChannel } from './../mockClasses'; -import { Installer, Product } from '../../client/common/installer'; +import * as path from 'path'; +import { Uri } from 'vscode'; import { EnumEx } from '../../client/common/enumUtils'; +import { Installer, Product } from '../../client/common/installer'; +import { closeActiveWindows, initializeTest, IS_MULTI_ROOT_TEST, IS_TRAVIS } from './../initialize'; +import { MockOutputChannel } from './../mockClasses'; // TODO: Need to mock the command runner, to check what commands are being sent. // Instead of altering the environment. @@ -10,23 +12,25 @@ import { EnumEx } from '../../client/common/enumUtils'; suite('Installer', () => { let outputChannel: MockOutputChannel; let installer: Installer; - - suiteSetup(() => { + const workspaceUri = Uri.file(path.join(__dirname, '..', '..', '..', 'src', 'test')); + const resource = IS_MULTI_ROOT_TEST ? workspaceUri : undefined; + suiteSetup(async () => { outputChannel = new MockOutputChannel('Installer'); installer = new Installer(outputChannel); + await initializeTest(); }); - setup(() => initializeTest()); - suiteTeardown(() => closeActiveWindows()); - teardown(() => closeActiveWindows()); + setup(initializeTest); + suiteTeardown(closeActiveWindows); + teardown(closeActiveWindows); async function testUninstallingProduct(product: Product) { - const isInstalled = await installer.isInstalled(product); + let isInstalled = await installer.isInstalled(product, resource); if (isInstalled) { - await installer.uninstall(product); - const isInstalled = await installer.isInstalled(product); + await installer.uninstall(product, resource); + isInstalled = await installer.isInstalled(product, resource); // Someimtes installation doesn't work on Travis if (!IS_TRAVIS) { - assert.equal(isInstalled, false, `Product uninstall failed`); + assert.equal(isInstalled, false, 'Product uninstall failed'); } } } @@ -41,14 +45,14 @@ suite('Installer', () => { }); async function testInstallingProduct(product: Product) { - const isInstalled = await installer.isInstalled(product); + const isInstalled = await installer.isInstalled(product, resource); if (!isInstalled) { - await installer.install(product); + await installer.install(product, resource); } - const checkIsInstalledAgain = await installer.isInstalled(product); + const checkIsInstalledAgain = await installer.isInstalled(product, resource); // Someimtes installation doesn't work on Travis if (!IS_TRAVIS) { - assert.notEqual(checkIsInstalledAgain, false, `Product installation failed`); + assert.notEqual(checkIsInstalledAgain, false, 'Product installation failed'); } } EnumEx.getNamesAndValues(Product).forEach(prod => { diff --git a/src/test/initialize.ts b/src/test/initialize.ts index 5c030b306800..19a464e58345 100644 --- a/src/test/initialize.ts +++ b/src/test/initialize.ts @@ -1,52 +1,58 @@ -// -// Note: This example test is leveraging the Mocha test framework. -// Please refer to their documentation on https://mochajs.org/ for help. -// - -// You can import and use all API from the 'vscode' module -// as well as import your extension to test it import * as assert from 'assert'; import * as fs from 'fs'; -import * as vscode from 'vscode'; import * as path from 'path'; -let dummyPythonFile = path.join(__dirname, '..', '..', 'src', 'test', 'pythonFiles', 'dummy.py'); +import * as vscode from 'vscode'; +import { clearPythonPathInWorkspaceFolder } from './common'; +const dummyPythonFile = path.join(__dirname, '..', '..', 'src', 'test', 'pythonFiles', 'dummy.py'); //First thing to be executed +// tslint:disable-next-line:no-string-literal process.env['PYTHON_DONJAYAMANNE_TEST'] = '1'; -let configSettings: any = undefined; +// tslint:disable-next-line:no-any +let configSettings: any; let extensionActivated: boolean = false; +// tslint:disable-next-line:no-any export async function initialize(): Promise { await initializePython(); // Opening a python file activates the extension. await vscode.workspace.openTextDocument(dummyPythonFile); if (!extensionActivated) { + // tslint:disable-next-line:no-require-imports const ext = require('../client/extension'); + // tslint:disable-next-line:no-unsafe-any await ext.activated; extensionActivated = true; } if (!configSettings) { + // tslint:disable-next-line:no-require-imports configSettings = await require('../client/common/configSettings'); } // Dispose any cached python settings (used only in test env). + // tslint:disable-next-line:no-unsafe-any) configSettings.PythonSettings.dispose(); } +// tslint:disable-next-line:no-any export async function initializeTest(): Promise { await initializePython(); await closeActiveWindows(); if (!configSettings) { + // tslint:disable-next-line:no-require-imports no-unsafe-any configSettings = await require('../client/common/configSettings'); } // Dispose any cached python settings (used only in test env) + // tslint:disable-next-line:no-unsafe-any configSettings.PythonSettings.dispose(); } export async function wait(timeoutMilliseconds: number) { return new Promise(resolve => { + // tslint:disable-next-line:no-string-based-set-timeout setTimeout(resolve, timeoutMilliseconds); }); } +// tslint:disable-next-line:no-any export async function closeActiveWindows(): Promise { // https://github.com/Microsoft/vscode/blob/master/extensions/vscode-api-tests/src/utils.ts return new Promise(resolve => { @@ -71,6 +77,7 @@ export async function closeActiveWindows(): Promise { return resolve(); } vscode.commands.executeCommand('workbench.action.closeAllEditors') + // tslint:disable-next-line:no-any .then(() => null, (err: any) => { clearInterval(interval); resolve(); @@ -82,9 +89,10 @@ export async function closeActiveWindows(): Promise { }); } - function getPythonPath(): string { + // tslint:disable-next-line:no-unsafe-any if (process.env.TRAVIS_PYTHON_PATH && fs.existsSync(process.env.TRAVIS_PYTHON_PATH)) { + // tslint:disable-next-line:no-unsafe-any return process.env.TRAVIS_PYTHON_PATH; } return 'python'; @@ -95,6 +103,7 @@ function isMultitrootTest() { } const PYTHON_PATH = getPythonPath(); +// tslint:disable-next-line:no-string-literal prefer-template export const IS_TRAVIS = (process.env['TRAVIS'] + '') === 'true'; export const TEST_TIMEOUT = 25000; export const IS_MULTI_ROOT_TEST = isMultitrootTest(); @@ -104,6 +113,8 @@ export async function initializePython() { const pythonConfig = vscode.workspace.getConfiguration('python'); const value = pythonConfig.inspect('pythonPath'); if (value && value.workspaceValue !== PYTHON_PATH) { - return await pythonConfig.update('pythonPath', PYTHON_PATH, vscode.ConfigurationTarget.Workspace); + await pythonConfig.update('pythonPath', PYTHON_PATH, vscode.ConfigurationTarget.Workspace); } + + await clearPythonPathInWorkspaceFolder(dummyPythonFile); } diff --git a/src/test/interpreters/display.multiroot.test.ts b/src/test/interpreters/display.multiroot.test.ts index c92e3b6a4a72..c2710638b23e 100644 --- a/src/test/interpreters/display.multiroot.test.ts +++ b/src/test/interpreters/display.multiroot.test.ts @@ -26,9 +26,9 @@ suite('Multiroot Interpreters Display', () => { setup(initializeTest); suiteTeardown(initializePython); teardown(async () => { + await clearPythonPathInWorkspaceFolder(fileToOpen); await initialize(); await closeActiveWindows(); - await clearPythonPathInWorkspaceFolder(fileToOpen); }); test('Must get display name from workspace folder interpreter and not from interpreter in workspace', async () => { diff --git a/src/test/interpreters/display.test.ts b/src/test/interpreters/display.test.ts index f26dfadd4cda..0cf3271124a3 100644 --- a/src/test/interpreters/display.test.ts +++ b/src/test/interpreters/display.test.ts @@ -26,9 +26,9 @@ suite('Interpreters Display', () => { } }); teardown(async () => { + await clearPythonPathInWorkspaceFolder(fileInNonRootWorkspace); await initialize(); await closeActiveWindows(); - await clearPythonPathInWorkspaceFolder(fileInNonRootWorkspace); }); test('Must have command name', () => { const statusBar = new MockStatusBarItem(); From df14e358dca04c9940b3302e58d9cec4a8d9488c Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 24 Oct 2017 13:47:49 -0700 Subject: [PATCH 35/43] rename test env variable --- pythonFiles/PythonTools/ipythonServer.py | 2 +- pythonFiles/completionServer.py | 2 +- src/client/common/configSettings.ts | 2 +- src/client/common/logger.ts | 2 +- src/client/jupyter/jupyter_client/main.ts | 2 +- src/test/index.ts | 1 + src/test/initialize.ts | 2 +- src/test/jupyter/jupyterClient.test.ts | 12 ++++++------ src/test/jupyter/jupyterKernel.test.ts | 4 ++-- src/test/jupyter/jupyterKernelManager.test.ts | 6 +++--- 10 files changed, 18 insertions(+), 17 deletions(-) diff --git a/pythonFiles/PythonTools/ipythonServer.py b/pythonFiles/PythonTools/ipythonServer.py index 99ff5dc7ac6c..0aac8c30bfb4 100644 --- a/pythonFiles/PythonTools/ipythonServer.py +++ b/pythonFiles/PythonTools/ipythonServer.py @@ -55,7 +55,7 @@ from queue import Empty, Queue # Python 3 DEBUG = os.environ.get('DEBUG_DJAYAMANNE_IPYTHON', '0') == '1' -TEST = os.environ.get('PYTHON_DONJAYAMANNE_TEST', '0') == '1' +TEST = os.environ.get('VSC_PYTHON_CI_TEST', '0') == '1' # The great "support IPython 2, 3, 4" strat begins if not TEST: diff --git a/pythonFiles/completionServer.py b/pythonFiles/completionServer.py index f364f1fddce3..2e2934724841 100644 --- a/pythonFiles/completionServer.py +++ b/pythonFiles/completionServer.py @@ -55,7 +55,7 @@ from queue import Empty, Queue # Python 3 DEBUG = os.environ.get('DEBUG_DJAYAMANNE_IPYTHON', '0') == '1' -TEST = os.environ.get('PYTHON_DONJAYAMANNE_TEST', '0') == '1' +TEST = os.environ.get('VSC_PYTHON_CI_TEST', '0') == '1' def _debug_write(out): if DEBUG: diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index 78bba31f34d9..b009b3274dde 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -137,7 +137,7 @@ export interface JupyterSettings { } // tslint:disable-next-line:no-string-literal -const IS_TEST_EXECUTION = process.env['PYTHON_DONJAYAMANNE_TEST'] === '1'; +const IS_TEST_EXECUTION = process.env['VSC_PYTHON_CI_TEST'] === '1'; // tslint:disable-next-line:completed-docs export class PythonSettings extends EventEmitter implements IPythonSettings { diff --git a/src/client/common/logger.ts b/src/client/common/logger.ts index 173da70d78ab..076e1a008964 100644 --- a/src/client/common/logger.ts +++ b/src/client/common/logger.ts @@ -22,7 +22,7 @@ class Logger { Logger.writeLine(category, message); } static writeLine(category: string = "log", line: any) { - if (process.env['PYTHON_DONJAYAMANNE_TEST'] !== '1') { + if (process.env['VSC_PYTHON_CI_TEST'] !== '1') { console[category](line); } if (outChannel) { diff --git a/src/client/jupyter/jupyter_client/main.ts b/src/client/jupyter/jupyter_client/main.ts index 8d715fa6940a..76ef4b6f4dfa 100644 --- a/src/client/jupyter/jupyter_client/main.ts +++ b/src/client/jupyter/jupyter_client/main.ts @@ -70,7 +70,7 @@ export class JupyterClientAdapter extends EventEmitter implements IJupyterClient let processStarted = false; let handshakeDone = false; - let isInTestRun = newEnv['PYTHON_DONJAYAMANNE_TEST'] === "1"; + let isInTestRun = newEnv['VSC_PYTHON_CI_TEST'] === "1"; const testDef = createDeferred(); const promiseToResolve = isInTestRun ? testDef.resolve.bind(testDef) : def.resolve.bind(def); diff --git a/src/test/index.ts b/src/test/index.ts index 52c578617499..077d65e200e8 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -1,5 +1,6 @@ import { initializePython, IS_MULTI_ROOT_TEST } from './initialize'; const testRunner = require('vscode/lib/testrunner'); +process.env['VSC_PYTHON_CI_TEST'] = '1'; // You can directly control Mocha options by uncommenting the following lines. // See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info. diff --git a/src/test/initialize.ts b/src/test/initialize.ts index 19a464e58345..884a49663f1d 100644 --- a/src/test/initialize.ts +++ b/src/test/initialize.ts @@ -7,7 +7,7 @@ const dummyPythonFile = path.join(__dirname, '..', '..', 'src', 'test', 'pythonF //First thing to be executed // tslint:disable-next-line:no-string-literal -process.env['PYTHON_DONJAYAMANNE_TEST'] = '1'; +process.env['VSC_PYTHON_CI_TEST'] = '1'; // tslint:disable-next-line:no-any let configSettings: any; diff --git a/src/test/jupyter/jupyterClient.test.ts b/src/test/jupyter/jupyterClient.test.ts index 182c94e2a2d2..60cac7bb9d66 100644 --- a/src/test/jupyter/jupyterClient.test.ts +++ b/src/test/jupyter/jupyterClient.test.ts @@ -15,14 +15,14 @@ suite('JupyterClient', () => { await initialize(); }); setup(() => { - process.env['PYTHON_DONJAYAMANNE_TEST'] = '0'; + process.env['VSC_PYTHON_CI_TEST'] = '0'; process.env['DEBUG_DJAYAMANNE_IPYTHON'] = '1'; output = new MockOutputChannel('Jupyter'); jupyter = new JupyterClientAdapter(output, __dirname); return initializeTest(); }); teardown(() => { - process.env['PYTHON_DONJAYAMANNE_TEST'] = '1'; + process.env['VSC_PYTHON_CI_TEST'] = '1'; process.env['DEBUG_DJAYAMANNE_IPYTHON'] = '0'; output.dispose(); jupyter.dispose(); @@ -32,7 +32,7 @@ suite('JupyterClient', () => { let jupyter: JupyterClientAdapter; test('Ping (Process and Socket)', done => { - jupyter.start({ 'PYTHON_DONJAYAMANNE_TEST': '1', 'DEBUG_DJAYAMANNE_IPYTHON': '1' }).then(() => { + jupyter.start({ 'VSC_PYTHON_CI_TEST': '1', 'DEBUG_DJAYAMANNE_IPYTHON': '1' }).then(() => { done(); }).catch(reason => { assert.fail(reason, undefined, 'Starting Jupyter failed', ''); @@ -88,7 +88,7 @@ suite('JupyterClient', () => { }); test('Start Kernel (without start)', done => { jupyter.getAllKernelSpecs().then(kernelSpecs => { - process.env['PYTHON_DONJAYAMANNE_TEST'] = '0'; + process.env['VSC_PYTHON_CI_TEST'] = '0'; // Ok we got the kernelspecs, now create another new jupyter client // and tell it to start a specific kernel @@ -104,7 +104,7 @@ suite('JupyterClient', () => { done(); }); - process.env['PYTHON_DONJAYAMANNE_TEST'] = '1'; + process.env['VSC_PYTHON_CI_TEST'] = '1'; }).catch(reason => { assert.fail(reason, undefined, 'Failed to retrieve kernelspecs', ''); @@ -414,7 +414,7 @@ suite('JupyterClient', () => { assert.fail(reason, undefined, 'Failed to retrieve kernelspecs', ''); done(); }); - process.env['PYTHON_DONJAYAMANNE_TEST'] = '1'; + process.env['VSC_PYTHON_CI_TEST'] = '1'; }); test('Execute multiple blocks of Code', done => { jupyter.start().then(() => { diff --git a/src/test/jupyter/jupyterKernel.test.ts b/src/test/jupyter/jupyterKernel.test.ts index a10a399d8985..89377d2c897c 100644 --- a/src/test/jupyter/jupyterKernel.test.ts +++ b/src/test/jupyter/jupyterKernel.test.ts @@ -16,7 +16,7 @@ suite('Jupyter Kernel', () => { await initialize(); }); setup(() => { - process.env['PYTHON_DONJAYAMANNE_TEST'] = '0'; + process.env['VSC_PYTHON_CI_TEST'] = '0'; process.env['DEBUG_DJAYAMANNE_IPYTHON'] = '1'; disposables = []; output = new MockOutputChannel('Jupyter'); @@ -26,7 +26,7 @@ suite('Jupyter Kernel', () => { return initializeTest(); }); teardown(() => { - process.env['PYTHON_DONJAYAMANNE_TEST'] = '1'; + process.env['VSC_PYTHON_CI_TEST'] = '1'; process.env['DEBUG_DJAYAMANNE_IPYTHON'] = '0'; output.dispose(); jupyter.dispose(); diff --git a/src/test/jupyter/jupyterKernelManager.test.ts b/src/test/jupyter/jupyterKernelManager.test.ts index aa151ba2bef0..27b99e77c5e6 100644 --- a/src/test/jupyter/jupyterKernelManager.test.ts +++ b/src/test/jupyter/jupyterKernelManager.test.ts @@ -14,7 +14,7 @@ suite('Kernel Manager', () => { await initialize(); }); setup(() => { - process.env['PYTHON_DONJAYAMANNE_TEST'] = '0'; + process.env['VSC_PYTHON_CI_TEST'] = '0'; process.env['DEBUG_DJAYAMANNE_IPYTHON'] = '1'; disposables = []; output = new MockOutputChannel('Jupyter'); @@ -26,7 +26,7 @@ suite('Kernel Manager', () => { return initializeTest(); }); teardown(() => { - process.env['PYTHON_DONJAYAMANNE_TEST'] = '1'; + process.env['VSC_PYTHON_CI_TEST'] = '1'; process.env['DEBUG_DJAYAMANNE_IPYTHON'] = '0'; output.dispose(); jupyter.dispose(); @@ -46,7 +46,7 @@ suite('Kernel Manager', () => { const oldRegisterCommand = vscode.commands.registerCommand; test('GetAllKernelSpecsFor python', done => { - process.env['PYTHON_DONJAYAMANNE_TEST'] = '0'; + process.env['VSC_PYTHON_CI_TEST'] = '0'; const mgr = new KernelManagerImpl(output, jupyter); disposables.push(mgr); mgr.getAllKernelSpecsFor('python').then(specMetadata => { From 1b58b20c3ddea719e0630c7d615ff1fabfbf07cc Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 24 Oct 2017 14:26:39 -0700 Subject: [PATCH 36/43] dispose cfg settings --- src/test/initialize.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/test/initialize.ts b/src/test/initialize.ts index 884a49663f1d..b74afdf70dbe 100644 --- a/src/test/initialize.ts +++ b/src/test/initialize.ts @@ -114,6 +114,13 @@ export async function initializePython() { const value = pythonConfig.inspect('pythonPath'); if (value && value.workspaceValue !== PYTHON_PATH) { await pythonConfig.update('pythonPath', PYTHON_PATH, vscode.ConfigurationTarget.Workspace); + if (!configSettings) { + // tslint:disable-next-line:no-require-imports no-unsafe-any + configSettings = await require('../client/common/configSettings'); + } + // Dispose any cached python settings (used only in test env) + // tslint:disable-next-line:no-unsafe-any + configSettings.PythonSettings.dispose(); } await clearPythonPathInWorkspaceFolder(dummyPythonFile); From f9e89150177b1b3ff5de88a0e8b38b7bb6e57cf4 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 24 Oct 2017 15:07:33 -0700 Subject: [PATCH 37/43] dispose cfg settings --- src/test/common.ts | 8 ++++++-- src/test/common/configSettings.multiroot.test.ts | 1 + src/test/initialize.ts | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/test/common.ts b/src/test/common.ts index 8d73936ac320..49c85430955d 100644 --- a/src/test/common.ts +++ b/src/test/common.ts @@ -40,7 +40,7 @@ function getWorkspaceRoot() { } // tslint:disable-next-line:no-any -export function retryAsync(wrapped: any, retryCount: number = 2) { +export function retryAsync(wrapped: Function, retryCount: number = 2) { // tslint:disable-next-line:no-any return async (...args: any[]) => { return new Promise((resolve, reject) => { @@ -49,7 +49,8 @@ export function retryAsync(wrapped: any, retryCount: number = 2) { const makeCall = () => { // tslint:disable-next-line:no-unsafe-any no-any - wrapped.apply(...args) + // tslint:disable-next-line:no-invalid-this + wrapped.call(this, ...args) // tslint:disable-next-line:no-unsafe-any no-any .then(resolve, (reason: any) => { reasons.push(reason); @@ -78,6 +79,9 @@ async function clearPythonPathInWorkspaceFolderImpl(resource: string | Uri) { await settings.update('pythonPath', undefined, ConfigurationTarget.WorkspaceFolder); PythonSettings.dispose(); } + const settings2 = workspace.getConfiguration('python', resourceUri); + const value2 = settings.inspect('pythonPath'); + const y = ''; } export const clearPythonPathInWorkspaceFolder = async (resource: string | Uri) => retryAsync(clearPythonPathInWorkspaceFolderImpl)(resource); diff --git a/src/test/common/configSettings.multiroot.test.ts b/src/test/common/configSettings.multiroot.test.ts index 81d092a99da6..dc7ef9590885 100644 --- a/src/test/common/configSettings.multiroot.test.ts +++ b/src/test/common/configSettings.multiroot.test.ts @@ -22,6 +22,7 @@ suite('Multiroot Config Settings', () => { teardown(async () => { await closeActiveWindows(); await clearPythonPathInWorkspaceFolder(Uri.file(path.join(multirootPath, 'workspace1'))); + await initializeTest(); }); async function enableDisableLinterSetting(resource: Uri, configTarget: ConfigurationTarget, setting: string, enabled: boolean | undefined): Promise { diff --git a/src/test/initialize.ts b/src/test/initialize.ts index b74afdf70dbe..574aab3f7623 100644 --- a/src/test/initialize.ts +++ b/src/test/initialize.ts @@ -110,6 +110,8 @@ export const IS_MULTI_ROOT_TEST = isMultitrootTest(); // Ability to use custom python environments for testing export async function initializePython() { + await clearPythonPathInWorkspaceFolder(dummyPythonFile); + const pythonConfig = vscode.workspace.getConfiguration('python'); const value = pythonConfig.inspect('pythonPath'); if (value && value.workspaceValue !== PYTHON_PATH) { @@ -122,6 +124,4 @@ export async function initializePython() { // tslint:disable-next-line:no-unsafe-any configSettings.PythonSettings.dispose(); } - - await clearPythonPathInWorkspaceFolder(dummyPythonFile); } From 7d4f0f752a51c378be0885799dc22d2972ac4c49 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 24 Oct 2017 15:11:24 -0700 Subject: [PATCH 38/43] update comment --- src/test/common.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/common.ts b/src/test/common.ts index 49c85430955d..060325a500d1 100644 --- a/src/test/common.ts +++ b/src/test/common.ts @@ -57,8 +57,9 @@ export function retryAsync(wrapped: Function, retryCount: number = 2) { if (reasons.length >= retryCount) { reject(reasons); } else { + // If failed once, lets wait for some time before trying again. // tslint:disable-next-line:no-string-based-set-timeout - setTimeout(makeCall, 1); + setTimeout(makeCall, 500); } }); }; From 4f880a1a1bbd9d09d02b507b44d21319295b8564 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 24 Oct 2017 15:36:17 -0700 Subject: [PATCH 39/43] clean up --- src/test/common.ts | 15 +++++---- src/test/initialize.ts | 70 +++++++++++++----------------------------- 2 files changed, 28 insertions(+), 57 deletions(-) diff --git a/src/test/common.ts b/src/test/common.ts index 060325a500d1..486293b24015 100644 --- a/src/test/common.ts +++ b/src/test/common.ts @@ -69,20 +69,19 @@ export function retryAsync(wrapped: Function, retryCount: number = 2) { }; } -async function clearPythonPathInWorkspaceFolderImpl(resource: string | Uri) { - if (!IS_MULTI_ROOT_TEST) { +async function setPythonPathInWorkspace(resource: string | Uri | undefined, config: ConfigurationTarget, pythonPath?: string) { + if (config === ConfigurationTarget.WorkspaceFolder && !IS_MULTI_ROOT_TEST) { return; } const resourceUri = typeof resource === 'string' ? Uri.file(resource) : resource; const settings = workspace.getConfiguration('python', resourceUri); const value = settings.inspect('pythonPath'); - if (value && typeof value.workspaceFolderValue === 'string') { - await settings.update('pythonPath', undefined, ConfigurationTarget.WorkspaceFolder); + const prop: 'workspaceFolderValue' | 'workspaceValue' = config === ConfigurationTarget.Workspace ? 'workspaceValue' : 'workspaceFolderValue'; + if (value && value[prop] !== pythonPath) { + await settings.update('pythonPath', pythonPath, config); PythonSettings.dispose(); } - const settings2 = workspace.getConfiguration('python', resourceUri); - const value2 = settings.inspect('pythonPath'); - const y = ''; } -export const clearPythonPathInWorkspaceFolder = async (resource: string | Uri) => retryAsync(clearPythonPathInWorkspaceFolderImpl)(resource); +export const clearPythonPathInWorkspaceFolder = async (resource: string | Uri) => retryAsync(setPythonPathInWorkspace)(resource, ConfigurationTarget.WorkspaceFolder); +export const setPythonPathInWorkspaceRoot = async (pythonPath: string) => retryAsync(setPythonPathInWorkspace)(undefined, ConfigurationTarget.Workspace, pythonPath); diff --git a/src/test/initialize.ts b/src/test/initialize.ts index 574aab3f7623..f67afc30b8ec 100644 --- a/src/test/initialize.ts +++ b/src/test/initialize.ts @@ -2,47 +2,43 @@ import * as assert from 'assert'; import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; -import { clearPythonPathInWorkspaceFolder } from './common'; +import { PythonSettings } from '../client/common/configSettings'; +import { activated } from '../client/extension'; +import { clearPythonPathInWorkspaceFolder, setPythonPathInWorkspaceRoot } from './common'; + const dummyPythonFile = path.join(__dirname, '..', '..', 'src', 'test', 'pythonFiles', 'dummy.py'); -//First thing to be executed +//First thing to be executed. // tslint:disable-next-line:no-string-literal process.env['VSC_PYTHON_CI_TEST'] = '1'; -// tslint:disable-next-line:no-any -let configSettings: any; -let extensionActivated: boolean = false; +const PYTHON_PATH = getPythonPath(); +// tslint:disable-next-line:no-string-literal prefer-template +export const IS_TRAVIS = (process.env['TRAVIS'] + '') === 'true'; +export const TEST_TIMEOUT = 25000; +export const IS_MULTI_ROOT_TEST = isMultitrootTest(); + +// Ability to use custom python environments for testing +export async function initializePython() { + await clearPythonPathInWorkspaceFolder(dummyPythonFile); + await setPythonPathInWorkspaceRoot(PYTHON_PATH); +} + // tslint:disable-next-line:no-any export async function initialize(): Promise { await initializePython(); // Opening a python file activates the extension. await vscode.workspace.openTextDocument(dummyPythonFile); - if (!extensionActivated) { - // tslint:disable-next-line:no-require-imports - const ext = require('../client/extension'); - // tslint:disable-next-line:no-unsafe-any - await ext.activated; - extensionActivated = true; - } - if (!configSettings) { - // tslint:disable-next-line:no-require-imports - configSettings = await require('../client/common/configSettings'); - } + await activated; // Dispose any cached python settings (used only in test env). - // tslint:disable-next-line:no-unsafe-any) - configSettings.PythonSettings.dispose(); + PythonSettings.dispose(); } // tslint:disable-next-line:no-any export async function initializeTest(): Promise { await initializePython(); await closeActiveWindows(); - if (!configSettings) { - // tslint:disable-next-line:no-require-imports no-unsafe-any - configSettings = await require('../client/common/configSettings'); - } - // Dispose any cached python settings (used only in test env) - // tslint:disable-next-line:no-unsafe-any - configSettings.PythonSettings.dispose(); + // Dispose any cached python settings (used only in test env). + PythonSettings.dispose(); } export async function wait(timeoutMilliseconds: number) { @@ -101,27 +97,3 @@ function getPythonPath(): string { function isMultitrootTest() { return Array.isArray(vscode.workspace.workspaceFolders) && vscode.workspace.workspaceFolders.length > 1; } - -const PYTHON_PATH = getPythonPath(); -// tslint:disable-next-line:no-string-literal prefer-template -export const IS_TRAVIS = (process.env['TRAVIS'] + '') === 'true'; -export const TEST_TIMEOUT = 25000; -export const IS_MULTI_ROOT_TEST = isMultitrootTest(); - -// Ability to use custom python environments for testing -export async function initializePython() { - await clearPythonPathInWorkspaceFolder(dummyPythonFile); - - const pythonConfig = vscode.workspace.getConfiguration('python'); - const value = pythonConfig.inspect('pythonPath'); - if (value && value.workspaceValue !== PYTHON_PATH) { - await pythonConfig.update('pythonPath', PYTHON_PATH, vscode.ConfigurationTarget.Workspace); - if (!configSettings) { - // tslint:disable-next-line:no-require-imports no-unsafe-any - configSettings = await require('../client/common/configSettings'); - } - // Dispose any cached python settings (used only in test env) - // tslint:disable-next-line:no-unsafe-any - configSettings.PythonSettings.dispose(); - } -} From 000a25a8a4e90b781de4277f3bddd75762d1a115 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 24 Oct 2017 15:39:30 -0700 Subject: [PATCH 40/43] rearrange to ensurfe launching ext is first debug option --- .vscode/launch.json | 51 +++++++++------------------------------------ 1 file changed, 10 insertions(+), 41 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 8c80fc1679cd..3cc9f6c671c6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,25 +1,7 @@ // A launch configuration that compiles the extension and then opens it inside a new window { "version": "0.1.0", - "configurations": [{ - "name": "CompletionServer.ppy", - "type": "python", - "request": "launch", - "stopOnEntry": true, - "pythonPath": "python", - "program": "${workspaceRoot}/pythonFiles/completionServer.py", - "cwd": "${workspaceRoot}", - "env": {}, - "args": [ - "123" - ], - "envFile": "${workspaceRoot}/.env", - "debugOptions": [ - "WaitOnAbnormalExit", - "WaitOnNormalExit", - "RedirectOutput" - ] - }, + "configurations": [ { "name": "Launch Extension", "type": "extensionHost", @@ -83,28 +65,15 @@ "${workspaceRoot}/out/**/*.js" ], "preLaunchTask": "compile" - }, - { - "name": "Python", - "type": "python", - "request": "launch", - "stopOnEntry": true, - "pythonPath": "python", - "program": "${file}", - "console": "integratedTerminal", - "args": [], - "debugOptions": [ - "WaitOnAbnormalExit", - "WaitOnNormalExit", - "RedirectOutput" - ], - "cwd": "${workspaceRoot}" } ], - "compounds": [{ - "name": "Extension + Debugger", - "configurations": [ - "Launch Extension", "Launch Extension as debugServer" - ] - }] + "compounds": [ + { + "name": "Extension + Debugger", + "configurations": [ + "Launch Extension", + "Launch Extension as debugServer" + ] + } + ] } From 665a233753f1afb67fc3ce940f1154089577caa4 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 24 Oct 2017 16:00:50 -0700 Subject: [PATCH 41/43] bug fix for display name --- src/client/common/utils.ts | 2 +- src/client/interpreter/index.ts | 2 +- src/test/common/common.test.ts | 8 +++++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/client/common/utils.ts b/src/client/common/utils.ts index 2c862808d627..2d171de81d19 100644 --- a/src/client/common/utils.ts +++ b/src/client/common/utils.ts @@ -425,7 +425,7 @@ export function areBasePathsSame(path1: string, path2: string) { } export async function getInterpreterDisplayName(pythonPath: string) { return await new Promise((resolve, reject) => { - child_process.execFile(pythonPath, ['--version'], (stdErr, stdout) => { + child_process.execFile(pythonPath, ['--version'], (error, stdout, stdErr) => { const out = (typeof stdErr === 'string' ? stdErr : '') + os.EOL + (typeof stdout === 'string' ? stdout : ''); const lines = out.split(/\r?\n/g).map(line => line.trim()).filter(line => line.length > 0); resolve(lines.length > 0 ? lines[0] : ''); diff --git a/src/client/interpreter/index.ts b/src/client/interpreter/index.ts index 62f389c7c60e..e6ebea67f7a9 100644 --- a/src/client/interpreter/index.ts +++ b/src/client/interpreter/index.ts @@ -23,7 +23,7 @@ export class InterpreterManager implements Disposable { const versionService = new InterpreterVersionService(); this.display = new InterpreterDisplay(statusBar, this.interpreterProvider, virtualEnvMgr, versionService); PythonSettings.getInstance().addListener('change', () => this.onConfigChanged()); - + this.disposables.push(window.onDidChangeActiveTextEditor(() => this.refresh())); this.disposables.push(statusBar); this.disposables.push(this.display); } diff --git a/src/test/common/common.test.ts b/src/test/common/common.test.ts index d1353819c584..0e72ac770b71 100644 --- a/src/test/common/common.test.ts +++ b/src/test/common/common.test.ts @@ -2,7 +2,7 @@ import * as assert from 'assert'; import { EOL } from 'os'; import * as vscode from 'vscode'; import { createDeferred } from '../../client/common/helpers'; -import { execPythonFile } from '../../client/common/utils'; +import { execPythonFile, getInterpreterDisplayName } from '../../client/common/utils'; import { initialize } from './../initialize'; // Defines a Mocha test suite to group tests of similar kind together @@ -81,4 +81,10 @@ suite('ChildProc', () => { def.promise.then(done).catch(done); }); + + test('Get Python display name', async () => { + const displayName = await getInterpreterDisplayName('python'); + assert.equal(typeof displayName, 'string', 'Display name not returned'); + assert.notEqual(displayName.length, 0, 'Display name cannot be empty'); + }); }); From 22cc2e75b7f4fb3d07244acc54d3ba417fcd5c47 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 26 Oct 2017 14:30:11 -0700 Subject: [PATCH 42/43] resolved code review comment --- src/client/common/interpreterInfoCache.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/common/interpreterInfoCache.ts b/src/client/common/interpreterInfoCache.ts index ffb46dc02b0b..f23f6ef7563b 100644 --- a/src/client/common/interpreterInfoCache.ts +++ b/src/client/common/interpreterInfoCache.ts @@ -34,7 +34,7 @@ export class InterpreterInfoCache { InterpreterInfoCache.setCacheData('customEnvVariables', resource, envVars); } // tslint:disable-next-line:no-any function-name - private static setCacheData(property: keyof InterpreterCache, resource?: Uri, value?: string | any) { + private static setCacheData(property: keyof InterpreterCache, resource?: Uri, value?: any) { const cacheKey = InterpreterInfoCache.getCacheKey(resource) || ''; // tslint:disable-next-line:prefer-type-cast const data = cache.has(cacheKey) ? cache.get(cacheKey) : {} as InterpreterCache; From 3cbc06728b943a6c3cf8c662e95090191384a3f6 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 26 Oct 2017 14:31:03 -0700 Subject: [PATCH 43/43] Fixed typp --- src/client/common/configSettings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index b009b3274dde..797e644d3b3f 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -399,7 +399,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { }; // If workspace config changes, then we could have a cascading effect of on change events. - // Lets defer the change notification. + // Let's defer the change notification. setTimeout(() => this.emit('change'), 1); }