Skip to content

Commit 2a02301

Browse files
committed
enable and configure unittest #459
1 parent 3137fa4 commit 2a02301

File tree

9 files changed

+284
-51
lines changed

9 files changed

+284
-51
lines changed

src/client/common/utils.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,4 +238,26 @@ export function formatErrorForLogging(error: Error | string): string {
238238
}
239239
}
240240
return message;
241+
}
242+
243+
export function getSubDirectories(rootDir: string): Promise<string[]> {
244+
return new Promise<string[]>(resolve => {
245+
fs.readdir(rootDir, (error, files) => {
246+
if (error) {
247+
return resolve([]);
248+
}
249+
const subDirs = [];
250+
files.forEach(name => {
251+
const fullPath = path.join(rootDir, name);
252+
try {
253+
if (fs.statSync(fullPath).isDirectory()) {
254+
subDirs.push(fullPath);
255+
}
256+
}
257+
catch (ex) {
258+
}
259+
});
260+
resolve(subDirs);
261+
});
262+
});
241263
}

src/client/unittests/common/baseTestManager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { resetTestResults, displayTestErrorMessage, storeDiscoveredTests } from
55
import * as telemetryHelper from '../../common/telemetry';
66
import * as telemetryContracts from '../../common/telemetryContracts';
77
import { Installer, Product } from '../../common/installer';
8-
import {isNotInstalledError} from '../../common/helpers';
8+
import { isNotInstalledError } from '../../common/helpers';
99

1010
export abstract class BaseTestManager {
1111
private tests: Tests;
@@ -96,7 +96,7 @@ export abstract class BaseTestManager {
9696

9797
return tests;
9898
}).catch(reason => {
99-
if (isNotInstalledError(reason) && !quietMode){
99+
if (isNotInstalledError(reason) && !quietMode) {
100100
this.installer.promptToInstall(this.product);
101101
}
102102

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import * as vscode from 'vscode';
2+
import { createDeferred } from '../../common/helpers';
3+
import { getSubDirectories } from '../../common/utils';
4+
import * as path from 'path';
5+
6+
export abstract class TestConfigurationManager {
7+
public abstract enable();
8+
public abstract disable();
9+
10+
public abstract configure(rootDir: string): Promise<any>;
11+
12+
protected selectTestDir(rootDir: string, subDirs: string[]): Promise<string> {
13+
const options = {
14+
matchOnDescription: true,
15+
matchOnDetail: true,
16+
placeHolder: 'Select the directory containing the unit tests'
17+
};
18+
let items: vscode.QuickPickItem[] = subDirs.map(dir => {
19+
const dirName = path.relative(rootDir, dir);
20+
if (dirName.indexOf('.') === 0) {
21+
return null;
22+
}
23+
return <vscode.QuickPickItem>{
24+
label: dirName,
25+
description: '',
26+
};
27+
}).filter(item => item !== null);
28+
29+
items = [{ label: '.', description: 'Root directory' }, ...items];
30+
31+
const def = createDeferred<string>();
32+
vscode.window.showQuickPick(items, options).then(item => {
33+
if (!item) {
34+
return def.resolve();
35+
}
36+
37+
def.resolve(item.label);
38+
});
39+
40+
return def.promise;
41+
}
42+
43+
protected selectTestFilePattern(): Promise<string> {
44+
const options = {
45+
matchOnDescription: true,
46+
matchOnDetail: true,
47+
placeHolder: 'Select the pattern to identify test files'
48+
};
49+
let items: vscode.QuickPickItem[] = [
50+
{ label: '*test.py', description: `Python Files ending with 'test'` },
51+
{ label: '*_test.py', description: `Python Files ending with '_test'` },
52+
{ label: 'test*.py', description: `Python Files begining with 'test'` },
53+
{ label: 'test_*.py', description: `Python Files begining with 'test_'` },
54+
{ label: '*test*.py', description: `Python Files containing the word 'test'` }
55+
];
56+
57+
const def = createDeferred<string>();
58+
vscode.window.showQuickPick(items, options).then(item => {
59+
if (!item) {
60+
return def.resolve();
61+
}
62+
63+
def.resolve(item.label);
64+
});
65+
66+
return def.promise;
67+
}
68+
protected getTestDirs(rootDir): Promise<string[]> {
69+
return getSubDirectories(rootDir).then(subDirs => {
70+
subDirs.sort();
71+
72+
// Find out if there are any dirs with the name test and place them on the top
73+
let possibleTestDirs = subDirs.filter(dir => dir.match(/test/i));
74+
let nonTestDirs = subDirs.filter(dir => possibleTestDirs.indexOf(dir) === -1);
75+
possibleTestDirs.push(...nonTestDirs);
76+
77+
// The test dirs are now on top
78+
return possibleTestDirs;
79+
});
80+
}
81+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
'use strict';
2+
import * as vscode from 'vscode';
3+
import { PythonSettings } from '../common/configSettings';
4+
import { Product } from '../common/installer';
5+
import { TestConfigurationManager } from './common/testConfigurationManager';
6+
import * as nose from './nosetest/testConfigurationManager';
7+
import * as pytest from './pytest/testConfigurationManager';
8+
import * as unittest from './unittest/testConfigurationManager';
9+
import { createDeferred } from '../common/helpers';
10+
11+
const settings = PythonSettings.getInstance();
12+
13+
function promptToEnableAndConfigureTestFramework(messageToDisplay: string = 'Select a test framework/tool to enable', enableOnly: boolean = false): Thenable<any> {
14+
const items = [{
15+
label: 'unittest',
16+
product: Product.unittest,
17+
description: 'Standard Python test framework',
18+
detail: 'https://docs.python.org/2/library/unittest.html'
19+
},
20+
{
21+
label: 'pytest',
22+
product: Product.pytest,
23+
description: 'Can run unittest (including trial) and nose test suites out of the box',
24+
detail: 'http://docs.pytest.org/en/latest/'
25+
},
26+
{
27+
label: 'nose',
28+
product: Product.nosetest,
29+
description: 'nose framework',
30+
detail: 'https://docs.python.org/2/library/unittest.html'
31+
}];
32+
const options = {
33+
matchOnDescription: true,
34+
matchOnDetail: true,
35+
placeHolder: messageToDisplay
36+
};
37+
return vscode.window.showQuickPick(items, options).then(item => {
38+
if (!item) {
39+
return Promise.reject(null);
40+
}
41+
let configMgr: TestConfigurationManager;
42+
switch (item.product) {
43+
case Product.unittest: {
44+
configMgr = new unittest.ConfigurationManager();
45+
break;
46+
}
47+
case Product.pytest: {
48+
configMgr = new pytest.ConfigurationManager();
49+
break;
50+
}
51+
case Product.nosetest: {
52+
configMgr = new nose.ConfigurationManager();
53+
break;
54+
}
55+
default: {
56+
throw new Error('Invalid test configuration');
57+
}
58+
}
59+
60+
configMgr.enable();
61+
if (enableOnly) {
62+
// Ensure others are disabled
63+
if (item.product !== Product.unittest) {
64+
(new unittest.ConfigurationManager()).disable();
65+
}
66+
if (item.product !== Product.pytest) {
67+
(new pytest.ConfigurationManager()).disable();
68+
}
69+
if (item.product !== Product.nosetest) {
70+
(new nose.ConfigurationManager()).disable();
71+
}
72+
return Promise.resolve();
73+
}
74+
return configMgr.configure(vscode.workspace.rootPath);
75+
});
76+
}
77+
export function displayTestFrameworkError(): Thenable<any> {
78+
let enabledCount = settings.unitTest.pyTestEnabled ? 1 : 0;
79+
enabledCount += settings.unitTest.nosetestsEnabled ? 1 : 0;
80+
enabledCount += settings.unitTest.unittestEnabled ? 1 : 0;
81+
if (enabledCount > 1) {
82+
return promptToEnableAndConfigureTestFramework('Enable only one of the test frameworks (unittest, pytest or nosetest).', true);
83+
}
84+
else {
85+
const option = 'Enable and configure a Test Framework';
86+
return vscode.window.showInformationMessage('No test framework configured (unittest, pytest or nosetest)', option).then(item => {
87+
if (item === option) {
88+
return promptToEnableAndConfigureTestFramework();
89+
}
90+
return Promise.reject(null);
91+
});
92+
}
93+
}

src/client/unittests/main.ts

Lines changed: 3 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
'use strict';
22
import * as vscode from 'vscode';
3-
import { Tests, TestsToRun, TestFolder, TestFile, TestStatus, TestSuite, TestFunction, FlattenedTestFunction, CANCELLATION_REASON } from './common/contracts';
3+
import { TestsToRun, TestStatus, TestFunction, FlattenedTestFunction, CANCELLATION_REASON } from './common/contracts';
44
import * as nosetests from './nosetest/main';
55
import * as pytest from './pytest/main';
66
import * as unittest from './unittest/main';
77
import { resolveValueAsTestToRun, getDiscoveredTests } from './common/testUtils';
88
import { BaseTestManager } from './common/baseTestManager';
9-
import { PythonSettings, IUnitTestSettings } from '../common/configSettings';
9+
import { PythonSettings } from '../common/configSettings';
1010
import { TestResultDisplay } from './display/main';
1111
import { TestDisplay } from './display/picker';
12-
import * as fs from 'fs';
13-
import * as path from 'path';
1412
import * as constants from '../common/constants';
1513
import { activateCodeLenses } from './codeLenses/main';
16-
import { Product } from '../common/installer';
14+
import { displayTestFrameworkError } from './configuration';
1715

1816
const settings = PythonSettings.getInstance();
1917
let testManager: BaseTestManager;
@@ -23,7 +21,6 @@ let nosetestManager: nosetests.TestManager;
2321
let testResultDisplay: TestResultDisplay;
2422
let testDisplay: TestDisplay;
2523
let outChannel: vscode.OutputChannel;
26-
let lastRanTests: TestsToRun = null;
2724

2825
export function activate(context: vscode.ExtensionContext, outputChannel: vscode.OutputChannel) {
2926
context.subscriptions.push({ dispose: dispose });
@@ -152,44 +149,6 @@ function onConfigChanged() {
152149
// No need to display errors
153150
discoverTests(true, true);
154151
}
155-
function promptToEnableTestFramework() {
156-
const items = [{
157-
label: 'unittest', settingToEnable: 'unitTest.unittestEnabled',
158-
description: 'Standard Python test framework',
159-
detail: 'https://docs.python.org/2/library/unittest.html'
160-
},
161-
{
162-
label: 'pytest', settingToEnable: 'unitTest.pyTestEnabled',
163-
description: 'Can run unittest (including trial) and nose test suites out of the box',
164-
detail: 'http://docs.pytest.org/en/latest/'
165-
},
166-
{
167-
label: 'nose', settingToEnable: 'unitTest.nosetestsEnabled',
168-
description: 'nose framework',
169-
detail: 'https://docs.python.org/2/library/unittest.html'
170-
}];
171-
vscode.window.showQuickPick(items, { matchOnDescription: true, matchOnDetail: true, placeHolder: 'Select a test framework/tool to enable' }).then(item => {
172-
if (!item) {
173-
return;
174-
}
175-
const pythonConfig = vscode.workspace.getConfiguration('python');
176-
pythonConfig.update(item.settingToEnable, true);
177-
});
178-
}
179-
function displayTestFrameworkError() {
180-
if (settings.unitTest.pyTestEnabled && settings.unitTest.nosetestsEnabled && settings.unitTest.unittestEnabled) {
181-
vscode.window.showErrorMessage("Enable only one of the test frameworks (nosetest or pytest), not both.");
182-
}
183-
else {
184-
const option = 'Enable a Test Framework/Tool';
185-
vscode.window.showInformationMessage('Please enable one of the test frameworks (unittest, pytest or nosetest)', option).then(item => {
186-
if (item === option) {
187-
promptToEnableTestFramework();
188-
}
189-
});
190-
}
191-
return null;
192-
}
193152
function getTestRunner() {
194153
const rootDirectory = vscode.workspace.rootPath;
195154
if (settings.unitTest.nosetestsEnabled) {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import * as vscode from 'vscode';
2+
import {TestConfigurationManager} from '../common/testConfigurationManager';
3+
4+
export class ConfigurationManager extends TestConfigurationManager {
5+
public enable() {
6+
const pythonConfig = vscode.workspace.getConfiguration('python');
7+
pythonConfig.update('unitTest.nosetestsEnabled', true);
8+
}
9+
public disable() {
10+
const pythonConfig = vscode.workspace.getConfiguration('python');
11+
pythonConfig.update('unitTest.nosetestsEnabled', false);
12+
}
13+
14+
public configure(rootDir: string): Promise<any> {
15+
return Promise.resolve();
16+
}
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import * as vscode from 'vscode';
2+
import {TestConfigurationManager} from '../common/testConfigurationManager';
3+
4+
export class ConfigurationManager extends TestConfigurationManager {
5+
public enable() {
6+
const pythonConfig = vscode.workspace.getConfiguration('python');
7+
pythonConfig.update('unitTest.pyTestEnabled', true);
8+
}
9+
public disable() {
10+
const pythonConfig = vscode.workspace.getConfiguration('python');
11+
pythonConfig.update('unitTest.pyTestEnabled', false);
12+
}
13+
14+
public configure(rootDir: string): Promise<any> {
15+
return Promise.resolve();
16+
}
17+
}

src/client/unittests/unittest/main.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
'use strict';
2-
import {PythonSettings} from '../../common/configSettings';
3-
import {TestsToRun, Tests, TestStatus} from '../common/contracts';
4-
import {runTest} from './runner';
2+
import { PythonSettings } from '../../common/configSettings';
3+
import { TestsToRun, Tests, TestStatus } from '../common/contracts';
4+
import { runTest } from './runner';
55
import * as vscode from 'vscode';
6-
import {discoverTests} from './collector';
7-
import {BaseTestManager} from '../common/baseTestManager';
6+
import { discoverTests } from './collector';
7+
import { BaseTestManager } from '../common/baseTestManager';
88
import { Product } from '../../common/installer';
99

1010
const settings = PythonSettings.getInstance();
1111
export class TestManager extends BaseTestManager {
1212
constructor(rootDirectory: string, outputChannel: vscode.OutputChannel) {
1313
super('unitest', Product.unittest, rootDirectory, outputChannel);
1414
}
15+
configure() {
16+
}
1517
discoverTestsImpl(ignoreCache: boolean): Promise<Tests> {
1618
let args = settings.unitTest.unittestArgs.slice(0);
1719
return discoverTests(this.rootDirectory, args, this.cancellationToken);

0 commit comments

Comments
 (0)