Skip to content

Commit 14ffa65

Browse files
authored
Replace unitTest with testing in settings (#5218)
For #4999
1 parent 2c7a461 commit 14ffa65

68 files changed

Lines changed: 680 additions & 326 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"typescript.tsdk": "./node_modules/typescript/lib", // we want to use the TS server from our node_modules folder to control its version
2121
"tslint.enable": true,
2222
"python.linting.enabled": false,
23-
"python.unitTest.promptToConfigure": false,
23+
"python.testing.promptToConfigure": false,
2424
"python.workspaceSymbols.enabled": false,
2525
"python.formatting.provider": "none",
2626
"typescript.preferences.quoteStyle": "single",

news/.vscode/settings.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
"python.jediEnabled": false,
33
"python.formatting.provider": "black",
44
"editor.formatOnSave": true,
5-
"python.unitTest.pyTestArgs": [
5+
"python.testing.pyTestArgs": [
66
"."
77
],
8-
"python.unitTest.unittestEnabled": false,
9-
"python.unitTest.nosetestsEnabled": false,
10-
"python.unitTest.pyTestEnabled": true
8+
"python.testing.unittestEnabled": false,
9+
"python.testing.nosetestsEnabled": false,
10+
"python.testing.pyTestEnabled": true
1111
}

news/1 Enhancements/5219.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Change settings from `python.unitTest.*` to `python.testing.*`.

package.json

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,10 +1005,10 @@
10051005
}
10061006
},
10071007
{
1008-
"label": "Python: Unit Tests",
1009-
"description": "%python.snippet.launch.unitTests.description%",
1008+
"label": "Python: Tests",
1009+
"description": "%python.snippet.launch.test.description%",
10101010
"body": {
1011-
"name": "Unit Tests",
1011+
"name": "Tests",
10121012
"type": "python",
10131013
"request": "test"
10141014
}
@@ -2036,19 +2036,19 @@
20362036
"description": "Python launch arguments to use when executing a file in the terminal.",
20372037
"scope": "resource"
20382038
},
2039-
"python.unitTest.cwd": {
2039+
"python.testing.cwd": {
20402040
"type": "string",
20412041
"default": null,
20422042
"description": "Optional working directory for unit tests.",
20432043
"scope": "resource"
20442044
},
2045-
"python.unitTest.debugPort": {
2045+
"python.testing.debugPort": {
20462046
"type": "number",
20472047
"default": 3000,
2048-
"description": "Port number used for debugging of unittests.",
2048+
"description": "Port number used for debugging of tests.",
20492049
"scope": "resource"
20502050
},
2051-
"python.unitTest.nosetestArgs": {
2051+
"python.testing.nosetestArgs": {
20522052
"type": "array",
20532053
"description": "Arguments passed in. Each argument is a separate item in the array.",
20542054
"default": [],
@@ -2057,25 +2057,25 @@
20572057
},
20582058
"scope": "resource"
20592059
},
2060-
"python.unitTest.nosetestsEnabled": {
2060+
"python.testing.nosetestsEnabled": {
20612061
"type": "boolean",
20622062
"default": false,
20632063
"description": "Enable unit testing using nosetests.",
20642064
"scope": "resource"
20652065
},
2066-
"python.unitTest.nosetestPath": {
2066+
"python.testing.nosetestPath": {
20672067
"type": "string",
20682068
"default": "nosetests",
20692069
"description": "Path to nosetests, you can use a custom version of nosetests by modifying this setting to include the full path.",
20702070
"scope": "resource"
20712071
},
2072-
"python.unitTest.promptToConfigure": {
2072+
"python.testing.promptToConfigure": {
20732073
"type": "boolean",
20742074
"default": true,
20752075
"description": "Prompt to configure a test framework if potential tests directories are discovered.",
20762076
"scope": "resource"
20772077
},
2078-
"python.unitTest.pyTestArgs": {
2078+
"python.testing.pyTestArgs": {
20792079
"type": "array",
20802080
"description": "Arguments passed in. Each argument is a separate item in the array.",
20812081
"default": [],
@@ -2084,19 +2084,19 @@
20842084
},
20852085
"scope": "resource"
20862086
},
2087-
"python.unitTest.pyTestEnabled": {
2087+
"python.testing.pyTestEnabled": {
20882088
"type": "boolean",
20892089
"default": false,
20902090
"description": "Enable unit testing using pytest.",
20912091
"scope": "resource"
20922092
},
2093-
"python.unitTest.pyTestPath": {
2093+
"python.testing.pyTestPath": {
20942094
"type": "string",
20952095
"default": "pytest",
20962096
"description": "Path to pytest (pytest), you can use a custom version of pytest by modifying this setting to include the full path.",
20972097
"scope": "resource"
20982098
},
2099-
"python.unitTest.unittestArgs": {
2099+
"python.testing.unittestArgs": {
21002100
"type": "array",
21012101
"description": "Arguments passed in. Each argument is a separate item in the array.",
21022102
"default": [
@@ -2111,13 +2111,13 @@
21112111
},
21122112
"scope": "resource"
21132113
},
2114-
"python.unitTest.unittestEnabled": {
2114+
"python.testing.unittestEnabled": {
21152115
"type": "boolean",
21162116
"default": false,
21172117
"description": "Enable unit testing using unittest.",
21182118
"scope": "resource"
21192119
},
2120-
"python.unitTest.autoTestDiscoverOnSaveEnabled": {
2120+
"python.testing.autoTestDiscoverOnSaveEnabled": {
21212121
"type": "boolean",
21222122
"default": true,
21232123
"description": "Enable auto run test discovery when saving a unit test file.",

package.nls.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"python.command.python.datascience.collapseallcells.title": "Collapse all Python Interactive cells",
5656
"python.snippet.launch.standard.label": "Python: Current File",
5757
"python.snippet.launch.standard.description": "Debug a Python Program with Standard Output",
58+
"python.snippet.launch.test.description": "Python: Tests",
5859
"python.snippet.launch.pyspark.label": "Python: PySpark",
5960
"python.snippet.launch.pyspark.description": "Debug PySpark",
6061
"python.snippet.launch.module.label": "Python: Module",
@@ -117,7 +118,7 @@
117118
"DataScience.connectingToJupyter": "Connecting to Jupyter server",
118119
"Interpreters.RefreshingInterpreters": "Refreshing Python Interpreters",
119120
"Interpreters.LoadingInterpreters": "Loading Python Interpreters",
120-
"Interpreters.doNotShowAgain": "Do not show again",
121+
"Common.doNotShowAgain": "Do not show again",
121122
"Interpreters.environmentPromptMessage": "We noticed a new virtual environment has been created. Do you want to select it for the workspace folder?",
122123
"DataScience.restartKernelMessage": "Do you want to restart the iPython kernel? All variables will be lost.",
123124
"DataScience.restartKernelMessageYes": "Restart",
@@ -177,6 +178,7 @@
177178
"diagnostics.justMyCodeDiagnostic": "Configuration \"debugStdLib\" in launch.json is no longer supported. It's recommended to replace it with \"justMyCode\", which is the exact opposite of using \"debugStdLib\". Would you like to automatically update your launch.json file to do that?",
178179
"diagnostics.yesUpdateLaunch": "Yes, update launch.json",
179180
"diagnostics.bannerLabelNo": "No, I will do it later",
181+
"diagnostics.invalidTestSettings": "Your settings needs to be updated to change the setting \"python.unitTest.\" to \"python.testing.\", otherwise testing Python code using the extension may not work. Would you like to automatically update your settings now?",
180182
"DataScience.interruptKernel": "Interrupt iPython Kernel",
181183
"DataScience.exportingFormat": "Exporting {0}",
182184
"DataScience.exportCancel": "Cancel",
@@ -245,5 +247,7 @@
245247
"DataScience.pandasTooOldForViewingFormat": "Python package 'pandas' is version {0}. Version 0.20 or greater is required for viewing data.",
246248
"DataScience.pandasRequiredForViewing": "Python package 'pandas' is required for viewing data.",
247249
"DataScience.valuesColumn": "values",
248-
"DataScience.liveShareInvalid": "One or more guests in the session do not have the Python [extension](https://marketplace.visualstudio.com/itemdetails?itemName=ms-python.python) installed.\r\nYour Live Share session cannot continue and will be closed."
249-
}
250+
"DataScience.liveShareInvalid": "One or more guests in the session do not have the Python [extension](https://marketplace.visualstudio.com/itemdetails?itemName=ms-python.python) installed.\r\nYour Live Share session cannot continue and will be closed.",
251+
"diagnostics.updateSettings": "Yes, update settings",
252+
"Common.noIWillDoItLater": "No, I will do it later"
253+
}

src/client/activation/jedi.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { JediSymbolProvider } from '../providers/symbolProvider';
1919
import { BlockFormatProviders } from '../typeFormatters/blockFormatProvider';
2020
import { OnTypeFormattingDispatcher } from '../typeFormatters/dispatcher';
2121
import { OnEnterFormatter } from '../typeFormatters/onEnterFormatter';
22-
import { IUnitTestManagementService } from '../unittests/types';
22+
import { ITestManagementService } from '../unittests/types';
2323
import { WorkspaceSymbols } from '../workspaceSymbols/main';
2424
import { ILanguageServerActivator } from './types';
2525

@@ -108,7 +108,7 @@ export class JediExtensionActivator implements ILanguageServerActivator {
108108
languages.registerRenameProvider(PYTHON, new PythonRenameProvider(serviceContainer))
109109
);
110110

111-
const testManagementService = this.serviceManager.get<IUnitTestManagementService>(IUnitTestManagementService);
111+
const testManagementService = this.serviceManager.get<ITestManagementService>(ITestManagementService);
112112
testManagementService
113113
.activate(symbolProvider)
114114
.catch(ex => this.serviceManager.get<ILogger>(ILogger).logError('Failed to activate Unit Tests', ex));

src/client/activation/languageServer/languageServer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { noop } from '../../common/utils/misc';
1414
import { LanguageServerSymbolProvider } from '../../providers/symbolProvider';
1515
import { captureTelemetry, sendTelemetryEvent } from '../../telemetry';
1616
import { EventName } from '../../telemetry/constants';
17-
import { IUnitTestManagementService } from '../../unittests/types';
17+
import { ITestManagementService } from '../../unittests/types';
1818
import { ILanguageClientFactory, ILanguageServer, LanguageClientFactory } from '../types';
1919
import { ProgressReporting } from './progress';
2020

@@ -30,7 +30,7 @@ export class LanguageServer implements ILanguageServer {
3030
@inject(ILanguageClientFactory)
3131
@named(LanguageClientFactory.base)
3232
private readonly factory: ILanguageClientFactory,
33-
@inject(IUnitTestManagementService) private readonly testManager: IUnitTestManagementService,
33+
@inject(ITestManagementService) private readonly testManager: ITestManagementService,
3434
@inject(IConfigurationService) private readonly configurationService: IConfigurationService
3535
) {
3636
this.startupCompleted = createDeferred<void>();

src/client/application/diagnostics/checks/invalidDebuggerType.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { IWorkspaceService } from '../../../common/application/types';
1010
import '../../../common/extensions';
1111
import { IFileSystem } from '../../../common/platform/types';
1212
import { IDisposableRegistry, Resource } from '../../../common/types';
13-
import { Diagnostics } from '../../../common/utils/localize';
13+
import { Common, Diagnostics } from '../../../common/utils/localize';
1414
import { IServiceContainer } from '../../../ioc/types';
1515
import { BaseDiagnostic, BaseDiagnosticsService } from '../base';
1616
import { DiagnosticCodes } from '../constants';
@@ -102,7 +102,7 @@ export class InvalidLaunchJsonDebuggerService extends BaseDiagnosticsService {
102102
}
103103
},
104104
{
105-
prompt: Diagnostics.bannerLabelNo()
105+
prompt: Common.noIWillDoItLater()
106106
}
107107
];
108108

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
import { inject, injectable, named } from 'inversify';
7+
import * as path from 'path';
8+
import { DiagnosticSeverity } from 'vscode';
9+
import { IApplicationEnvironment, IWorkspaceService } from '../../../common/application/types';
10+
import { IFileSystem } from '../../../common/platform/types';
11+
import { IDisposableRegistry, IPersistentState, IPersistentStateFactory, Resource } from '../../../common/types';
12+
import { swallowExceptions } from '../../../common/utils/decorators';
13+
import { Common, Diagnostics } from '../../../common/utils/localize';
14+
import { IServiceContainer } from '../../../ioc/types';
15+
import { BaseDiagnostic, BaseDiagnosticsService } from '../base';
16+
import { IDiagnosticsCommandFactory } from '../commands/types';
17+
import { DiagnosticCodes } from '../constants';
18+
import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../promptHandler';
19+
import { DiagnosticScope, IDiagnostic, IDiagnosticHandlerService } from '../types';
20+
21+
export class InvalidTestSettingsDiagnostic extends BaseDiagnostic {
22+
constructor() {
23+
super(
24+
DiagnosticCodes.InvalidTestSettingDiagnostic,
25+
Diagnostics.invalidTestSettings(),
26+
DiagnosticSeverity.Error,
27+
DiagnosticScope.WorkspaceFolder,
28+
undefined,
29+
'always'
30+
);
31+
}
32+
}
33+
34+
export const InvalidTestSettingsDiagnosticscServiceId = 'InvalidTestSettingsDiagnosticscServiceId';
35+
36+
@injectable()
37+
export class InvalidTestSettingDiagnosticsService extends BaseDiagnosticsService {
38+
protected readonly stateStore: IPersistentState<string[]>;
39+
private readonly handledWorkspaces: Set<string>;
40+
constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer,
41+
@inject(IFileSystem) private readonly fs: IFileSystem,
42+
@inject(IApplicationEnvironment) private readonly application: IApplicationEnvironment,
43+
@inject(IPersistentStateFactory) stateFactory: IPersistentStateFactory,
44+
@inject(IDiagnosticHandlerService) @named(DiagnosticCommandPromptHandlerServiceId) private readonly messageService: IDiagnosticHandlerService<MessageCommandPrompt>,
45+
@inject(IDiagnosticsCommandFactory) private readonly commandFactory: IDiagnosticsCommandFactory,
46+
@inject(IWorkspaceService) private readonly workspace: IWorkspaceService,
47+
@inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry) {
48+
super([DiagnosticCodes.InvalidEnvironmentPathVariableDiagnostic], serviceContainer, disposableRegistry, true);
49+
this.stateStore = stateFactory.createGlobalPersistentState<string[]>('python.unitTest.Settings', []);
50+
this.handledWorkspaces = new Set<string>();
51+
}
52+
public async diagnose(resource: Resource): Promise<IDiagnostic[]> {
53+
if (!this.shouldHandleResource(resource)) {
54+
return [];
55+
}
56+
const filesToBeFixed = await this.getFilesToBeFixed();
57+
if (filesToBeFixed.length === 0) {
58+
return [];
59+
} else {
60+
return [new InvalidTestSettingsDiagnostic()];
61+
}
62+
}
63+
public async onHandle(diagnostics: IDiagnostic[]): Promise<void> {
64+
// This class can only handle one type of diagnostic, hence just use first item in list.
65+
if (diagnostics.length === 0 || !this.canHandle(diagnostics[0]) ||
66+
!(diagnostics[0] instanceof InvalidTestSettingsDiagnostic)) {
67+
return;
68+
}
69+
const diagnostic = diagnostics[0];
70+
const options = [
71+
{
72+
prompt: Diagnostics.updateSettings(),
73+
command: {
74+
diagnostic,
75+
invoke: async (): Promise<void> => {
76+
const filesToBeFixed = await this.getFilesToBeFixed();
77+
await Promise.all(filesToBeFixed.map(file => this.fixSettingInFile(file)));
78+
}
79+
}
80+
},
81+
{ prompt: Common.noIWillDoItLater() },
82+
{
83+
prompt: Common.doNotShowAgain(),
84+
command: this.commandFactory.createCommand(diagnostic, { type: 'ignore', options: DiagnosticScope.Global })
85+
}
86+
];
87+
88+
await this.messageService.handle(diagnostic, { commandPrompts: options });
89+
}
90+
public getSettingsFiles() {
91+
if (!this.workspace.hasWorkspaceFolders) {
92+
return this.application.userSettingsFile ? [this.application.userSettingsFile] : [];
93+
}
94+
return this.workspace.workspaceFolders!
95+
.map(item => path.join(item.uri.fsPath, '.vscode', 'settings.json'))
96+
.concat(this.application.userSettingsFile ? [this.application.userSettingsFile] : []);
97+
}
98+
public async getFilesToBeFixed() {
99+
const files = this.getSettingsFiles();
100+
const result = await Promise.all(files.map(async file => {
101+
const needsFixing = await this.doesFileNeedToBeFixed(file);
102+
return { file, needsFixing };
103+
}));
104+
return result.filter(item => item.needsFixing).map(item => item.file);
105+
}
106+
@swallowExceptions('Failed to update settings.json')
107+
public async fixSettingInFile(filePath: string) {
108+
const fileContents = await this.fs.readFile(filePath);
109+
const setting = new RegExp('"python.unitTest', 'g');
110+
111+
await this.fs.writeFile(filePath, fileContents.replace(setting, '"python.testing'));
112+
113+
// Keep track of updated file.
114+
this.stateStore.value.push(filePath);
115+
await this.stateStore.updateValue(this.stateStore.value.slice());
116+
}
117+
@swallowExceptions('Failed to check if file needs to be fixed')
118+
private async doesFileNeedToBeFixed(filePath: string) {
119+
// If we have fixed the path to this file once before,
120+
// then no need to check agian. If user adds subsequently, nothing we can do,
121+
// as user will see warnings in editor about invalid entries.
122+
// This will speed up loading of extension (reduce unwanted disc IO).
123+
if (this.stateStore.value.indexOf(filePath) >= 0) {
124+
return false;
125+
}
126+
const contents = await this.fs.readFile(filePath);
127+
return contents.indexOf('python.unitTest.') > 0;
128+
}
129+
/**
130+
* Checks whether to handle a particular workspace resource.
131+
* If required, we'll track that resource to ensure we don't handle it again.
132+
* This is necessary for multi-root workspaces.
133+
*
134+
* @param {Resource} resource
135+
* @returns {boolean}
136+
* @memberof InvalidTestSettingDiagnosticsService
137+
*/
138+
private shouldHandleResource(resource: Resource): boolean {
139+
const folder = this.workspace.getWorkspaceFolder(resource);
140+
141+
if (!folder || !resource || !this.workspace.hasWorkspaceFolders) {
142+
if (this.handledWorkspaces.has('')) {
143+
return false;
144+
}
145+
this.handledWorkspaces.add('');
146+
return true;
147+
}
148+
149+
if (this.handledWorkspaces.has(folder.uri.fsPath)) {
150+
return false;
151+
}
152+
this.handledWorkspaces.add(folder.uri.fsPath);
153+
return true;
154+
}
155+
}

src/client/application/diagnostics/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
export enum DiagnosticCodes {
77
InvalidEnvironmentPathVariableDiagnostic = 'InvalidEnvironmentPathVariableDiagnostic',
88
InvalidDebuggerTypeDiagnostic = 'InvalidDebuggerTypeDiagnostic',
9+
InvalidTestSettingDiagnostic = 'InvalidTestSettingDiagnostic',
910
NoPythonInterpretersDiagnostic = 'NoPythonInterpretersDiagnostic',
1011
MacInterpreterSelectedAndNoOtherInterpretersDiagnostic = 'MacInterpreterSelectedAndNoOtherInterpretersDiagnostic',
1112
MacInterpreterSelectedAndHaveOtherInterpretersDiagnostic = 'MacInterpreterSelectedAndHaveOtherInterpretersDiagnostic',

0 commit comments

Comments
 (0)