Add tests for deploying CSharp and JavaScript projects (#1190)

* Add tests for deploying CSharp and JavaScript projects

* Modify the code according to FTE's comments

* Solve the file conflict in the package.json

* Modify the code according to FTE's comments

* Modify file configuration information in package.json

* Modify the code according to FTE's comments

* Modify the code according to FTE's comments

* Modify the code according to FTE's comments

* Adjust the code format

* Update the path to match docker repo

* Update code according to the comments

* Update code according to the comments

* Update code according to the comments

* Update code according to the comments
This commit is contained in:
v-wuzhai 2019-07-01 19:45:00 -07:00 коммит произвёл GitHub
Родитель 60f3590bd9
Коммит 4ae2b4fe87
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
7 изменённых файлов: 124 добавлений и 33 удалений

6
.vscode/launch.json поставляемый
Просмотреть файл

@ -48,7 +48,8 @@
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionTestsPath=${workspaceFolder}/out/test"
"--extensionTestsPath=${workspaceFolder}/out/test",
"${workspaceFolder}/test/test.code-workspace"
],
"stopOnEntry": false,
"sourceMaps": true,
@ -73,7 +74,8 @@
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionTestsPath=${workspaceFolder}/dist/test"
"--extensionTestsPath=${workspaceFolder}/dist/test",
"${workspaceFolder}/test/test.code-workspace"
],
"stopOnEntry": false,
"sourceMaps": true,

Просмотреть файл

@ -30,8 +30,10 @@ export * from './src/templates/IFunctionTemplate';
export * from './src/templates/ScriptTemplateRetriever';
export * from './src/templates/TemplateProvider';
export * from './src/tree/AzureAccountTreeItemWithProjects';
export * from './src/tree/FunctionTreeItem';
export * from './src/tree/SubscriptionTreeItem';
export * from './src/utils/fs';
export * from './src/utils/delay';
export * from './src/utils/cpUtils';
export * from './src/utils/venvUtils';
export * from './src/vsCodeConfig/extensions';

Просмотреть файл

@ -26,6 +26,7 @@ function test() {
const env = process.env;
env.DEBUGTELEMETRY = '1';
env.MOCHA_timeout = String(20 * 1000);
env.CODE_TESTS_WORKSPACE = path.join(__dirname, 'test/test.code-workspace');
env.CODE_TESTS_PATH = path.join(__dirname, 'dist/test');
return cp.spawn('node', ['./node_modules/vscode/bin/test'], { stdio: 'inherit', env });
}

Просмотреть файл

@ -10,6 +10,7 @@ import { extensionPrefix, funcHostStartCommand, isWindows } from '../constants';
import { IPreDebugValidateResult, preDebugValidate } from '../debug/validatePreDebug';
import { isFuncHostTask, stopFuncHost } from '../funcCoreTools/funcHostTask';
import { localize } from '../localize';
import { delay } from '../utils/delay';
import { getWindowsProcessTree, IProcessTreeNode, IWindowsProcessTree } from '../utils/windowsProcessTree';
import { getWorkspaceSetting } from '../vsCodeConfig/settings';
@ -113,7 +114,3 @@ async function getInnermostWindowsPid(pid: string, timeoutInSeconds: number, tim
throw timeoutError;
}
async function delay(ms: number): Promise<void> {
await new Promise<void>((resolve: () => void): NodeJS.Timer => setTimeout(resolve, ms));
}

8
src/utils/delay.ts Normal file
Просмотреть файл

@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export async function delay(ms: number): Promise<void> {
await new Promise<void>((resolve: () => void): NodeJS.Timer => setTimeout(resolve, ms));
}

Просмотреть файл

@ -6,30 +6,31 @@
import * as assert from 'assert';
import { ResourceManagementClient } from 'azure-arm-resource';
import { WebSiteManagementClient, WebSiteManagementModels } from 'azure-arm-website';
import * as fse from 'fs-extra';
import { IHookCallbackContext, ISuiteCallbackContext } from 'mocha';
import * as path from 'path';
import * as request from 'request-promise';
import * as vscode from 'vscode';
import { AzExtTreeDataProvider, AzureAccountTreeItemWithProjects, DialogResponses, ext, getGlobalSetting, getRandomHexString, ProjectLanguage, projectLanguageSetting, TestAzureAccount, TestUserInput, updateGlobalSetting } from '../extension.bundle';
import { AzExtTreeDataProvider, AzureAccountTreeItemWithProjects, delay, DialogResponses, ext, getRandomHexString, ProjectLanguage, TestAzureAccount, TestUserInput } from '../extension.bundle';
import { longRunningTestsEnabled } from './global.test';
import { runWithFuncSetting } from './runWithSetting';
import { getCSharpValidateOptions, getJavaScriptValidateOptions, IValidateProjectOptions, validateProject } from './validateProject';
// tslint:disable-next-line:max-func-body-length
suite('Create Azure Resources', async function (this: ISuiteCallbackContext): Promise<void> {
this.timeout(1200 * 1000);
const resourceGroupsToDelete: string[] = [];
const testAccount: TestAzureAccount = new TestAzureAccount();
let oldProjectLanguage: ProjectLanguage | undefined;
let webSiteClient: WebSiteManagementClient;
const resourceName1: string = getRandomHexString().toLowerCase();
// Get the *.code-workspace workspace file path
const projectPath: string = getTestRootFolder();
suiteSetup(async function (this: IHookCallbackContext): Promise<void> {
if (!longRunningTestsEnabled) {
this.skip();
}
// set project language so that test isn't prompted for runtime
oldProjectLanguage = getGlobalSetting(projectLanguageSetting);
await updateGlobalSetting(projectLanguageSetting, ProjectLanguage.JavaScript);
this.timeout(120 * 1000);
await testAccount.signIn();
ext.azureAccountTreeItem = new AzureAccountTreeItemWithProjects(testAccount);
@ -42,9 +43,7 @@ suite('Create Azure Resources', async function (this: ISuiteCallbackContext): Pr
this.skip();
}
this.timeout(1200 * 1000);
await updateGlobalSetting(projectLanguageSetting, oldProjectLanguage);
await fse.emptyDir(projectPath);
const client: ResourceManagementClient = getResourceManagementClient(testAccount);
for (const resourceGroup of resourceGroupsToDelete) {
if (await client.resourceGroups.checkExistence(resourceGroup)) {
@ -59,26 +58,34 @@ suite('Create Azure Resources', async function (this: ISuiteCallbackContext): Pr
ext.azureAccountTreeItem.dispose();
});
test('createFunctionApp (Basic)', async () => {
resourceGroupsToDelete.push(resourceName1);
await runWithFuncSetting('advancedCreation', undefined, async () => {
ext.ui = new TestUserInput([resourceName1]);
await vscode.commands.executeCommand('azureFunctions.createFunctionApp');
const createdApp: WebSiteManagementModels.Site = await webSiteClient.webApps.get(resourceName1, resourceName1);
assert.ok(createdApp);
});
test('Create JavaScript project and deploy', async () => {
const functionName: string = 'HttpTrigger';
const templateId: string = 'HTTP trigger';
const authLevel: string = 'Function';
const testInputs: string[] = [projectPath, ProjectLanguage.JavaScript, templateId, functionName, authLevel];
await testCreateProjectAndDeploy(testInputs, getJavaScriptValidateOptions(true), resourceName1, functionName, `Hello ${resourceName1}`);
});
test('Create CSharp project and deploy', async () => {
const functionName: string = 'HttpTriggerCSharp';
const templateId: string = 'HttpTrigger';
const nameSpace: string = 'Company.Function';
const accessRights: string = 'Function';
const resourceName2: string = getRandomHexString().toLowerCase();
const testInputs: string[] = [projectPath, ProjectLanguage.CSharp, templateId, functionName, nameSpace, accessRights];
await testCreateProjectAndDeploy(testInputs, getCSharpValidateOptions('testOutput', 'netcoreapp2.1'), resourceName2, functionName, `Hello, ${resourceName2}`);
});
test('createFunctionApp (Advanced)', async () => {
const resourceName2: string = getRandomHexString();
const resourceName3: string = getRandomHexString();
const resourceGroupName: string = getRandomHexString();
const storageAccountName: string = getRandomHexString().toLowerCase();
resourceGroupsToDelete.push(resourceGroupName);
await runWithFuncSetting('advancedCreation', 'true', async () => {
const testInputs: string[] = [resourceName2, 'Windows', 'Consumption Plan', '.NET', '$(plus) Create new resource group', resourceGroupName, '$(plus) Create new storage account', storageAccountName, 'East US'];
const testInputs: string[] = [resourceName3, 'Windows', 'Consumption Plan', '.NET', '$(plus) Create new resource group', resourceGroupName, '$(plus) Create new storage account', storageAccountName, 'East US'];
ext.ui = new TestUserInput(testInputs);
await vscode.commands.executeCommand('azureFunctions.createFunctionApp');
const createdApp: WebSiteManagementModels.Site = await webSiteClient.webApps.get(resourceGroupName, resourceName2);
const createdApp: WebSiteManagementModels.Site = await webSiteClient.webApps.get(resourceGroupName, resourceName3);
assert.ok(createdApp, 'Create windows Function App with new rg/sa failed.');
});
});
@ -124,14 +131,17 @@ suite('Create Azure Resources', async function (this: ISuiteCallbackContext): Pr
test('createFunctionApp API', async () => {
const resourceGroupName: string = getRandomHexString();
resourceGroupsToDelete.push(resourceGroupName);
const appAndStorageName1: string = getRandomHexString().toLowerCase(); // storage accounts cannot contain upper case chars
const testInputs1: string[] = [appAndStorageName1];
ext.ui = new TestUserInput(testInputs1);
const apiResult1: string = <string>await vscode.commands.executeCommand('azureFunctions.createFunctionApp', testAccount.getSubscriptionId(), resourceGroupName);
const createdApp1: WebSiteManagementModels.Site = await webSiteClient.webApps.get(resourceGroupName, appAndStorageName1);
assert.ok(createdApp1, 'Function app with new rg/sa failed.');
assert.equal(apiResult1, createdApp1.id, 'Function app with new rg/sa failed.');
await runWithFuncSetting('advancedCreation', undefined, async () => {
await runWithFuncSetting('projectLanguage', ProjectLanguage.JavaScript, async () => {
const testInputs1: string[] = [appAndStorageName1];
ext.ui = new TestUserInput(testInputs1);
const apiResult1: string = <string>await vscode.commands.executeCommand('azureFunctions.createFunctionApp', testAccount.getSubscriptionId(), resourceGroupName);
const createdApp1: WebSiteManagementModels.Site = await webSiteClient.webApps.get(resourceGroupName, appAndStorageName1);
assert.ok(createdApp1, 'Function app with new rg/sa failed.');
assert.equal(apiResult1, createdApp1.id, 'Function app with new rg/sa failed.');
});
});
// Create another function app, but use the existing resource group and storage account through advanced creation
await runWithFuncSetting('advancedCreation', 'true', async () => {
@ -146,6 +156,26 @@ suite('Create Azure Resources', async function (this: ISuiteCallbackContext): Pr
// NOTE: We currently don't support 'delete' in our API, so no need to test that
});
async function testCreateProjectAndDeploy(createProjectInputs: string[], projectVerification: IValidateProjectOptions, resourceName: string, functionName: string, expectResult: string): Promise<void> {
await fse.emptyDir(projectPath);
resourceGroupsToDelete.push(resourceName);
ext.ui = new TestUserInput(createProjectInputs);
await vscode.commands.executeCommand('azureFunctions.createNewProject');
await validateProject(projectPath, projectVerification);
await runWithFuncSetting('advancedCreation', undefined, async () => {
ext.ui = new TestUserInput(['$(plus) Create New Function App in Azure', resourceName]);
await vscode.commands.executeCommand('azureFunctions.deploy');
});
await delay(500);
// Verify the deployment result through copyFunctionUrl
await vscode.env.clipboard.writeText(''); // Clear the clipboard
ext.ui = new TestUserInput([resourceName, functionName]);
await vscode.commands.executeCommand('azureFunctions.copyFunctionUrl');
const functionUrl: string = await vscode.env.clipboard.readText();
const result: string = await getBody(functionUrl, resourceName);
assert.equal(result, expectResult, `The result should be "${expectResult}" rather than ${result} and the triggerUrl is ${functionUrl}`);
}
});
function getWebsiteManagementClient(testAccount: TestAzureAccount): WebSiteManagementClient {
@ -155,3 +185,44 @@ function getWebsiteManagementClient(testAccount: TestAzureAccount): WebSiteManag
function getResourceManagementClient(testAccount: TestAzureAccount): ResourceManagementClient {
return new ResourceManagementClient(testAccount.getSubscriptionCredentials(), testAccount.getSubscriptionId());
}
// The root workspace folder that vscode is opened against for tests
function getTestRootFolder(): string {
let testRootFolder: string = '';
const testOutputName: string = 'testOutput';
if (!testRootFolder) {
// We're expecting to be opened against the test/test.code-workspace
// workspace.
const workspaceFolders: vscode.WorkspaceFolder[] | undefined = vscode.workspace.workspaceFolders;
if (!workspaceFolders || workspaceFolders.length === 0) {
console.error("No workspace is open.");
process.exit(1);
} else {
if (workspaceFolders.length > 1) {
console.error("There are unexpected multiple workspaces open");
process.exit(1);
}
testRootFolder = workspaceFolders[0].uri.fsPath;
console.log(`testRootFolder: ${testRootFolder}`);
if (path.basename(testRootFolder) !== testOutputName) {
console.error("vscode is opened against the wrong folder for tests");
process.exit(1);
}
fse.ensureDirSync(testRootFolder);
fse.emptyDirSync(testRootFolder);
}
}
return testRootFolder;
}
async function getBody(url: string, name: string): Promise<string> {
const options: request.OptionsWithUri = {
method: 'GET',
uri: url,
body: {
name: name
},
json: true
};
return await <Thenable<string>>request(options).promise();
}

10
test/test.code-workspace Normal file
Просмотреть файл

@ -0,0 +1,10 @@
{
"folders": [
{
"path": "../../testOutput"
}
],
"settings": {
"debug.internalConsoleOptions": "neverOpen"
}
}