From efb6c2252a1696d1cc124a8765714fdf772de2d0 Mon Sep 17 00:00:00 2001 From: Boulanger Date: Mon, 18 Mar 2024 15:09:21 +0100 Subject: [PATCH 1/5] Fix unsafe call to Update_Sources Update_Sources has for precondition "not Self.Are_Sources_Loaded" Thus the sources must always be invalidated before or it will raise an exception and corrupt the contexts. Add a test. eng/ide/ada_language_server#1272 --- .../ada/lsp-ada_handlers-project_loading.adb | 9 + source/ada/lsp-ada_handlers.adb | 12 + .../foo-bar1.ads | 3 + .../foo.ads | 3 + .../main.adb | 6 + .../test.gpr | 3 + .../test.json | 792 ++++++++++++++++++ .../test.yaml | 1 + 8 files changed, 829 insertions(+) create mode 100644 testsuite/ada_lsp/Did_Rename_Files.context_corruption/foo-bar1.ads create mode 100644 testsuite/ada_lsp/Did_Rename_Files.context_corruption/foo.ads create mode 100644 testsuite/ada_lsp/Did_Rename_Files.context_corruption/main.adb create mode 100644 testsuite/ada_lsp/Did_Rename_Files.context_corruption/test.gpr create mode 100644 testsuite/ada_lsp/Did_Rename_Files.context_corruption/test.json create mode 100644 testsuite/ada_lsp/Did_Rename_Files.context_corruption/test.yaml diff --git a/source/ada/lsp-ada_handlers-project_loading.adb b/source/ada/lsp-ada_handlers-project_loading.adb index e6db1fb1b..304e6d430 100644 --- a/source/ada/lsp-ada_handlers-project_loading.adb +++ b/source/ada/lsp-ada_handlers-project_loading.adb @@ -423,6 +423,11 @@ package body LSP.Ada_Handlers.Project_Loading is Build_Path => Project_Environment.Build_Path, Environment => Environment); + if Self.Project_Tree.Are_Sources_Loaded then + -- Update_Sources can't be called when the sources are already + -- loaded + Self.Project_Tree.Invalidate_Sources; + end if; Self.Project_Tree.Update_Sources (With_Runtime => True); exception @@ -687,6 +692,10 @@ package body LSP.Ada_Handlers.Project_Loading is Project => Project, Context => GPR2.Context.Empty); + if Self.Project_Tree.Are_Sources_Loaded then + -- Update_Sources can't be called when the sources are already loaded + Self.Project_Tree.Invalidate_Sources; + end if; Self.Project_Tree.Update_Sources (With_Runtime => True); exception diff --git a/source/ada/lsp-ada_handlers.adb b/source/ada/lsp-ada_handlers.adb index 064dea57c..bda17393d 100644 --- a/source/ada/lsp-ada_handlers.adb +++ b/source/ada/lsp-ada_handlers.adb @@ -2295,6 +2295,10 @@ package body LSP.Ada_Handlers is -- New sources were created on this project, so recompute its view + if Self.Project_Tree.Are_Sources_Loaded then + -- Update_Sources can't be called when the sources are already loaded + Self.Project_Tree.Invalidate_Sources; + end if; Self.Project_Tree.Update_Sources (With_Runtime => True); -- For each created file of Value.files: @@ -2346,6 +2350,10 @@ package body LSP.Ada_Handlers is -- Some project sources were deleted, so recompute its view + if Self.Project_Tree.Are_Sources_Loaded then + -- Update_Sources can't be called when the sources are already loaded + Self.Project_Tree.Invalidate_Sources; + end if; Self.Project_Tree.Update_Sources (With_Runtime => True); -- For each delete file of Value.files: @@ -2461,6 +2469,10 @@ package body LSP.Ada_Handlers is -- Some project sources were renamed, so recompute its view + if Self.Project_Tree.Are_Sources_Loaded then + -- Update_Sources can't be called when the sources are already loaded + Self.Project_Tree.Invalidate_Sources; + end if; Self.Project_Tree.Update_Sources (With_Runtime => True); -- For each oldUri of Value.files: diff --git a/testsuite/ada_lsp/Did_Rename_Files.context_corruption/foo-bar1.ads b/testsuite/ada_lsp/Did_Rename_Files.context_corruption/foo-bar1.ads new file mode 100644 index 000000000..fa090612f --- /dev/null +++ b/testsuite/ada_lsp/Did_Rename_Files.context_corruption/foo-bar1.ads @@ -0,0 +1,3 @@ +package Foo.Bar1 is + procedure N is null; +end Foo.Bar1; diff --git a/testsuite/ada_lsp/Did_Rename_Files.context_corruption/foo.ads b/testsuite/ada_lsp/Did_Rename_Files.context_corruption/foo.ads new file mode 100644 index 000000000..633f971fe --- /dev/null +++ b/testsuite/ada_lsp/Did_Rename_Files.context_corruption/foo.ads @@ -0,0 +1,3 @@ +package Foo is + procedure Do_Nothing is null; +end Foo; diff --git a/testsuite/ada_lsp/Did_Rename_Files.context_corruption/main.adb b/testsuite/ada_lsp/Did_Rename_Files.context_corruption/main.adb new file mode 100644 index 000000000..49e2d1028 --- /dev/null +++ b/testsuite/ada_lsp/Did_Rename_Files.context_corruption/main.adb @@ -0,0 +1,6 @@ +with Foo.Bar1; + +procedure Main is +begin + Foo.Bar1.N; +end Main; diff --git a/testsuite/ada_lsp/Did_Rename_Files.context_corruption/test.gpr b/testsuite/ada_lsp/Did_Rename_Files.context_corruption/test.gpr new file mode 100644 index 000000000..13539987d --- /dev/null +++ b/testsuite/ada_lsp/Did_Rename_Files.context_corruption/test.gpr @@ -0,0 +1,3 @@ +project Test is + for Main use ("main.adb"); +end Test; diff --git a/testsuite/ada_lsp/Did_Rename_Files.context_corruption/test.json b/testsuite/ada_lsp/Did_Rename_Files.context_corruption/test.json new file mode 100644 index 000000000..4d57ee14e --- /dev/null +++ b/testsuite/ada_lsp/Did_Rename_Files.context_corruption/test.json @@ -0,0 +1,792 @@ +[ + { + "comment": [ + "Test the context after a DidRenameFiles notification. ", + "The references requests should work before and after." + ] + }, + { + "start": { + "cmd": [ + "${ALS}" + ] + } + }, + { + "send": { + "request": { + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "processId": 257038, + "rootUri": "$URI{.}", + "capabilities": { + "workspace": { + "applyEdit": true, + "workspaceEdit": { + "documentChanges": true, + "resourceOperations": [ + "rename" + ] + }, + "fileOperations": { + "didRename": true + } + }, + "textDocument": { + "synchronization": {}, + "completion": { + "dynamicRegistration": true, + "completionItem": { + "snippetSupport": false, + "documentationFormat": [ + "plaintext", + "markdown" + ], + "resolveSupport": { + "properties": [ + "detail", + "documentation" + ] + } + } + }, + "hover": {}, + "signatureHelp": {}, + "declaration": {}, + "definition": {}, + "typeDefinition": {}, + "implementation": {}, + "documentSymbol": { + "hierarchicalDocumentSymbolSupport": true + }, + "formatting": { + "dynamicRegistration": false + }, + "rangeFormatting": { + "dynamicRegistration": false + }, + "onTypeFormatting": { + "dynamicRegistration": false + }, + "foldingRange": { + "lineFoldingOnly": true + } + }, + "experimental": { + "advanced_refactorings": [ + "add_parameter" + ] + } + } + } + }, + "wait": [ + { + "id": 1, + "result": { + "capabilities": { + "textDocumentSync": 2, + "completionProvider": { + "triggerCharacters": [ + ".", + ",", + "'", + "(" + ], + "resolveProvider": true + }, + "renameProvider": { + "prepareProvider": true + } + } + } + } + ] + } + }, + { + "send": { + "request": { + "jsonrpc": "2.0", + "method": "initialized" + }, + "wait": [] + } + }, + { + "send": { + "request": { + "jsonrpc": "2.0", + "method": "workspace/didChangeConfiguration", + "params": { + "settings": { + "ada": { + "projectFile": "$URI{test.gpr}", + "scenarioVariables": {}, + "defaultCharset": "ISO-8859-1", + "enableDiagnostics": true, + "followSymlinks": false, + "documentationStyle": "gnat", + "namedNotationThreshold": 3, + "foldComments": false + } + } + } + }, + "wait": [] + } + }, + { + "send": { + "request": { + "jsonrpc": "2.0", + "method": "textDocument/didOpen", + "params": { + "textDocument": { + "uri": "$URI{foo-bar1.ads}", + "languageId": "Ada", + "version": 0, + "text": "package Foo.Bar1 is\n procedure N is null;\nend Foo.Bar1;\n" + } + } + }, + "wait": [] + } + }, + { + "send": { + "request": { + "jsonrpc": "2.0", + "method": "textDocument/didOpen", + "params": { + "textDocument": { + "uri": "$URI{main.adb}", + "languageId": "Ada", + "version": 0, + "text": "with Foo.Bar1;\n\nprocedure Main is\n begin\n Foo.Bar1.N;\nend Main;\n" + } + } + }, + "wait": [] + } + }, + { + "send": { + "request": { + "jsonrpc": "2.0", + "id": 7, + "method": "textDocument/references", + "params": { + "textDocument": { + "uri": "$URI{foo-bar1.ads}" + }, + "position": { + "line": 1, + "character": 13 + }, + "context": { + "includeDeclaration": true + } + } + }, + "wait": [ + { + "id": 7, + "result": [ + { + "uri": "$URI{foo-bar1.ads}", + "range": { + "start": { + "line": 1, + "character": 13 + }, + "end": { + "line": 1, + "character": 14 + } + }, + "alsKind": [ + "reference" + ] + }, + { + "uri": "$URI{main.adb}", + "range": { + "start": { + "line": 4, + "character": 12 + }, + "end": { + "line": 4, + "character": 13 + } + }, + "alsKind": [ + "call" + ] + } + ] + } + ] + } + }, + { + "send": { + "request": { + "jsonrpc": "2.0", + "id": 11, + "method": "textDocument/rename", + "params": { + "textDocument": { + "uri": "$URI{foo-bar1.ads}" + }, + "position": { + "line": 0, + "character": 12 + }, + "newName": "Bar2" + } + }, + "wait": [ + { + "id": 11, + "result": { + "documentChanges": [ + { + "textDocument": { + "uri": "$URI{foo-bar1.ads}", + "version": 1 + }, + "edits": [ + { + "range": { + "start": { + "line": 0, + "character": 12 + }, + "end": { + "line": 0, + "character": 16 + } + }, + "newText": "Bar2", + "annotationId": "" + }, + { + "range": { + "start": { + "line": 2, + "character": 8 + }, + "end": { + "line": 2, + "character": 12 + } + }, + "newText": "Bar2", + "annotationId": "" + } + ] + }, + { + "textDocument": { + "uri": "$URI{main.adb}", + "version": 1 + }, + "edits": [ + { + "range": { + "start": { + "line": 0, + "character": 9 + }, + "end": { + "line": 0, + "character": 13 + } + }, + "newText": "Bar2", + "annotationId": "" + }, + { + "range": { + "start": { + "line": 4, + "character": 7 + }, + "end": { + "line": 4, + "character": 11 + } + }, + "newText": "Bar2", + "annotationId": "" + } + ] + }, + { + "kind": "rename", + "oldUri": "$URI{foo-bar1.ads}", + "newUri": "$URI{foo-bar2.ads}" + } + ] + } + } + ] + } + }, + { + "send": { + "request": { + "jsonrpc": "2.0", + "method": "textDocument/didChange", + "params": { + "textDocument": { + "uri": "$URI{foo-bar1.ads}", + "version": 1 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 2, + "character": 8 + }, + "end": { + "line": 2, + "character": 12 + } + }, + "text": "" + } + ] + } + }, + "wait": [] + } + }, + { + "send": { + "request": { + "jsonrpc": "2.0", + "method": "textDocument/didChange", + "params": { + "textDocument": { + "uri": "$URI{foo-bar1.ads}", + "version": 2 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 2, + "character": 8 + }, + "end": { + "line": 2, + "character": 8 + } + }, + "text": "Bar2" + } + ] + } + }, + "wait": [] + } + }, + { + "send": { + "request": { + "jsonrpc": "2.0", + "method": "textDocument/didChange", + "params": { + "textDocument": { + "uri": "$URI{foo-bar1.ads}", + "version": 3 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 0, + "character": 12 + }, + "end": { + "line": 0, + "character": 16 + } + }, + "text": "" + } + ] + } + }, + "wait": [] + } + }, + { + "send": { + "request": { + "jsonrpc": "2.0", + "method": "textDocument/didChange", + "params": { + "textDocument": { + "uri": "$URI{foo-bar1.ads}", + "version": 4 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 0, + "character": 12 + }, + "end": { + "line": 0, + "character": 12 + } + }, + "text": "Bar2" + } + ] + } + }, + "wait": [] + } + }, + { + "send": { + "request": { + "jsonrpc": "2.0", + "method": "textDocument/didOpen", + "params": { + "textDocument": { + "uri": "$URI{main.adb}", + "languageId": "Ada", + "version": 0, + "text": "with Foo.Bar1;\n\nprocedure Main is\nbegin\n Foo.Bar1.N;\nend Main;\n" + } + } + }, + "wait": [] + } + }, + { + "send": { + "request": { + "jsonrpc": "2.0", + "method": "textDocument/didChange", + "params": { + "textDocument": { + "uri": "$URI{main.adb}", + "version": 1 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 4, + "character": 7 + }, + "end": { + "line": 4, + "character": 11 + } + }, + "text": "" + } + ] + } + }, + "wait": [] + } + }, + { + "send": { + "request": { + "jsonrpc": "2.0", + "method": "textDocument/didChange", + "params": { + "textDocument": { + "uri": "$URI{main.adb}", + "version": 2 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 4, + "character": 7 + }, + "end": { + "line": 4, + "character": 7 + } + }, + "text": "Bar2" + } + ] + } + }, + "wait": [] + } + }, + { + "send": { + "request": { + "jsonrpc": "2.0", + "method": "textDocument/didChange", + "params": { + "textDocument": { + "uri": "$URI{main.adb}", + "version": 3 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 0, + "character": 9 + }, + "end": { + "line": 0, + "character": 13 + } + }, + "text": "" + } + ] + } + }, + "wait": [] + } + }, + { + "send": { + "request": { + "jsonrpc": "2.0", + "method": "textDocument/didChange", + "params": { + "textDocument": { + "uri": "$URI{main.adb}", + "version": 4 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 0, + "character": 9 + }, + "end": { + "line": 0, + "character": 9 + } + }, + "text": "Bar2" + } + ] + } + }, + "wait": [] + } + }, + { + "send": { + "request": { + "jsonrpc": "2.0", + "method": "textDocument/didClose", + "params": { + "textDocument": { + "uri": "$URI{foo-bar1.ads}" + } + } + }, + "wait": [] + } + }, + { + "send": { + "request": { + "jsonrpc": "2.0", + "method": "textDocument/didOpen", + "params": { + "textDocument": { + "uri": "$URI{foo-bar2.ads}", + "languageId": "Ada", + "version": 0, + "text": "package Foo.Bar2 is\n procedure N is null;\nend Foo.Bar2;\n" + } + } + }, + "wait": [] + } + }, + { + "send": { + "request": { + "jsonrpc": "2.0", + "id": 12, + "method": "workspace/executeCommand", + "params": { + "arguments": [], + "command": "als-reload-project" + } + }, + "wait": [] + } + }, + { + "send": { + "request": { + "jsonrpc": "2.0", + "method": "textDocument/didClose", + "params": { + "textDocument": { + "uri": "$URI{foo-bar1.ads}" + } + } + }, + "wait": [] + } + }, + { + "send": { + "request": { + "jsonrpc": "2.0", + "method": "textDocument/didOpen", + "params": { + "textDocument": { + "uri": "$URI{foo-bar2.ads}", + "languageId": "Ada", + "version": 0, + "text": "package Foo.Bar2 is\n procedure N is null;\nend Foo.Bar2;\n" + } + } + }, + "wait": [] + } + }, + { + "send": { + "request": { + "jsonrpc": "2.0", + "method": "workspace/didRenameFiles", + "params": { + "files": [ + { + "oldUri": "$URI{foo-bar1.ads}", + "newUri": "$URI{foo-bar2.ads}" + } + ] + } + }, + "wait": [] + } + }, + { + "send": { + "request": { + "jsonrpc": "2.0", + "id": 22, + "method": "textDocument/references", + "params": { + "textDocument": { + "uri": "$URI{foo-bar2.ads}" + }, + "position": { + "line": 1, + "character": 13 + }, + "context": { + "includeDeclaration": true + } + } + }, + "wait": [ + { + "id": 22, + "result": [ + { + "uri": "$URI{foo-bar2.ads}", + "range": { + "start": { + "line": 1, + "character": 13 + }, + "end": { + "line": 1, + "character": 14 + } + }, + "alsKind": [ + "reference" + ] + }, + { + "uri": "$URI{main.adb}", + "range": { + "start": { + "line": 4, + "character": 12 + }, + "end": { + "line": 4, + "character": 13 + } + }, + "alsKind": [ + "call" + ] + } + ] + } + ] + } + }, + { + "send": { + "request": { + "jsonrpc": "2.0", + "method": "textDocument/didClose", + "params": { + "textDocument": { + "uri": "$URI{foo-bar2.ads}" + } + } + }, + "wait": [ + { + "method": "textDocument/publishDiagnostics", + "params": { + "uri": "$URI{foo-bar2.ads}", + "diagnostics": [] + } + } + ] + } + }, + { + "send": { + "request": { + "jsonrpc": "2.0", + "id": 24, + "method": "shutdown" + }, + "wait": [ + { + "id": 24, + "result": null + } + ] + } + }, + { + "stop": { + "exit_code": 0 + } + } +] diff --git a/testsuite/ada_lsp/Did_Rename_Files.context_corruption/test.yaml b/testsuite/ada_lsp/Did_Rename_Files.context_corruption/test.yaml new file mode 100644 index 000000000..947a313ae --- /dev/null +++ b/testsuite/ada_lsp/Did_Rename_Files.context_corruption/test.yaml @@ -0,0 +1 @@ +title: 'Did_Rename_Files.context_corruption' From a706d1d7383be63c01ba3c998e5554148ea148f1 Mon Sep 17 00:00:00 2001 From: Elie Richa Date: Mon, 11 Mar 2024 16:14:02 +0000 Subject: [PATCH 2/5] Make test restore file content --- .../vscode/ada/test/suite/general/extension.test.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/integration/vscode/ada/test/suite/general/extension.test.ts b/integration/vscode/ada/test/suite/general/extension.test.ts index 8a3e55f47..e11f57486 100644 --- a/integration/vscode/ada/test/suite/general/extension.test.ts +++ b/integration/vscode/ada/test/suite/general/extension.test.ts @@ -5,6 +5,7 @@ import { getProjectFile, getObjectDir } from '../../../src/helpers'; import { assertEqualToFileContent, activate } from '../utils'; import * as vscode from 'vscode'; +import { readFileSync, writeFileSync } from 'fs'; suite('Extensions Test Suite', function () { // Make sure the extension is activated @@ -38,6 +39,7 @@ suite('Extensions Test Suite', function () { ]; const folder = vscode.workspace.workspaceFolders[0].uri; const fileUri = vscode.Uri.joinPath(folder, 'src', 'test_subprogram_box.adb'); + const contentBefore = readFileSync(fileUri.fsPath); const expectedUri = vscode.Uri.joinPath(folder, 'src', 'test_subprogram_box.expected'); for (const cursorPos of cursorPositions) { @@ -47,8 +49,16 @@ suite('Extensions Test Suite', function () { await vscode.commands.executeCommand('ada.subprogramBox'); } const editorText = vscode.window.activeTextEditor?.document.getText() ?? ''; + vscode.window.activeTextEditor?.hide(); - assertEqualToFileContent(editorText, expectedUri); + try { + assertEqualToFileContent(editorText, expectedUri); + } finally { + /** + * Restore the old file content + */ + writeFileSync(fileUri.fsPath, contentBefore); + } } else { throw new Error('No workspace folder found for the specified URI'); } From 97135ad1d06db79ae41cbfeea93badd1c5104e14 Mon Sep 17 00:00:00 2001 From: Elie Richa Date: Wed, 6 Mar 2024 13:19:18 +0000 Subject: [PATCH 3/5] Migrate vscode testing to newer framework --- .vscode/settings.json.tmpl | 5 ++ doc/HACKING.md | 7 +- integration/vscode/ada/.vscode-test.mjs | 86 +++++++++++++++++++ integration/vscode/ada/.vscodeignore | 1 + integration/vscode/ada/package.json | 6 +- .../ada/test/suite/general/codelens.test.ts | 1 - .../ada/test/suite/general/debug.test.ts | 1 - .../vscode/ada/test/suite/general/env.test.ts | 1 - .../ada/test/suite/general/extension.test.ts | 1 - .../test/suite/general/highlighting.test.ts | 1 - .../vscode/ada/test/suite/general/index.ts | 5 -- .../ada/test/suite/general/syntax.test.ts | 1 - .../ada/test/suite/general/tasks.test.ts | 1 - .../ada/test/suite/gnattest/gnattest.test.ts | 1 - .../vscode/ada/test/suite/gnattest/index.ts | 5 -- integration/vscode/ada/test/suite/utils.ts | 60 ------------- .../suite/workspace_missing_dirs/index.ts | 5 -- .../workspace_missing_dirs.test.ts | 1 - 18 files changed, 98 insertions(+), 91 deletions(-) create mode 100644 integration/vscode/ada/.vscode-test.mjs delete mode 100644 integration/vscode/ada/test/suite/general/index.ts delete mode 100644 integration/vscode/ada/test/suite/gnattest/index.ts delete mode 100644 integration/vscode/ada/test/suite/workspace_missing_dirs/index.ts diff --git a/.vscode/settings.json.tmpl b/.vscode/settings.json.tmpl index 3b9325730..bec9ca153 100644 --- a/.vscode/settings.json.tmpl +++ b/.vscode/settings.json.tmpl @@ -88,5 +88,10 @@ "**/node_modules/*/**": true, "**/.hg/store/**": true, ".obj/": true + }, + "extension-test-runner.extractSettings": { + "suite": ["describe", "suite"], + "test": ["it", "test"], + "extractWith": "syntax" } } diff --git a/doc/HACKING.md b/doc/HACKING.md index 9c6dee45c..1c2df3881 100644 --- a/doc/HACKING.md +++ b/doc/HACKING.md @@ -142,10 +142,9 @@ To write a functional test for Ada Language Server: Run `make vscode-test` to run the VS Code testsuite. If you open the ALS repository in VS Code, it is also possible to run VS Code -integration tests using the provided launch configurations: - -- `(vscode) Run testsuite 'general'` -- `(vscode) Run testsuite 'gnattest'` +integration tests using the Testing view. +The `integration/vscode/ada/.vscode-test.mjs` contains a configuration allowing to load the tests in the Testing view. +The UI offers ways to run all or a subset of the tests. ### Other tests diff --git a/integration/vscode/ada/.vscode-test.mjs b/integration/vscode/ada/.vscode-test.mjs new file mode 100644 index 000000000..44cb3a281 --- /dev/null +++ b/integration/vscode/ada/.vscode-test.mjs @@ -0,0 +1,86 @@ +import { defineConfig } from '@vscode/test-cli'; +import { mkdtempSync } from 'fs'; +import * as os from 'os'; +import { join } from 'path'; + +let baseMochaOptions = { + ui: 'tdd', + color: true, +}; + +if (process.env.MOCHA_REPORTER) { + // If a reporter was specified externally, use it. For example, the CI + // environment could set this to 'mocha-junit-reporter' to produce JUnit + // results. + baseMochaOptions.reporter = process.env.MOCHA_REPORTER; +} + +if (!baseMochaOptions.reporterOptions) { + baseMochaOptions.reporterOptions = { + maxDiffSize: 0, + }; +} + +if (process.env['MOCHA_TIMEOUT']) { + baseMochaOptions.timeout = process.env['MOCHA_TIMEOUT']; +} else { + /** + * Some tests involve calling gprbuild which takes time. So we disable test + * timeouts altogether. + */ + baseMochaOptions.timeout = '0'; +} + +if (process.env['MOCHA_GREP']) { + baseMochaOptions.grep = process.env['MOCHA_GREP']; +} + +const testsuites = ['general', 'gnattest', 'workspace_missing_dirs']; + +export default defineConfig( + testsuites.map((suiteName) => { + // --user-data-dir is set to a unique dirctory under the OS + // default tmp directory for temporary files to avoid + // warnings related to longs paths in IPC sockets created by + // VSCode. The directory is made unique to avoid + // interference between successive runs. + // + // It also allows multiple testsuites to run concurrently with each VS + // Code instance using a different User data directory. This can happen + // when tests are launched from the VS Code UI. + const tmpdir = mkdtempSync(`${os.tmpdir()}/vsc-ada-test-`); + + // Create a mocha options objects by copying the base one + let mochaOptions = { ...baseMochaOptions }; + + if (process.env.MOCHA_REPORTER) { + /** + * Produce results for each testsuite separately + */ + const mochaFile = process.env.MOCHA_RESULTS_DIR + ? join(process.env.MOCHA_RESULTS_DIR, `${suiteName}.xml`) + : `${suiteName}.xml`; + mochaOptions.reporterOptions = { mochaFile: mochaFile }; + } + + return { + label: `Ada extension testsuite: ${suiteName}`, + files: `out/test/suite/${suiteName}/**/*.test.js`, + workspaceFolder: `./test/workspaces/${suiteName}`, + mocha: mochaOptions, + env: { + // When working remotely on Linux, it is necessary to have "Xvfb + // :99" running in the background, and this env variable set for + // the VS Code instances spawned for testing. + // + // This may prevent running locally on Linux and having the test + // windows visible, but we consider this a minor use case for + // now. A workaround is to remove this line. + DISPLAY: ':99', + }, + launchArgs: ['--user-data-dir', tmpdir], + // Use external installation if provided in the VSCODE env variable + useInstallation: process.env.VSCODE ? { fromPath: process.env.VSCODE } : undefined, + }; + }) +); diff --git a/integration/vscode/ada/.vscodeignore b/integration/vscode/ada/.vscodeignore index a195a29d3..9c0e0a8e4 100644 --- a/integration/vscode/ada/.vscodeignore +++ b/integration/vscode/ada/.vscodeignore @@ -1,4 +1,5 @@ # This file contains patterns to exclude from the packaging into a .vsix +.vscode-test.mjs .vscode-test/ advanced/ node_modules/ diff --git a/integration/vscode/ada/package.json b/integration/vscode/ada/package.json index 5afb4d7c8..b0f30a6f1 100644 --- a/integration/vscode/ada/package.json +++ b/integration/vscode/ada/package.json @@ -883,9 +883,9 @@ "pretest": "npm run compile", "lint": "eslint \"./src/**/*.{js,ts,tsx}\" --quiet --fix", "cilint": "eslint \"./src/**/*.{js,ts,tsx}\"", - "test": "node ./out/test/runTest.js", - "vscode-test": "vscode-test", - "resolve-backtrace": "npx stacktracify" + "test": "vscode-test", + "resolve-backtrace": "npx stacktracify", + "clean": "node -e \"fs.rmSync('out',{force:true,recursive:true})\"" }, "dependencies": { "@types/command-exists": "1.2.0", diff --git a/integration/vscode/ada/test/suite/general/codelens.test.ts b/integration/vscode/ada/test/suite/general/codelens.test.ts index eef1fceb5..c07ca4a03 100644 --- a/integration/vscode/ada/test/suite/general/codelens.test.ts +++ b/integration/vscode/ada/test/suite/general/codelens.test.ts @@ -1,5 +1,4 @@ import assert from 'assert'; -import { suite, test } from 'mocha'; import { Uri, window, workspace } from 'vscode'; import { adaExtState } from '../../../src/extension'; import { activate } from '../utils'; diff --git a/integration/vscode/ada/test/suite/general/debug.test.ts b/integration/vscode/ada/test/suite/general/debug.test.ts index 33a515a59..92b146cb7 100644 --- a/integration/vscode/ada/test/suite/general/debug.test.ts +++ b/integration/vscode/ada/test/suite/general/debug.test.ts @@ -1,5 +1,4 @@ import assert from 'assert'; -import { suite, test } from 'mocha'; import { AdaConfig, adaDynamicDebugConfigProvider, diff --git a/integration/vscode/ada/test/suite/general/env.test.ts b/integration/vscode/ada/test/suite/general/env.test.ts index e2fda93ed..8e412d37b 100644 --- a/integration/vscode/ada/test/suite/general/env.test.ts +++ b/integration/vscode/ada/test/suite/general/env.test.ts @@ -1,5 +1,4 @@ import assert from 'assert'; -import { suite, test } from 'mocha'; import { setTerminalEnvironment } from '../../../src/helpers'; suite('Environment init', () => { diff --git a/integration/vscode/ada/test/suite/general/extension.test.ts b/integration/vscode/ada/test/suite/general/extension.test.ts index e11f57486..268d93db1 100644 --- a/integration/vscode/ada/test/suite/general/extension.test.ts +++ b/integration/vscode/ada/test/suite/general/extension.test.ts @@ -1,6 +1,5 @@ import * as assert from 'assert'; import { adaExtState } from '../../../src/extension'; -import { suite, test } from 'mocha'; import { getProjectFile, getObjectDir } from '../../../src/helpers'; import { assertEqualToFileContent, activate } from '../utils'; diff --git a/integration/vscode/ada/test/suite/general/highlighting.test.ts b/integration/vscode/ada/test/suite/general/highlighting.test.ts index d5184af8a..a4f70b74d 100644 --- a/integration/vscode/ada/test/suite/general/highlighting.test.ts +++ b/integration/vscode/ada/test/suite/general/highlighting.test.ts @@ -1,7 +1,6 @@ import assert from 'assert'; import * as vscode from 'vscode'; import { spawnSync } from 'child_process'; -import { suite, test } from 'mocha'; import { existsSync, opendirSync, renameSync } from 'fs'; import path, { basename, dirname } from 'path'; import { SemanticTokensParams, SemanticTokensRequest, integer } from 'vscode-languageclient'; diff --git a/integration/vscode/ada/test/suite/general/index.ts b/integration/vscode/ada/test/suite/general/index.ts deleted file mode 100644 index fc214d3df..000000000 --- a/integration/vscode/ada/test/suite/general/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { runMochaTestsuite } from './../utils'; - -export function run(): Promise { - return runMochaTestsuite('general', __dirname); -} diff --git a/integration/vscode/ada/test/suite/general/syntax.test.ts b/integration/vscode/ada/test/suite/general/syntax.test.ts index dd2f99a26..48e8836ac 100644 --- a/integration/vscode/ada/test/suite/general/syntax.test.ts +++ b/integration/vscode/ada/test/suite/general/syntax.test.ts @@ -1,7 +1,6 @@ import * as assert from 'assert'; import { adaExtState } from '../../../src/extension'; import { AdaGrammarRule, AdaSyntaxCheckProvider } from '../../../src/alsProtocolExtensions'; -import { suite, test } from 'mocha'; import { activate } from '../utils'; suite('Syntax Check Test Suite', function () { diff --git a/integration/vscode/ada/test/suite/general/tasks.test.ts b/integration/vscode/ada/test/suite/general/tasks.test.ts index abe72580a..eb02c1976 100644 --- a/integration/vscode/ada/test/suite/general/tasks.test.ts +++ b/integration/vscode/ada/test/suite/general/tasks.test.ts @@ -1,6 +1,5 @@ import assert from 'assert'; import { existsSync } from 'fs'; -import { suite, test } from 'mocha'; import * as vscode from 'vscode'; import { exe, getProjectFile } from '../../../src/helpers'; import { diff --git a/integration/vscode/ada/test/suite/gnattest/gnattest.test.ts b/integration/vscode/ada/test/suite/gnattest/gnattest.test.ts index b7c324ecd..3d395253f 100644 --- a/integration/vscode/ada/test/suite/gnattest/gnattest.test.ts +++ b/integration/vscode/ada/test/suite/gnattest/gnattest.test.ts @@ -1,4 +1,3 @@ -import { suite } from 'mocha'; import { activate } from '../utils'; suite('GNATtest Integration Tests', function () { diff --git a/integration/vscode/ada/test/suite/gnattest/index.ts b/integration/vscode/ada/test/suite/gnattest/index.ts deleted file mode 100644 index 672e2c15f..000000000 --- a/integration/vscode/ada/test/suite/gnattest/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { runMochaTestsuite } from './../utils'; - -export function run(): Promise { - return runMochaTestsuite('gnattest', __dirname); -} diff --git a/integration/vscode/ada/test/suite/utils.ts b/integration/vscode/ada/test/suite/utils.ts index de59b69ed..f1938daa0 100644 --- a/integration/vscode/ada/test/suite/utils.ts +++ b/integration/vscode/ada/test/suite/utils.ts @@ -60,63 +60,3 @@ export async function activate(): Promise { } } } - -export function runMochaTestsuite(suiteName: string, suiteDirectory: string) { - const mochaOptions: MochaOptions = { - ui: 'bdd', - color: true, - }; - - if (process.env.MOCHA_REPORTER) { - // If a reporter was specified externally, use it. For example, the CI - // environment could set this to 'mocha-junit-reporter' to produce JUnit - // results. - mochaOptions.reporter = process.env.MOCHA_REPORTER; - const mochaFile = process.env.MOCHA_RESULTS_DIR - ? path.join(`${process.env.MOCHA_RESULTS_DIR}`, `${suiteName}.xml`) - : `${suiteName}.xml`; - mochaOptions.reporterOptions = { - mochaFile, - }; - } - - if (!mochaOptions.reporterOptions) { - mochaOptions.reporterOptions = { - maxDiffSize: 0, - }; - } - - // Create the mocha test - const mocha = new Mocha(mochaOptions); - - return new Promise((resolve, reject) => { - const globOptions: GlobOptionsWithFileTypesUnset = { cwd: suiteDirectory }; - const glob = new Glob('**/*.test.js', globOptions); - for (const file of glob) { - mocha.addFile(path.resolve(suiteDirectory, file)); - } - try { - // This variable is set in the launch configuration (launch.json) of - // the VS Code workspace to allow debugging without triggering test - // timeouts. - if (env['MOCHA_TIMEOUT']) { - mocha.timeout(env['MOCHA_TIMEOUT']); - } - - if (env['MOCHA_GREP']) { - mocha.grep(env['MOCHA_GREP']); - } - - // Run the mocha test - mocha.run((failures) => { - if (failures > 0) { - reject(new Error(`${failures} tests failed.`)); - } else { - resolve(); - } - }); - } catch (err) { - reject(err); - } - }); -} diff --git a/integration/vscode/ada/test/suite/workspace_missing_dirs/index.ts b/integration/vscode/ada/test/suite/workspace_missing_dirs/index.ts deleted file mode 100644 index bb9c85217..000000000 --- a/integration/vscode/ada/test/suite/workspace_missing_dirs/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { runMochaTestsuite } from '../utils'; - -export function run(): Promise { - return runMochaTestsuite('workspace_missing_dirs', __dirname); -} diff --git a/integration/vscode/ada/test/suite/workspace_missing_dirs/workspace_missing_dirs.test.ts b/integration/vscode/ada/test/suite/workspace_missing_dirs/workspace_missing_dirs.test.ts index 619991ba8..d9a7afd10 100644 --- a/integration/vscode/ada/test/suite/workspace_missing_dirs/workspace_missing_dirs.test.ts +++ b/integration/vscode/ada/test/suite/workspace_missing_dirs/workspace_missing_dirs.test.ts @@ -1,5 +1,4 @@ import * as assert from 'assert'; -import { suite, test } from 'mocha'; import { activate } from '../utils'; import * as vscode from 'vscode'; From 82bfe5a18dc2639dce69c61bd4c1ecb67c02b6a6 Mon Sep 17 00:00:00 2001 From: Anthony Leonardo Gracio Date: Fri, 22 Mar 2024 10:45:05 +0000 Subject: [PATCH 4/5] Apply 1 suggestion(s) to 1 file(s) --- integration/vscode/ada/.vscode-test.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/vscode/ada/.vscode-test.mjs b/integration/vscode/ada/.vscode-test.mjs index 44cb3a281..3a5d36d5a 100644 --- a/integration/vscode/ada/.vscode-test.mjs +++ b/integration/vscode/ada/.vscode-test.mjs @@ -39,7 +39,7 @@ const testsuites = ['general', 'gnattest', 'workspace_missing_dirs']; export default defineConfig( testsuites.map((suiteName) => { - // --user-data-dir is set to a unique dirctory under the OS + // --user-data-dir is set to a unique directory under the OS // default tmp directory for temporary files to avoid // warnings related to longs paths in IPC sockets created by // VSCode. The directory is made unique to avoid From 772eb16925bc8fe58dc48ac8d3b77a96bc7c2233 Mon Sep 17 00:00:00 2001 From: Elie Richa Date: Fri, 22 Mar 2024 11:43:39 +0000 Subject: [PATCH 5/5] Add Extension Test Runner to recommended extensions --- .vscode/extensions.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 6fdf84f32..1736b5d26 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -8,6 +8,7 @@ "esbenp.prettier-vscode", "gruntfuggly.triggertaskonsave", "davidanson.vscode-markdownlint", - "adacore.ada" + "adacore.ada", + "ms-vscode.extension-test-runner" ] }