-
-
Notifications
You must be signed in to change notification settings - Fork 172
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
1 parent
ed93614
commit 78c62cf
Showing
2 changed files
with
207 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
194 changes: 194 additions & 0 deletions
194
tests/unit/application/Parser/Executable/Validation/ExecutableErrorContextMessage.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |