Trim compiler error output for better readability

Previously, compiler outputted whole executable in error context. This
caused long and hard to read error messages, especially when the
executable is a long category with many children. This commit improves
readability by trimming the error output.

Changes:

- Trim the error output (max characters: 1000).
- Improve indenting and newlines.
This commit is contained in:
undergroundwires
2024-06-21 12:46:30 +02:00
parent ed93614ca3
commit 78c62cfc95
2 changed files with 207 additions and 5 deletions

View File

@@ -0,0 +1,194 @@
import { describe, it, expect } from 'vitest';
import { createExecutableErrorContextStub } from '@tests/unit/shared/Stubs/ExecutableErrorContextStub';
import { createExecutableContextErrorMessage } from '@/application/Parser/Executable/Validation/ExecutableErrorContextMessage';
import type { ExecutableErrorContext } from '@/application/Parser/Executable/Validation/ExecutableErrorContext';
import { ExecutableType } from '@/application/Parser/Executable/Validation/ExecutableType';
import { CategoryDataStub } from '@tests/unit/shared/Stubs/CategoryDataStub';
describe('ExecutableErrorContextMessage', () => {
describe('createExecutableContextErrorMessage', () => {
it('includes the specified error message', () => {
// arrange
const expectedErrorMessage = 'expected error message';
const context = new TestContext()
.withErrorMessage(expectedErrorMessage);
// act
const actualMessage = context.createExecutableContextErrorMessage();
// assert
expect(actualMessage).to.include(expectedErrorMessage);
});
it('includes the type of executable', () => {
// arrange
const executableType = ExecutableType.Category;
const expectedType = ExecutableType[executableType];
const errorContext: ExecutableErrorContext = {
type: executableType,
self: new CategoryDataStub(),
};
const context = new TestContext()
.withErrorContext(errorContext);
// act
const actualMessage = context.createExecutableContextErrorMessage();
// assert
expect(actualMessage).to.include(expectedType);
});
it('includes details of the self executable', () => {
// arrange
const expectedName = 'expected name';
const selfExecutable = new CategoryDataStub()
.withName(expectedName);
const errorContext: ExecutableErrorContext = {
type: ExecutableType.Category,
self: selfExecutable,
};
const context = new TestContext()
.withErrorContext(errorContext);
// act
const actualMessage = context.createExecutableContextErrorMessage();
// assert
expect(actualMessage).to.include(expectedName);
});
it('includes details of the parent category', () => {
// arrange
const expectedName = 'expected parent name';
const parentCategoryData = new CategoryDataStub()
.withName(expectedName);
const errorContext: ExecutableErrorContext = {
type: ExecutableType.Category,
self: new CategoryDataStub(),
parentCategory: parentCategoryData,
};
const context = new TestContext()
.withErrorContext(errorContext);
// act
const actualMessage = context.createExecutableContextErrorMessage();
// assert
expect(actualMessage).to.include(expectedName);
});
it('constructs the complete message format correctly', () => {
// arrange
const errorMessage = 'expected error message';
const expectedName = 'expected name';
const expectedFormat = new RegExp(`^${escapeRegExp(errorMessage)}\\s+Executable:\\s+{\\s+"name":\\s+"${escapeRegExp(expectedName)}"\\s+}`);
const errorContext: ExecutableErrorContext = {
self: {
name: expectedName,
} as unknown as ExecutableErrorContext['self'],
};
const context = new TestContext()
.withErrorContext(errorContext)
.withErrorMessage(errorMessage);
// act
const actualMessage = context.createExecutableContextErrorMessage();
// assert
expect(actualMessage).to.match(expectedFormat);
});
describe('output trimming', () => {
const totalLongTextDataCharacters = 5000;
const expectedTrimmedText = '[Rest of the executable trimmed]';
const longName = 'a'.repeat(totalLongTextDataCharacters);
const testScenarios: readonly {
readonly description: string;
readonly errorContext: ExecutableErrorContext;
} [] = [
{
description: 'long text from parent category data',
errorContext: {
type: ExecutableType.Category,
self: new CategoryDataStub(),
parentCategory: new CategoryDataStub().withName(longName),
},
},
{
description: 'long text from self executable data',
errorContext: {
type: ExecutableType.Category,
self: new CategoryDataStub().withName(longName),
},
},
];
testScenarios.forEach(({
description, errorContext,
}) => {
it(description, () => {
const context = new TestContext()
.withErrorContext(errorContext);
// act
const actualMessage = context.createExecutableContextErrorMessage();
// assert
expect(actualMessage).to.include(expectedTrimmedText);
expect(actualMessage).to.have.length.lessThan(totalLongTextDataCharacters);
});
});
});
describe('missing data handling', () => {
it('generates a message when the executable type is undefined', () => {
// arrange
const errorContext: ExecutableErrorContext = {
type: undefined,
self: new CategoryDataStub(),
};
const context = new TestContext()
.withErrorContext(errorContext);
// act
const actualMessage = context.createExecutableContextErrorMessage();
// assert
expect(actualMessage).to.have.length.greaterThan(0);
});
it('generates a message when executable data is missing', () => {
// arrange
const errorContext: ExecutableErrorContext = {
type: undefined,
self: undefined as unknown as ExecutableErrorContext['self'],
};
const context = new TestContext()
.withErrorContext(errorContext);
// act
const actualMessage = context.createExecutableContextErrorMessage();
// assert
expect(actualMessage).to.have.length.greaterThan(0);
});
it('generates a message when parent category is missing', () => {
// arrange
const errorContext: ExecutableErrorContext = {
type: undefined,
self: new CategoryDataStub(),
parentCategory: undefined,
};
const context = new TestContext()
.withErrorContext(errorContext);
// act
const actualMessage = context.createExecutableContextErrorMessage();
// assert
expect(actualMessage).to.have.length.greaterThan(0);
});
});
});
});
class TestContext {
private errorMessage = `[${TestContext.name}] error message`;
private errorContext: ExecutableErrorContext = createExecutableErrorContextStub();
public withErrorMessage(errorMessage: string): this {
this.errorMessage = errorMessage;
return this;
}
public withErrorContext(context: ExecutableErrorContext): this {
this.errorContext = context;
return this;
}
public createExecutableContextErrorMessage() {
return createExecutableContextErrorMessage(
this.errorMessage,
this.errorContext,
);
}
}
function escapeRegExp(string: string): string {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');// $& means the whole matched string
}