diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b2ac9f8..5173c93 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -50,13 +50,7 @@ jobs: - name: Build package run: npm run esbuild - - name: Test language server - run: npm run test:server - - - name: Test debugger - run: npm run test:debugger - - - name: Test language server client + - name: Tests run: | Xvfb -ac :99 -screen 0 1280x1024x16 & export DISPLAY=:99 diff --git a/README.md b/README.md index b2db48f..14edd68 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ licensed under the MIT license and adapted to the MetaModelica language server. The debugger is based on [microsoft/vscode-mock-debug](https://github.com/microsoft/vscode-mock-debug) licensed under MIT. [OpenModelica/tree-sitter-metamodelica](https://github.com/OpenModelica/tree-sitter-metamodelica) -v0.2.0 is included in this extension and is licensed under the [OSMC-PL +is included in this extension and is licensed under the [OSMC-PL v1.8](./server/OSMC-License.txt). ## Acknowledgments diff --git a/package-lock.json b/package-lock.json index 10eaec1..7f8b910 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,14 +29,13 @@ "eslint": "^9.22.0", "glob": "^11.0.1", "mocha": "^11.1.0", - "run-script-os": "^1.1.6", "tree-sitter-cli": "^0.23.2", "tree-sitter-gdbmi": "github:novafacing/tree-sitter-gdbmi", "ts-node": "^10.9.2" }, "engines": { "node": "22", - "vscode": "^1.88.0" + "vscode": "^1.98.0" } }, "node_modules/@cspotcode/source-map-support": { @@ -3516,17 +3515,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/run-script-os": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/run-script-os/-/run-script-os-1.1.6.tgz", - "integrity": "sha512-ql6P2LzhBTTDfzKts+Qo4H94VUKpxKDFz6QxxwaUZN0mwvi7L3lpOI7BqPCq7lgDh3XLl0dpeXwfcVIitlrYrw==", - "dev": true, - "license": "MIT", - "bin": { - "run-os": "index.js", - "run-script-os": "index.js" - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", diff --git a/package.json b/package.json index c19305c..6fb55a0 100644 --- a/package.json +++ b/package.json @@ -130,15 +130,10 @@ "test-compile": "tsc -b ./", "lint": "eslint ./client/src ./server/src ./debugger/src --ext .ts,.tsx", "tree-sitter-metamodelica": "cd src/server && node tree.sitter.metamodelica.download.js", - "tree-sitter-gdbmi": "cd node_modules/tree-sitter-gdbmi && npx tree-sitter generate && npx tree-sitter build --wasm --docker . && cd ../../scripts && node tree.sitter.gdbmi.js", + "tree-sitter-gdbmi": "cd node_modules/tree-sitter-gdbmi && npx tree-sitter generate && npx tree-sitter build --wasm --docker . && cd ../../src/debugger/parser && node tree.sitter.gdbmi.js", "postinstall": "npm run tree-sitter-metamodelica && npm run tree-sitter-gdbmi", - "test-compile-post": "run-script-os", - "test-compile-post:win32": ".\\scripts\\e2e.bat", - "test-compile-post:default": "sh ./scripts/e2e.sh", - "test": "npm run test-compile && npm run test-compile-post", - "test:server": "cd test && npx mocha -r ts-node/register server/**/*.test.ts", - "test:debugger": "cd test && npx mocha -r ts-node/register debugger/**/*.test.ts", - "all": "npm run postinstall && npm run esbuild && npm run lint && npm run test:server && npm run test:debugger && npm run test && npm run vscode:prepublish" + "test": "npm run test-compile && node ./dist/test/runTest", + "all": "npm run postinstall && npm run esbuild && npm run lint && npm run test && npm run vscode:prepublish" }, "dependencies": { "vscode-languageclient": "^9.0.1", @@ -156,7 +151,6 @@ "eslint": "^9.22.0", "mocha": "^11.1.0", "ts-node": "^10.9.2", - "run-script-os": "^1.1.6", "@types/vscode": "^1.98.0", "@vscode/test-electron": "^2.4.1", "glob": "^11.0.1", diff --git a/scripts/e2e.bat b/scripts/e2e.bat deleted file mode 100644 index 9b39a09..0000000 --- a/scripts/e2e.bat +++ /dev/null @@ -1,3 +0,0 @@ -@echo off - -node "%CD%/dist/test/client/runTest" diff --git a/scripts/e2e.sh b/scripts/e2e.sh deleted file mode 100644 index 27426ac..0000000 --- a/scripts/e2e.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -node "$(pwd)/dist/test/client/runTest" diff --git a/scripts/tree.sitter.gdbmi.js b/scripts/tree.sitter.gdbmi.js deleted file mode 100644 index 001243b..0000000 --- a/scripts/tree.sitter.gdbmi.js +++ /dev/null @@ -1,3 +0,0 @@ -const fs = require('fs'); - -fs.copyFileSync('../node_modules/tree-sitter-gdbmi/tree-sitter-gdbmi.wasm', '../src/debugger/parser/tree-sitter-gdbmi.wasm'); diff --git a/src/debugger/gdb/gdbAdapter.ts b/src/debugger/gdb/gdbAdapter.ts index 4fa879d..bf6ce9a 100644 --- a/src/debugger/gdb/gdbAdapter.ts +++ b/src/debugger/gdb/gdbAdapter.ts @@ -36,11 +36,12 @@ import { ChildProcess, spawn } from 'child_process'; import { EventEmitter } from 'events'; import { existsSync } from 'fs'; +import * as vscode from 'vscode'; import * as CommandFactory from './commandFactory'; import { logger } from '../../util/logger'; import { GDBMIParser, GDBMIOutput, - GDBMIResult, GDBMIOutOfBandRecord, GDBMIAsyncOutput + GDBMIResult, GDBMIOutOfBandRecord, GDBMIAsyncOutput, GDBMIStreamRecordType } from '../parser/gdbParser'; export enum GDBCommandFlag { @@ -199,8 +200,7 @@ export class GDBAdapter extends EventEmitter { this.processGDBMIOutOfBandRecord(miOutOfBandRecord); } } else { - this.programOutput += response; - // todo + this.writeToDebugConsole(response); } if (this.gdbmiCommandOutput) { @@ -227,6 +227,13 @@ export class GDBAdapter extends EventEmitter { }); } + private writeToDebugConsole(output: string) { + this.programOutput += output; + const debugConsole = vscode.debug.activeDebugConsole; + // Use the appendLine method + debugConsole.appendLine(output); + } + /** * Processes GDB/MI out-of-band records. * @@ -249,8 +256,19 @@ export class GDBAdapter extends EventEmitter { } else if (outOfBandRecord.miStreamRecord) { // Handle stream records (e.g., console, target, or log output) const streamOutput = outOfBandRecord.miStreamRecord?.value; - if (streamOutput) { - this.programOutput += streamOutput; + switch (outOfBandRecord.miStreamRecord?.type) { + case GDBMIStreamRecordType.consoleStream: + // todo. Add configuration to show/hide console output + break; + case GDBMIStreamRecordType.targetStream: + this.writeToDebugConsole(streamOutput); + break; + case GDBMIStreamRecordType.logStream: + // todo. Add configuration to show/hide log output + break; + default: + logger.error(`GDB: Unknown stream record type: ${outOfBandRecord.miStreamRecord?.type}`); + break; } } } diff --git a/src/debugger/parser/tree.sitter.gdbmi.js b/src/debugger/parser/tree.sitter.gdbmi.js new file mode 100644 index 0000000..6ed6bb9 --- /dev/null +++ b/src/debugger/parser/tree.sitter.gdbmi.js @@ -0,0 +1,3 @@ +const fs = require('fs'); + +fs.copyFileSync('../../../node_modules/tree-sitter-gdbmi/tree-sitter-gdbmi.wasm', 'tree-sitter-gdbmi.wasm'); diff --git a/test/debugger/test/breakpoints.test.ts b/test/debugger/breakpoints.test.ts similarity index 91% rename from test/debugger/test/breakpoints.test.ts rename to test/debugger/breakpoints.test.ts index 443ef65..bf2570a 100644 --- a/test/debugger/test/breakpoints.test.ts +++ b/test/debugger/breakpoints.test.ts @@ -33,12 +33,12 @@ * */ -import { BreakpointHandler } from '../../../src/debugger/breakpoints/breakpoints'; +import { BreakpointHandler } from '../../src/debugger/breakpoints/breakpoints'; import { DebugProtocol } from '@vscode/debugprotocol'; import assert from 'assert'; -describe('Breakpoints', () => { - it('Add a new breakpoint', () => { +suite('Breakpoints', () => { + test('Add a new breakpoint', () => { const handler = new BreakpointHandler(); const source: DebugProtocol.Source = { path: 'test/path' }; handler.addBreakpoint(1, source, 10); @@ -49,7 +49,7 @@ describe('Breakpoints', () => { assert.strictEqual(breakpoints[0].line, 10); }); - it('Retrieve breakpoints by source', () => { + test('Retrieve breakpoints by source', () => { const handler = new BreakpointHandler(); const source1: DebugProtocol.Source = { path: 'test/path1' }; const source2: DebugProtocol.Source = { path: 'test/path2' }; @@ -68,7 +68,7 @@ describe('Breakpoints', () => { assert.strictEqual(breakpoints2[0].line, 20); }); - it('Retrieve breakpoint IDs by file path', () => { + test('Retrieve breakpoint IDs by file path', () => { const handler = new BreakpointHandler(); const source: DebugProtocol.Source = { path: 'test/path' }; handler.addBreakpoint(1, source, 10); @@ -78,7 +78,7 @@ describe('Breakpoints', () => { assert.deepStrictEqual(ids, [1, 2]); }); - it('Delete breakpoints by IDs', () => { + test('Delete breakpoints by IDs', () => { const handler = new BreakpointHandler(); const source: DebugProtocol.Source = { path: 'test/path' }; handler.addBreakpoint(1, source, 10); diff --git a/test/debugger/test/gdbAdapter.test.ts b/test/debugger/gdbAdapter.test.ts similarity index 91% rename from test/debugger/test/gdbAdapter.test.ts rename to test/debugger/gdbAdapter.test.ts index fa33df4..9c89465 100644 --- a/test/debugger/test/gdbAdapter.test.ts +++ b/test/debugger/gdbAdapter.test.ts @@ -37,9 +37,9 @@ import * as assert from 'assert'; import { exec } from 'child_process'; import * as process from 'process'; -import { GDBAdapter } from '../../../src/debugger/gdb/gdbAdapter'; -import * as CommandFactory from '../../../src/debugger/gdb/commandFactory'; -import { setLogLevel } from '../../../src/util/logger'; +import { GDBAdapter } from '../../src/debugger/gdb/gdbAdapter'; +import * as CommandFactory from '../../src/debugger/gdb/commandFactory'; +import { setLogLevel } from '../../src/util/logger'; async function which(programName: string): Promise { return new Promise((resolve, reject) => { @@ -75,8 +75,8 @@ async function getOMCAndGDB(): Promise<[string, string]> { return [omcExecutable, gdbExecutable]; } -describe('GDBAdapter', () => { - it('Start and stop GDBAdapter with omc', async () => { +suite('GDBAdapter', () => { + test('Start and stop GDBAdapter with omc', async () => { setLogLevel('warning'); const adapter = new GDBAdapter(); @@ -87,7 +87,7 @@ describe('GDBAdapter', () => { assert.equal(adapter.isGDBRunning(), false, "Assert GDB is not running any more."); }).timeout("10s"); - it('Run GDBAdapter with omc --version', async () => { + test('Run GDBAdapter with omc --version', async () => { setLogLevel('warning'); const adapter = new GDBAdapter(); const [omcExecutable, gdbExecutable] = await getOMCAndGDB(); @@ -105,7 +105,7 @@ describe('GDBAdapter', () => { assert.equal(adapter.isGDBRunning(), false, "Assert GDB is not running any more."); }).timeout("10s"); - it('Run GDBAdapter with omc and setupGDB', async () => { + test('Run GDBAdapter with omc and setupGDB', async () => { setLogLevel('warning'); const adapter = new GDBAdapter(); const [omcExecutable, gdbExecutable] = await getOMCAndGDB(); diff --git a/test/debugger/test/gdbParser.test.ts b/test/debugger/gdbParser.test.ts similarity index 91% rename from test/debugger/test/gdbParser.test.ts rename to test/debugger/gdbParser.test.ts index ba1a7fd..8a7b82b 100644 --- a/test/debugger/test/gdbParser.test.ts +++ b/test/debugger/gdbParser.test.ts @@ -34,16 +34,16 @@ */ import * as assert from 'assert'; -import { GDBMIParser } from '../../../src/debugger/parser/gdbParser'; -import { setLogLevel } from '../../../src/util/logger'; +import { GDBMIParser } from '../../src/debugger/parser/gdbParser'; +import { setLogLevel } from '../../src/util/logger'; -describe('GDB/MI Parser', () => { - it('Initialize parser', async () => { +suite('GDB/MI Parser', () => { + test('Initialize parser', async () => { const gdbMiParser = new GDBMIParser(); await gdbMiParser.initialize(); }); - it('Parse -break-insert output', async () => { + test('Parse -break-insert output', async () => { setLogLevel("warning"); const gdbMiParser = new GDBMIParser(); await gdbMiParser.initialize(); @@ -80,7 +80,7 @@ describe('GDB/MI Parser', () => { assert.deepEqual(gdbmiOutput, breakpointOutput); }).timeout("2s"); - it('Parse -exec-run output', async () => { + test('Parse -exec-run output', async () => { const gdbMiParser = new GDBMIParser(); await gdbMiParser.initialize(); const gdbmiOutput = gdbMiParser.parse('10^running\n'); @@ -88,7 +88,7 @@ describe('GDB/MI Parser', () => { assert.equal(gdbmiOutput.miResultRecord?.cls, "running", `Expected result class "running" got ${gdbmiOutput.miResultRecord?.cls}`); }).timeout("2s"); - it('Parse stop event output', async () => { + test('Parse stop event output', async () => { const gdbMiParser = new GDBMIParser(); await gdbMiParser.initialize(); const gdbmiOutput = gdbMiParser.parse('*stopped,reason="exited-normally"\n'); diff --git a/test/client/index.ts b/test/index.ts similarity index 69% rename from test/client/index.ts rename to test/index.ts index fb2051a..6a29b3b 100644 --- a/test/client/index.ts +++ b/test/index.ts @@ -36,6 +36,7 @@ import * as path from 'path'; import Mocha from 'mocha'; import {glob} from 'glob'; +import * as fs from 'fs'; export function run(): Promise { // Create the mocha test @@ -48,7 +49,19 @@ export function run(): Promise { const testsRoot = __dirname; return new Promise((resolve, reject) => { - glob('**.test.js', { cwd: testsRoot }).then(files => { + // this code is running from dist/test folder so set the paths accordingly + // copy tree-sitter-metamodelica.wasm + let sourcePath = path.resolve(__dirname, '../../src/server/tree-sitter-metamodelica.wasm'); + let destinationPath = path.resolve(__dirname, '../src/server/tree-sitter-metamodelica.wasm'); + fs.mkdirSync(path.dirname(destinationPath), { recursive: true }); + fs.copyFileSync(sourcePath, destinationPath); + // copy tree-sitter-gdbmi.wasm + sourcePath = path.resolve(__dirname, '../../src/debugger/parser/tree-sitter-gdbmi.wasm'); + destinationPath = path.resolve(__dirname, '../src/debugger/parser/tree-sitter-gdbmi.wasm'); + fs.mkdirSync(path.dirname(destinationPath), { recursive: true }); + fs.copyFileSync(sourcePath, destinationPath); + // add all test files to the mocha test suite + glob('**/**.test.js', { cwd: testsRoot }).then(files => { // Add files to the test suite files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); diff --git a/test/client/runTest.ts b/test/runTest.ts similarity index 95% rename from test/client/runTest.ts rename to test/runTest.ts index 88c1995..0546b0b 100644 --- a/test/client/runTest.ts +++ b/test/runTest.ts @@ -41,13 +41,11 @@ async function main() { try { // The folder containing the Extension Manifest package.json // Passed to `--extensionDevelopmentPath` - const extensionDevelopmentPath = path.resolve(__dirname, '../../../'); - console.log(extensionDevelopmentPath); + const extensionDevelopmentPath = path.resolve(__dirname, '../../'); // The path to test runner // Passed to --extensionTestsPath const extensionTestsPath = path.resolve(__dirname, './index'); - console.log(extensionTestsPath); // Download VS Code, unzip it and run the integration test await runTests({ extensionDevelopmentPath, extensionTestsPath }); diff --git a/test/server/declarations.test.ts b/test/server/declarations.test.ts index 79b692d..b3d4d35 100644 --- a/test/server/declarations.test.ts +++ b/test/server/declarations.test.ts @@ -99,8 +99,8 @@ const expectedSymbols = [ ) ]; -describe('nodeToDocumentSymbol', () => { - it('type to TypeParameter', async () => { +suite('nodeToDocumentSymbol', () => { + test('type to TypeParameter', async () => { const parser = await initializeMetaModelicaParser(); const tree = parser.parse("type Temperature = Real(unit = \"K \");"); const queries = new MetaModelicaQueries(parser.getLanguage()); @@ -111,8 +111,8 @@ describe('nodeToDocumentSymbol', () => { }); }); -describe('getAllDeclarationsInTree', () => { - it('Definitions and types', async () => { +suite('getAllDeclarationsInTree', () => { + test('Definitions and types', async () => { const parser = await initializeMetaModelicaParser(); const tree = parser.parse(metaModelicaTestString); const queries = new MetaModelicaQueries(parser.getLanguage()); diff --git a/test/server/diagnostics.test.ts b/test/server/diagnostics.test.ts index a4d7c0c..abd50b9 100644 --- a/test/server/diagnostics.test.ts +++ b/test/server/diagnostics.test.ts @@ -175,8 +175,8 @@ const expectedDiagnostics: LSP.Diagnostic[] = [ } ]; -describe('getAllDeclarationsInTree', () => { - it('Definitions and types', async () => { +suite('getAllDeclarationsInTree', () => { + test('Definitions and types', async () => { const parser = await initializeMetaModelicaParser(); const tree = parser.parse(metaModelicaTestString); const queries = new MetaModelicaQueries(parser.getLanguage()); diff --git a/test/server/server.test.ts b/test/server/server.test.ts index 1689294..268a512 100644 --- a/test/server/server.test.ts +++ b/test/server/server.test.ts @@ -46,19 +46,19 @@ end M; `; const parsedMetaModelicaTestString = "(stored_definition classDefinition: (class_definition classType: (class_type model: (MODEL)) classSpecifier: (class_specifier identifier: (identifier (IDENT)) comment: (string_comment (STRING)) composition: (composition element: (element componentClause: (component_clause typeSpecifier: (type_specifier (T_REAL)) componentDeclaration: (component_declaration declaration: (declaration identifier: (IDENT) modification: (modification classModification: (class_modification (LPAR) argument: (argument (element_modification_or_replaceable (element_modification componentReference: (component_reference (IDENT)) modification: (modification (EQUALS) expression: (expression (simple_expression (UNSIGNED_INTEGER))))))) (COMMA) argument: (argument (element_modification_or_replaceable (element_modification componentReference: (component_reference (IDENT)) modification: (modification (EQUALS) expression: (expression (simple_expression (T_TRUE))))))) (RPAR)))) comment: (comment (string_comment (STRING)))))) element: (element componentClause: (component_clause typeSpecifier: (type_specifier namePath: (name_path identifier: (IDENT))) componentDeclaration: (component_declaration declaration: (declaration identifier: (IDENT) modification: (modification classModification: (class_modification (LPAR) argument: (argument (element_modification_or_replaceable (element_modification componentReference: (component_reference (IDENT))))) (RPAR)) (EQUALS) expression: (expression (simple_expression (MINUS) (UNSIGNED_REAL) (STAR) (component_reference__function_call componentReference: (component_reference (IDENT))))))))))) (T_END) endIdentifier: (identifier (IDENT)))))"; -describe('MetaModelica tree-sitter parser', () => { - it('Initialize parser', async () => { +suite('MetaModelica tree-sitter parser', () => { + test('Initialize parser', async () => { await initializeMetaModelicaParser(); }); - it('Parse string', async () => { + test('Parse string', async () => { const parser = await initializeMetaModelicaParser(); const tree = parser.parse(metaModelicaTestString); const parsedString = tree.rootNode.toString(); assert.equal(parsedString, parsedMetaModelicaTestString); }); - it('Identifier of type class', async () => { + test('Identifier of type class', async () => { const parser = await initializeMetaModelicaParser(); const tree = parser.parse("type Temperature = Real(unit = \"K \");"); const classNode = tree.rootNode.childForFieldName("classDefinitionList")!;