Skip to content

Commit

Permalink
End to end testing for completion, references and definitions
Browse files Browse the repository at this point in the history
  • Loading branch information
jpogran committed Apr 11, 2024
1 parent e53a35e commit 797e793
Show file tree
Hide file tree
Showing 10 changed files with 426 additions and 89 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ npm-debug.log
lsp/
testFixture/.terraform.lock.hcl
testFixture/.vscode
test/fixtures/.vscode
.terraform/
.vscode-test-web
.wdio-vscode-service
2 changes: 1 addition & 1 deletion .vscode-test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { defineConfig } from '@vscode/test-cli';

const config = defineConfig({
version: process.env['VSCODE_VERSION'] ?? 'stable',
workspaceFolder: process.env['VSCODE_WORKSPACE_FOLDER'] ?? './testFixture',
workspaceFolder: process.env['VSCODE_WORKSPACE_FOLDER'] ?? './test/fixtures',
launchArgs: ['--disable-extensions', '--disable-workspace-trust'],
files: 'out/test/**/*.test.js',
mocha: {
Expand Down
129 changes: 108 additions & 21 deletions src/test/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,125 @@

import * as vscode from 'vscode';
import * as path from 'path';

export let doc: vscode.TextDocument;
export let editor: vscode.TextEditor;
export let documentEol: string;
export let platformEol: string;
import * as assert from 'assert';

export async function open(docUri: vscode.Uri): Promise<void> {
try {
doc = await vscode.workspace.openTextDocument(docUri);
editor = await vscode.window.showTextDocument(doc);
const doc = await vscode.workspace.openTextDocument(docUri);
await vscode.window.showTextDocument(doc);
} catch (e) {
console.error(e);
throw e;
}
}

export function getExtensionId(): string {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const pjson = require('../../package.json');
return `${pjson.publisher}.${pjson.name}`;
export const getDocUri = (p: string): vscode.Uri => {
const documentPath = path.resolve(__dirname, '../../test/fixtures', p);
return vscode.Uri.file(documentPath);
};

export async function testCompletion(
docUri: vscode.Uri,
position: vscode.Position,
expectedCompletionList: vscode.CompletionList,
) {
const actualCompletionList = (await vscode.commands.executeCommand(
'vscode.executeCompletionItemProvider',
docUri,
position,
)) as vscode.CompletionList;

assert.equal(actualCompletionList.items.length, expectedCompletionList.items.length);
expectedCompletionList.items.forEach((expectedItem, i) => {
const actualItem = actualCompletionList.items[i];
assert.deepStrictEqual(actualItem.label, expectedItem.label);
assert.deepStrictEqual(actualItem.kind, expectedItem.kind);
});
}

export const testFolderPath = path.resolve(__dirname, '..', '..', 'testFixture');
export async function testHover(docUri: vscode.Uri, position: vscode.Position, expectedCompletionList: vscode.Hover[]) {
const actualhover = (await vscode.commands.executeCommand(
'vscode.executeHoverProvider',
docUri,
position,
)) as vscode.Hover[];

export const getDocPath = (p: string): string => {
return path.resolve(__dirname, '../../testFixture', p);
};
export const getDocUri = (p: string): vscode.Uri => {
return vscode.Uri.file(getDocPath(p));
};
assert.equal(actualhover.length, expectedCompletionList.length);
expectedCompletionList.forEach((expectedItem, i) => {
const actualItem = actualhover[i];

assert.deepStrictEqual(actualItem.contents, expectedItem.contents);
});
}

export async function testDefinitions(
docUri: vscode.Uri,
position: vscode.Position,
expectedDefinitions: vscode.Location[],
) {
const actualDefinitions = await vscode.commands.executeCommand<vscode.Location[] | vscode.LocationLink[]>(
'vscode.executeDefinitionProvider',
docUri,
position,
);

assert.equal(actualDefinitions.length, expectedDefinitions.length);
expectedDefinitions.forEach((expectedItem, i) => {
const actualItem = actualDefinitions[i];
if (actualItem instanceof vscode.Location) {
assert.deepStrictEqual(actualItem.uri.path, expectedItem.uri.path);
assert.deepStrictEqual(actualItem.range.start, expectedItem.range.start);
assert.deepStrictEqual(actualItem.range.end, expectedItem.range.end);
return;
} else {
// } else if (actualItem instanceof vscode.LocationLink) {
assert.deepStrictEqual(actualItem.targetUri.path, expectedItem.uri.path);
assert.deepStrictEqual(actualItem.targetRange.start, expectedItem.range.start);
assert.deepStrictEqual(actualItem.targetRange.end, expectedItem.range.end);
}
});
}

export async function testReferences(
docUri: vscode.Uri,
position: vscode.Position,
expectedDefinitions: vscode.Location[],
) {
const actualDefinitions = await vscode.commands.executeCommand<vscode.Location[]>(
'vscode.executeReferenceProvider',
docUri,
position,
);

assert.equal(actualDefinitions.length, expectedDefinitions.length);
expectedDefinitions.forEach((expectedItem, i) => {
const actualItem = actualDefinitions[i];
assert.deepStrictEqual(actualItem.uri.path, expectedItem.uri.path);
assert.deepStrictEqual(actualItem.range.start, expectedItem.range.start);
assert.deepStrictEqual(actualItem.range.end, expectedItem.range.end);
});
}

export async function testSymbols(docUri: vscode.Uri, symbolNames: string[]) {
const symbols = (await vscode.commands.executeCommand(
'vscode.executeDocumentSymbolProvider',
docUri,
)) as vscode.SymbolInformation[];

assert.strictEqual(symbols.length, symbolNames.length);
symbols.forEach((symbol, i) => {
assert.strictEqual(symbol.name, symbolNames[i]);
});
}

export async function activateExtension() {
const ext = vscode.extensions.getExtension('hashicorp.terraform');
if (!ext?.isActive) {
await ext?.activate();
sleep(1000);
}
}

export async function setTestContent(content: string): Promise<boolean> {
const all = new vscode.Range(doc.positionAt(0), doc.positionAt(doc.getText().length));
return editor.edit((eb) => eb.replace(all, content));
export async function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
19 changes: 14 additions & 5 deletions src/test/integration/codeAction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,25 @@
import * as vscode from 'vscode';
import * as assert from 'assert';
import { expect } from 'chai';
import { getDocUri, open } from '../helper';
import { activateExtension, getDocUri, open } from '../helper';

suite('code actions', function suite() {
const docUri = getDocUri('actions.tf');

this.beforeAll(async () => {
await open(docUri);
await activateExtension();
});

suite('code actions', () => {
teardown(async () => {
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
});

test('language is registered', async () => {
const doc = await vscode.workspace.openTextDocument(docUri);
assert.equal(doc.languageId, 'terraform', 'document language should be `terraform`');
});

test('supported actions', async () => {
await vscode.workspace
.getConfiguration('terraform')
Expand All @@ -22,9 +34,6 @@ suite('code actions', () => {
// eslint-disable-next-line @typescript-eslint/naming-convention
.update('codeActionsOnSave', { 'source.formatAll.terraform': true }, vscode.ConfigurationTarget.Workspace);

const docUri = getDocUri('actions.tf');
await open(docUri);

const supported = [
new vscode.CodeAction('Format Document', vscode.CodeActionKind.Source.append('formatAll').append('terraform')),
];
Expand Down
159 changes: 119 additions & 40 deletions src/test/integration/completion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,37 @@
*/

import * as vscode from 'vscode';
import * as assert from 'assert';
import { expect } from 'chai';
import { getDocUri, open } from '../helper';
import { assert } from 'chai';
import { activateExtension, getDocUri, open, testCompletion } from '../helper';

const snippets = [
new vscode.CompletionItem({ label: 'fore', description: 'For Each' }, vscode.CompletionItemKind.Snippet),
new vscode.CompletionItem({ label: 'module', description: 'Module' }, vscode.CompletionItemKind.Snippet),
new vscode.CompletionItem({ label: 'output', description: 'Output' }, vscode.CompletionItemKind.Snippet),
new vscode.CompletionItem({ label: 'provisioner', description: 'Provisioner' }, vscode.CompletionItemKind.Snippet),
new vscode.CompletionItem({ label: 'vare', description: 'Empty variable' }, vscode.CompletionItemKind.Snippet),
new vscode.CompletionItem({ label: 'varm', description: 'Map variable' }, vscode.CompletionItemKind.Snippet),
];

suite('Language Server Completion', function suite() {
const docUri = getDocUri('actions.tf');

this.beforeAll(async () => {
await open(docUri);
await activateExtension();
});

suite('completion', () => {
teardown(async () => {
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
});

test('language is registered', async () => {
const doc = await vscode.workspace.openTextDocument(docUri);
assert.equal(doc.languageId, 'terraform', 'document language should be `terraform`');
});

test('simple completion', async () => {
const wanted = new vscode.CompletionList([
const expected = [
new vscode.CompletionItem('check', vscode.CompletionItemKind.Class),
new vscode.CompletionItem('data', vscode.CompletionItemKind.Class),
new vscode.CompletionItem('import', vscode.CompletionItemKind.Class),
Expand All @@ -23,45 +43,104 @@ suite('completion', () => {
new vscode.CompletionItem('moved', vscode.CompletionItemKind.Class),
new vscode.CompletionItem('output', vscode.CompletionItemKind.Class),
new vscode.CompletionItem('provider', vscode.CompletionItemKind.Class),
new vscode.CompletionItem('removed', vscode.CompletionItemKind.Class),
new vscode.CompletionItem('resource', vscode.CompletionItemKind.Class),
new vscode.CompletionItem('terraform', vscode.CompletionItemKind.Class),
new vscode.CompletionItem('variable', vscode.CompletionItemKind.Class),
new vscode.CompletionItem({ label: 'fore', description: 'For Each' }, vscode.CompletionItemKind.Snippet),
new vscode.CompletionItem({ label: 'module', description: 'Module' }, vscode.CompletionItemKind.Snippet),
new vscode.CompletionItem({ label: 'output', description: 'Output' }, vscode.CompletionItemKind.Snippet),
new vscode.CompletionItem(
{ label: 'provisioner', description: 'Provisioner' },
vscode.CompletionItemKind.Snippet,
),
new vscode.CompletionItem({ label: 'vare', description: 'Empty variable' }, vscode.CompletionItemKind.Snippet),
new vscode.CompletionItem({ label: 'varm', description: 'Map variable' }, vscode.CompletionItemKind.Snippet),
]);

const docUri = getDocUri('actions.tf');
];
// expected.push(...snippets);
await testCompletion(docUri, new vscode.Position(0, 0), {
items: expected,
});
});
});

suite('Module Completion', function suite() {
const docUri = getDocUri('main.tf');

this.beforeAll(async () => {
await open(docUri);
await activateExtension();
});

teardown(async () => {
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
});

test('language is registered', async () => {
const doc = await vscode.workspace.openTextDocument(docUri);
assert.equal(doc.languageId, 'terraform', 'document language should be `terraform`');
});

// Completion for inputs of a local module
test('inputs of a local module', async () => {
const expected = [
new vscode.CompletionItem('count', vscode.CompletionItemKind.Property),
new vscode.CompletionItem('depends_on', vscode.CompletionItemKind.Property),
new vscode.CompletionItem('for_each', vscode.CompletionItemKind.Property),
new vscode.CompletionItem('machine_type', vscode.CompletionItemKind.Property),
new vscode.CompletionItem('providers', vscode.CompletionItemKind.Property),
new vscode.CompletionItem('version', vscode.CompletionItemKind.Property),
];
expected.push(...snippets);

await testCompletion(docUri, new vscode.Position(21, 0), {
items: expected,
});
});

// Completion for a local module sources (prefix ./)
test('local module sources', async () => {
const expected = [
new vscode.CompletionItem('"./ai"', vscode.CompletionItemKind.Text),
new vscode.CompletionItem('"./compute"', vscode.CompletionItemKind.Text),
new vscode.CompletionItem('"cloudposse/label/null"', vscode.CompletionItemKind.Text),
new vscode.CompletionItem('"terraform-aws-modules/eks/aws"', vscode.CompletionItemKind.Text),
new vscode.CompletionItem('"terraform-aws-modules/iam/aws"', vscode.CompletionItemKind.Text),
new vscode.CompletionItem('"terraform-aws-modules/kms/aws"', vscode.CompletionItemKind.Text),
new vscode.CompletionItem('"terraform-aws-modules/lambda/aws"', vscode.CompletionItemKind.Text),
new vscode.CompletionItem('"terraform-aws-modules/rds/aws"', vscode.CompletionItemKind.Text),
new vscode.CompletionItem('"terraform-aws-modules/s3-bucket/aws"', vscode.CompletionItemKind.Text),
new vscode.CompletionItem('"terraform-aws-modules/security-group/aws"', vscode.CompletionItemKind.Text),
new vscode.CompletionItem('"terraform-aws-modules/vpc/aws"', vscode.CompletionItemKind.Text),
new vscode.CompletionItem('"terraform-google-modules/project-factory/google"', vscode.CompletionItemKind.Text),
];
expected.push(...snippets);

// module "compute" {
// source = "./compute"
await testCompletion(docUri, new vscode.Position(18, 11), {
items: expected,
});
});
});

suite('TFVars Completion', function suite() {
const docUri = getDocUri('terraform.tfvars');

this.beforeAll(async () => {
await open(docUri);
await activateExtension();
});

teardown(async () => {
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
});

test('language is registered', async () => {
const doc = await vscode.workspace.openTextDocument(docUri);
assert.equal(doc.languageId, 'terraform-vars', 'document language should be `terraform-vars`');
});

const list = await vscode.commands.executeCommand<vscode.CompletionList>(
'vscode.executeCompletionItemProvider',
docUri,
new vscode.Position(0, 0),
);

assert.ok(list);
expect(list).not.to.be.undefined;
expect(list.items).not.to.be.undefined;
expect(list.items.length).to.be.greaterThanOrEqual(1);

for (let index = 0; index < list.items.length; index++) {
const element: vscode.CompletionItem = list.items[index];
assert.ok(element);
expect(element).not.to.be.undefined;

const w = wanted.items[index];
assert.ok(w);
expect(w).not.to.be.undefined;
assert.strictEqual(element.kind, w.kind);
// this can either be a string or a vscode.CompletionItemLabel, so use deep
assert.deepStrictEqual(element.label, w.label);
}
test('simple variable completion', async () => {
const expected = [
new vscode.CompletionItem('credentials_file', vscode.CompletionItemKind.Property),
new vscode.CompletionItem('project', vscode.CompletionItemKind.Property),
new vscode.CompletionItem('region', vscode.CompletionItemKind.Property),
];
expected.push(...snippets);
await testCompletion(docUri, new vscode.Position(1, 0), {
items: expected,
});
});
});
Loading

0 comments on commit 797e793

Please sign in to comment.