From 69e74ff11470823fda3e9fa2f38f0c7a573a9430 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 12 Aug 2019 10:10:32 -0700 Subject: [PATCH 1/8] Fix smoke tests (#6936) --- build/ci/templates/test_phases.yml | 2 +- src/test/standardTest.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/build/ci/templates/test_phases.yml b/build/ci/templates/test_phases.yml index 57151c964efb..75f772f5b462 100644 --- a/build/ci/templates/test_phases.yml +++ b/build/ci/templates/test_phases.yml @@ -553,7 +553,7 @@ steps: npm run package npx gulp clean:cleanExceptTests cp -r ./tmp/test ./out/test - npm run testSmoke + node ./out/test/smokeTest.js displayName: 'Run Smoke Tests' condition: and(succeeded(), contains(variables['TestsToRun'], 'testSmoke')) env: diff --git a/src/test/standardTest.ts b/src/test/standardTest.ts index f76d712fba62..237960cb1fcf 100644 --- a/src/test/standardTest.ts +++ b/src/test/standardTest.ts @@ -7,12 +7,13 @@ import { EXTENSION_ROOT_DIR_FOR_TESTS } from './constants'; process.env.IS_CI_SERVER_TEST_DEBUGGER = ''; process.env.VSC_PYTHON_CI_TEST = '1'; const workspacePath = process.env.CODE_TESTS_WORKSPACE ? process.env.CODE_TESTS_WORKSPACE : path.join(__dirname, '..', '..', 'src', 'test'); +const extensionDevelopmentPath = process.env.CODE_EXTENSIONS_PATH ? process.env.CODE_EXTENSIONS_PATH : EXTENSION_ROOT_DIR_FOR_TESTS; function start() { console.log('*'.repeat(100)); console.log('Start Standard tests'); runTests({ - extensionDevelopmentPath: EXTENSION_ROOT_DIR_FOR_TESTS, + extensionDevelopmentPath: extensionDevelopmentPath, extensionTestsPath: path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'out', 'test', 'index'), launchArgs: [workspacePath], version: 'stable' From 9c61d0e0a99caf3a4be1d75db440b27b4f15fcd2 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 12 Aug 2019 10:46:20 -0700 Subject: [PATCH 2/8] Use codecov for coverage reports (#6941) --- CONTRIBUTING.md | 2 +- build/ci/codecov.yml | 27 +++++++++++++++++++ .../ci/templates/generate_upload_coverage.yml | 15 ----------- news/3 Code Health/6938.md | 1 + package-lock.json | 26 ------------------ package.json | 3 +-- 6 files changed, 30 insertions(+), 44 deletions(-) create mode 100644 build/ci/codecov.yml create mode 100644 news/3 Code Health/6938.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c80391626648..5cfc0ee77616 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ | `release` branch | `master` branch | Nightly CI | coverage (`master` branch) | |-|-|-|-| -| [![Build Status](https://dev.azure.com/ms/vscode-python/_apis/build/status/CI?branchName=release)](https://dev.azure.com/ms/vscode-python/_build/latest?definitionId=88&branchName=release) | [![Build Status](https://dev.azure.com/ms/vscode-python/_apis/build/status/CI?branchName=master)](https://dev.azure.com/ms/vscode-python/_build/latest?definitionId=88&branchName=master) | [![Build Status](https://dev.azure.com/ms/vscode-python/_apis/build/status/Nightly%20Build?branchName=master)](https://dev.azure.com/ms/vscode-python/_build/latest?definitionId=85&branchName=master) | [![Coverage Status](https://coveralls.io/repos/github/microsoft/vscode-python/badge.svg?branch=master)](https://coveralls.io/github/microsoft/vscode-python?branch=master) | +| [![Build Status](https://dev.azure.com/ms/vscode-python/_apis/build/status/CI?branchName=release)](https://dev.azure.com/ms/vscode-python/_build/latest?definitionId=88&branchName=release) | [![Build Status](https://dev.azure.com/ms/vscode-python/_apis/build/status/CI?branchName=master)](https://dev.azure.com/ms/vscode-python/_build/latest?definitionId=88&branchName=master) | [![Build Status](https://dev.azure.com/ms/vscode-python/_apis/build/status/Nightly%20Build?branchName=master)](https://dev.azure.com/ms/vscode-python/_build/latest?definitionId=85&branchName=master) | [![codecov](https://codecov.io/gh/microsoft/vscode-python/branch/master/graph/badge.svg)](https://codecov.io/gh/microsoft/vscode-python) | [[Development build](https://pvsc.blob.core.windows.net/extension-builds/ms-python-insiders.vsix)] diff --git a/build/ci/codecov.yml b/build/ci/codecov.yml new file mode 100644 index 000000000000..817313b270d3 --- /dev/null +++ b/build/ci/codecov.yml @@ -0,0 +1,27 @@ +codecov: + notify: + require_ci_to_pass: yes + +coverage: + precision: 0 + round: down + range: "70...100" + + status: + project: yes + patch: yes + changes: no + +parsers: + gcov: + branch_detection: + conditional: yes + loop: yes + method: no + macro: no + +comment: + branch: !release* + layout: "header, diff, files" + behavior: default + require_changes: no diff --git a/build/ci/templates/generate_upload_coverage.yml b/build/ci/templates/generate_upload_coverage.yml index 927e3206cda8..a0f2bfb98849 100644 --- a/build/ci/templates/generate_upload_coverage.yml +++ b/build/ci/templates/generate_upload_coverage.yml @@ -15,21 +15,6 @@ steps: summaryFileLocation: "$(System.DefaultWorkingDirectory)/coverage/cobertura-coverage.xml" reportDirectory: "$(System.DefaultWorkingDirectory)/coverage" - - bash: cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js - displayName: 'Upload coverage to coveralls' - continueOnError: true - condition: contains(variables['TestsToRun'], 'testUnitTests') - failOnStderr: false - # Set necessary env variables for coveralls, as they don't support Azure Devops. - # Set variables based on documentation and the coveralls (npm package) source code. 😊. - env: - COVERALLS_SERVICE_JOB_ID: $(Build.BuildId) - COVERALLS_REPO_TOKEN: $(COVERALLS_REPO_TOKEN) - COVERALLS_SERVICE_NAME: $(COVERALLS_SERVICE_NAME) - COVERALLS_GIT_COMMIT: $(Build.SourceVersion) - COVERALLS_GIT_BRANCH: $(Build.SourceBranchName) - CI_PULL_REQUEST: $(System.PullRequest.PullRequestNumber) - - bash: cat ./coverage/lcov.info | ./node_modules/.bin/codecov --pipe displayName: 'Upload coverage to codecov' continueOnError: true diff --git a/news/3 Code Health/6938.md b/news/3 Code Health/6938.md new file mode 100644 index 000000000000..db733330f20c --- /dev/null +++ b/news/3 Code Health/6938.md @@ -0,0 +1 @@ +Upload coverage reports to [codecov](https://codecov.io/gh/microsoft/vscode-python). diff --git a/package-lock.json b/package-lock.json index 71b1f11f9b84..b6b9cfb95603 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5404,20 +5404,6 @@ "parse-json": "^4.0.0" } }, - "coveralls": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.4.tgz", - "integrity": "sha512-eyqUWA/7RT0JagiL0tThVhjbIjoiEUyWCjtUJoOPcWoeofP5WK/jb2OJYoBFrR6DvplR+AxOyuBqk4JHkk5ykA==", - "dev": true, - "requires": { - "growl": "~> 1.10.0", - "js-yaml": "^3.11.0", - "lcov-parse": "^0.0.10", - "log-driver": "^1.2.7", - "minimist": "^1.2.0", - "request": "^2.86.0" - } - }, "cp-file": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-6.2.0.tgz", @@ -11438,12 +11424,6 @@ "invert-kv": "^1.0.0" } }, - "lcov-parse": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-0.0.10.tgz", - "integrity": "sha1-GwuP+ayceIklBYK3C3ExXZ2m2aM=", - "dev": true - }, "lead": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", @@ -11803,12 +11783,6 @@ "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=", "dev": true }, - "log-driver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", - "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", - "dev": true - }, "log-symbols": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", diff --git a/package.json b/package.json index 6374896c570b..c022da8be5d3 100644 --- a/package.json +++ b/package.json @@ -2545,8 +2545,8 @@ "@types/glob": "^5.0.35", "@types/html-webpack-plugin": "^3.2.0", "@types/iconv-lite": "^0.0.1", - "@types/jsdom": "^11.12.0", "@types/jquery": "^1.10.35", + "@types/jsdom": "^11.12.0", "@types/loader-utils": "^1.1.3", "@types/lodash": "^4.14.104", "@types/md5": "^2.1.32", @@ -2592,7 +2592,6 @@ "codecov": "^3.5.0", "colors": "^1.2.1", "copy-webpack-plugin": "^4.6.0", - "coveralls": "^3.0.4", "cross-spawn": "^6.0.5", "css-loader": "^1.0.1", "cucumber-html-reporter": "^4.0.5", From 2402dd5fe184b5c38b9d22062f6a676a5d526e22 Mon Sep 17 00:00:00 2001 From: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com> Date: Mon, 12 Aug 2019 14:19:53 -0700 Subject: [PATCH 3/8] Revert "Add regex to dedent `else` and friends (#6497)" (#6945) This reverts commit 245442325ff02a85dfbac2f2a4d1b649c0fa9f85. --- src/client/extension.ts | 6 +- src/client/language/languageConfiguration.ts | 34 ++++----- .../languageConfiguration.unit.test.ts | 73 +------------------ 3 files changed, 20 insertions(+), 93 deletions(-) diff --git a/src/client/extension.ts b/src/client/extension.ts index 67a6f9dd9507..db56a5b24c66 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -40,7 +40,7 @@ import { registerTypes as appRegisterTypes } from './application/serviceRegistry import { IApplicationDiagnostics } from './application/types'; import { DebugService } from './common/application/debugService'; import { IApplicationShell, ICommandManager, IWorkspaceService } from './common/application/types'; -import { Commands, isTestExecution, PYTHON, PYTHON_LANGUAGE, STANDARD_OUTPUT_CHANNEL } from './common/constants'; +import { Commands, isTestExecution, PYTHON, STANDARD_OUTPUT_CHANNEL } from './common/constants'; import { registerTypes as registerDotNetTypes } from './common/dotnet/serviceRegistry'; import { registerTypes as installerRegisterTypes } from './common/installer/serviceRegistry'; import { traceError } from './common/logger'; @@ -85,7 +85,7 @@ import { registerTypes as interpretersRegisterTypes } from './interpreter/servic import { ServiceContainer } from './ioc/container'; import { ServiceManager } from './ioc/serviceManager'; import { IServiceContainer, IServiceManager } from './ioc/types'; -import { getLanguageConfiguration } from './language/languageConfiguration'; +import { setLanguageConfiguration } from './language/languageConfiguration'; import { LinterCommands } from './linters/linterCommands'; import { registerTypes as lintersRegisterTypes } from './linters/serviceRegistry'; import { ILintingEngine } from './linters/types'; @@ -176,7 +176,7 @@ async function activateUnsafe(context: ExtensionContext): Promise const linterProvider = new LinterProvider(context, serviceManager); context.subscriptions.push(linterProvider); - languages.setLanguageConfiguration(PYTHON_LANGUAGE, getLanguageConfiguration()); + setLanguageConfiguration(); if (pythonSettings && pythonSettings.formatting && pythonSettings.formatting.provider !== 'internalConsole') { const formatProvider = new PythonFormattingEditProvider(context, serviceContainer); diff --git a/src/client/language/languageConfiguration.ts b/src/client/language/languageConfiguration.ts index fa8f86d966f3..19eebd8d0131 100644 --- a/src/client/language/languageConfiguration.ts +++ b/src/client/language/languageConfiguration.ts @@ -2,24 +2,19 @@ // Licensed under the MIT License. 'use strict'; -import { IndentAction } from 'vscode'; +import { IndentAction, languages } from 'vscode'; +import { PYTHON_LANGUAGE } from '../common/constants'; export const MULTILINE_SEPARATOR_INDENT_REGEX = /^(?!\s+\\)[^#\n]+\\$/; -/** - * This does not handle all cases. However, it does handle nearly all usage. - * Here's what it does not cover: - * - the statement is split over multiple lines (and hence the ":" is on a different line) - * - the code block is inlined (after the ":") - * - there are multiple statements on the line (separated by semicolons) - * Also note that `lambda` is purposefully excluded. - */ -export const INCREASE_INDENT_REGEX = /^\s*(?:(?:async|class|def|elif|except|for|if|while|with)\b.*|(else|finally|try))\s*:\s*(#.*)?$/; -export const DECREASE_INDENT_REGEX = /^\s*(?:else|finally|(?:elif|except)\b.*)\s*:\s*(#.*)?$/; -export const OUTDENT_ONENTER_REGEX = /^\s*(?:break|continue|pass|(?:raise|return)\b.*)\s*(#.*)?$/; -export function getLanguageConfiguration() { - return { +export function setLanguageConfiguration() { + // Enable indentAction + languages.setLanguageConfiguration(PYTHON_LANGUAGE, { onEnterRules: [ + { + beforeText: /^\s*(?:def|class|for|if|elif|else|while|try|with|finally|except|async)\b.*:\s*/, + action: { indentAction: IndentAction.Indent } + }, { beforeText: MULTILINE_SEPARATOR_INDENT_REGEX, action: { indentAction: IndentAction.Indent } @@ -30,13 +25,10 @@ export function getLanguageConfiguration() { action: { indentAction: IndentAction.None, appendText: '# ' } }, { - beforeText: OUTDENT_ONENTER_REGEX, + beforeText: /^\s+(continue|break|return)\b.*/, + afterText: /\s+$/, action: { indentAction: IndentAction.Outdent } } - ], - indentationRules: { - increaseIndentPattern: INCREASE_INDENT_REGEX, - decreaseIndentPattern: DECREASE_INDENT_REGEX - } - }; + ] + }); } diff --git a/src/test/language/languageConfiguration.unit.test.ts b/src/test/language/languageConfiguration.unit.test.ts index 37433879daa8..1356995cfdf4 100644 --- a/src/test/language/languageConfiguration.unit.test.ts +++ b/src/test/language/languageConfiguration.unit.test.ts @@ -5,84 +5,19 @@ import { expect } from 'chai'; -import { DECREASE_INDENT_REGEX, INCREASE_INDENT_REGEX, MULTILINE_SEPARATOR_INDENT_REGEX, OUTDENT_ONENTER_REGEX } from '../../client/language/languageConfiguration'; +import { MULTILINE_SEPARATOR_INDENT_REGEX } from '../../client/language/languageConfiguration'; suite('Language configuration regexes', () => { test('Multiline separator indent regex should not pick up strings with no multiline separator', async () => { const result = MULTILINE_SEPARATOR_INDENT_REGEX.test('a = "test"'); - expect(result).to.be.equal(false, 'Multiline separator indent regex for regular strings should not have matches'); + expect (result).to.be.equal(false, 'Multiline separator indent regex for regular strings should not have matches'); }); - test('Multiline separator indent regex should not pick up strings with escaped characters', async () => { const result = MULTILINE_SEPARATOR_INDENT_REGEX.test('a = \'hello \\n\''); - expect(result).to.be.equal(false, 'Multiline separator indent regex for strings with escaped characters should not have matches'); + expect (result).to.be.equal(false, 'Multiline separator indent regex for strings with escaped characters should not have matches'); }); - test('Multiline separator indent regex should pick up strings ending with a multiline separator', async () => { const result = MULTILINE_SEPARATOR_INDENT_REGEX.test('a = \'multiline \\'); - expect(result).to.be.equal(true, 'Multiline separator indent regex for strings with newline separator should have matches'); - }); - - [ - 'async def test(self):', - 'class TestClass:', - 'def foo(self, node, namespace=""):', - 'for item in items:', - 'if foo is None:', - 'try:', - 'while \'::\' in macaddress:', - 'with self.test:' - ].forEach(example => { - const keyword = example.split(' ')[0]; - - test(`Increase indent regex should pick up lines containing the ${keyword} keyword`, async () => { - const result = INCREASE_INDENT_REGEX.test(example); - expect(result).to.be.equal(true, `Increase indent regex should pick up lines containing the ${keyword} keyword`); - }); - - test(`Decrease indent regex should not pick up lines containing the ${keyword} keyword`, async () => { - const result = DECREASE_INDENT_REGEX.test(example); - expect(result).to.be.equal(false, `Decrease indent regex should not pick up lines containing the ${keyword} keyword`); - }); - }); - - ['elif x < 5:', 'else:', 'except TestError:', 'finally:'].forEach(example => { - const keyword = example.split(' ')[0]; - - test(`Increase indent regex should pick up lines containing the ${keyword} keyword`, async () => { - const result = INCREASE_INDENT_REGEX.test(example); - expect(result).to.be.equal(true, `Increase indent regex should pick up lines containing the ${keyword} keyword`); - }); - - test(`Decrease indent regex should pick up lines containing the ${keyword} keyword`, async () => { - const result = DECREASE_INDENT_REGEX.test(example); - expect(result).to.be.equal(true, `Decrease indent regex should pick up lines containing the ${keyword} keyword`); - }); - }); - - test('Increase indent regex should not pick up lines without keywords', async () => { - const result = INCREASE_INDENT_REGEX.test('a = \'hello \\n \''); - expect(result).to.be.equal(false, 'Increase indent regex should not pick up lines without keywords'); - }); - - test('Decrease indent regex should not pick up lines without keywords', async () => { - const result = DECREASE_INDENT_REGEX.test('a = \'hello \\n \''); - expect(result).to.be.equal(false, 'Decrease indent regex should not pick up lines without keywords'); - }); - - [' break', '\t\t continue', ' pass', 'raise Exception(\'Unknown Exception\'', ' return [ True, False, False ]'].forEach(example => { - const keyword = example.trim().split(' ')[0]; - - const testWithoutComments = `Outdent regex for on enter rule should pick up lines containing the ${keyword} keyword`; - test(testWithoutComments, () => { - const result = OUTDENT_ONENTER_REGEX.test(example); - expect(result).to.be.equal(true, testWithoutComments); - }); - - const testWithComments = `Outdent regex on enter should pick up lines containing the ${keyword} keyword and ending with comments`; - test(testWithComments, () => { - const result = OUTDENT_ONENTER_REGEX.test(`${example} # test comment`); - expect(result).to.be.equal(true, testWithComments); - }); + expect (result).to.be.equal(true, 'Multiline separator indent regex for strings with newline separator should have matches'); }); }); From 707b5e7201a171a8f52d31e7e7057e23913dc6fe Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 12 Aug 2019 16:49:19 -0600 Subject: [PATCH 4/8] Fix failing functional extension tests in CI related to pytest discovery. (#6944) (for #6940) This is essentially a follow-up to #6877 (for #6758). --- news/2 Fixes/6940.md | 1 + .../testFiles/pytestFiles/results/five.output | 26 +++-- .../testFiles/pytestFiles/results/four.output | 26 +++-- .../testFiles/pytestFiles/results/one.output | 26 +++-- .../pytestFiles/results/three.output | 26 +++-- .../testFiles/pytestFiles/results/two.output | 26 +++-- .../testing/pytest/pytest.discovery.test.ts | 101 ++++++++++++++++-- 7 files changed, 176 insertions(+), 56 deletions(-) create mode 100644 news/2 Fixes/6940.md diff --git a/news/2 Fixes/6940.md b/news/2 Fixes/6940.md new file mode 100644 index 000000000000..be1445749588 --- /dev/null +++ b/news/2 Fixes/6940.md @@ -0,0 +1 @@ +Fix failing functional tests (for pytest) in the extension. diff --git a/src/test/pythonFiles/testFiles/pytestFiles/results/five.output b/src/test/pythonFiles/testFiles/pytestFiles/results/five.output index 125a1d107372..c7b9d058f784 100644 --- a/src/test/pythonFiles/testFiles/pytestFiles/results/five.output +++ b/src/test/pythonFiles/testFiles/pytestFiles/results/five.output @@ -7,7 +7,8 @@ "id": "./test_root.py", "kind": "file", "name": "test_root.py", - "parentid": "." + "parentid": ".", + "relpath": "./test_root.py" }, { "id": "./test_root.py::Test_Root_test1", @@ -19,13 +20,15 @@ "id": "./tests", "kind": "folder", "name": "tests", - "parentid": "." + "parentid": ".", + "relpath": "./tests" }, { "id": "./tests/test_another_pytest.py", "kind": "file", "name": "test_another_pytest.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/test_another_pytest.py" }, { "id": "./tests/test_another_pytest.py::test_parametrized_username", @@ -37,7 +40,8 @@ "id": "./tests/test_foreign_nested_tests.py", "kind": "file", "name": "test_foreign_nested_tests.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/test_foreign_nested_tests.py" }, { "id": "./tests/test_foreign_nested_tests.py::TestNestedForeignTests", @@ -61,7 +65,8 @@ "id": "./tests/test_pytest.py", "kind": "file", "name": "test_pytest.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/test_pytest.py" }, { "id": "./tests/test_pytest.py::Test_CheckMyApp", @@ -91,7 +96,8 @@ "id": "./tests/test_unittest_one.py", "kind": "file", "name": "test_unittest_one.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/test_unittest_one.py" }, { "id": "./tests/test_unittest_one.py::Test_test1", @@ -103,7 +109,8 @@ "id": "./tests/test_unittest_two.py", "kind": "file", "name": "test_unittest_two.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/test_unittest_two.py" }, { "id": "./tests/test_unittest_two.py::Test_test2", @@ -121,7 +128,8 @@ "id": "./tests/unittest_three_test.py", "kind": "file", "name": "unittest_three_test.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/unittest_three_test.py" }, { "id": "./tests/unittest_three_test.py::Test_test3", @@ -364,4 +372,4 @@ } ] } -] +] \ No newline at end of file diff --git a/src/test/pythonFiles/testFiles/pytestFiles/results/four.output b/src/test/pythonFiles/testFiles/pytestFiles/results/four.output index 125a1d107372..c7b9d058f784 100644 --- a/src/test/pythonFiles/testFiles/pytestFiles/results/four.output +++ b/src/test/pythonFiles/testFiles/pytestFiles/results/four.output @@ -7,7 +7,8 @@ "id": "./test_root.py", "kind": "file", "name": "test_root.py", - "parentid": "." + "parentid": ".", + "relpath": "./test_root.py" }, { "id": "./test_root.py::Test_Root_test1", @@ -19,13 +20,15 @@ "id": "./tests", "kind": "folder", "name": "tests", - "parentid": "." + "parentid": ".", + "relpath": "./tests" }, { "id": "./tests/test_another_pytest.py", "kind": "file", "name": "test_another_pytest.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/test_another_pytest.py" }, { "id": "./tests/test_another_pytest.py::test_parametrized_username", @@ -37,7 +40,8 @@ "id": "./tests/test_foreign_nested_tests.py", "kind": "file", "name": "test_foreign_nested_tests.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/test_foreign_nested_tests.py" }, { "id": "./tests/test_foreign_nested_tests.py::TestNestedForeignTests", @@ -61,7 +65,8 @@ "id": "./tests/test_pytest.py", "kind": "file", "name": "test_pytest.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/test_pytest.py" }, { "id": "./tests/test_pytest.py::Test_CheckMyApp", @@ -91,7 +96,8 @@ "id": "./tests/test_unittest_one.py", "kind": "file", "name": "test_unittest_one.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/test_unittest_one.py" }, { "id": "./tests/test_unittest_one.py::Test_test1", @@ -103,7 +109,8 @@ "id": "./tests/test_unittest_two.py", "kind": "file", "name": "test_unittest_two.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/test_unittest_two.py" }, { "id": "./tests/test_unittest_two.py::Test_test2", @@ -121,7 +128,8 @@ "id": "./tests/unittest_three_test.py", "kind": "file", "name": "unittest_three_test.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/unittest_three_test.py" }, { "id": "./tests/unittest_three_test.py::Test_test3", @@ -364,4 +372,4 @@ } ] } -] +] \ No newline at end of file diff --git a/src/test/pythonFiles/testFiles/pytestFiles/results/one.output b/src/test/pythonFiles/testFiles/pytestFiles/results/one.output index 125a1d107372..c7b9d058f784 100644 --- a/src/test/pythonFiles/testFiles/pytestFiles/results/one.output +++ b/src/test/pythonFiles/testFiles/pytestFiles/results/one.output @@ -7,7 +7,8 @@ "id": "./test_root.py", "kind": "file", "name": "test_root.py", - "parentid": "." + "parentid": ".", + "relpath": "./test_root.py" }, { "id": "./test_root.py::Test_Root_test1", @@ -19,13 +20,15 @@ "id": "./tests", "kind": "folder", "name": "tests", - "parentid": "." + "parentid": ".", + "relpath": "./tests" }, { "id": "./tests/test_another_pytest.py", "kind": "file", "name": "test_another_pytest.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/test_another_pytest.py" }, { "id": "./tests/test_another_pytest.py::test_parametrized_username", @@ -37,7 +40,8 @@ "id": "./tests/test_foreign_nested_tests.py", "kind": "file", "name": "test_foreign_nested_tests.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/test_foreign_nested_tests.py" }, { "id": "./tests/test_foreign_nested_tests.py::TestNestedForeignTests", @@ -61,7 +65,8 @@ "id": "./tests/test_pytest.py", "kind": "file", "name": "test_pytest.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/test_pytest.py" }, { "id": "./tests/test_pytest.py::Test_CheckMyApp", @@ -91,7 +96,8 @@ "id": "./tests/test_unittest_one.py", "kind": "file", "name": "test_unittest_one.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/test_unittest_one.py" }, { "id": "./tests/test_unittest_one.py::Test_test1", @@ -103,7 +109,8 @@ "id": "./tests/test_unittest_two.py", "kind": "file", "name": "test_unittest_two.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/test_unittest_two.py" }, { "id": "./tests/test_unittest_two.py::Test_test2", @@ -121,7 +128,8 @@ "id": "./tests/unittest_three_test.py", "kind": "file", "name": "unittest_three_test.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/unittest_three_test.py" }, { "id": "./tests/unittest_three_test.py::Test_test3", @@ -364,4 +372,4 @@ } ] } -] +] \ No newline at end of file diff --git a/src/test/pythonFiles/testFiles/pytestFiles/results/three.output b/src/test/pythonFiles/testFiles/pytestFiles/results/three.output index 125a1d107372..c7b9d058f784 100644 --- a/src/test/pythonFiles/testFiles/pytestFiles/results/three.output +++ b/src/test/pythonFiles/testFiles/pytestFiles/results/three.output @@ -7,7 +7,8 @@ "id": "./test_root.py", "kind": "file", "name": "test_root.py", - "parentid": "." + "parentid": ".", + "relpath": "./test_root.py" }, { "id": "./test_root.py::Test_Root_test1", @@ -19,13 +20,15 @@ "id": "./tests", "kind": "folder", "name": "tests", - "parentid": "." + "parentid": ".", + "relpath": "./tests" }, { "id": "./tests/test_another_pytest.py", "kind": "file", "name": "test_another_pytest.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/test_another_pytest.py" }, { "id": "./tests/test_another_pytest.py::test_parametrized_username", @@ -37,7 +40,8 @@ "id": "./tests/test_foreign_nested_tests.py", "kind": "file", "name": "test_foreign_nested_tests.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/test_foreign_nested_tests.py" }, { "id": "./tests/test_foreign_nested_tests.py::TestNestedForeignTests", @@ -61,7 +65,8 @@ "id": "./tests/test_pytest.py", "kind": "file", "name": "test_pytest.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/test_pytest.py" }, { "id": "./tests/test_pytest.py::Test_CheckMyApp", @@ -91,7 +96,8 @@ "id": "./tests/test_unittest_one.py", "kind": "file", "name": "test_unittest_one.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/test_unittest_one.py" }, { "id": "./tests/test_unittest_one.py::Test_test1", @@ -103,7 +109,8 @@ "id": "./tests/test_unittest_two.py", "kind": "file", "name": "test_unittest_two.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/test_unittest_two.py" }, { "id": "./tests/test_unittest_two.py::Test_test2", @@ -121,7 +128,8 @@ "id": "./tests/unittest_three_test.py", "kind": "file", "name": "unittest_three_test.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/unittest_three_test.py" }, { "id": "./tests/unittest_three_test.py::Test_test3", @@ -364,4 +372,4 @@ } ] } -] +] \ No newline at end of file diff --git a/src/test/pythonFiles/testFiles/pytestFiles/results/two.output b/src/test/pythonFiles/testFiles/pytestFiles/results/two.output index 125a1d107372..c7b9d058f784 100644 --- a/src/test/pythonFiles/testFiles/pytestFiles/results/two.output +++ b/src/test/pythonFiles/testFiles/pytestFiles/results/two.output @@ -7,7 +7,8 @@ "id": "./test_root.py", "kind": "file", "name": "test_root.py", - "parentid": "." + "parentid": ".", + "relpath": "./test_root.py" }, { "id": "./test_root.py::Test_Root_test1", @@ -19,13 +20,15 @@ "id": "./tests", "kind": "folder", "name": "tests", - "parentid": "." + "parentid": ".", + "relpath": "./tests" }, { "id": "./tests/test_another_pytest.py", "kind": "file", "name": "test_another_pytest.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/test_another_pytest.py" }, { "id": "./tests/test_another_pytest.py::test_parametrized_username", @@ -37,7 +40,8 @@ "id": "./tests/test_foreign_nested_tests.py", "kind": "file", "name": "test_foreign_nested_tests.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/test_foreign_nested_tests.py" }, { "id": "./tests/test_foreign_nested_tests.py::TestNestedForeignTests", @@ -61,7 +65,8 @@ "id": "./tests/test_pytest.py", "kind": "file", "name": "test_pytest.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/test_pytest.py" }, { "id": "./tests/test_pytest.py::Test_CheckMyApp", @@ -91,7 +96,8 @@ "id": "./tests/test_unittest_one.py", "kind": "file", "name": "test_unittest_one.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/test_unittest_one.py" }, { "id": "./tests/test_unittest_one.py::Test_test1", @@ -103,7 +109,8 @@ "id": "./tests/test_unittest_two.py", "kind": "file", "name": "test_unittest_two.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/test_unittest_two.py" }, { "id": "./tests/test_unittest_two.py::Test_test2", @@ -121,7 +128,8 @@ "id": "./tests/unittest_three_test.py", "kind": "file", "name": "unittest_three_test.py", - "parentid": "./tests" + "parentid": "./tests", + "relpath": "./tests/unittest_three_test.py" }, { "id": "./tests/unittest_three_test.py::Test_test3", @@ -364,4 +372,4 @@ } ] } -] +] \ No newline at end of file diff --git a/src/test/testing/pytest/pytest.discovery.test.ts b/src/test/testing/pytest/pytest.discovery.test.ts index 3f9e3dc021ad..b2539580879e 100644 --- a/src/test/testing/pytest/pytest.discovery.test.ts +++ b/src/test/testing/pytest/pytest.discovery.test.ts @@ -95,19 +95,83 @@ suite('Unit Tests - pytest - discovery with mocked process output', () => { rootid: '.', root: '/Users/donjayamanne/.vscode-insiders/extensions/pythonVSCode/src/test/pythonFiles/testFiles/single', parents: [ - { id: './test_root.py', kind: 'file', name: 'test_root.py', parentid: '.' }, - { id: './test_root.py::Test_Root_test1', kind: 'suite', name: 'Test_Root_test1', parentid: './test_root.py' }, - { id: './tests', kind: 'folder', name: 'tests', parentid: '.' }, - { id: './tests/test_one.py', kind: 'file', name: 'test_one.py', parentid: './tests' }, - { id: './tests/test_one.py::Test_test1', kind: 'suite', name: 'Test_test1', parentid: './tests/test_one.py' } + { + id: './test_root.py', + kind: 'file', + name: 'test_root.py', + relpath: './test_root.py', + parentid: '.' + }, + { + id: './test_root.py::Test_Root_test1', + kind: 'suite', + name: 'Test_Root_test1', + parentid: './test_root.py' + }, + { + id: './tests', + kind: 'folder', + name: 'tests', + relpath: './tests', + parentid: '.' + }, + { + id: './tests/test_one.py', + kind: 'file', + name: 'test_one.py', + relpath: './tests/test_one.py', + parentid: './tests' + }, + { + id: './tests/test_one.py::Test_test1', + kind: 'suite', + name: 'Test_test1', + parentid: './tests/test_one.py' + } ], tests: [ - { id: './test_root.py::Test_Root_test1::test_Root_A', name: 'test_Root_A', source: './test_root.py:6', markers: [], parentid: './test_root.py::Test_Root_test1' }, - { id: './test_root.py::Test_Root_test1::test_Root_B', name: 'test_Root_B', source: './test_root.py:9', markers: [], parentid: './test_root.py::Test_Root_test1' }, - { id: './test_root.py::Test_Root_test1::test_Root_c', name: 'test_Root_c', source: './test_root.py:12', markers: [], parentid: './test_root.py::Test_Root_test1' }, - { id: './tests/test_one.py::Test_test1::test_A', name: 'test_A', source: 'tests/test_one.py:6', markers: [], parentid: './tests/test_one.py::Test_test1' }, - { id: './tests/test_one.py::Test_test1::test_B', name: 'test_B', source: 'tests/test_one.py:9', markers: [], parentid: './tests/test_one.py::Test_test1' }, - { id: './tests/test_one.py::Test_test1::test_c', name: 'test_c', source: 'tests/test_one.py:12', markers: [], parentid: './tests/test_one.py::Test_test1' } + { + id: './test_root.py::Test_Root_test1::test_Root_A', + name: 'test_Root_A', + source: './test_root.py:6', + markers: [], + parentid: './test_root.py::Test_Root_test1' + }, + { + id: './test_root.py::Test_Root_test1::test_Root_B', + name: 'test_Root_B', + source: './test_root.py:9', + markers: [], + parentid: './test_root.py::Test_Root_test1' + }, + { + id: './test_root.py::Test_Root_test1::test_Root_c', + name: 'test_Root_c', + source: './test_root.py:12', + markers: [], + parentid: './test_root.py::Test_Root_test1' + }, + { + id: './tests/test_one.py::Test_test1::test_A', + name: 'test_A', + source: 'tests/test_one.py:6', + markers: [], + parentid: './tests/test_one.py::Test_test1' + }, + { + id: './tests/test_one.py::Test_test1::test_B', + name: 'test_B', + source: 'tests/test_one.py:9', + markers: [], + parentid: './tests/test_one.py::Test_test1' + }, + { + id: './tests/test_one.py::Test_test1::test_c', + name: 'test_c', + source: 'tests/test_one.py:12', + markers: [], + parentid: './tests/test_one.py::Test_test1' + } ] }])); const factory = ioc.serviceContainer.get(ITestManagerFactory); @@ -132,6 +196,7 @@ suite('Unit Tests - pytest - discovery with mocked process output', () => { parents: [ { id: './test_root.py', + relpath: './test_root.py', kind: 'file', name: 'test_root.py', parentid: '.' @@ -144,12 +209,14 @@ suite('Unit Tests - pytest - discovery with mocked process output', () => { }, { id: './tests', + relpath: './tests', kind: 'folder', name: 'tests', parentid: '.' }, { id: './tests/test_another_pytest.py', + relpath: './tests/test_another_pytest.py', kind: 'file', name: 'test_another_pytest.py', parentid: './tests' @@ -162,6 +229,7 @@ suite('Unit Tests - pytest - discovery with mocked process output', () => { }, { id: './tests/test_foreign_nested_tests.py', + relpath: './tests/test_foreign_nested_tests.py', kind: 'file', name: 'test_foreign_nested_tests.py', parentid: './tests' @@ -186,6 +254,7 @@ suite('Unit Tests - pytest - discovery with mocked process output', () => { }, { id: './tests/test_pytest.py', + relpath: './tests/test_pytest.py', kind: 'file', name: 'test_pytest.py', parentid: './tests' @@ -216,6 +285,7 @@ suite('Unit Tests - pytest - discovery with mocked process output', () => { }, { id: './tests/test_unittest_one.py', + relpath: './tests/test_unittest_one.py', kind: 'file', name: 'test_unittest_one.py', parentid: './tests' @@ -228,6 +298,7 @@ suite('Unit Tests - pytest - discovery with mocked process output', () => { }, { id: './tests/test_unittest_two.py', + relpath: './tests/test_unittest_two.py', kind: 'file', name: 'test_unittest_two.py', parentid: './tests' @@ -246,6 +317,7 @@ suite('Unit Tests - pytest - discovery with mocked process output', () => { }, { id: './tests/unittest_three_test.py', + relpath: './tests/unittest_three_test.py', kind: 'file', name: 'unittest_three_test.py', parentid: './tests' @@ -523,12 +595,14 @@ suite('Unit Tests - pytest - discovery with mocked process output', () => { id: './tests', kind: 'folder', name: 'tests', + relpath: './tests', parentid: '.' }, { id: './tests/unittest_three_test.py', kind: 'file', name: 'unittest_three_test.py', + relpath: './tests/unittest_three_test.py', parentid: './tests' }, { @@ -580,12 +654,14 @@ suite('Unit Tests - pytest - discovery with mocked process output', () => { parents: [ { id: './other', + relpath: './other', kind: 'folder', name: 'other', parentid: '.' }, { id: './other/test_pytest.py', + relpath: './other/test_pytest.py', kind: 'file', name: 'test_pytest.py', parentid: './other' @@ -616,6 +692,7 @@ suite('Unit Tests - pytest - discovery with mocked process output', () => { }, { id: './other/test_unittest_one.py', + relpath: './other/test_unittest_one.py', kind: 'file', name: 'test_unittest_one.py', parentid: './other' @@ -756,12 +833,14 @@ suite('Unit Tests - pytest - discovery with mocked process output', () => { id: './tests', kind: 'folder', name: 'tests', + relpath: './tests', parentid: '.' }, { id: './tests/test_cwd.py', kind: 'file', name: 'test_cwd.py', + relpath: './tests/test_cwd.py', parentid: './tests' }, { From 10ab7e7197bde155b3eaa146d98ad1a73a2e1a43 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 13 Aug 2019 12:15:11 -0700 Subject: [PATCH 5/8] Throw error when unable to compute file hash (#6954) --- src/client/common/platform/fileSystem.ts | 6 +++--- src/client/common/platform/types.ts | 2 +- src/test/common/platform/filesystem.unit.test.ts | 11 +++++++++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/client/common/platform/fileSystem.ts b/src/client/common/platform/fileSystem.ts index 8e31446c4774..dede671d3fc6 100644 --- a/src/client/common/platform/fileSystem.ts +++ b/src/client/common/platform/fileSystem.ts @@ -146,11 +146,11 @@ export class FileSystem implements IFileSystem { return deferred.promise; } - public getFileHash(filePath: string): Promise { - return new Promise(resolve => { + public getFileHash(filePath: string): Promise { + return new Promise((resolve, reject) => { fs.lstat(filePath, (err, stats) => { if (err) { - resolve(); + reject(err); } else { const actual = createHash('sha512').update(`${stats.ctimeMs}-${stats.mtimeMs}`).digest('hex'); resolve(actual); diff --git a/src/client/common/platform/types.ts b/src/client/common/platform/types.ts index d14a5001cef1..aa0baa2e2aa5 100644 --- a/src/client/common/platform/types.ts +++ b/src/client/common/platform/types.ts @@ -56,7 +56,7 @@ export interface IFileSystem { getRealPath(path: string): Promise; copyFile(src: string, dest: string): Promise; deleteFile(filename: string): Promise; - getFileHash(filePath: string): Promise; + getFileHash(filePath: string): Promise; search(globPattern: string): Promise; createTemporaryFile(extension: string): Promise; createWriteStream(path: string): fs.WriteStream; diff --git a/src/test/common/platform/filesystem.unit.test.ts b/src/test/common/platform/filesystem.unit.test.ts index 2e65335b057f..2100e0810d9a 100644 --- a/src/test/common/platform/filesystem.unit.test.ts +++ b/src/test/common/platform/filesystem.unit.test.ts @@ -9,6 +9,7 @@ import { FileSystem } from '../../../client/common/platform/fileSystem'; import { IFileSystem, IPlatformService, TemporaryFile } from '../../../client/common/platform/types'; // tslint:disable-next-line:no-require-imports no-var-requires const assertArrays = require('chai-arrays'); +use(require('chai-as-promised')); use(assertArrays); // tslint:disable-next-line:max-func-body-length @@ -116,4 +117,14 @@ suite('FileSystem', () => { }); }); }); + test('Getting hash for non existent file should throw error', async () => { + const promise = fileSystem.getFileHash('some unknown file'); + + await expect(promise).to.eventually.be.rejected; + }); + test('Getting hash for a file should return non-empty string', async () => { + const hash = await fileSystem.getFileHash(__filename); + + expect(hash).to.be.length.greaterThan(0); + }); }); From 11dd074dacff980569224f29ecd0d66d0d2a0e3b Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 13 Aug 2019 14:05:34 -0600 Subject: [PATCH 6/8] Drop dedent-on-enter for "return" statements. (#6939) (for #6813) We'll work on adding support back in #6564. --- news/2 Fixes/6813.md | 2 + src/client/common/utils/regexp.ts | 22 ++ src/client/extension.ts | 6 +- src/client/language/languageConfiguration.ts | 112 ++++++-- src/test/common/utils/regexp.unit.test.ts | 75 ++++++ .../languageConfiguration.unit.test.ts | 254 +++++++++++++++++- src/test/mocks/vsc/index.ts | 6 + src/test/vscode-mock.ts | 1 + 8 files changed, 447 insertions(+), 31 deletions(-) create mode 100644 news/2 Fixes/6813.md create mode 100644 src/client/common/utils/regexp.ts create mode 100644 src/test/common/utils/regexp.unit.test.ts diff --git a/news/2 Fixes/6813.md b/news/2 Fixes/6813.md new file mode 100644 index 000000000000..4c813edd73f6 --- /dev/null +++ b/news/2 Fixes/6813.md @@ -0,0 +1,2 @@ +Drop dedent-on-enter for "return" statements. It will be addressed in: + https://github.com/microsoft/vscode-python/issues/6564 diff --git a/src/client/common/utils/regexp.ts b/src/client/common/utils/regexp.ts new file mode 100644 index 000000000000..2d20b73e7f28 --- /dev/null +++ b/src/client/common/utils/regexp.ts @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +/* Generate a RegExp from a "verbose" pattern. + * + * All whitespace in the pattern is removed, including newlines. This + * allows the pattern to be much more readable by allowing it to span + * multiple lines and to separate tokens with insignificant whitespace. + * The functionality is similar to the VERBOSE ("x") flag in Python's + * regular expressions. + * + * Note that significant whitespace in the pattern must be explicitly + * indicated by "\s". Also, unlike with regular expression literals, + * backslashes must be escaped. Conversely, forward slashes do not + * need to be escaped. + */ +export function verboseRegExp(pattern: string): RegExp { + pattern = pattern.replace(/\s+?/g, ''); + return RegExp(pattern); +} diff --git a/src/client/extension.ts b/src/client/extension.ts index db56a5b24c66..67a6f9dd9507 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -40,7 +40,7 @@ import { registerTypes as appRegisterTypes } from './application/serviceRegistry import { IApplicationDiagnostics } from './application/types'; import { DebugService } from './common/application/debugService'; import { IApplicationShell, ICommandManager, IWorkspaceService } from './common/application/types'; -import { Commands, isTestExecution, PYTHON, STANDARD_OUTPUT_CHANNEL } from './common/constants'; +import { Commands, isTestExecution, PYTHON, PYTHON_LANGUAGE, STANDARD_OUTPUT_CHANNEL } from './common/constants'; import { registerTypes as registerDotNetTypes } from './common/dotnet/serviceRegistry'; import { registerTypes as installerRegisterTypes } from './common/installer/serviceRegistry'; import { traceError } from './common/logger'; @@ -85,7 +85,7 @@ import { registerTypes as interpretersRegisterTypes } from './interpreter/servic import { ServiceContainer } from './ioc/container'; import { ServiceManager } from './ioc/serviceManager'; import { IServiceContainer, IServiceManager } from './ioc/types'; -import { setLanguageConfiguration } from './language/languageConfiguration'; +import { getLanguageConfiguration } from './language/languageConfiguration'; import { LinterCommands } from './linters/linterCommands'; import { registerTypes as lintersRegisterTypes } from './linters/serviceRegistry'; import { ILintingEngine } from './linters/types'; @@ -176,7 +176,7 @@ async function activateUnsafe(context: ExtensionContext): Promise const linterProvider = new LinterProvider(context, serviceManager); context.subscriptions.push(linterProvider); - setLanguageConfiguration(); + languages.setLanguageConfiguration(PYTHON_LANGUAGE, getLanguageConfiguration()); if (pythonSettings && pythonSettings.formatting && pythonSettings.formatting.provider !== 'internalConsole') { const formatProvider = new PythonFormattingEditProvider(context, serviceContainer); diff --git a/src/client/language/languageConfiguration.ts b/src/client/language/languageConfiguration.ts index 19eebd8d0131..409371555210 100644 --- a/src/client/language/languageConfiguration.ts +++ b/src/client/language/languageConfiguration.ts @@ -2,33 +2,111 @@ // Licensed under the MIT License. 'use strict'; -import { IndentAction, languages } from 'vscode'; -import { PYTHON_LANGUAGE } from '../common/constants'; +import { IndentAction, LanguageConfiguration } from 'vscode'; +import { verboseRegExp } from '../common/utils/regexp'; -export const MULTILINE_SEPARATOR_INDENT_REGEX = /^(?!\s+\\)[^#\n]+\\$/; +// tslint:disable:no-multiline-string -export function setLanguageConfiguration() { - // Enable indentAction - languages.setLanguageConfiguration(PYTHON_LANGUAGE, { +// tslint:disable-next-line:max-func-body-length +export function getLanguageConfiguration(): LanguageConfiguration { + return { onEnterRules: [ + // multi-line separator { - beforeText: /^\s*(?:def|class|for|if|elif|else|while|try|with|finally|except|async)\b.*:\s*/, - action: { indentAction: IndentAction.Indent } - }, - { - beforeText: MULTILINE_SEPARATOR_INDENT_REGEX, - action: { indentAction: IndentAction.Indent } + beforeText: verboseRegExp(` + ^ + (?! \\s+ \\\\ ) + [^#\n]+ + \\\\ + $ + `), + action: { + indentAction: IndentAction.Indent + } }, + // continue comments { beforeText: /^\s*#.*/, afterText: /.+$/, - action: { indentAction: IndentAction.None, appendText: '# ' } + action: { + indentAction: IndentAction.None, + appendText: '# ' + } + }, + // indent on enter (block-beginning statements) + { + /** + * This does not handle all cases. However, it does handle nearly all usage. + * Here's what it does not cover: + * - the statement is split over multiple lines (and hence the ":" is on a different line) + * - the code block is inlined (after the ":") + * - there are multiple statements on the line (separated by semicolons) + * Also note that `lambda` is purposefully excluded. + */ + beforeText: verboseRegExp(` + ^ + \\s* + (?: + (?: + (?: + class | + def | + async \\s+ def | + except | + for | + if | + elif | + while | + with + ) + \\b .* + ) | + else | + try | + finally + ) + \\s* + [:] + \\s* + (?: [#] .* )? + $ + `), + action: { + indentAction: IndentAction.Indent + } }, + // outdent on enter (block-ending statements) { - beforeText: /^\s+(continue|break|return)\b.*/, - afterText: /\s+$/, - action: { indentAction: IndentAction.Outdent } + beforeText: verboseRegExp(` + ^ + (?: + (?: + \\s* + (?: + pass | + raise \\s+ [^#\\s] [^#]* + ) + ) | + (?: + \\s+ + (?: + raise | + break | + continue + ) + ) + ) + \\s* + (?: [#] .* )? + $ + `), + action: { + indentAction: IndentAction.Outdent + } } + // Note that we do not currently have an auto-dedent + // solution for "elif", "else", "except", and "finally". + // We had one but had to remove it (see issue #6886). ] - }); + }; } diff --git a/src/test/common/utils/regexp.unit.test.ts b/src/test/common/utils/regexp.unit.test.ts new file mode 100644 index 000000000000..9e8c8049bec6 --- /dev/null +++ b/src/test/common/utils/regexp.unit.test.ts @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:no-multiline-string + +import { expect } from 'chai'; + +import { + verboseRegExp +} from '../../../client/common/utils/regexp'; + +suite('Utils for regular expressions - verboseRegExp()', () => { + test('whitespace removed in multiline pattern (example of typical usage)', () => { + const regex = verboseRegExp(` + ^ + (?: + spam \\b .* + ) | + (?: + eggs \\b .* + ) + $ + `); + + expect(regex.source).to.equal('^(?:spam\\b.*)|(?:eggs\\b.*)$', 'mismatch'); + }); + + const whitespaceTests = [ + ['spam eggs', 'spameggs'], + [`spam + eggs`, 'spameggs'], + // empty + [' ', '(?:)'], + [` + `, '(?:)'] + ]; + for (const [pat, expected] of whitespaceTests) { + test(`whitespace removed ("${pat}")`, () => { + const regex = verboseRegExp(pat); + + expect(regex.source).to.equal(expected, 'mismatch'); + }); + } + + const noopPatterns = [ + '^(?:spam\\b.*)$', + 'spam', + '^spam$', + 'spam$', + '^spam' + ]; + for (const pat of noopPatterns) { + test(`pattern not changed ("${pat}")`, () => { + const regex = verboseRegExp(pat); + + expect(regex.source).to.equal(pat, 'mismatch'); + }); + } + + const emptyPatterns = [ + '', + ` + `, + ' ' + ]; + for (const pat of emptyPatterns) { + test(`no pattern ("${pat}")`, () => { + const regex = verboseRegExp(pat); + + expect(regex.source).to.equal('(?:)', 'mismatch'); + }); + } +}); diff --git a/src/test/language/languageConfiguration.unit.test.ts b/src/test/language/languageConfiguration.unit.test.ts index 1356995cfdf4..00ec4cd9a353 100644 --- a/src/test/language/languageConfiguration.unit.test.ts +++ b/src/test/language/languageConfiguration.unit.test.ts @@ -3,21 +3,253 @@ 'use strict'; +// tslint:disable:max-func-body-length + import { expect } from 'chai'; -import { MULTILINE_SEPARATOR_INDENT_REGEX } from '../../client/language/languageConfiguration'; +import { + getLanguageConfiguration +} from '../../client/language/languageConfiguration'; + +const NEEDS_INDENT = [ + /^break$/, + /^continue$/, + /^raise$/, // only re-raise + /^return\b/ +]; +const INDENT_ON_ENTER = [ // block-beginning statements + /^async\s+def\b/, + /^class\b/, + /^def\b/, + /^with\b/, + /^try\b/, + /^except\b/, + /^finally\b/, + /^while\b/, + /^for\b/, + /^if\b/, + /^elif\b/, + /^else\b/ +]; +const DEDENT_ON_ENTER = [ // block-ending statements + // For now we are ignoring "return" completely. See gh-6564. + ///^return\b/, + /^break$/, + /^continue$/, + /^raise\b/, + /^pass\b/ +]; + +function isMember(line: string, regexes: RegExp[]): boolean { + for (const regex of regexes) { + if (regex.test(line)) { + return true; + } + } + return false; +} + +function resolveExample( + base: string, + leading: string, + postKeyword: string, + preColon: string, + trailing: string +): [string | undefined, string | undefined, boolean] { + let invalid: string | undefined; + if (base.trim() === '') { + invalid = 'blank line'; + } else if (leading === '' && isMember(base, NEEDS_INDENT)) { + invalid = 'expected indent'; + } else if (leading.trim() !== '') { + invalid = 'look-alike - pre-keyword'; + } else if (postKeyword.trim() !== '') { + invalid = 'look-alike - post-keyword'; + } -suite('Language configuration regexes', () => { - test('Multiline separator indent regex should not pick up strings with no multiline separator', async () => { - const result = MULTILINE_SEPARATOR_INDENT_REGEX.test('a = "test"'); - expect (result).to.be.equal(false, 'Multiline separator indent regex for regular strings should not have matches'); + let resolvedBase = base; + if (postKeyword !== '') { + if (resolvedBase.includes(' ')) { + const kw = resolvedBase.split(' ', 1)[0]; + const remainder = resolvedBase.substring(kw.length); + resolvedBase = `${kw}${postKeyword} ${remainder}`; + } else { + if (resolvedBase.endsWith(':')) { + resolvedBase = `${resolvedBase.substring(0, resolvedBase.length - 1)}${postKeyword}:`; + } else { + resolvedBase = `${resolvedBase}${postKeyword}`; + } + } + } + if (preColon !== '') { + if (resolvedBase.endsWith(':')) { + resolvedBase = `${resolvedBase.substring(0, resolvedBase.length - 1)}${preColon}:`; + } else { + return [undefined, undefined, true]; + } + } + const example = `${leading}${resolvedBase}${trailing}`; + return [example, invalid, false]; +} + +suite('Language Configuration', () => { + const cfg = getLanguageConfiguration(); + + suite('"brackets"', () => { + test('brackets is not defined', () => { + expect(cfg.brackets).to.be.equal(undefined, 'missing tests'); + }); }); - test('Multiline separator indent regex should not pick up strings with escaped characters', async () => { - const result = MULTILINE_SEPARATOR_INDENT_REGEX.test('a = \'hello \\n\''); - expect (result).to.be.equal(false, 'Multiline separator indent regex for strings with escaped characters should not have matches'); + + suite('"comments"', () => { + test('comments is not defined', () => { + expect(cfg.comments).to.be.equal(undefined, 'missing tests'); + }); }); - test('Multiline separator indent regex should pick up strings ending with a multiline separator', async () => { - const result = MULTILINE_SEPARATOR_INDENT_REGEX.test('a = \'multiline \\'); - expect (result).to.be.equal(true, 'Multiline separator indent regex for strings with newline separator should have matches'); + + suite('"indentationRules"', () => { + test('indentationRules is not defined', () => { + expect(cfg.indentationRules).to.be.equal(undefined, 'missing tests'); + }); + }); + + suite('"onEnterRules"', () => { + const MULTILINE_SEPARATOR_INDENT_REGEX = cfg.onEnterRules![0].beforeText; + const INDENT_ONENTER_REGEX = cfg.onEnterRules![2].beforeText; + const OUTDENT_ONENTER_REGEX = cfg.onEnterRules![3].beforeText; + // To see the actual (non-verbose) regex patterns, un-comment + // the following lines: + //console.log(INDENT_ONENTER_REGEX.source); + //console.log(OUTDENT_ONENTER_REGEX.source); + + test('Multiline separator indent regex should not pick up strings with no multiline separator', async () => { + const result = MULTILINE_SEPARATOR_INDENT_REGEX.test('a = "test"'); + expect(result).to.be.equal(false, 'Multiline separator indent regex for regular strings should not have matches'); + }); + + test('Multiline separator indent regex should not pick up strings with escaped characters', async () => { + const result = MULTILINE_SEPARATOR_INDENT_REGEX.test('a = \'hello \\n\''); + expect(result).to.be.equal(false, 'Multiline separator indent regex for strings with escaped characters should not have matches'); + }); + + test('Multiline separator indent regex should pick up strings ending with a multiline separator', async () => { + const result = MULTILINE_SEPARATOR_INDENT_REGEX.test('a = \'multiline \\'); + expect(result).to.be.equal(true, 'Multiline separator indent regex for strings with newline separator should have matches'); + }); + + [ + // compound statements + 'async def test(self):', + 'async def :', + 'async :', + 'class Test:', + 'class Test(object):', + 'class :', + 'def spam():', + 'def spam(self, node, namespace=""):', + 'def :', + 'for item in items:', + 'for item in :', + 'for :', + 'if foo is None:', + 'if :', + 'try:', + 'while \'::\' in macaddress:', + 'while :', + 'with self.test:', + 'with :', + 'elif x < 5:', + 'elif :', + 'else:', + 'except TestError:', + 'except :', + 'finally:', + // simple statemenhts + 'pass', + 'raise Exception(msg)', + 'raise Exception', + 'raise', // re-raise + 'break', + 'continue', + 'return', + 'return True', + 'return (True, False, False)', + 'return [True, False, False]', + 'return {True, False, False}', + 'return (', + 'return [', + 'return {', + 'return', + // bogus + '', + ' ', + ' ' + ].forEach(base => { + [ + ['', '', '', ''], + // leading + [' ', '', '', ''], + [' ', '', '', ''], // unusual indent + ['\t\t', '', '', ''], + // pre-keyword + ['x', '', '', ''], + // post-keyword + ['', 'x', '', ''], + // pre-colon + ['', '', ' ', ''], + // trailing + ['', '', '', ' '], + ['', '', '', '# a comment'], + ['', '', '', ' # ...'] + ].forEach(whitespace => { + const [leading, postKeyword, preColon, trailing] = whitespace; + const [_example, invalid, ignored] = resolveExample(base, leading, postKeyword, preColon, trailing); + if (ignored) { + return; + } + const example = _example!; + + if (invalid) { + test(`Line "${example}" ignored (${invalid})`, () => { + let result: boolean; + + result = INDENT_ONENTER_REGEX.test(example); + expect(result).to.be.equal(false, 'unexpected match'); + + result = OUTDENT_ONENTER_REGEX.test(example); + expect(result).to.be.equal(false, 'unexpected match'); + }); + return; + } + + test(`Check indent-on-enter for line "${example}"`, () => { + let expected = false; + if (isMember(base, INDENT_ON_ENTER)) { + expected = true; + } + + const result = INDENT_ONENTER_REGEX.test(example); + + expect(result).to.be.equal(expected, 'unexpected result'); + }); + + test(`Check dedent-on-enter for line "${example}"`, () => { + let expected = false; + if (isMember(base, DEDENT_ON_ENTER)) { + expected = true; + } + + const result = OUTDENT_ONENTER_REGEX.test(example); + + expect(result).to.be.equal(expected, 'unexpected result'); + }); + }); + }); + }); + + suite('"wordPattern"', () => { + test('wordPattern is not defined', () => { + expect(cfg.wordPattern).to.be.equal(undefined, 'missing tests'); + }); }); }); diff --git a/src/test/mocks/vsc/index.ts b/src/test/mocks/vsc/index.ts index d9586c4a4d35..64cbc6721eb9 100644 --- a/src/test/mocks/vsc/index.ts +++ b/src/test/mocks/vsc/index.ts @@ -172,6 +172,12 @@ export namespace vscMock { Operator = 24, TypeParameter = 25 } + export enum IndentAction { + None = 0, + Indent = 1, + IndentOutdent = 2, + Outdent = 3 + } export class CodeActionKind { public static readonly Empty: CodeActionKind = new CodeActionKind('empty'); diff --git a/src/test/vscode-mock.ts b/src/test/vscode-mock.ts index cd99771644b4..647142bdaace 100644 --- a/src/test/vscode-mock.ts +++ b/src/test/vscode-mock.ts @@ -54,6 +54,7 @@ mockedVSCode.EventEmitter = vscodeMocks.vscMock.EventEmitter; mockedVSCode.CancellationTokenSource = vscodeMocks.vscMock.CancellationTokenSource; mockedVSCode.CompletionItemKind = vscodeMocks.vscMock.CompletionItemKind; mockedVSCode.SymbolKind = vscodeMocks.vscMock.SymbolKind; +mockedVSCode.IndentAction = vscodeMocks.vscMock.IndentAction; mockedVSCode.Uri = vscodeMocks.vscMock.Uri as any; mockedVSCode.Range = vscodeMocks.vscMockExtHostedTypes.Range; mockedVSCode.Position = vscodeMocks.vscMockExtHostedTypes.Position; From 1ab2bcea6cf3cf9c331b37d0cbe64b132f0fe2fd Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 13 Aug 2019 14:19:34 -0700 Subject: [PATCH 7/8] Updated ptvsd to 4.3.2 --- news/3 Code Health/6961.md | 1 + requirements.txt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 news/3 Code Health/6961.md diff --git a/news/3 Code Health/6961.md b/news/3 Code Health/6961.md new file mode 100644 index 000000000000..30f86c6f80ef --- /dev/null +++ b/news/3 Code Health/6961.md @@ -0,0 +1 @@ +Updated version of [PTVSD](https://pypi.org/project/ptvsd/) to `4.3.2` diff --git a/requirements.txt b/requirements.txt index 74689971a572..b51a49aa3cf6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ jedi==0.13.3 parso==0.5.0 isort==4.3.21 -ptvsd==4.3.0 +ptvsd==4.3.2 From 6467c2597a6a6a44b42b0e9b7105e4e8f92b6432 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 13 Aug 2019 14:31:26 -0700 Subject: [PATCH 8/8] Update release info --- news/3 Code Health/6961.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/news/3 Code Health/6961.md b/news/3 Code Health/6961.md index 30f86c6f80ef..5fa38ef4bc85 100644 --- a/news/3 Code Health/6961.md +++ b/news/3 Code Health/6961.md @@ -1 +1,4 @@ -Updated version of [PTVSD](https://pypi.org/project/ptvsd/) to `4.3.2` +Bump version of [PTVSD](https://pypi.org/project/ptvsd/) to `4.3.2` + * Fix an issue with Jump to cursor command. [#1667](https://github.com/microsoft/ptvsd/issues/1667) + * Fix "Unable to find threadStateIndex for the current thread" message in terminal. [#1587](https://github.com/microsoft/ptvsd/issues/1587) + * Fixes crash when using python 3.7.4. [#1688](https://github.com/microsoft/ptvsd/issues/1688)