mirror of
https://github.com/fluencelabs/aqua-vscode
synced 2025-03-14 21:30:52 +00:00
feat(tests): Add unit tests [LNG-323] (#81)
* Add imports tests * Add unit tests on document info * Add workflow * PR fixes * Update server/src/test/info.test.ts Co-authored-by: shamsartem <shamsartem@gmail.com> * PR fixes * Update target, PR fixes * Update .vscodeignore --------- Co-authored-by: shamsartem <shamsartem@gmail.com>
This commit is contained in:
parent
0ff680eb4a
commit
89fcde5d26
8
.github/workflows/run-tests.yml
vendored
8
.github/workflows/run-tests.yml
vendored
@ -22,7 +22,13 @@ concurrency:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tests:
|
tests:
|
||||||
name: 'aqua-vscode'
|
name: 'Integration tests'
|
||||||
uses: ./.github/workflows/tests.yml
|
uses: ./.github/workflows/tests.yml
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.ref }}
|
ref: ${{ github.ref }}
|
||||||
|
|
||||||
|
unit-tests:
|
||||||
|
name: 'Unit tests'
|
||||||
|
uses: ./.github/workflows/unit-tests.yml
|
||||||
|
with:
|
||||||
|
ref: ${{ github.ref }}
|
||||||
|
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@ -17,7 +17,7 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
aqua-vscode:
|
aqua-vscode:
|
||||||
name: 'Run tests'
|
name: 'Run integration tests'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
|
|
||||||
|
39
.github/workflows/unit-tests.yml
vendored
Normal file
39
.github/workflows/unit-tests.yml
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
name: Run tests with workflow_call
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
ref:
|
||||||
|
description: 'git ref to checkout to'
|
||||||
|
type: string
|
||||||
|
default: 'main'
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_COLOR: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
aqua-vscode:
|
||||||
|
name: 'Run unit tests'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 60
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: fluencelabs/aqua-vscode
|
||||||
|
ref: ${{ inputs.ref }}
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
uses: coactions/setup-xvfb@v1
|
||||||
|
with:
|
||||||
|
run: npm run test:unit
|
@ -2,4 +2,5 @@
|
|||||||
.vscode-test/**
|
.vscode-test/**
|
||||||
.gitignore
|
.gitignore
|
||||||
.github
|
.github
|
||||||
vsc-extension-quickstart.md
|
integration-tests
|
||||||
|
**/src/test
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"target": "ES5",
|
"target": "es2020",
|
||||||
"outDir": "out",
|
"outDir": "out",
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"sourceMap": true
|
"sourceMap": true
|
||||||
|
@ -128,6 +128,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"before-tests": "bash integration-tests/before-tests.sh",
|
"before-tests": "bash integration-tests/before-tests.sh",
|
||||||
"test": "npm run compile && vscode-test",
|
"test": "npm run compile && vscode-test",
|
||||||
|
"test:unit": "npm run test -C server",
|
||||||
"vscode:prepublish": "npm run compile",
|
"vscode:prepublish": "npm run compile",
|
||||||
"compile": "tsc -b",
|
"compile": "tsc -b",
|
||||||
"watch": "tsc -b -w",
|
"watch": "tsc -b -w",
|
||||||
|
3
server/.mocharc.cjs
Normal file
3
server/.mocharc.cjs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
spec: 'src/test/**/*.test.ts',
|
||||||
|
};
|
2059
server/package-lock.json
generated
2059
server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -17,7 +17,12 @@
|
|||||||
"vscode-languageserver-textdocument": "^1.0.4",
|
"vscode-languageserver-textdocument": "^1.0.4",
|
||||||
"vscode-uri": "3.0.8"
|
"vscode-uri": "3.0.8"
|
||||||
},
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/mocha": "^10.0.0",
|
||||||
|
"ts-mocha": "^10.0.0"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"test": "ts-mocha",
|
||||||
"get-root": "npm root --global"
|
"get-root": "npm root --global"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -48,11 +48,19 @@ export function normalizeImports(imports: unknown): Imports {
|
|||||||
// Deep merge two import settings, overriding the first with the second
|
// Deep merge two import settings, overriding the first with the second
|
||||||
export function uniteImports(pre: Imports, post: Imports): Imports {
|
export function uniteImports(pre: Imports, post: Imports): Imports {
|
||||||
const result: Imports = { ...pre };
|
const result: Imports = { ...pre };
|
||||||
for (const [importPrefix, locations] of Object.entries(post)) {
|
for (const [pathPrefix, info] of Object.entries(post)) {
|
||||||
if (importPrefix in result) {
|
const resultInfo = result[pathPrefix];
|
||||||
result[importPrefix] = { ...result[importPrefix], ...locations };
|
if (resultInfo) {
|
||||||
|
for (const [importPrefix, paths] of Object.entries(info)) {
|
||||||
|
const importInfo = resultInfo[importPrefix];
|
||||||
|
if (importInfo) {
|
||||||
|
resultInfo[importPrefix] = [...importInfo, ...paths];
|
||||||
|
} else {
|
||||||
|
resultInfo[importPrefix] = paths;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
result[importPrefix] = locations;
|
result[pathPrefix] = info;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,8 +60,8 @@ documents.onDidClose((e) => {
|
|||||||
|
|
||||||
connection.onInitialize((params: InitializeParams) => {
|
connection.onInitialize((params: InitializeParams) => {
|
||||||
connection.console.log('onInitialize event');
|
connection.console.log('onInitialize event');
|
||||||
const capabilities = params.capabilities;
|
|
||||||
|
|
||||||
|
const capabilities = params.capabilities;
|
||||||
hasConfigurationCapability = !!(capabilities.workspace && !!capabilities.workspace.configuration);
|
hasConfigurationCapability = !!(capabilities.workspace && !!capabilities.workspace.configuration);
|
||||||
hasWorkspaceFolderCapability = !!(capabilities.workspace && !!capabilities.workspace.workspaceFolders);
|
hasWorkspaceFolderCapability = !!(capabilities.workspace && !!capabilities.workspace.workspaceFolders);
|
||||||
|
|
||||||
|
30
server/src/test/aqua/simple.aqua
Normal file
30
server/src/test/aqua/simple.aqua
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
aqua Simple
|
||||||
|
|
||||||
|
export main
|
||||||
|
|
||||||
|
func getStr() -> string:
|
||||||
|
<- "test string"
|
||||||
|
|
||||||
|
func consumeStr(str: string) -> string:
|
||||||
|
<- str
|
||||||
|
|
||||||
|
service Srv("test-srv"):
|
||||||
|
consumeStr(str: string) -> string
|
||||||
|
|
||||||
|
ability Ab:
|
||||||
|
field: string
|
||||||
|
arrow(s: string) -> string
|
||||||
|
|
||||||
|
func main() -> string:
|
||||||
|
-- Definition
|
||||||
|
testVar <- getStr()
|
||||||
|
-- Use as argument to function
|
||||||
|
newVar1 <- consumeStr(testVar)
|
||||||
|
-- Use as argument to service
|
||||||
|
newVar2 <- Srv.consumeStr(testVar)
|
||||||
|
-- Use as argument to ability creation
|
||||||
|
ab = Ab(field = testVar, arrow = consumeStr)
|
||||||
|
-- Use as argument to ability call
|
||||||
|
newVar3 <- ab.arrow(testVar)
|
||||||
|
-- Use in return statement
|
||||||
|
<- testVar
|
109
server/src/test/imports.test.ts
Normal file
109
server/src/test/imports.test.ts
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import * as assert from 'assert';
|
||||||
|
|
||||||
|
import { normalizeImports, uniteImports } from '../imports';
|
||||||
|
|
||||||
|
describe('Imports Test Suite', () => {
|
||||||
|
describe('normalizeImports', () => {
|
||||||
|
it('should normalize empty imports', async () => {
|
||||||
|
assert.deepStrictEqual(normalizeImports(undefined), {});
|
||||||
|
assert.deepStrictEqual(normalizeImports(null), {});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should normalize legacy imports', async () => {
|
||||||
|
const imports = ['a', 'b', 'c'];
|
||||||
|
const normalized = {
|
||||||
|
'/': {
|
||||||
|
'': imports,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
assert.deepStrictEqual(normalizeImports(imports), normalized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should normalize imports', async () => {
|
||||||
|
const imports = {
|
||||||
|
'/': {
|
||||||
|
'': ['a', 'b', 'c'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
assert.deepStrictEqual(normalizeImports(imports), imports);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should normalize imports with single paths', async () => {
|
||||||
|
const imports = {
|
||||||
|
'/': {
|
||||||
|
'': 'a',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const normalized = {
|
||||||
|
'/': {
|
||||||
|
'': ['a'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
assert.deepStrictEqual(normalizeImports(imports), normalized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw on invalid imports', async () => {
|
||||||
|
assert.throws(() => normalizeImports(123));
|
||||||
|
assert.throws(() => normalizeImports(['a', 123]));
|
||||||
|
assert.throws(() => normalizeImports({ a: 123 }));
|
||||||
|
assert.throws(() => normalizeImports({ a: { b: 123 } }));
|
||||||
|
assert.throws(() => normalizeImports({ a: { b: ['a', 123] } }));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('uniteImports', () => {
|
||||||
|
it('should unite distinct imports', async () => {
|
||||||
|
const lhs = {
|
||||||
|
'/left': {
|
||||||
|
'': ['l'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const rhs = {
|
||||||
|
'/right': {
|
||||||
|
'': ['r'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const result = { ...lhs, ...rhs };
|
||||||
|
assert.deepStrictEqual(uniteImports(lhs, rhs), result);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should unite path-intersecting imports', async () => {
|
||||||
|
const lhs = {
|
||||||
|
'/': {
|
||||||
|
left: ['l'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const rhs = {
|
||||||
|
'/': {
|
||||||
|
right: ['r'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const result = {
|
||||||
|
'/': {
|
||||||
|
left: ['l'],
|
||||||
|
right: ['r'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
assert.deepStrictEqual(uniteImports(lhs, rhs), result);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should unite path-prefix-intersecting imports', async () => {
|
||||||
|
const lhs = {
|
||||||
|
'/': {
|
||||||
|
'': ['l'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const rhs = {
|
||||||
|
'/': {
|
||||||
|
'': ['r'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const result = {
|
||||||
|
'/': {
|
||||||
|
'': ['l', 'r'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
assert.deepStrictEqual(uniteImports(lhs, rhs), result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
105
server/src/test/info.test.ts
Normal file
105
server/src/test/info.test.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import * as assert from 'assert';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as fs from 'fs/promises';
|
||||||
|
import { TextDocument } from 'vscode-languageserver-textdocument';
|
||||||
|
import { Location, Position, Range } from 'vscode-languageserver';
|
||||||
|
|
||||||
|
import { compileAqua } from '../validation';
|
||||||
|
import { DocumentInfo } from '../info';
|
||||||
|
import { pathToUri, tokenToLocation } from '../utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load document from file, compile it and return info
|
||||||
|
* @param relPath path relative to test folder
|
||||||
|
* @returns document, document info after compilation
|
||||||
|
*/
|
||||||
|
async function openDocument(relPath: string): Promise<[TextDocument, DocumentInfo]> {
|
||||||
|
const absPath = path.join(__dirname, relPath);
|
||||||
|
|
||||||
|
const content = await fs.readFile(absPath, 'utf-8');
|
||||||
|
const document = TextDocument.create(pathToUri(absPath), 'aqua', 0, content);
|
||||||
|
// Compile without imports
|
||||||
|
const [_, info] = await compileAqua({ imports: {} }, document);
|
||||||
|
|
||||||
|
return [document, info];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find all locations of a variable in a document
|
||||||
|
* @param name variable name
|
||||||
|
* @param doc text document to search in
|
||||||
|
* @returns locations of variable in document
|
||||||
|
*/
|
||||||
|
function locationsOf(name: string, doc: TextDocument): Location[] {
|
||||||
|
const regex = new RegExp(`(?<=\\b)${name}(?=\\b)`, 'g');
|
||||||
|
return [...doc.getText().matchAll(regex)].map((match) => {
|
||||||
|
// `index` will always be presented, see
|
||||||
|
// https://github.com/microsoft/TypeScript/issues/36788
|
||||||
|
const index = match.index as number;
|
||||||
|
return {
|
||||||
|
uri: doc.uri,
|
||||||
|
range: {
|
||||||
|
start: doc.positionAt(index),
|
||||||
|
end: doc.positionAt(index + match[0].length),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate all positions in a range
|
||||||
|
* @param doc text document
|
||||||
|
* @param range range
|
||||||
|
* @returns positions in range
|
||||||
|
*/
|
||||||
|
function genRange(doc: TextDocument, range: Range): Position[] {
|
||||||
|
const positions: Position[] = [];
|
||||||
|
|
||||||
|
const begOff = doc.offsetAt(range.start);
|
||||||
|
const endOff = doc.offsetAt(range.end);
|
||||||
|
for (let off = begOff; off < endOff; off++) {
|
||||||
|
positions.push(doc.positionAt(off));
|
||||||
|
}
|
||||||
|
|
||||||
|
return positions;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('DocumentInfo Test Suite', () => {
|
||||||
|
describe('infoAt', () => {
|
||||||
|
it('should return type information on each occurrence (simple)', async () => {
|
||||||
|
const [document, docInfo] = await openDocument('aqua/simple.aqua');
|
||||||
|
const locations = locationsOf('testVar', document);
|
||||||
|
|
||||||
|
assert.strictEqual(locations.length, 6, 'Not all occurrences found');
|
||||||
|
|
||||||
|
for (const loc of locations) {
|
||||||
|
for (const pos of genRange(document, loc.range)) {
|
||||||
|
const info = docInfo.infoAt(pos);
|
||||||
|
|
||||||
|
assert.ok(info, 'Info not found');
|
||||||
|
assert.strictEqual(info.type, 'string', 'Wrong type info');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('defAt', () => {
|
||||||
|
it('should return definition location on each occurrence (simple)', async () => {
|
||||||
|
const [document, docInfo] = await openDocument('aqua/simple.aqua');
|
||||||
|
const locations = locationsOf('testVar', document);
|
||||||
|
|
||||||
|
assert.strictEqual(locations.length, 6, 'Not all occurrences found');
|
||||||
|
|
||||||
|
const definition = locations[0];
|
||||||
|
|
||||||
|
for (const loc of locations.slice(1)) {
|
||||||
|
for (const pos of genRange(document, loc.range)) {
|
||||||
|
const def = docInfo.defAt(pos);
|
||||||
|
|
||||||
|
assert.ok(def, 'Definition not found');
|
||||||
|
assert.deepStrictEqual(tokenToLocation(def), definition, 'Wrong definition location');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES5",
|
"target": "es2020",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
@ -10,6 +10,6 @@
|
|||||||
"rootDir": "src"
|
"rootDir": "src"
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"exclude": ["node_modules"],
|
"exclude": ["node_modules", "src/test"],
|
||||||
"extends": "@tsconfig/node16-strictest/tsconfig.json"
|
"extends": "@tsconfig/node16-strictest/tsconfig.json"
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
"declaration": true,
|
"declaration": true,
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"target": "ES5",
|
"target": "es2020",
|
||||||
"outDir": "out",
|
"outDir": "out",
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"sourceMap": true
|
"sourceMap": true
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
|
||||||
"types": ["node"],
|
"types": ["node"],
|
||||||
|
"include": [],
|
||||||
"exclude": ["node_modules", ".vscode-test"],
|
"exclude": ["node_modules", ".vscode-test"],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user