From 2e7b512b4ce146f397553d165aadb1314bed851c Mon Sep 17 00:00:00 2001 From: Armin Zavada Date: Mon, 4 Nov 2024 18:00:30 +0100 Subject: [PATCH] Implemented model tests --- test/global.d.ts | 9 ++ test/linking/linking.test.ts | 53 --------- test/parsing/parsing.test.ts | 180 ++++++++++++++++++++++------- test/validating/validating.test.ts | 66 ----------- 4 files changed, 150 insertions(+), 158 deletions(-) create mode 100644 test/global.d.ts delete mode 100644 test/linking/linking.test.ts delete mode 100644 test/validating/validating.test.ts diff --git a/test/global.d.ts b/test/global.d.ts new file mode 100644 index 0000000..642da4f --- /dev/null +++ b/test/global.d.ts @@ -0,0 +1,9 @@ +declare global { + namespace jest { + interface Matchers { + toBeValid(): R; + } + } +} + +export {}; diff --git a/test/linking/linking.test.ts b/test/linking/linking.test.ts deleted file mode 100644 index 3e39c12..0000000 --- a/test/linking/linking.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { afterEach, beforeAll, describe, expect, test } from "vitest"; -import { EmptyFileSystem, type LangiumDocument } from "langium"; -import { expandToString as s } from "langium/generate"; -import { clearDocuments, parseHelper } from "langium/test"; -import { createHw2Services } from "../../src/language/hw-2-module.js"; -import { Model, isModel } from "../../src/language/generated/ast.js"; - -let services: ReturnType; -let parse: ReturnType>; -let document: LangiumDocument | undefined; - -beforeAll(async () => { - services = createHw2Services(EmptyFileSystem); - parse = parseHelper(services.Hw2); - - // activate the following if your linking test requires elements from a built-in library, for example - // await services.shared.workspace.WorkspaceManager.initializeWorkspace([]); -}); - -afterEach(async () => { - document && clearDocuments(services.shared, [ document ]); -}); - -describe('Linking tests', () => { - - test('linking of greetings', async () => { - document = await parse(` - person Langium - Hello Langium! - `); - - expect( - // here we first check for validity of the parsed document object by means of the reusable function - // 'checkDocumentValid()' to sort out (critical) typos first, - // and then evaluate the cross references we're interested in by checking - // the referenced AST element as well as for a potential error message; - checkDocumentValid(document) - || document.parseResult.value.greetings.map(g => g.person.ref?.name || g.person.error?.message).join('\n') - ).toBe(s` - Langium - `); - }); -}); - -function checkDocumentValid(document: LangiumDocument): string | undefined { - return document.parseResult.parserErrors.length && s` - Parser errors: - ${document.parseResult.parserErrors.map(e => e.message).join('\n ')} - ` - || document.parseResult.value === undefined && `ParseResult is 'undefined'.` - || !isModel(document.parseResult.value) && `Root AST object is a ${document.parseResult.value.$type}, expected a '${Model}'.` - || undefined; -} diff --git a/test/parsing/parsing.test.ts b/test/parsing/parsing.test.ts index 7b7adbe..da0384b 100644 --- a/test/parsing/parsing.test.ts +++ b/test/parsing/parsing.test.ts @@ -3,58 +3,160 @@ import { EmptyFileSystem, type LangiumDocument } from "langium"; import { expandToString as s } from "langium/generate"; import { parseHelper } from "langium/test"; import { createHw2Services } from "../../src/language/hw-2-module.js"; -import { Model, isModel } from "../../src/language/generated/ast.js"; +import {CPSModel, isCPSModel } from "../../src/language/generated/ast.js"; let services: ReturnType; -let parse: ReturnType>; -let document: LangiumDocument | undefined; +let parse: ReturnType>; +let document: LangiumDocument | undefined; beforeAll(async () => { services = createHw2Services(EmptyFileSystem); - parse = parseHelper(services.Hw2); + parse = parseHelper(services.Hw2); +}); + +expect.extend({ + toBeValid(received: LangiumDocument) { + if (received.parseResult.lexerErrors.length != 0) { + return { + message: () => s` + Lexer errors: + ${received.parseResult.lexerErrors.map(e => e.message).join('\n ')} + `, + pass: false + }; + } + + if (received.parseResult.parserErrors.length != 0) { + return { + message: () => s` + Parser errors: + ${received.parseResult.parserErrors.map(e => e.message).join('\n ')} + `, + pass: false + }; + } + + if (received.parseResult.value === undefined) { + return { + message: () => `ParseResult is 'undefined'.`, + pass: false + } + } + + if (!isCPSModel(received.parseResult.value)) { + return { + message: () => `ParseResult is not a CPSModel instance.`, + pass: false + } + } - // activate the following if your linking test requires elements from a built-in library, for example - // await services.shared.workspace.WorkspaceManager.initializeWorkspace([]); + return { + message: () => `Model successfully parsed!`, + pass: true + } + } }); describe('Parsing tests', () => { - test('parse simple model', async () => { + test('Test model 1: Empty sections with comments', async () => { document = await parse(` - person Langium - Hello Langium! + repository { + // there will be a section for different softwares + } + market { + // there will be a section for different computers + } + cps SmartHome { + // This will be a simplified specification for our smart home + } `); - // check for absensce of parser errors the classic way: - // deacivated, find a much more human readable way below! - // expect(document.parseResult.parserErrors).toHaveLength(0); - - expect( - // here we use a (tagged) template expression to create a human readable representation - // of the AST part we are interested in and that is to be compared to our expectation; - // prior to the tagged template expression we check for validity of the parsed document object - // by means of the reusable function 'checkDocumentValid()' to sort out (critical) typos first; - checkDocumentValid(document) || s` - Persons: - ${document.parseResult.value?.persons?.map(p => p.name)?.join('\n ')} - Greetings to: - ${document.parseResult.value?.greetings?.map(g => g.person.$refText)?.join('\n ')} - ` - ).toBe(s` - Persons: - Langium - Greetings to: - Langium + expect(document).toBeValid(); + }); + + test('Test model 2: Computers specified in the market', async () => { + document = await parse(` + repository { } + market { + computer RaspBerr5M2G processor 2.4 GHz 4 cores memory 2 GB; + computer RaspBerr5M4G processor 2.4 GHz 4 cores memory 4 GB; + computer RaspBerr5M8G processor 2.4 GHz 4 cores memory 8 GB; + computer PersonalComputer processor 3.6 GHz memory 32 GB; + } + cps SmartHome { } `); + + expect(document).toBeValid(); }); -}); -function checkDocumentValid(document: LangiumDocument): string | undefined { - return document.parseResult.parserErrors.length && s` - Parser errors: - ${document.parseResult.parserErrors.map(e => e.message).join('\n ')} - ` - || document.parseResult.value === undefined && `ParseResult is 'undefined'.` - || !isModel(document.parseResult.value) && `Root AST object is a ${document.parseResult.value.$type}, expected a '${Model}'.` - || undefined; -} + test('Test model 3: Software in repository and computers in market', async () => { + document = await parse(` + repository { + software RaspberryOS requires memory 1 GB; + software BasicSensorDriverPack depends on RaspberryOS; + software RabbitMQApp depends on BasicSensorDriverPack RaspberryOS; + software FedoraWorkstation requires memory 4 GB; + software OpenControlPanel depends on FedoraWorkstation; + } + market { + computer RaspBerr5M2G processor 2.4 GHz 4 cores memory 2 GB; + computer RaspBerr5M4G processor 2.4 GHz 4 cores memory 4 GB; + computer RaspBerr5M8G processor 2.4 GHz 4 cores memory 8 GB; + computer PersonalComputer processor 3.6 GHz memory 32 GB; + } + cps SmartHome { } + `); + + expect(document).toBeValid(); + }); + + test('Test model 4: Full CPS specification with computers and connections', async () => { + document = await parse(` + repository { + software RaspberryOS requires memory 1 GB; + software BasicSensorDriverPack depends on RaspberryOS; + software RabbitMQApp depends on BasicSensorDriverPack RaspberryOS; + software FedoraWorkstation requires memory 4 GB; + software OpenControlPanel depends on FedoraWorkstation; + } + market { + computer RaspBerr5M2G processor 2.4 GHz 4 cores memory 2 GB; + computer RaspBerr5M4G processor 2.4 GHz 4 cores memory 4 GB; + computer RaspBerr5M8G processor 2.4 GHz 4 cores memory 8 GB; + computer PersonalComputer processor 3.6 GHz memory 32 GB; + } + cps SmartHome { + computer RaspBerr5M2G termometer1 { + add Thermometer + install RaspberryOS + install BasicSensorDriverPack + } + computer RaspBerr5M2G termometer2 { + add Thermometer + install RaspberryOS + install BasicSensorDriverPack + } + computer RaspBerr5M2G heatController { + install RaspberryOS + install BasicSensorDriverPack + add HeatController + } + + termometer1 -o)- heatController + termometer2 -o)- heatController + + computer RaspBerr5M2G smokeDetector { + add SmokeDetector + add FireAlarm + install RaspberryOS + install BasicSensorDriverPack + } + + smokeDetector -o)- smokeDetector + } + `); + + expect(document).toBeValid(); + }); +}); diff --git a/test/validating/validating.test.ts b/test/validating/validating.test.ts deleted file mode 100644 index 2f0d08f..0000000 --- a/test/validating/validating.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { beforeAll, describe, expect, test } from "vitest"; -import { EmptyFileSystem, type LangiumDocument } from "langium"; -import { expandToString as s } from "langium/generate"; -import { parseHelper } from "langium/test"; -import type { Diagnostic } from "vscode-languageserver-types"; -import { createHw2Services } from "../../src/language/hw-2-module.js"; -import { Model, isModel } from "../../src/language/generated/ast.js"; - -let services: ReturnType; -let parse: ReturnType>; -let document: LangiumDocument | undefined; - -beforeAll(async () => { - services = createHw2Services(EmptyFileSystem); - const doParse = parseHelper(services.Hw2); - parse = (input: string) => doParse(input, { validation: true }); - - // activate the following if your linking test requires elements from a built-in library, for example - // await services.shared.workspace.WorkspaceManager.initializeWorkspace([]); -}); - -describe('Validating', () => { - - test('check no errors', async () => { - document = await parse(` - person Langium - `); - - expect( - // here we first check for validity of the parsed document object by means of the reusable function - // 'checkDocumentValid()' to sort out (critical) typos first, - // and then evaluate the diagnostics by converting them into human readable strings; - // note that 'toHaveLength()' works for arrays and strings alike ;-) - checkDocumentValid(document) || document?.diagnostics?.map(diagnosticToString)?.join('\n') - ).toHaveLength(0); - }); - - test('check capital letter validation', async () => { - document = await parse(` - person langium - `); - - expect( - checkDocumentValid(document) || document?.diagnostics?.map(diagnosticToString)?.join('\n') - ).toEqual( - // 'expect.stringContaining()' makes our test robust against future additions of further validation rules - expect.stringContaining(s` - [1:19..1:26]: Person name should start with a capital. - `) - ); - }); -}); - -function checkDocumentValid(document: LangiumDocument): string | undefined { - return document.parseResult.parserErrors.length && s` - Parser errors: - ${document.parseResult.parserErrors.map(e => e.message).join('\n ')} - ` - || document.parseResult.value === undefined && `ParseResult is 'undefined'.` - || !isModel(document.parseResult.value) && `Root AST object is a ${document.parseResult.value.$type}, expected a '${Model}'.` - || undefined; -} - -function diagnosticToString(d: Diagnostic) { - return `[${d.range.start.line}:${d.range.start.character}..${d.range.end.line}:${d.range.end.character}]: ${d.message}`; -}