Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dotnet tests for containerized function apps #4045

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion test/global.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ let oldRequestTimeout: number | undefined;

let testWorkspaceFolders: string[];
let workspaceFolderIndex = 0;
export function getTestWorkspaceFolder(): string {
export function getTestWorkspaceFolder(folder?: string): string {
if (folder) {
return testWorkspaceFolders.find(f => f.toLowerCase().includes(folder.toLowerCase())) || testWorkspaceFolders[0];
}
if (workspaceFolderIndex >= testWorkspaceFolders.length) {
throw new Error('Not enough workspace folders. Add more in "test/test.code-workspace".')
}
Expand Down Expand Up @@ -170,6 +173,12 @@ async function initTestWorkspaceFolders(): Promise<string[]> {
for (let i = 0; i < workspaceFolders.length; i++) {
const workspacePath: string = workspaceFolders[i].uri.fsPath;
const folderName = path.basename(workspacePath);
if (folderName === 'containerizedFunctionProject') {
await AzExtFsExtra.ensureDir(workspacePath);
await AzExtFsExtra.emptyDir(workspacePath);
folders.push(workspacePath);
continue;
}
assert.equal(folderName, String(i), `Unexpected workspace folder name "${folderName}".`);
await AzExtFsExtra.ensureDir(workspacePath);
await AzExtFsExtra.emptyDir(workspacePath);
Expand Down
36 changes: 34 additions & 2 deletions test/project/createAndValidateProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
*--------------------------------------------------------------------------------------------*/

import { type TestActionContext, type TestInput } from '@microsoft/vscode-azext-dev';
import { AzExtFsExtra } from '@microsoft/vscode-azext-utils';
import * as path from 'path';
import { ProjectLanguage, createNewProjectInternal, getRandomHexString, hiddenStacksSetting } from '../../extension.bundle';
// eslint-disable-next-line no-restricted-imports
import { CreateDockerfileProjectStep } from '../../src/commands/createNewProject/dockerfileSteps/CreateDockerfileProjectStep';
// eslint-disable-next-line no-restricted-imports
import type * as api from '../../src/vscode-azurefunctions.api';
import { testFolderPath } from '../global.test';
import { getTestWorkspaceFolder, testFolderPath } from '../global.test';
import { runWithFuncSetting } from '../runWithSetting';
import { validateProject, type IValidateProjectOptions } from './validateProject';
import { validateContainerizedProject, validateProject, type IValidateProjectOptions } from './validateProject';

export interface ICreateProjectTestOptions extends IValidateProjectOptions {
isHiddenLanguage?: boolean;
Expand Down Expand Up @@ -50,3 +53,32 @@ export async function createAndValidateProject(context: TestActionContext, optio

await validateProject(projectPath, options);
}

export async function createAndValidateContainerizedProject(context: TestActionContext, options: ICreateProjectTestOptions): Promise<void> {
// Clone inputs here so we have a different array each time
const inputs: (string | TestInput | RegExp)[] = options.inputs ? [...options.inputs] : [];
const language: ProjectLanguage = options.language;
const projectPath: string = getTestWorkspaceFolder('containerizedFunctionProject');
//Empty folder for next test
await AzExtFsExtra.emptyDir(projectPath);

if (!options.isHiddenLanguage) {
inputs.unshift(options.displayLanguage || language);
}

inputs.unshift('containerizedFunctionProject');

await runWithFuncSetting(hiddenStacksSetting, true, async () => {
await context.ui.runWithInputs(inputs, async () => {
await createNewProjectInternal(context,
{
executeStep: new CreateDockerfileProjectStep(),
languageFilter: /Python|C\#|(Java|Type)Script|PowerShell$/i,
version: options.version,
suppressOpenFolder: true
});
});
});

await validateContainerizedProject(projectPath, options);
}
29 changes: 29 additions & 0 deletions test/project/createNewContainerizedDotenetProject.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { runWithTestActionContext } from "@microsoft/vscode-azext-dev";
import { FuncVersion, getRandomHexString } from "../../extension.bundle";
import { getRotatingAuthLevel } from "../nightly/getRotatingValue";
import { createAndValidateContainerizedProject } from "./createAndValidateProject";
import { getCSharpValidateOptions } from "./validateProject";

suite('Create New Dotnet Project', () => {
test('checkDockerfileDotnetIsolated', async () => {
const functionName: string = 'func' + getRandomHexString();
const input = [/7.*isolated/i, /http\s*trigger/i, functionName, 'Company.Function', getRotatingAuthLevel()]
await runWithTestActionContext('createProject', async context => {
await createAndValidateContainerizedProject(context, { ...getCSharpValidateOptions('net7.0', FuncVersion.v4), inputs: input })
});
});

test('checkDockerfileDotnetLTS', async () => {
const functionName: string = 'func' + getRandomHexString();
const input = [/6/i, /http\s*trigger/i, functionName, 'Company.Function', getRotatingAuthLevel()]
await runWithTestActionContext('createProject', async context => {
await createAndValidateContainerizedProject(context, { ...getCSharpValidateOptions('net6.0', FuncVersion.v4), inputs: input })
});
});
});

33 changes: 33 additions & 0 deletions test/project/validateProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import * as assert from 'assert';
import * as fse from 'fs-extra';
import * as globby from 'globby';
import * as path from 'path';
import * as semver from 'semver';
import { FuncVersion, JavaBuildTool, ProjectLanguage, extensionId, getContainingWorkspace, type IExtensionsJson, type ILaunchJson, type ITasksJson } from '../../extension.bundle';
// eslint-disable-next-line no-restricted-imports
import { detectFunctionsDockerfile } from '../../src/commands/createFunctionApp/containerImage/detectDockerfile';

export const defaultTestFuncVersion: FuncVersion = FuncVersion.v4;

Expand Down Expand Up @@ -425,3 +428,33 @@ export async function validateProject(projectPath: string, options: IValidatePro
const gitignoreContents: string = (await fse.readFile(path.join(projectPath, '.gitignore'))).toString();
assert.equal(gitignoreContents.indexOf('.vscode'), -1, 'The ".vscode" folder is being ignored.');
}

export async function validateContainerizedProject(projectPath: string, options: IValidateProjectOptions): Promise<void> {
//
// Validate dockerfile is functions dockerfile
const expectedPaths: ExpectedPath[] = getCommonExpectedPaths(projectPath, options.workspaceFolder).filter(p1 => !options.excludedPaths || !options.excludedPaths.find(p2 => p1 === p2));
expectedPaths.push('Dockerfile');
if (expectedPaths.find(p => typeof p === 'string' && p.includes('Dockerfile'))) {
assert.ok(await AzExtFsExtra.pathExists(path.join(projectPath, 'Dockerfile')), 'Dockerfile does not exist.');
const dockerfileFound = await detectFunctionsDockerfile(path.join(projectPath, 'Dockerfile'));
assert.ok(dockerfileFound, 'Dockerfile does not contain the expected functions image.');
}

//validate csproj version matches dockerfile type
const dockerfileContents: string = (await fse.readFile(path.join(projectPath, 'Dockerfile'))).toString();
const dockerfileLines: string[] = dockerfileContents.split('\n');
const csprojContents: string = (await fse.readFile(path.join(projectPath, 'containerizedFunctionProject.csproj'))).toString();
const csprojLines: string[] = csprojContents.split('\n');
for (const line of dockerfileLines) {
if (line.includes('dotnet-isolated')) {
const azureFunctionsWorkerVersion = csprojLines.find(l => l.includes('Include=\"Microsoft.Azure.Functions.Worker\"'))?.match(/Version=\"(\d+\.\d+\.\d+)\"/)?.[1];
assert.ok(azureFunctionsWorkerVersion, 'csproj does not contain a Microsoft.Azure.Functions.Worker version.');
assert.ok(semver.satisfies(azureFunctionsWorkerVersion, '1.20.x'), 'csproj does not contain the expected Microsoft.Azure.Functions.Worker version.');
} else if (line.includes('dotnet:4')) {
const netSdkFunctionsVersion = csprojLines.find(l => l.includes('Include=\"Microsoft.NET.Sdk.Functions\"'))?.match(/Version=\"(\d+\.\d+\.\d+)\"/)?.[1];
assert.ok(netSdkFunctionsVersion, 'csproj does not contain an sdk package.');
assert.ok(semver.satisfies(netSdkFunctionsVersion, '4.x.x'), 'csproj does not contain the expected sdk package.');
assert.ok(csprojLines.some(l => l.includes('Include=\"Microsoft.NET.Sdk.Functions\" Version=\"4.2.0\"')), 'csproj does not contain the expected sdk package.');
}
}
}
3 changes: 3 additions & 0 deletions test/test.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
},
{
"path": "../testWorkspace/9"
},
{
"path": "../testWorkspace/containerizedFunctionProject"
}
],
"settings": {
Expand Down
Loading