Skip to content

Commit

Permalink
Gracefully handle rcode compiled with the MIN-SIZE option (#222)
Browse files Browse the repository at this point in the history
  • Loading branch information
kenherring authored Oct 30, 2024
1 parent 6311c5e commit 5b38e77
Show file tree
Hide file tree
Showing 17 changed files with 289 additions and 48 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@
*.i linguist-language=OpenEdge-ABL
*.p linguist-language=OpenEdge-ABL
*.w linguist-language=OpenEdge-ABL

test_projects/proj1/import_charset.p text working-tree-encoding=UTF-8
2 changes: 2 additions & 0 deletions resources/ablunit-test-profile.detail.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@
////# the command line, and the "profiler.options" file will not be created.
////# * When the "profiler" key is set to null the 'profile.options' file will not
////# be created and the `-profile` option will not be passed to the command line.
////# * When "enabled" is false, the profiler information is not collection and coverage cannot
////# be reported. This is not recommended. The option is included here for completeness.
////#
////# See the OpenEdge documentation for more information about these values.
////# * https://docs.progress.com/bundle/openedge-startup-and-parameter-reference/page/Profiler-profile.html
Expand Down
16 changes: 12 additions & 4 deletions src/ABLDebugLines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,20 @@ import { log } from './ChannelLogger'
import { ISourceMap, getSourceMapFromRCode } from './parse/RCodeParser'
import { getSourceMapFromSource } from './parse/SourceParser'

const maps = new Map<string, ISourceMap>()

export class ABLDebugLines {
private readonly maps = new Map<string, ISourceMap>()
private readonly processingMethodMap = new Map<string, 'rcode' | 'parse'>()
propath: PropathParser

constructor (propath: PropathParser) {
this.propath = propath
}

getProcessingMethod (debugSource: string) {
return this.processingMethodMap.get(debugSource)
}

async getSourceLine (debugSource: string, debugLine: number) {
// if (debugSource.startsWith('OpenEdge.') || debugSource.includes('ABLUnitCore')) {
// return undefined
Expand All @@ -26,19 +31,22 @@ export class ABLDebugLines {
log.trace('cannot find debug source in propath (' + debugSource + ')')
return undefined
}
let map = maps.get(debugSource)

let map = this.maps.get(debugSource)
if (!map) {
try {
map = await getSourceMapFromRCode(this.propath, await this.propath.getRCodeUri(debugSource))
this.processingMethodMap.set(debugSource, 'rcode')
} catch (e) {
log.debug('cannot parse source map from rcode, falling back to source parser (debugSource=' + debugSource + ', e=' + e + ')')
log.warn('cannot parse source map from rcode, falling back to source parser (debugSource=' + debugSource + ', e=' + e + ')')
map = await getSourceMapFromSource(this.propath, debugSource)
this.processingMethodMap.set(debugSource, 'parse')
}

if (!map) {
throw new Error('failed to parse source map (' + debugSource + ')')
} else {
maps.set(debugSource, map)
this.maps.set(debugSource, map)
}
}
const ret = map.items.find((line) => line.debugLine === debugLine)
Expand Down
2 changes: 1 addition & 1 deletion src/ABLPropath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export class PropathParser {
if (file instanceof Uri) {
return this.searchUri(file)
}
let relativeFile = file
let relativeFile = isRelativePath(file) ? file : workspace.asRelativePath(Uri.file(file), false)
if (!relativeFile.endsWith('.cls') && !relativeFile.endsWith('.p') && !relativeFile.endsWith('.w') && !relativeFile.endsWith('.i')) {
relativeFile = relativeFile.replace(/\./g, '/') + '.cls'
}
Expand Down
6 changes: 5 additions & 1 deletion src/ABLUnitCommon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ export function doesFileExist (uri: Uri) {
return false
}

export function deleteFile (file: Uri | Uri[]) {
export function deleteFile (file: Uri | Uri[] | undefined) {
if (!file) {
return
}
let files: Uri[]
if (!Array.isArray(file)) {
files = [file]
Expand All @@ -48,6 +51,7 @@ export function deleteFile (file: Uri | Uri[]) {
if (doesFileExist(file)) {
fs.rmSync(file.fsPath)
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (err) { /* do nothing */ }
}
}
Expand Down
9 changes: 8 additions & 1 deletion src/ABLUnitRun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,14 @@ export const ablunitRun = async (options: TestRun, res: ABLResults, cancellation
}

const runCommand = () => {
deleteFile(res.cfg.ablunitConfig.profFilenameUri)
// deleteFile(res.cfg.ablunitConfig.config_uri)
deleteFile(res.cfg.ablunitConfig.optionsUri.filenameUri)
deleteFile(res.cfg.ablunitConfig.optionsUri.jsonUri)
deleteFile(res.cfg.ablunitConfig.optionsUri.updateUri)
deleteFile(res.cfg.ablunitConfig.profFilenameUri)
// deleteFile(res.cfg.ablunitConfig.profOptsUri)

log.debug('ablunit command dir=\'' + res.cfg.ablunitConfig.workspaceFolder.uri.fsPath + '\'')
if (cancellation?.isCancellationRequested) {
log.info('cancellation requested - runCommand')
Expand All @@ -198,7 +206,6 @@ export const ablunitRun = async (options: TestRun, res: ABLResults, cancellation

const updateUri = res.cfg.ablunitConfig.optionsUri.updateUri
if (updateUri) {
deleteFile(updateUri)
updateParserInit()
log.info('watching test run update/event file: ' + updateUri.fsPath)
watcherUpdate = workspace.createFileSystemWatcher(updateUri.fsPath)
Expand Down
3 changes: 1 addition & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ export async function activate (context: ExtensionContext) {
log.info('loadDetailedCoverage uri="' + fileCoverage.uri.fsPath + '", testRun=' + testRun.name)
const d = resultData.get(testRun)
const det: FileCoverageDetail[] = []

if (d) {
d.flatMap((r) => {
const rec = r.coverage.get(fileCoverage.uri.fsPath)
Expand Down Expand Up @@ -292,7 +291,7 @@ export async function activate (context: ExtensionContext) {
for (const res of data) {
log.info('res.filecoverage.length=' + res.filecoverage.length)
if (res.filecoverage.length === 0) {
log.warn('no coverage data found (profile data path=' + res.cfg.ablunitConfig.profFilenameUri + ')')
log.warn('no coverage data found (profile data path=' + res.cfg.ablunitConfig.profFilenameUri.fsPath + ')')
}
res.filecoverage.forEach((c) => {
run.addCoverage(c)
Expand Down
3 changes: 2 additions & 1 deletion src/parse/RCodeParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { TextDecoder } from 'util'
import { Uri, workspace } from 'vscode'
import { PropathParser } from '../ABLPropath'
import { log } from '../ChannelLogger'
import { isRelativePath } from 'ABLUnitCommon'

const headerLength = 68

Expand Down Expand Up @@ -240,7 +241,7 @@ export const getSourceMapFromRCode = (propath: PropathParser, uri: Uri) => {
if (sourceNum == undefined) {
throw new Error('invalid source number: ' + sourceNum + ' ' + sourceName)
}
const sourceUri = Uri.joinPath(propath.workspaceFolder.uri, sourceName)
const sourceUri = isRelativePath(sourceName) ? Uri.joinPath(propath.workspaceFolder.uri, sourceName) : Uri.file(sourceName)

sources.push({
sourceName: sourceName.replace(/\\/g, '/'),
Expand Down
24 changes: 18 additions & 6 deletions src/parse/ResultsParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,20 +92,32 @@ export class ABLResultsParser {
parseXml (xmlData: string): string {
let res: string | undefined

parseString(xmlData, function (err: Error | null, resultsRaw: any) {
if (err) {
throw new Error('error parsing XML file: ' + err)
parseString(xmlData, function (e: Error | null, resultsRaw: unknown) {
if (e) {
log.info('error parsing XML file: ' + e)
throw e
}
res = resultsRaw
return String(resultsRaw)
if (!resultsRaw) {
throw new Error('malformed results file (2) - could not parse XML - resultsRaw is null')
}
if (typeof resultsRaw === 'object' && resultsRaw !== null) {
res = JSON.stringify(resultsRaw)
return JSON.stringify(resultsRaw)
}
log.error('resultsRaw=' + JSON.stringify(resultsRaw))
throw new Error('malformed results file (2) - could not parse XML - resultsRaw is not an object')
})
if (!res) {
throw new Error('malformed results file (2) - could not parse XML')
}
return res
}

async parseSuites (res: any) {
async parseSuites (results: string) {
if (!results) {
throw new Error('malformed results file (1) - res is null')
}
let res = JSON.parse(results)
if(!res.testsuites) {
log.error('malformed results file (1) - could not find top-level \'testsuites\' node')
throw new Error('malformed results file (1) - could not find top-level \'testsuites\' node')
Expand Down
97 changes: 82 additions & 15 deletions test/suites/proj1.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Selection, commands, window } from 'vscode'
import { Selection, commands, tasks, window } from 'vscode'
import { after, afterEach, beforeEach } from 'mocha'
import { Uri, assert, getWorkspaceUri, log, runAllTests, sleep, updateConfig, getTestCount, workspace, suiteSetupCommon, getWorkspaceFolders, oeVersion, runTestAtLine, beforeCommon, updateTestProfile, runTestsInFile } from '../testCommon'
import { Uri, assert, getWorkspaceUri, log, runAllTests, sleep, updateConfig, getTestCount, workspace, suiteSetupCommon, getWorkspaceFolders, oeVersion, runTestAtLine, beforeCommon, updateTestProfile, runTestsInFile, deleteFiles } from '../testCommon'
import { getOEVersion } from 'parse/OpenedgeProjectParser'
import { execSync } from 'child_process'
import * as glob from 'glob'
Expand Down Expand Up @@ -63,27 +63,27 @@ suite('proj1 - Extension Test Suite', () => {
})

test('proj1.2 - output files exist 2 - exclude compileError.p', () => {
return workspace.getConfiguration('ablunit').update('files.exclude', [ '.builder/**', 'compileError.p' ])
const p = workspace.getConfiguration('ablunit').update('files.exclude', [ '.builder/**', 'compileError.p' ])
.then(() => { return runAllTests() })
.then(() => {
assert.tests.count(30)
assert.tests.passed(24)
assert.tests.count(31)
assert.tests.passed(25)
assert.tests.failed(2)
assert.tests.errored(3)
assert.tests.skipped(1)
return true
}, (e: unknown) => { throw e })

},
(e: unknown) => {
throw e
})
return p
})

test('proj1.3 - output files exist 3 - exclude compileError.p as string', async () => {
// this isn't officially supported and won't syntac check in the settings.json file(s), but it works
await updateConfig('ablunit.files.exclude', 'compileError.p')
await runAllTests()

const resultsJson = Uri.joinPath(workspaceUri, 'results.json')
const testCount = await getTestCount(resultsJson)
assert.equal(testCount, 30)
assert.tests.count(31)
})

test('proj1.4 - run test case in file', async () => {
Expand Down Expand Up @@ -159,7 +159,7 @@ suite('proj1 - Extension Test Suite', () => {

test('proj1.9 - check startup parmaeters for -y -yx', async () => {
await workspace.fs.copy(Uri.joinPath(workspaceUri, 'openedge-project.proj1.9.json'), Uri.joinPath(workspaceUri, 'openedge-project.json'), { overwrite: true })
await runTestAtLine('import_charset.p', 64)
await runTestAtLine('import_charset.p', 68)
.then(() => {
log.info('testing.runAtCursor complete')
assert.tests.count(1)
Expand Down Expand Up @@ -210,8 +210,8 @@ suite('proj1 - Extension Test Suite', () => {
// run tests and assert test count
await runAllTests()
.then(() => {
assert.tests.count(29)
assert.tests.passed(23)
assert.tests.count(30)
assert.tests.passed(24)
assert.tests.failed(2)
assert.tests.errored(3)
assert.tests.skipped(1)
Expand Down Expand Up @@ -258,7 +258,7 @@ suite('proj1 - Extension Test Suite', () => {

// default profile passes, but there is an 'ablunit' profile which is used first
test('proj1.14 - run profile \'ablunit\'', async () => {
return workspace.fs.copy(Uri.joinPath(workspaceUri, 'openedge-project.proj1.14.json'), Uri.joinPath(workspaceUri, 'openedge-project.json'), { overwrite: true })
const p = workspace.fs.copy(Uri.joinPath(workspaceUri, 'openedge-project.proj1.14.json'), Uri.joinPath(workspaceUri, 'openedge-project.json'), { overwrite: true })
.then(() => { return runTestsInFile('test_14.p') })
.then(() => {
assert.tests.count(1)
Expand All @@ -271,7 +271,74 @@ suite('proj1 - Extension Test Suite', () => {
log.error('e=' + e)
throw e
})
return p
})

test('proj1.15A - compile option without MIN-SIZE without xref', () => {
const p = compileWithTaskAndCoverage('ant build')
.then(() => {
assert.linesExecuted('test_15.p', [9, 10, 13])
assert.coverageProcessingMethod('test_15.p', 'rcode')
return true
})
return p
})

test('proj1.15B - compile option with MIN-SIZE without xref', () => {
const p = compileWithTaskAndCoverage('ant build min-size')
.then(() => {
assert.linesExecuted('test_15.p', [9, 10, 13])
assert.coverageProcessingMethod('test_15.p', 'parse')
return true
})
return p
})

})

async function compileWithTaskAndCoverage (taskName: string) {
deleteFiles([
Uri.joinPath(workspaceUri, 'test_15.r'),
Uri.joinPath(workspaceUri, 'openedge-project.json'),
])
await workspace.fs.copy(Uri.joinPath(workspaceUri, '.vscode', 'ablunit-test-profile.proj1.15.json'), Uri.joinPath(workspaceUri, '.vscode', 'ablunit-test-profile.json'), { overwrite: true })

await tasks.fetchTasks()
.then((r) => {
log.info('fetchTasks complete')
for (const task of r) {
log.info('task.name=' + task.name)
}
const task = r.find((t) => t.name === taskName)
log.info('task=' + task?.name)
if (!task) {
throw new Error('task not found')
}
return tasks.executeTask(task)
}).then((r) => {
log.info('executeTask started (r=' + JSON.stringify(r, null, 2) + ')')
}, (e: unknown) => {
log.error('error=' + e)
throw e
})
await new Promise((resolve) => {
tasks.onDidEndTask((t) => {
log.info('task complete t.name=' + t.execution.task.name)
setTimeout(() => { resolve(true) }, 500)
})
})

const testRcode = Uri.joinPath(workspaceUri, 'test_15.r')
log.info('testRcode=' + testRcode.fsPath)
assert.fileExists(testRcode)
log.info('compile complete!')

await runTestsInFile('test_15.p', 1, true)
.then(() => {
assert.tests.count(1)
assert.tests.passed(1)
assert.tests.failed(0)
assert.tests.errored(0)
assert.tests.skipped(0)
})
}
Loading

0 comments on commit 5b38e77

Please sign in to comment.