diff --git a/apps/docs/docs/dev/12-jayvee-testing.md b/apps/docs/docs/dev/12-jayvee-testing.md index 7936a75fe..1121c1ef9 100644 --- a/apps/docs/docs/dev/12-jayvee-testing.md +++ b/apps/docs/docs/dev/12-jayvee-testing.md @@ -118,6 +118,11 @@ At the moment this only contains two functions: - `clearConstraintExecutorRegistry` clearing the corresponding `ConstraintExecutor`s registry. They are required in case the tested method initializes Jayvee itself (see [smoke test](#existing-tests-1)). +[**test-logger.ts**](https://github.com/jvalue/jayvee/blob/dev/libs/execution/test/test-logger.ts): +This contains a subclass of the [`DefaultLogger`](https://github.com/jvalue/jayvee/blob/dev/libs/execution/src/lib/logging/default-logger.ts) used for tests which require a `Logger` implementation. The `TestLogger` contains the following tests functionality: +- `getLogs`: retrieve the cached logs that the logger received. +- `clearLogs`: clear the cached logs. + [**block-executor-mocks.ts**](https://github.com/jvalue/jayvee/blob/dev/libs/execution/test/block-executor-mock.ts): `BlockExecutorMock` interface for defining mocks for `AbstractBlockExecutor`. Generally only loader and executor blocks require mocks, because they interact with "the outside world" (i.e. `HttpExtractor` making http calls). Due to how vastly different each `BlockExecutor` can be, this interface is very simple, containing only a `setup(...args: unknown[])` and a `restore()` method. See below for existing implementations. diff --git a/libs/execution/src/lib/blocks/block-execution-util.ts b/libs/execution/src/lib/blocks/block-execution-util.ts index 81dc3cec9..ed17c157b 100644 --- a/libs/execution/src/lib/blocks/block-execution-util.ts +++ b/libs/execution/src/lib/blocks/block-execution-util.ts @@ -8,7 +8,7 @@ import { } from '@jvalue/jayvee-language-server'; import { ExecutionContext } from '../execution-context'; -import { Logger } from '../logger'; +import { Logger } from '../logging/logger'; import { IOTypeImplementation, NONE } from '../types'; // eslint-disable-next-line import/no-cycle diff --git a/libs/execution/src/lib/debugging/debug-log-visitor.ts b/libs/execution/src/lib/debugging/debug-log-visitor.ts index 6c17ea75f..00a1fa070 100644 --- a/libs/execution/src/lib/debugging/debug-log-visitor.ts +++ b/libs/execution/src/lib/debugging/debug-log-visitor.ts @@ -4,7 +4,7 @@ import { internalValueToString } from '@jvalue/jayvee-language-server'; -import { Logger } from '../logger'; +import { Logger } from '../logging/logger'; import { Workbook } from '../types'; import { FileSystem } from '../types/io-types/filesystem'; import { BinaryFile } from '../types/io-types/filesystem-node-file-binary'; diff --git a/libs/execution/src/lib/execution-context.ts b/libs/execution/src/lib/execution-context.ts index ef5f649db..41409624c 100644 --- a/libs/execution/src/lib/execution-context.ts +++ b/libs/execution/src/lib/execution-context.ts @@ -28,7 +28,7 @@ import { DebugGranularity, DebugTargets, } from './debugging/debug-configuration'; -import { Logger } from './logger'; +import { Logger } from './logging/logger'; export type StackNode = | BlockDefinition diff --git a/libs/execution/src/lib/index.ts b/libs/execution/src/lib/index.ts index 6f5364141..fcd0b0895 100644 --- a/libs/execution/src/lib/index.ts +++ b/libs/execution/src/lib/index.ts @@ -12,4 +12,4 @@ export * from './types/valuetypes/visitors'; export * from './execution-context'; export * from './extension'; -export * from './logger'; +export * from './logging'; diff --git a/libs/interpreter-lib/src/logging/default-logger.ts b/libs/execution/src/lib/logging/default-logger.ts similarity index 93% rename from libs/interpreter-lib/src/logging/default-logger.ts rename to libs/execution/src/lib/logging/default-logger.ts index 24bc98846..26c9c45a7 100644 --- a/libs/interpreter-lib/src/logging/default-logger.ts +++ b/libs/execution/src/lib/logging/default-logger.ts @@ -2,20 +2,21 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { DiagnosticSeverity, Logger } from '@jvalue/jayvee-execution'; import * as chalk from 'chalk'; import { LangiumDocument } from 'langium'; import { assertUnreachable } from 'langium/lib/utils/errors'; import { Range } from 'vscode-languageserver'; import { uinteger } from 'vscode-languageserver-types'; +import { DiagnosticSeverity, Logger } from './logger'; + export class DefaultLogger extends Logger { private readonly TAB_TO_SPACES = 4; constructor( - private readonly enableDebugLogging: boolean, - private loggingContext?: string, - private depth: number = 0, + protected readonly enableDebugLogging: boolean, + protected loggingContext?: string, + protected depth: number = 0, ) { super(); } @@ -54,7 +55,7 @@ export class DefaultLogger extends Logger { return '\t'.repeat(this.depth); } - private getContext(): string { + protected getContext(): string { return this.loggingContext !== undefined ? chalk.grey(`[${this.loggingContext}] `) : ''; @@ -74,7 +75,7 @@ export class DefaultLogger extends Logger { printFn(''); } - private logDiagnosticMessage( + protected logDiagnosticMessage( severityName: string, message: string, printFn: (message: string) => void, @@ -85,7 +86,7 @@ export class DefaultLogger extends Logger { ); } - private logDiagnosticInfo( + protected logDiagnosticInfo( range: Range, document: LangiumDocument, printFn: (message: string) => void, @@ -171,7 +172,7 @@ export class DefaultLogger extends Logger { }, ''); } - private inferPrintFunction( + protected inferPrintFunction( severity: DiagnosticSeverity, ): (message: string) => void { switch (severity) { @@ -187,7 +188,7 @@ export class DefaultLogger extends Logger { } } - private inferChalkColor( + protected inferChalkColor( severity: DiagnosticSeverity, ): (message: string) => string { switch (severity) { diff --git a/libs/execution/src/lib/logging/index.ts b/libs/execution/src/lib/logging/index.ts new file mode 100644 index 000000000..28eda9e2e --- /dev/null +++ b/libs/execution/src/lib/logging/index.ts @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: 2023 Friedrich-Alexander-Universitat Erlangen-Nurnberg +// +// SPDX-License-Identifier: AGPL-3.0-only + +export * from './logger'; +export * from './default-logger'; diff --git a/libs/execution/src/lib/logger.ts b/libs/execution/src/lib/logging/logger.ts similarity index 100% rename from libs/execution/src/lib/logger.ts rename to libs/execution/src/lib/logging/logger.ts diff --git a/libs/execution/test/index.ts b/libs/execution/test/index.ts index 6b29ad45a..28b73965e 100644 --- a/libs/execution/test/index.ts +++ b/libs/execution/test/index.ts @@ -4,3 +4,4 @@ export * from './block-executor-mock'; export * from './utils'; +export * from './test-logger'; diff --git a/libs/execution/test/test-logger.ts b/libs/execution/test/test-logger.ts index 5e4e2e929..1d73bebf1 100644 --- a/libs/execution/test/test-logger.ts +++ b/libs/execution/test/test-logger.ts @@ -4,48 +4,92 @@ import * as chalk from 'chalk'; import { LangiumDocument } from 'langium'; -import { assertUnreachable } from 'langium/lib/utils/errors'; import { Range } from 'vscode-languageserver'; -import { uinteger } from 'vscode-languageserver-types'; -import { DiagnosticSeverity, Logger } from '../src/lib'; +import { DefaultLogger, DiagnosticSeverity } from '../src/lib'; -export class TestLogger extends Logger { - private readonly TAB_TO_SPACES = 4; +export interface ClearLogsOptions { + clearInfo: boolean; + clearError: boolean; + clearDebug: boolean; + clearDiagnostic: boolean; +} + +export class TestLogger extends DefaultLogger { + private infoLogs: string[] = []; + private errorLogs: string[] = []; + private debugLogs: string[] = []; + private diagnosticLogs: string[] = []; constructor( - private readonly enableDebugLogging: boolean, - private loggingContext?: string, + enableDebugLogging: boolean, + loggingContext?: string, + private printLogs: boolean = true, + depth = 0, ) { - super(); + super(enableDebugLogging, loggingContext, depth); + } + + public getLogs(): { + infoLogs: string[]; + errorLogs: string[]; + debugLogs: string[]; + diagnosticLogs: string[]; + } { + return { + infoLogs: Array.from(this.infoLogs), + errorLogs: Array.from(this.errorLogs), + debugLogs: Array.from(this.debugLogs), + diagnosticLogs: Array.from(this.diagnosticLogs), + }; } - override setLoggingDepth(): void { - return undefined; + public clearLogs( + options: ClearLogsOptions = { + clearInfo: true, + clearDebug: true, + clearError: true, + clearDiagnostic: true, + }, + ): void { + if (options.clearInfo) { + this.infoLogs = []; + } + if (options.clearError) { + this.errorLogs = []; + } + if (options.clearDebug) { + this.debugLogs = []; + } + if (options.clearDiagnostic) { + this.diagnosticLogs = []; + } } override logInfo(message: string): void { - console.log(`${chalk.bold(this.getContext())}${message}`); + const msg = `${chalk.bold(this.getContext())}${message}`; + this.infoLogs.push(msg); + if (this.printLogs) { + console.log(msg); + } } override logDebug(message: string): void { if (this.enableDebugLogging) { - console.log(`${chalk.bold(this.getContext())}${message}`); + const msg = `${chalk.bold(this.getContext())}${message}`; + this.debugLogs.push(msg); + if (this.printLogs) { + console.log(msg); + } } } override logErr(message: string): void { - console.error(`${chalk.bold(this.getContext())}${chalk.red(message)}`); - } - - override setLoggingContext(loggingContext: string | undefined) { - this.loggingContext = loggingContext; - } - - private getContext(): string { - return this.loggingContext !== undefined - ? chalk.grey(`[${this.loggingContext}] `) - : ''; + const msg = `${chalk.bold(this.getContext())}${chalk.red(message)}`; + this.errorLogs.push(msg); + if (this.printLogs) { + console.error(msg); + } } protected override logDiagnostic( @@ -54,138 +98,18 @@ export class TestLogger extends Logger { range: Range, document: LangiumDocument, ) { - const printFn = this.inferPrintFunction(severity); + const printFn = (msg: string) => { + const basePrintFn = this.inferPrintFunction(severity); + + this.diagnosticLogs.push(msg); + if (this.printLogs) { + basePrintFn(msg); + } + }; const colorFn = this.inferChalkColor(severity); this.logDiagnosticMessage(severity, message, printFn, colorFn); this.logDiagnosticInfo(range, document, printFn, colorFn); printFn(''); } - - private logDiagnosticMessage( - severityName: string, - message: string, - printFn: (message: string) => void, - colorFn: (message: string) => string, - ) { - printFn(`${chalk.bold(colorFn(severityName))}: ${message}`); - } - - private logDiagnosticInfo( - range: Range, - document: LangiumDocument, - printFn: (message: string) => void, - colorFn: (message: string) => string, - ): void { - const startLineNumber = range.start.line + 1; - const endLineNumber = range.end.line + 1; - - const fullRange: Range = { - start: { - line: range.start.line, - character: 0, - }, - end: { - line: range.end.line, - character: uinteger.MAX_VALUE, - }, - }; - const text = document.textDocument.getText(fullRange).trimEnd(); - const lines = text.split('\n'); - - const lineNumberLength = Math.floor(Math.log10(endLineNumber)) + 1; - - printFn( - `In ${document.uri.path}:${startLineNumber}:${range.start.character + 1}`, - ); - lines.forEach((line, i) => { - const lineNumber = startLineNumber + i; - const paddedLineNumber = String(lineNumber).padStart( - lineNumberLength, - ' ', - ); - printFn( - `${chalk.grey(`${paddedLineNumber} |`)} ${line.replace( - /\t/g, - ' '.repeat(this.TAB_TO_SPACES), - )}`, - ); - - let underlineFrom = 0; - let underlineTo = line.length; - if (lineNumber === startLineNumber) { - underlineFrom = range.start.character; - } - if (lineNumber === endLineNumber) { - underlineTo = range.end.character; - } - - const underlineIndent = this.repeatCharAccordingToString( - ' ', - line.substring(0, underlineFrom), - this.TAB_TO_SPACES, - ); - const underline = this.repeatCharAccordingToString( - '^', - line.substring(underlineFrom, underlineTo), - this.TAB_TO_SPACES, - ); - - printFn( - `${chalk.grey( - `${' '.repeat(lineNumberLength)} |`, - )} ${underlineIndent}${colorFn(underline)}`, - ); - }); - } - - /** - * Repeats {@link charToRepeat} as many times as {@link accordingTo} is long. - * For each occurrence of \t in {@link accordingTo}, - * {@link charToRepeat} is repeated {@link tabRepeats} times instead of once. - */ - private repeatCharAccordingToString( - charToRepeat: string, - accordingTo: string, - tabRepeats: number, - ): string { - return Array.from(accordingTo).reduce((prev, cur) => { - const repeatedChar = - cur === '\t' ? charToRepeat.repeat(tabRepeats) : charToRepeat; - return `${prev}${repeatedChar}`; - }, ''); - } - - private inferPrintFunction( - severity: DiagnosticSeverity, - ): (message: string) => void { - switch (severity) { - case DiagnosticSeverity.ERROR: - return console.error; - case DiagnosticSeverity.WARNING: - return console.warn; - case DiagnosticSeverity.INFO: - case DiagnosticSeverity.HINT: - return console.info; - default: - assertUnreachable(severity); - } - } - - private inferChalkColor( - severity: DiagnosticSeverity, - ): (message: string) => string { - switch (severity) { - case DiagnosticSeverity.ERROR: - return chalk.red; - case DiagnosticSeverity.WARNING: - return chalk.yellow; - case DiagnosticSeverity.INFO: - return chalk.green; - case DiagnosticSeverity.HINT: - return chalk.blue; - default: - assertUnreachable(severity); - } - } } diff --git a/libs/execution/test/utils.ts b/libs/execution/test/utils.ts index 90a3e0ca5..935c58446 100644 --- a/libs/execution/test/utils.ts +++ b/libs/execution/test/utils.ts @@ -41,6 +41,7 @@ export function getTestExecutionContext( debugGranularity: 'minimal', debugTargets: 'all', }, + loggerPrintLogs = true, ): ExecutionContext { const pipeline = locator.getAstNode( document.parseResult.value, @@ -49,7 +50,7 @@ export function getTestExecutionContext( const executionContext = new ExecutionContext( pipeline, - new TestLogger(runOptions.isDebugMode), + new TestLogger(runOptions.isDebugMode, undefined, loggerPrintLogs), runOptions, new EvaluationContext(new RuntimeParameterProvider()), ); diff --git a/libs/interpreter-lib/src/logging/index.ts b/libs/interpreter-lib/src/logging/index.ts index c09fa42fa..29070e0fb 100644 --- a/libs/interpreter-lib/src/logging/index.ts +++ b/libs/interpreter-lib/src/logging/index.ts @@ -3,4 +3,3 @@ // SPDX-License-Identifier: AGPL-3.0-only export * from './logger-factory'; -export * from './default-logger'; diff --git a/libs/interpreter-lib/src/logging/logger-factory.ts b/libs/interpreter-lib/src/logging/logger-factory.ts index f1c780427..dc8b6de62 100644 --- a/libs/interpreter-lib/src/logging/logger-factory.ts +++ b/libs/interpreter-lib/src/logging/logger-factory.ts @@ -2,9 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { Logger } from '@jvalue/jayvee-execution'; - -import { DefaultLogger } from './default-logger'; +import { DefaultLogger, Logger } from '@jvalue/jayvee-execution'; export class LoggerFactory { constructor(private readonly enableDebugLogging: boolean) {} diff --git a/libs/interpreter-lib/tsconfig.lib.json b/libs/interpreter-lib/tsconfig.lib.json index 10fd98271..33eca2c2c 100644 --- a/libs/interpreter-lib/tsconfig.lib.json +++ b/libs/interpreter-lib/tsconfig.lib.json @@ -7,4 +7,4 @@ }, "include": ["src/**/*.ts"], "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] -} \ No newline at end of file +}