From cf375b3fae14aae1ea928516ded318b3c1422c86 Mon Sep 17 00:00:00 2001 From: kenherring Date: Sat, 21 Sep 2024 03:12:17 -0400 Subject: [PATCH 1/8] Run test dir as folder type --- src/ABLResults.ts | 56 ++++++++++++------ src/ABLUnitConfigWriter.ts | 3 + src/ABLUnitRun.ts | 2 +- src/extension.ts | 104 ++++++++++++++-------------------- src/parse/TestParserCommon.ts | 2 +- 5 files changed, 85 insertions(+), 82 deletions(-) diff --git a/src/ABLResults.ts b/src/ABLResults.ts index f66b6e16..7a9f2453 100644 --- a/src/ABLResults.ts +++ b/src/ABLResults.ts @@ -6,7 +6,7 @@ import { FileType, MarkdownString, TestItem, TestItemCollection, TestMessage, Te TestRunProfileKind} from 'vscode' import { ABLUnitConfig } from './ABLUnitConfigWriter' import { ABLResultsParser, ITestCaseFailure, ITestCase, ITestSuite } from './parse/ResultsParser' -import { ABLTestSuite, ABLTestData } from './testTree' +import { ABLTestSuite, ABLTestData, ABLTestDir } from './testTree' import { parseCallstack } from './parse/CallStackParser' import { ABLProfile, ABLProfileJson, IModule } from './parse/ProfileParser' import { ABLDebugLines } from './ABLDebugLines' @@ -18,7 +18,8 @@ import { getDLC, IDlc } from './parse/OpenedgeProjectParser' import { Duration, isRelativePath } from './ABLUnitCommon' export interface ITestObj { - test: string + test?: string + folder?: string cases?: string[] } @@ -46,8 +47,9 @@ export class ABLResults implements Disposable { duration: Duration ablResults: ABLResultsParser | undefined tests: TestItem[] = [] + topLevelTests: ITestObj[] = [] testQueue: ITestObj[] = [] - testData!: WeakMap + testData = new WeakMap() skippedTests: TestItem[] = [] propath?: PropathParser debugLines?: ABLDebugLines @@ -125,7 +127,7 @@ export class ABLResults implements Disposable { const prom: (Thenable | Promise | Promise | undefined)[] = [] prom[0] = this.cfg.createProfileOptions(this.cfg.ablunitConfig.profOptsUri, this.cfg.ablunitConfig.profiler) prom[1] = this.cfg.createProgressIni(this.propath.toString(), this.dlc) - prom[2] = this.cfg.createAblunitJson(this.cfg.ablunitConfig.config_uri, this.cfg.ablunitConfig.options, this.testQueue) + prom[2] = this.cfg.createAblunitJson(this.cfg.ablunitConfig.config_uri, this.cfg.ablunitConfig.options, this.topLevelTests) prom[3] = this.cfg.createDbConnPf(this.cfg.ablunitConfig.dbConnPfUri, this.cfg.ablunitConfig.dbConns) return Promise.all(prom).then(() => { @@ -140,7 +142,7 @@ export class ABLResults implements Disposable { this.tests = [] } - async addTest (test: TestItem, options: TestRun) { + async addTest (test: TestItem, data: ABLTestData, options: TestRun, isTopLevel: boolean) { if (!test.uri) { log.error('test.uri is undefined (test.label = ' + test.label + ')', options) return @@ -162,29 +164,38 @@ export class ABLResults implements Disposable { } log.debug('addTest: ' + test.id + ', propathEntry=' + propathEntryTestFile) this.tests.push(test) + this.testData.set(test, data) let testCase: string | undefined = undefined - if (test.children.size === 0) { // test method or procedure + if (test.label === 'ABL Test Method' || test.label === 'ABL Test Proceudre') { testCase = test.label } const testUri = test.uri let testRel: string = workspace.asRelativePath(testUri, false) const p = await this.propath.search(testUri) - testRel = p?.propathRelativeFile ?? testRel.replace(/\\/g, '/') + testRel = (p?.propathRelativeFile ?? testRel).replace(/\\/g, '/') - const testObj: ITestObj = { test: testRel } - if (testCase) { - testObj.cases = [ testCase ] + let testObj: ITestObj | undefined = undefined + if (data instanceof ABLTestDir) { + // testObj = { folder: workspace.asRelativePath(testUri, false) } + testObj = { folder: p?.uri.fsPath.replace(/\\/g, '/') } + } else { + testObj = { test: testRel } + if (testCase) { + testObj.cases = [ testCase ] + } } - const existingTestObj = this.testQueue.find((t: ITestObj) => t.test === testRel) - if (testCase && existingTestObj) { - if(testObj.cases) { - if (!existingTestObj.cases) { - existingTestObj.cases = [] + if (testCase) { + const existingTestObj = this.testQueue.find((t: ITestObj) => t.test === testRel) + if (existingTestObj) { + if(testObj.cases) { + if (!existingTestObj.cases) { + existingTestObj.cases = [] + } + existingTestObj.cases.push(testCase) } - existingTestObj.cases.push(testCase) } return } @@ -193,6 +204,9 @@ export class ABLResults implements Disposable { log.warn('test already exists in configJson.tests: ' + testRel) } else { this.testQueue.push(testObj) + if (isTopLevel) { + this.topLevelTests.push(testObj) + } } } @@ -275,7 +289,13 @@ export class ABLResults implements Disposable { } async assignTestResults (item: TestItem, options: TestRun) { - + if (this.testData.get(item) instanceof ABLTestDir) { + log.debug('assigning test results for children of directory: ' + item.label) + for (const [ , child ] of item.children) { + await this.assignTestResults(child, options) + } + return + } if (this.skippedTests.includes(item)) { options.skipped(item) return @@ -352,7 +372,7 @@ export class ABLResults implements Disposable { private parseFinalSuite (item: TestItem, s: ITestSuite, options: TestRun) { if (!s.testcases) { - log.error('no test cases discovered or run - check the configuration for accuracy (item: ' + item.label + ')') + log.error('no test cases discovered or run - check the configuration for accuracy (item: ' + item.id + ')', options) options.errored(item, new TestMessage('no test cases discovered or run - check the configuration for accuracy'), this.duration.elapsed()) return } diff --git a/src/ABLUnitConfigWriter.ts b/src/ABLUnitConfigWriter.ts index a9135773..40de92ad 100644 --- a/src/ABLUnitConfigWriter.ts +++ b/src/ABLUnitConfigWriter.ts @@ -51,6 +51,9 @@ export class ABLUnitConfig { } createAblunitJson (_uri: Uri, cfg: CoreOptions, testQueue: ITestObj[]) { + if (!this.ablunitConfig.config_uri) { + throw new Error('Output location no defined!') + } log.info('creating ablunit.json: \'' + this.ablunitConfig.config_uri.fsPath + '\'') const promarr: PromiseLike[] = [] promarr.push( diff --git a/src/ABLUnitRun.ts b/src/ABLUnitRun.ts index ed86e6ce..4366a584 100644 --- a/src/ABLUnitRun.ts +++ b/src/ABLUnitRun.ts @@ -48,7 +48,7 @@ export const ablunitRun = async (options: TestRun, res: ABLResults, cancellation throw new CancellationError() }) - await res.cfg.createAblunitJson(res.cfg.ablunitConfig.config_uri, res.cfg.ablunitConfig.options, res.testQueue) + await res.cfg.createAblunitJson(res.cfg.ablunitConfig.config_uri, res.cfg.ablunitConfig.options, res.topLevelTests) const getCommand = () => { if (res.cfg.ablunitConfig.command.executable != '_progres' && diff --git a/src/extension.ts b/src/extension.ts index 7df51abe..a0b1f2ce 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -23,6 +23,7 @@ import { getContentFromFilesystem } from './parse/TestParserCommon' import { ABLTestCase, ABLTestClass, ABLTestData, ABLTestDir, ABLTestFile, ABLTestProgram, ABLTestSuite, resultData, testData } from './testTree' import { minimatch } from 'minimatch' import { ABLUnitRuntimeError } from 'ABLUnitRun' +import { basename } from 'path' export interface IExtensionTestReferences { testController: TestController @@ -146,9 +147,13 @@ export async function activate (context: ExtensionContext) { const startTestRun = (request: TestRunRequest, cancellation: CancellationToken) => { recentResults = [] + const topLevelTests: TestItem[] = [] - const discoverTests = async (tests: Iterable) => { + const discoverTests = async (tests: Iterable, isTopLevel: boolean) => { for (const test of tests) { + if (isTopLevel) { + topLevelTests.push(test) + } if (run.token.isCancellationRequested) { return } @@ -158,7 +163,7 @@ export async function activate (context: ExtensionContext) { const data = testData.get(test) - if (data instanceof ABLTestFile || data instanceof ABLTestCase) { + if (data instanceof ABLTestFile || data instanceof ABLTestCase || data instanceof ABLTestDir) { run.enqueued(test) queue.push({ test, data }) @@ -167,7 +172,7 @@ export async function activate (context: ExtensionContext) { } } else { - await discoverTests(gatherTestItems(test.children)) + await discoverTests(gatherTestItems(test.children), false) } } } @@ -188,7 +193,6 @@ export async function activate (context: ExtensionContext) { let ret = false for (const r of res) { - r.setTestData(testData.getMap()) if (res.length > 1) { log.info('starting ablunit tests for folder: ' + r.workspaceFolder.uri.fsPath, run) } @@ -298,15 +302,16 @@ export async function activate (context: ExtensionContext) { const createABLResults = async () => { const res: ABLResults[] = [] + const proms: Promise[] = [] - for(const itemData of queue) { + for(const {test, data } of queue) { if (run.token.isCancellationRequested) { return } - const wf = workspace.getWorkspaceFolder(itemData.test.uri!) + const wf = workspace.getWorkspaceFolder(test.uri!) if (!wf) { - log.error('Skipping test run for test item with no workspace folder: ' + itemData.test.uri!.fsPath) + log.error('Skipping test run for test item with no workspace folder: ' + test.uri!.fsPath) continue } let r = res.find(r => r.workspaceFolder === wf) @@ -320,9 +325,9 @@ export async function activate (context: ExtensionContext) { await r.start() res.push(r) } - await r.addTest(itemData.test, run) - // proms.push(r.addTest(itemData.test, run)) + proms.push(r.addTest(test, data, run, topLevelTests.includes(test))) } + await Promise.all(proms) resultData.set(run, res) log.debug('all tests added to test run results object, preparing test run ' + res[0].duration.toString()) @@ -341,7 +346,7 @@ export async function activate (context: ExtensionContext) { }) const tests = request.include ?? gatherTestItems(ctrl.items) - return discoverTests(tests) + return discoverTests(tests, true) .then(() => { return createABLResults() }) .then((res) => { if (!res) { @@ -414,34 +419,21 @@ export async function activate (context: ExtensionContext) { ctrl.refreshHandler = async (token: CancellationToken) => { log.info('ctrl.refreshHandler start') isRefreshTestsComplete = false - const prom = refreshTestTree(ctrl, token) - .then((r) => { - log.info('ctrl.refreshHandler post-refreshTestTree') - return r - }) + return refreshTestTree(ctrl, token) + .then((r) => { isRefreshTestsComplete = true; return }) .catch((e: unknown) => { throw e }) - log.info('ctrl.refreshHandler await prom') - const r = await prom.then((r) => { return r }, (e) => { throw e }) - log.info('ctrl.refreshHandler return (r=' + r + ')') - isRefreshTestsComplete = true - return - - // await prom - // await prom.then() - // const r = await prom.then(() => { log.info('ctrl.refreshHandler prom.then'); return true }, (e: unknown) => { throw unknownToError(e) }) - // log.info('ctrl.refreshHandler prom resolved (r=' + r + ')') - // return - // return refreshTestTree(ctrl, token).then(() => { - // log.info('refresh tests complete!') - // return - // }, (err: unknown) => { - // log.error('refresh tests failed. err=' + err) - // throw err - // }) } ctrl.resolveHandler = item => { - log.info('ctrl.resolveHandler') + + if (item?.uri) { + const relativePath = workspace.asRelativePath(item.uri) + log.info('ctrl.resolveHandler (relativePath=' + relativePath + ')') + } else if (item) { + log.info('ctrl.resolveHandler (item.label=' + item.label + ')') + } else { + log.info('ctrl.resolveHandler (item=undefined)') + } return resolveHandlerFunc(item) } @@ -568,10 +560,15 @@ function getOrCreateFile (controller: TestController, uri: Uri, excludePatterns? log.trace('No tests found in file: ' +workspace.asRelativePath(uri)) return { item: undefined, data: undefined } } - const file = controller.createTestItem(uri.fsPath, workspace.asRelativePath(uri.fsPath), uri) + const file = controller.createTestItem(uri.fsPath, basename(uri.fsPath), uri) testData.set(file, data) data.didResolve = false file.description = 'To be parsed...' + if (file.label.endsWith('.cls')) { + file.description = 'ABL Test Class' + } else if (file.label.endsWith('.p')) { + file.description = 'ABL Test Program' + } file.tags = [ new TestTag('runnable') ] const parent = getOrCreateDirNodeForFile(controller, uri, data instanceof ABLTestSuite) @@ -860,28 +857,6 @@ function findMatchingFiles (includePatterns: RelativePattern[], token: Cancellat }, (e) => { throw e }) } -// async function parseMatchingFiles (files: Uri[], controller: TestController, excludePatterns: RelativePattern[], token: CancellationToken, checkCancellationToken: () => void, resolvedCount: number, rejectedCount: number) { -async function parseMatchingFiles (files: Uri[], controller: TestController, excludePatterns: RelativePattern[], token: CancellationToken, checkCancellationToken: () => void): Promise { - const proms: Promise[] = [] - log.debug('parsing files... (count=' + files.length + ')') - for (const file of files) { - checkCancellationToken() - - const { item, data } = getOrCreateFile(controller, file, excludePatterns) - if (item && data instanceof ABLTestFile) { - const prom = data.updateFromDisk(controller, item, token).then((foundTestCase) => { - return foundTestCase - }, (e) => { - log.error('failed to update file from disk. err=' + e) - return false - }) - proms.push(prom) - } - } - const r = await Promise.all(proms).then(() => { return true }) - return r -} - function refreshTestTree (controller: TestController, token: CancellationToken): Promise { log.info('refreshing test tree...') const startTime = Date.now() @@ -918,16 +893,21 @@ function refreshTestTree (controller: TestController, token: CancellationToken): log.debug('finding files...') - const prom1 = findMatchingFiles(includePatterns, token, checkCancellationToken) + return findMatchingFiles(includePatterns, token, checkCancellationToken) .then((r) => { - log.info('return parseMatchingFiles (r=' + r + ')') - return parseMatchingFiles(r, controller, excludePatterns, token, checkCancellationToken) + for (const file of r) { + checkCancellationToken() + const { item, data } = getOrCreateFile(controller, file, excludePatterns) + if (!item || !data) { + log.warn('could not create test item for file: ' + file.fsPath) + } + } + return }) .then((r) => { log.info('return true (r=' + r + ')') return true - }) - return prom1.catch((e: unknown) => { throw e }) + }, (e) => { throw e }) } function getControllerTestFileCount (controller: TestController) { diff --git a/src/parse/TestParserCommon.ts b/src/parse/TestParserCommon.ts index 4eb657ba..bff2bed5 100644 --- a/src/parse/TestParserCommon.ts +++ b/src/parse/TestParserCommon.ts @@ -9,7 +9,7 @@ export async function getContentFromFilesystem (uri: Uri) { const rawContent = await workspace.fs.readFile(uri) return textDecoder.decode(rawContent) } catch (e) { - log.warn('Error providing tests for ${uri.fsPath}: ' + e) + log.warn('Error providing tests for ' + uri.fsPath + ': ' + e) return '' } } From 3161b61d25cebfc455c441bac79aa2353da82b3b Mon Sep 17 00:00:00 2001 From: kenherring Date: Sat, 21 Sep 2024 03:37:20 -0400 Subject: [PATCH 2/8] push top level individual test cases --- src/ABLResults.ts | 17 +++++++++++++++-- src/extension.ts | 1 + 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/ABLResults.ts b/src/ABLResults.ts index 7a9f2453..48d0f546 100644 --- a/src/ABLResults.ts +++ b/src/ABLResults.ts @@ -6,7 +6,7 @@ import { FileType, MarkdownString, TestItem, TestItemCollection, TestMessage, Te TestRunProfileKind} from 'vscode' import { ABLUnitConfig } from './ABLUnitConfigWriter' import { ABLResultsParser, ITestCaseFailure, ITestCase, ITestSuite } from './parse/ResultsParser' -import { ABLTestSuite, ABLTestData, ABLTestDir } from './testTree' +import { ABLTestSuite, ABLTestData, ABLTestDir, ABLTestCase } from './testTree' import { parseCallstack } from './parse/CallStackParser' import { ABLProfile, ABLProfileJson, IModule } from './parse/ProfileParser' import { ABLDebugLines } from './ABLDebugLines' @@ -167,15 +167,19 @@ export class ABLResults implements Disposable { this.testData.set(test, data) let testCase: string | undefined = undefined - if (test.label === 'ABL Test Method' || test.label === 'ABL Test Proceudre') { + log.info('100') + if (data instanceof ABLTestCase) { + log.info('101 test.label=' + test.label + ' is a test case') testCase = test.label } const testUri = test.uri let testRel: string = workspace.asRelativePath(testUri, false) + log.info('102 testRel=' + testRel) const p = await this.propath.search(testUri) testRel = (p?.propathRelativeFile ?? testRel).replace(/\\/g, '/') + log.info('103') let testObj: ITestObj | undefined = undefined if (data instanceof ABLTestDir) { // testObj = { folder: workspace.asRelativePath(testUri, false) } @@ -187,24 +191,33 @@ export class ABLResults implements Disposable { } } + log.info('104') if (testCase) { + log.info('105') const existingTestObj = this.testQueue.find((t: ITestObj) => t.test === testRel) if (existingTestObj) { if(testObj.cases) { if (!existingTestObj.cases) { existingTestObj.cases = [] } + log.info('106') existingTestObj.cases.push(testCase) } } + if (isTopLevel) { + this.topLevelTests.push(testObj) + } return } + log.info('107') if (this.testQueue.find((t: ITestObj) => t.test === testRel)) { log.warn('test already exists in configJson.tests: ' + testRel) } else { + log.info('108') this.testQueue.push(testObj) if (isTopLevel) { + log.info('109') this.topLevelTests.push(testObj) } } diff --git a/src/extension.ts b/src/extension.ts index a0b1f2ce..a180bc9d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -305,6 +305,7 @@ export async function activate (context: ExtensionContext) { const proms: Promise[] = [] for(const {test, data } of queue) { + log.info('test.id=' + test.id) if (run.token.isCancellationRequested) { return } From 591a0cd3acb21ee047c731e6d8e6ca5895bd31ea Mon Sep 17 00:00:00 2001 From: kenherring Date: Sat, 21 Sep 2024 15:36:10 -0400 Subject: [PATCH 3/8] proj0 passing --- src/ABLResults.ts | 97 ++++++++++++++--------- src/extension.ts | 107 ++++++++++++++++++++----- src/parse/TestParserCommon.ts | 22 +++--- src/testTree.ts | 33 ++++---- test/suites/proj0.test.ts | 145 ++++++++++++++-------------------- 5 files changed, 237 insertions(+), 167 deletions(-) diff --git a/src/ABLResults.ts b/src/ABLResults.ts index 48d0f546..25de270a 100644 --- a/src/ABLResults.ts +++ b/src/ABLResults.ts @@ -6,7 +6,7 @@ import { FileType, MarkdownString, TestItem, TestItemCollection, TestMessage, Te TestRunProfileKind} from 'vscode' import { ABLUnitConfig } from './ABLUnitConfigWriter' import { ABLResultsParser, ITestCaseFailure, ITestCase, ITestSuite } from './parse/ResultsParser' -import { ABLTestSuite, ABLTestData, ABLTestDir, ABLTestCase } from './testTree' +import { ABLTestSuite, ABLTestDir, ABLTestCase, testData} from './testTree' import { parseCallstack } from './parse/CallStackParser' import { ABLProfile, ABLProfileJson, IModule } from './parse/ProfileParser' import { ABLDebugLines } from './ABLDebugLines' @@ -15,13 +15,19 @@ import { PropathParser } from './ABLPropath' import { log } from './ChannelLogger' import { ABLUnitRuntimeError, RunStatus, ablunitRun } from './ABLUnitRun' import { getDLC, IDlc } from './parse/OpenedgeProjectParser' -import { Duration, isRelativePath } from './ABLUnitCommon' +import { Duration } from './ABLUnitCommon' -export interface ITestObj { - test?: string - folder?: string +export interface ITestFile { + test: string cases?: string[] } +export interface ITestFolder { + folder: string + test?: undefined + cases?: undefined +} + +export type ITestObj = ITestFile | ITestFolder export interface IABLUnitJson { options: { @@ -49,7 +55,6 @@ export class ABLResults implements Disposable { tests: TestItem[] = [] topLevelTests: ITestObj[] = [] testQueue: ITestObj[] = [] - testData = new WeakMap() skippedTests: TestItem[] = [] propath?: PropathParser debugLines?: ABLDebugLines @@ -98,10 +103,6 @@ export class ABLResults implements Disposable { log.info('STATUS: ' + status) } - setTestData (testData: WeakMap) { - this.testData = testData - } - start () { log.info('[start] workspaceFolder=' + this.workspaceFolder.uri.fsPath) this.cfg.setup(this.workspaceFolder, this.request) @@ -127,22 +128,18 @@ export class ABLResults implements Disposable { const prom: (Thenable | Promise | Promise | undefined)[] = [] prom[0] = this.cfg.createProfileOptions(this.cfg.ablunitConfig.profOptsUri, this.cfg.ablunitConfig.profiler) prom[1] = this.cfg.createProgressIni(this.propath.toString(), this.dlc) - prom[2] = this.cfg.createAblunitJson(this.cfg.ablunitConfig.config_uri, this.cfg.ablunitConfig.options, this.topLevelTests) + // prom[2] = this.cfg.createAblunitJson(this.cfg.ablunitConfig.config_uri, this.cfg.ablunitConfig.options, this.topLevelTests) prom[3] = this.cfg.createDbConnPf(this.cfg.ablunitConfig.dbConnPfUri, this.cfg.ablunitConfig.dbConns) return Promise.all(prom).then(() => { - log.info('done creating config files for run') + log.info('done creating config files for start') return }, (err) => { log.error('ABLResults.start() did not complete promises. err=' + err) }) } - resetTests () { - this.tests = [] - } - - async addTest (test: TestItem, data: ABLTestData, options: TestRun, isTopLevel: boolean) { + async addTest (test: TestItem, options: TestRun, isTopLevel: boolean) { if (!test.uri) { log.error('test.uri is undefined (test.label = ' + test.label + ')', options) return @@ -151,23 +148,19 @@ export class ABLResults implements Disposable { throw new Error('propath is undefined') } - const testPropath = await this.propath.search(test.uri) - if (!testPropath) { - this.skippedTests.push(test) - log.warn('skipping test, not found in propath: ' + workspace.asRelativePath(test.uri), options) - return + log.debug('addTest: ' + test.id + '; this.tests.length=' + this.tests.length) + if (this.tests.includes(test)) { + log.info('test already exists in tests: ' + test.id) } - - let propathEntryTestFile = testPropath.propathEntry.path - if (isRelativePath(testPropath.propathEntry.path)) { - propathEntryTestFile = workspace.asRelativePath(Uri.joinPath(this.workspaceFolder.uri, testPropath.propathEntry.path)) - } - log.debug('addTest: ' + test.id + ', propathEntry=' + propathEntryTestFile) this.tests.push(test) - this.testData.set(test, data) let testCase: string | undefined = undefined - log.info('100') + log.info('100 ' + test.id) + const data = testData.get(test) + if (!data) { + log.error('could not find test data for TestItem.id=' + test.id) + return + } if (data instanceof ABLTestCase) { log.info('101 test.label=' + test.label + ' is a test case') testCase = test.label @@ -177,13 +170,25 @@ export class ABLResults implements Disposable { let testRel: string = workspace.asRelativePath(testUri, false) log.info('102 testRel=' + testRel) const p = await this.propath.search(testUri) + if (!p?.propathRelativeFile && data instanceof ABLTestDir) { + log.info('directory ' + testRel + ' not found in propath, adding children') + for (const [ , child ] of test.children) { + await this.addTest(child, options, isTopLevel) + } + return + } testRel = (p?.propathRelativeFile ?? testRel).replace(/\\/g, '/') - log.info('103') + log.info('103.1') let testObj: ITestObj | undefined = undefined + log.info('103.2') + log.info('103.3 ' + JSON.stringify(data)) + log.info('104.4 ' + typeof data) + log.info('103.5 ' + data.label) + log.info('103.6 ' + data.type) if (data instanceof ABLTestDir) { // testObj = { folder: workspace.asRelativePath(testUri, false) } - testObj = { folder: p?.uri.fsPath.replace(/\\/g, '/') } + testObj = { folder: p?.uri.fsPath.replace(/\\/g, '/') ?? workspace.asRelativePath(test.uri) } } else { testObj = { test: testRel } if (testCase) { @@ -191,8 +196,12 @@ export class ABLResults implements Disposable { } } + if (isTopLevel) { + this.topLevelTests.push(testObj) + } + log.info('104') - if (testCase) { + if (testObj.test && testCase) { log.info('105') const existingTestObj = this.testQueue.find((t: ITestObj) => t.test === testRel) if (existingTestObj) { @@ -205,20 +214,26 @@ export class ABLResults implements Disposable { } } if (isTopLevel) { - this.topLevelTests.push(testObj) + if (!this.topLevelTests.includes(testObj)) { + this.topLevelTests.push(testObj) + } } return } - log.info('107') + log.info('107 ' + JSON.stringify(testObj)) if (this.testQueue.find((t: ITestObj) => t.test === testRel)) { log.warn('test already exists in configJson.tests: ' + testRel) + return } else { log.info('108') this.testQueue.push(testObj) if (isTopLevel) { - log.info('109') - this.topLevelTests.push(testObj) + log.info('109 this.topLevelTests.length=' + this.topLevelTests.length + '; ' + JSON.stringify(testObj)) + // if (this.tests || !this.topLevelTests.find((exist) => testObj.test == exist.test)) { + if (!this.topLevelTests.includes(testObj)) { + this.topLevelTests.push(testObj) + } } } } @@ -302,7 +317,8 @@ export class ABLResults implements Disposable { } async assignTestResults (item: TestItem, options: TestRun) { - if (this.testData.get(item) instanceof ABLTestDir) { + const itemData = testData.get(item) + if (itemData instanceof ABLTestDir) { log.debug('assigning test results for children of directory: ' + item.label) for (const [ , child ] of item.children) { await this.assignTestResults(child, options) @@ -332,12 +348,15 @@ export class ABLResults implements Disposable { const suiteName = await this.getSuiteName(item) const s = this.ablResults.resultsJson[0].testsuite.find((s: ITestSuite) => s.classname === suiteName || s.name === suiteName) if (!s) { + log.info('400') log.error('could not find test suite for \'' + suiteName + '\' in results (item=' + item.label + ')') + log.info('401') options.errored(item, new TestMessage('could not find test suite for \'' + suiteName + '\' in results'), this.duration.elapsed()) + log.info('402') return } - const data = this.testData.get(item) + const data = testData.get(item) if (data instanceof ABLTestSuite) { if (!s.testsuite) { log.error('no child testsuites found (item=' + item.label + ')') diff --git a/src/extension.ts b/src/extension.ts index a180bc9d..5a485a89 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -60,14 +60,18 @@ export async function activate (context: ExtensionContext) { workspace.onDidChangeConfiguration(e => { updateConfiguration(e) }), workspace.onDidOpenTextDocument(e => { - return new Disposable(async () => { - await updateNodeForDocument(e, 'didOpen').then(() => { - log.trace('updateNodeForDocument complete for ' + e.uri) - return - }, (e: unknown) => { - log.error('failed updateNodeForDocument onDidTextDocument! err=' + e) - }) + log.info('updateNodeForDocument e.fileName=' + e.fileName) + return updateNodeForDocument(e, 'didOpen').then(() => { + log.info('updateNodeForDocument complete') + log.info('updateNodeForDocument complete e.' + e.uri.fsPath) + log.trace('updateNodeForDocument complete for ' + e.uri) + return + }, (e: unknown) => { + log.error('failed updateNodeForDocument onDidTextDocument! err=' + e) }) + log.info('updateNodeForDocument complete') + // return new Disposable(() => { + // }) }) // watcher.onDidCreate(uri => { createOrUpdateFile(controller, uri) }) // watcher.onDidChange(uri => { createOrUpdateFile(controller, uri) }) @@ -149,6 +153,14 @@ export async function activate (context: ExtensionContext) { recentResults = [] const topLevelTests: TestItem[] = [] + + const enqueueTestsAndChildren = (tests: TestItemCollection) => { + for(const [, test ] of tests) { + run.enqueued(test) + enqueueTestsAndChildren(test.children) + } + } + const discoverTests = async (tests: Iterable, isTopLevel: boolean) => { for (const test of tests) { if (isTopLevel) { @@ -162,17 +174,21 @@ export async function activate (context: ExtensionContext) { } const data = testData.get(test) + log.info('300 ' + test.id) if (data instanceof ABLTestFile || data instanceof ABLTestCase || data instanceof ABLTestDir) { run.enqueued(test) + enqueueTestsAndChildren(test.children) + log.info('queue.push(' + test.id + ')') + log.info('push') queue.push({ test, data }) - for(const [,childTest] of test.children) { - run.enqueued(childTest) + if (data instanceof ABLTestDir) { + await discoverTests(gatherTestItems(test.children), isTopLevel) } } else { - await discoverTests(gatherTestItems(test.children), false) + await discoverTests(gatherTestItems(test.children), test.id.endsWith('#ABLTestSuiteGroup')) } } } @@ -245,18 +261,28 @@ export async function activate (context: ExtensionContext) { log.debug('cannot print totals - missing ablResults object') } + log.info('500.1') for (const { test } of queue) { - if (workspace.getWorkspaceFolder(test.uri!) === r.workspaceFolder) { + log.info('500.2') + if (test.uri && workspace.getWorkspaceFolder(test.uri) === r.workspaceFolder) { + log.info('500.3') if (run.token.isCancellationRequested) { + log.info('500.4') log.debug('cancellation requested - runTestQueue-2') throw new CancellationError() } else { + log.info('500.5') await r.assignTestResults(test, run) + log.info('500.6') } + log.info('500.7') } + log.info('500.8') } + log.info('501') } + log.info('502') if(!ret) { for (const { test } of queue) { run.errored(test, new TestMessage('ablunit run failed')) @@ -267,6 +293,7 @@ export async function activate (context: ExtensionContext) { run.end() return } + log.info('503') log.debug('ablunit test run complete', run) @@ -302,22 +329,39 @@ export async function activate (context: ExtensionContext) { const createABLResults = async () => { const res: ABLResults[] = [] - const proms: Promise[] = [] + log.info('200') for(const {test, data } of queue) { + log.info('201') log.info('test.id=' + test.id) if (run.token.isCancellationRequested) { return } - const wf = workspace.getWorkspaceFolder(test.uri!) + log.info('202 test.uri=' + test.uri) + + let wf: WorkspaceFolder | undefined = undefined + if (test.uri) { + wf = workspace.getWorkspaceFolder(test.uri) + } + log.info('203 wf=' + wf) if (!wf) { - log.error('Skipping test run for test item with no workspace folder: ' + test.uri!.fsPath) + if (test.id.endsWith('#ABLTestSuiteGroup')) { + log.info('Skip add for ABLTestSuiteGroup (children added separately)') + } else { + log.info('204') + log.error('Skipping test run for test item with no workspace folder: ' + test.uri?.fsPath) + log.info('205') + } continue } + log.info('206') let r = res.find(r => r.workspaceFolder === wf) + log.info('207') if (!r) { + log.info('208') r = new ABLResults(wf, await getStorageUri(wf), contextStorageUri, contextResourcesUri, request, cancellation) + log.info('209') cancellation.onCancellationRequested(() => { log.debug('cancellation requested - createABLResults-1') r?.dispose() @@ -326,12 +370,18 @@ export async function activate (context: ExtensionContext) { await r.start() res.push(r) } - proms.push(r.addTest(test, data, run, topLevelTests.includes(test))) + log.info('210') + await r.addTest(test, run, topLevelTests.includes(test) || data instanceof ABLTestDir) + log.info('211') } - await Promise.all(proms) + log.info('212') resultData.set(run, res) + log.info('213') log.debug('all tests added to test run results object, preparing test run ' + res[0].duration.toString()) + log.info('214') + log.info('all tests added to test run results object, preparing test run ' + res[0].duration.toString()) + log.info('215') return res } @@ -386,7 +436,9 @@ export async function activate (context: ExtensionContext) { log.info('skipping updateNodeForDocument for file not in workspace: ' + u.fsPath) return Promise.resolve() } + log.info('550') return updateNode(u, ctrl) + .then(() => { log.info('551'); return }) } function resolveHandlerFunc (item: TestItem | undefined) { @@ -426,7 +478,6 @@ export async function activate (context: ExtensionContext) { } ctrl.resolveHandler = item => { - if (item?.uri) { const relativePath = workspace.asRelativePath(item.uri) log.info('ctrl.resolveHandler (relativePath=' + relativePath + ')') @@ -478,14 +529,25 @@ function updateNode (uri: Uri, ctrl: TestController) { log.trace('updateNode uri=' + uri.fsPath) if(uri.scheme !== 'file' || isFileExcluded(uri, getExcludePatterns())) { return new Promise(() => { return false }) } + log.info('600') const { item, data } = getOrCreateFile(ctrl, uri) + log.info('601') if(!item || !data) { + log.info('602') return new Promise(() => { return false }) } + log.info('603') ctrl.invalidateTestResults(item) + log.info('604 uri=' + uri.fsPath) return getContentFromFilesystem(uri).then((contents) => { - return data.updateFromContents(ctrl, contents, item) + log.info('605') + data.updateFromContents(ctrl, contents, item) + log.info('606') + return + }, (e) => { + log.info('607 e=' + e) + throw e }) } @@ -700,7 +762,7 @@ function getTestFileAttrs (file: Uri) { return 'other' } -function gatherAllTestItems (collection: TestItemCollection) { +export function gatherAllTestItems (collection: TestItemCollection) { const items: TestItem[] = [] collection.forEach(item => { items.push(item) @@ -899,8 +961,11 @@ function refreshTestTree (controller: TestController, token: CancellationToken): for (const file of r) { checkCancellationToken() const { item, data } = getOrCreateFile(controller, file, excludePatterns) - if (!item || !data) { - log.warn('could not create test item for file: ' + file.fsPath) + if (!item) { + log.warn('could not create test item for file - item undefined: ' + file.fsPath) + } + if (!data) { + log.warn('could not create test item for file - data undefined: ' + file.fsPath) } } return diff --git a/src/parse/TestParserCommon.ts b/src/parse/TestParserCommon.ts index bff2bed5..a6da1101 100644 --- a/src/parse/TestParserCommon.ts +++ b/src/parse/TestParserCommon.ts @@ -3,15 +3,19 @@ import { TextDecoder } from 'util' import { log } from '../ChannelLogger' const textDecoder = new TextDecoder('utf-8') - -export async function getContentFromFilesystem (uri: Uri) { - try { - const rawContent = await workspace.fs.readFile(uri) - return textDecoder.decode(rawContent) - } catch (e) { - log.warn('Error providing tests for ' + uri.fsPath + ': ' + e) - return '' - } +export function getContentFromFilesystem (uri: Uri) { + log.info('700') + const v = workspace.fs.readFile(uri) + .then((rawContent) => { + log.info('701') + return textDecoder.decode(rawContent) + }, (e) => { + log.info('799') + log.warn('Error providing tests for ' + uri.fsPath + ': ' + e) + return '' + }) + log.info('702 v=' + JSON.stringify(v)) + return v } export function getLines (text: string, annotation: string): [ string[], boolean ] { diff --git a/src/testTree.ts b/src/testTree.ts index 3d29d500..779a1fa7 100644 --- a/src/testTree.ts +++ b/src/testTree.ts @@ -56,6 +56,7 @@ function createTestItem ( } interface ITestType { + type: string isFile: boolean didResolve: boolean runnable: boolean @@ -63,7 +64,10 @@ interface ITestType { description: string } +type TestReferenceType = 'ABLTestDir' | 'ABLTestFile' | 'ABLTestCase' | 'ABLTestClass' | 'ABLTestProgram' | 'ABLTestSuite' + class TestTypeObj implements ITestType { + public type: TestReferenceType public isFile = false public didResolve = false public runnable = false @@ -71,29 +75,30 @@ class TestTypeObj implements ITestType { public description: string public label: string - constructor (description: string, label: string) { + constructor (description: string, label: string, type: TestReferenceType) { this.description = description this.label = label + this.type = type } + } -export class ABLTestDir implements ITestType { - public isFile = false - public didResolve = true - public runnable = true - public canResolveChildren = false - public description: string +export class ABLTestDir extends TestTypeObj { public relativePath: string - public label = '' + constructor (desc: string, label: string, path: Uri | string) { - this.description = desc - this.label = label + super(desc, label, 'ABLTestDir') if (path instanceof Uri) { this.relativePath = workspace.asRelativePath(path.fsPath, false) } else { this.relativePath = path } + + this.isFile = false + this.didResolve = true + this.runnable = true + this.canResolveChildren = false } } @@ -101,7 +106,7 @@ export class ABLTestCase extends TestTypeObj { constructor ( public readonly id: string, label: string, - description: string) { super(description, label) } + description: string) { super(description, label, 'ABLTestCase') } } export class ABLTestFile extends TestTypeObj { @@ -248,7 +253,7 @@ export class ABLTestFile extends TestTypeObj { export class ABLTestSuite extends ABLTestFile { constructor (label: string) { - super('ABL Test Suite', label) + super('ABL Test Suite', label, 'ABLTestSuite') } public override updateFromContents (controller: TestController, content: string, item: TestItem) { @@ -315,7 +320,7 @@ export class ABLTestClass extends ABLTestFile { public classTypeName = '' constructor (label: string) { - super('ABL Test Class', label) + super('ABL Test Class', label, 'ABLTestClass') } setClassInfo (classTypeName?: string) { @@ -337,7 +342,7 @@ export class ABLTestClass extends ABLTestFile { export class ABLTestProgram extends ABLTestFile { constructor (label: string) { - super('ABL Test Program', label) + super('ABL Test Program', label, 'ABLTestProgram') } public override updateFromContents (controller: TestController, content: string, item: TestItem) { diff --git a/test/suites/proj0.test.ts b/test/suites/proj0.test.ts index d83888d1..28ec7d45 100644 --- a/test/suites/proj0.test.ts +++ b/test/suites/proj0.test.ts @@ -1,6 +1,16 @@ -import { Uri, commands, window, workspace, TestItem } from 'vscode' -import { assert, deleteFile, getResults, getTestController, log, refreshTests, runAllTests, runAllTestsWithCoverage, suiteSetupCommon, toUri, updateTestProfile } from '../testCommon' +import { Uri, commands, window, workspace, TestController } from 'vscode' +import { assert, deleteFile, getResults, getTestController, log, refreshTests, runAllTests, runAllTestsWithCoverage, sleep, suiteSetupCommon, toUri, updateTestProfile } from '../testCommon' import { ABLResultsParser } from 'parse/ResultsParser' +import { gatherAllTestItems } from 'extension' + +function getTestItem (ctrl: TestController, uri: Uri) { + const testItems = gatherAllTestItems(ctrl.items) + const testItem = testItems.find((item) => item.uri?.fsPath === uri.fsPath) + if (!testItem) { + throw new Error('cannot find TestItem for ' + uri.fsPath) + } + return testItem +} suite('proj0 - Extension Test Suite', () => { @@ -35,7 +45,7 @@ suite('proj0 - Extension Test Suite', () => { // is it possible to validate the line coverage displayed and not just the reported coverage? does it matter? test.skip('proj0.3 - open file, run test, validate coverage displays', async () => { - const testFileUri = Uri.joinPath(workspace.workspaceFolders![0].uri, 'src', 'dirA', 'dir1', 'testInDir.p') + const testFileUri = toUri('src/dirA/dir1/testInDir.p') await window.showTextDocument(testFileUri) await runAllTestsWithCoverage() @@ -59,102 +69,69 @@ suite('proj0 - Extension Test Suite', () => { assert.equal(0, executedLines.length, 'executed lines found for ' + workspace.asRelativePath(testFileUri) + '. should be empty') }) - test('proj0.5 - parse test class with expected error annotation', async () => { - const ctrl = await refreshTests() + test('proj0.5 - parse test class with expected error annotation', () => { + const testFileUri = toUri('src/threeTestMethods.cls') + return refreshTests() + .then(() => { return workspace.openTextDocument(testFileUri) }) + .then((e) => { + log.info('e=' + JSON.stringify(e)) + return sleep(100) + }) + .then(() => { return sleep(100) }) .then(() => { return getTestController() }) - - let testClassItem: TestItem | undefined - // find the TestItem for src/threeTestMethods.cls - ctrl.items.forEach((item) => { - log.info('item.label=' + item.label + '; item.id=' + item.id + '; item.uri' + item.uri) - if (item.label === 'src') { - item.children.forEach(element => { - if (element.label === 'threeTestMethods') { - testClassItem = element - } - }) - } - }) - - if (!testClassItem) { - throw new Error('cannot find TestItem for src/threeTestMethods.cls') - } - - assert.equal(testClassItem.children.size, 3, 'testClassItem.children.size should be 3') + .then((ctrl) => { + const testItem = getTestItem(ctrl, testFileUri) + assert.equal(testItem.children.size, 3, 'testClassItem.children.size should be 3') + return + }) }) - test('proj0.6 - parse test program with expected error annotation', async () => { - const ctrl = await refreshTests() + test('proj0.6 - parse test program with expected error annotation', () => { + const testFileUri = toUri('src/threeTestProcedures.p') + return refreshTests() + .then(() => { return workspace.openTextDocument(testFileUri) }) + .then((e) => { return sleep(100) }) + .then(() => { return sleep(100) }) .then(() => { return getTestController() }) - - let testClassItem: TestItem | undefined - // find the TestItem for src/threeTestProcedures.p - ctrl.items.forEach((item) => { - log.info('item.label=' + item.label + '; item.id=' + item.id + '; item.uri' + item.uri) - if (item.label === 'src') { - item.children.forEach(element => { - if (element.label === 'threeTestProcedures.p') { - testClassItem = element - } - }) - } - }) - - if (!testClassItem) { - throw new Error('cannot find TestItem for src/threeTestProcedures.p') - } - assert.equal(testClassItem.children.size, 3, 'testClassItem.children.size should be 3') + .then((ctrl) => { + const testItem = getTestItem(ctrl, testFileUri) + assert.equal(testItem.children.size, 3, 'testClassItem.children.size should be 3') + return + }) }) - test('proj0.7 - parse test class with skip annotation', async () => { - const ctrl = await refreshTests() + test('proj0.7 - parse test class with skip annotation', () => { + const testFileUri = toUri('src/ignoreMethod.cls') + return workspace.openTextDocument(testFileUri) + .then(() => { return refreshTests() }) + .then(() => { return sleep(100) }) + .then(() => { return sleep(100) }) .then(() => { return getTestController() }) - - let testClassItem: TestItem | undefined - // find the TestItem for src/ignoreMethod.cls - ctrl.items.forEach((item) => { - if (item.label === 'src') { - item.children.forEach(element => { - if (element.label === 'ignoreMethod') { - testClassItem = element - } - }) - } - }) - - if (!testClassItem) { - throw new Error('cannot find TestItem for src/ignoreMethod.cls') - } - assert.equal(testClassItem.children.size, 5, 'testClassItem.children.size should be 5') + .then((ctrl) => { + const testItem = getTestItem(ctrl, testFileUri) + assert.equal(testItem.children.size, 5, 'testClassItem.children.size should be 5') + return + }) }) - test('proj0.8 - parse test procedure with skip annotation', async () => { - const ctrl = await refreshTests() + test('proj0.8 - parse test procedure with skip annotation', () => { + const testFileUri = toUri('src/ignoreProcedure.p') + return workspace.openTextDocument(testFileUri) + .then(() => { return sleep(100) }) .then(() => { return getTestController() }) - - let testClassItem: TestItem | undefined - // find the TestItem for src/ignoreProcedure.p - ctrl.items.forEach((item) => { - if (item.label === 'src') { - item.children.forEach(element => { - if (element.label === 'ignoreProcedure.p') { - testClassItem = element - } - }) - } - }) - - if (!testClassItem) { - throw new Error('cannot find TestItem for src/ignoreProcedure.p') - } - assert.equal(testClassItem.children.size, 5, 'testClassItem.children.size should be 5') + .then((ctrl) => { + const testItem = getTestItem(ctrl, testFileUri) + assert.equal(testItem.children.size, 5, 'testClassItem.children.size should be 5') + return + }) }) - test('proj0.9 - ABLResultsParser', async () => { + test('proj0.9 - ABLResultsParser', () => { const rp = new ABLResultsParser() - await rp.parseResults(toUri('results_test1.xml')) + return rp.parseResults(toUri('results_test1.xml')) .then(() => { log.info('parsed results_test1.xml successfully') + return }, (e: unknown) => { if (e instanceof Error) { log.info('e.message=' + e.message) From 4acc90e90665a0d998bdc940ec0228330e223307 Mon Sep 17 00:00:00 2001 From: kenherring Date: Sat, 21 Sep 2024 18:39:11 -0400 Subject: [PATCH 4/8] incremental --- src/ABLResults.ts | 48 ++++++++++++++++++++++++-------------- test/suites/proj7A.test.ts | 2 +- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/ABLResults.ts b/src/ABLResults.ts index 25de270a..8be56c49 100644 --- a/src/ABLResults.ts +++ b/src/ABLResults.ts @@ -18,6 +18,7 @@ import { getDLC, IDlc } from './parse/OpenedgeProjectParser' import { Duration } from './ABLUnitCommon' export interface ITestFile { + folder?: undefined test: string cases?: string[] } @@ -152,6 +153,7 @@ export class ABLResults implements Disposable { if (this.tests.includes(test)) { log.info('test already exists in tests: ' + test.id) } + this.tests.push(test) let testCase: string | undefined = undefined @@ -170,12 +172,17 @@ export class ABLResults implements Disposable { let testRel: string = workspace.asRelativePath(testUri, false) log.info('102 testRel=' + testRel) const p = await this.propath.search(testUri) - if (!p?.propathRelativeFile && data instanceof ABLTestDir) { - log.info('directory ' + testRel + ' not found in propath, adding children') - for (const [ , child ] of test.children) { - await this.addTest(child, options, isTopLevel) + if (!p?.propathRelativeFile) { + if (data instanceof ABLTestDir) { + log.info('directory ' + testRel + ' not found in propath, adding children') + for (const [ , child ] of test.children) { + await this.addTest(child, options, isTopLevel) + } + return + } else { + log.warn('test file ' + testRel + ' not found in propath') + return } - return } testRel = (p?.propathRelativeFile ?? testRel).replace(/\\/g, '/') @@ -212,13 +219,15 @@ export class ABLResults implements Disposable { log.info('106') existingTestObj.cases.push(testCase) } + return } - if (isTopLevel) { - if (!this.topLevelTests.includes(testObj)) { - this.topLevelTests.push(testObj) - } - } - return + // } + // if (isTopLevel) { + // if (!this.topLevelTests.find((exist) => exist.test == testObj.test && exist.cases == testObj.cases)) { + // this.topLevelTests.push(testObj) + // } + // } + // return } log.info('107 ' + JSON.stringify(testObj)) @@ -227,14 +236,19 @@ export class ABLResults implements Disposable { return } else { log.info('108') - this.testQueue.push(testObj) - if (isTopLevel) { - log.info('109 this.topLevelTests.length=' + this.topLevelTests.length + '; ' + JSON.stringify(testObj)) - // if (this.tests || !this.topLevelTests.find((exist) => testObj.test == exist.test)) { - if (!this.topLevelTests.includes(testObj)) { - this.topLevelTests.push(testObj) + if (testObj.folder) { + if (this.testQueue.find((t: ITestObj) => t.folder === testObj.folder)) { + this.testQueue.push(testObj) } } + // if (isTopLevel) { + // log.info('109 this.topLevelTests.length=' + this.topLevelTests.length + '; ' + JSON.stringify(testObj)) + // if (this.tests || !this.topLevelTests.find((exist) => testObj.test == exist.test)) { + // if (!this.topLevelTests.includes(testObj)) { + // if (this.topLevelTests.find((exist) => testObj.folder == exist.folder)) { + // this.topLevelTests.push(testObj) + // } + // } } } diff --git a/test/suites/proj7A.test.ts b/test/suites/proj7A.test.ts index d166a51f..10ca458a 100644 --- a/test/suites/proj7A.test.ts +++ b/test/suites/proj7A.test.ts @@ -10,7 +10,7 @@ suite('proj7A - Extension Test Suite', () => { await beforeProj7() }) - test('proj7A.1 - test count', async () => { + test.skip('proj7A.1 - test count', async () => { await runAllTests() const resultsJson = Uri.joinPath(workspaceUri, 'temp', 'results.json') From 591915c902dd537e21ecd9eed06ab5730c78f6b4 Mon Sep 17 00:00:00 2001 From: kenherring Date: Sat, 21 Sep 2024 18:42:16 -0400 Subject: [PATCH 5/8] incremental --- src/ABLPropath.ts | 33 +++++++++- src/ABLResults.ts | 13 +++- src/extension.ts | 65 +++++++++++++++---- test/suites/proj9.test.ts | 15 +---- test_projects/proj9/openedge-project.json | 2 +- .../proj9/src/dirA/dir1/testInDir2.p | 23 ------- 6 files changed, 100 insertions(+), 51 deletions(-) delete mode 100644 test_projects/proj9/src/dirA/dir1/testInDir2.p diff --git a/src/ABLPropath.ts b/src/ABLPropath.ts index 92ea74b4..ecc66b54 100644 --- a/src/ABLPropath.ts +++ b/src/ABLPropath.ts @@ -1,7 +1,9 @@ import { Uri, workspace, WorkspaceFolder } from 'vscode' -import { IProjectJson } from './parse/OpenedgeProjectParser' +import { getActiveProfile, getOpenEdgeProfileConfig, IBuildPathEntry, IProjectJson } from './parse/OpenedgeProjectParser' import { isRelativePath } from './ABLUnitCommon' import { log } from './ChannelLogger' +import { ablunitConfig } from 'ABLUnitConfigWriter' +import { parseRunProfiles, RunConfig } from 'parse/TestProfileParser' interface IPropathEntry { uri: Uri @@ -44,6 +46,35 @@ export class PropathParser { this.buildmap = new Map() } + setPropathFromProjectJson () { + if (!workspace.workspaceFolders) { + return + } + const conf = getOpenEdgeProfileConfig(this.workspaceFolder.uri) + + if (conf && conf.buildPath.length > 0) { + const pathObj: IBuildPathEntry[] = [] + for (const e of conf.buildPath) { + pathObj.push({ + path: e.path, + type: e.type.toLowerCase(), + buildDir: e.buildDir, + xrefDir: e.xrefDir + }) + } + this.setPropath({ propathEntry: pathObj }) + } else { + this.setPropath({ propathEntry: [{ + path: '.', + type: 'source', + buildDir: '.', + xrefDir: '.' + }]}) + } + + log.info('set propath=\'' + this.toString() + '\'') + } + setPropath (importedPropath: IProjectJson) { log.debug('importedPropath.length=' + importedPropath.propathEntry.length) diff --git a/src/ABLResults.ts b/src/ABLResults.ts index 8be56c49..e758445e 100644 --- a/src/ABLResults.ts +++ b/src/ABLResults.ts @@ -204,7 +204,18 @@ export class ABLResults implements Disposable { } if (isTopLevel) { - this.topLevelTests.push(testObj) + if (testObj.folder) { + log.info('folder = ' + testObj.folder) + const existing = this.topLevelTests.find((exist) => testObj.folder === exist.folder) + log.info('existing = ' + JSON.stringify(existing)) + if (existing) { + log.warn('test dir already added to test run queue: ' + testObj.folder) + } else { + this.topLevelTests.push(testObj) + } + } else { + this.topLevelTests.push(testObj) + } } log.info('104') diff --git a/src/extension.ts b/src/extension.ts index 5a485a89..4e3c2402 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -24,6 +24,7 @@ import { ABLTestCase, ABLTestClass, ABLTestData, ABLTestDir, ABLTestFile, ABLTes import { minimatch } from 'minimatch' import { ABLUnitRuntimeError } from 'ABLUnitRun' import { basename } from 'path' +import { PropathParser } from 'ABLPropath' export interface IExtensionTestReferences { testController: TestController @@ -32,6 +33,7 @@ export interface IExtensionTestReferences { } let recentResults: ABLResults[] = [] +const propathMap = new Map() export async function activate (context: ExtensionContext) { const ctrl = tests.createTestController('ablunitTestController', 'ABLUnit Test') @@ -525,12 +527,12 @@ export async function activate (context: ExtensionContext) { let contextStorageUri: Uri let contextResourcesUri: Uri -function updateNode (uri: Uri, ctrl: TestController) { +async function updateNode (uri: Uri, ctrl: TestController) { log.trace('updateNode uri=' + uri.fsPath) if(uri.scheme !== 'file' || isFileExcluded(uri, getExcludePatterns())) { return new Promise(() => { return false }) } log.info('600') - const { item, data } = getOrCreateFile(ctrl, uri) + const { item, data } = await getOrCreateFile(ctrl, uri) log.info('601') if(!item || !data) { log.info('602') @@ -540,7 +542,7 @@ function updateNode (uri: Uri, ctrl: TestController) { log.info('603') ctrl.invalidateTestResults(item) log.info('604 uri=' + uri.fsPath) - return getContentFromFilesystem(uri).then((contents) => { + await getContentFromFilesystem(uri).then((contents) => { log.info('605') data.updateFromContents(ctrl, contents, item) log.info('606') @@ -591,7 +593,7 @@ function getExistingTestItem (controller: TestController, uri: Uri) { return undefined } -function getOrCreateFile (controller: TestController, uri: Uri, excludePatterns?: RelativePattern[]) { +async function getOrCreateFile (controller: TestController, uri: Uri, excludePatterns?: RelativePattern[]) { const existing = getExistingTestItem(controller, uri) if (excludePatterns && excludePatterns.length > 0 && isFileExcluded(uri, excludePatterns)) { @@ -601,6 +603,39 @@ function getOrCreateFile (controller: TestController, uri: Uri, excludePatterns? return { item: undefined, data: undefined } } + const wf = workspace.getWorkspaceFolder(uri) + log.info('wf=' + wf) + if (!wf) { + throw new Error('workspace folder not found for uri: ' + uri.fsPath) + } + let p = propathMap.get(wf.uri.fsPath) + log.info('p=' + p) + if (!p) { + p = new PropathParser(wf) + p.setPropathFromProjectJson() + log.info('p(2)=' + p) + if (!p) { + log.warn('unable to create propath for workspace folder: ' + wf.uri.fsPath) + if (existing) { + deleteTest(controller, existing) + } + return { item: undefined, data: undefined } + } + propathMap.set(wf.uri.fsPath, p) + return { item: undefined, data: undefined } + } + log.info('propath=' + JSON.stringify(p.getPropath())) + const pEntry = await p.search(uri) + log.info('pEntry=' + JSON.stringify(pEntry)) + if (!pEntry) { + log.info('no propath entry found for uri: ' + uri.fsPath) + if (existing) { + deleteTest(controller, existing) + } + return { item: undefined, data: undefined } + } + + if (existing) { const data = testData.get(existing) if (!data) { @@ -620,7 +655,7 @@ function getOrCreateFile (controller: TestController, uri: Uri, excludePatterns? const data = createFileNode(uri) if(!data) { - log.trace('No tests found in file: ' +workspace.asRelativePath(uri)) + log.trace('No tests found in file: ' + workspace.asRelativePath(uri)) return { item: undefined, data: undefined } } const file = controller.createTestItem(uri.fsPath, basename(uri.fsPath), uri) @@ -958,17 +993,21 @@ function refreshTestTree (controller: TestController, token: CancellationToken): return findMatchingFiles(includePatterns, token, checkCancellationToken) .then((r) => { + const proms = [] for (const file of r) { checkCancellationToken() - const { item, data } = getOrCreateFile(controller, file, excludePatterns) - if (!item) { - log.warn('could not create test item for file - item undefined: ' + file.fsPath) - } - if (!data) { - log.warn('could not create test item for file - data undefined: ' + file.fsPath) - } + const prom = getOrCreateFile(controller, file, excludePatterns).then(({item, data}) => { + if (!item) { + log.warn('could not create test item for file - item undefined: ' + file.fsPath) + } + if (!data) { + log.warn('could not create test item for file - data undefined: ' + file.fsPath) + } + return + }) + proms.push(prom) } - return + return Promise.all(proms) }) .then((r) => { log.info('return true (r=' + r + ')') diff --git a/test/suites/proj9.test.ts b/test/suites/proj9.test.ts index f4f4a8a7..f83fec53 100644 --- a/test/suites/proj9.test.ts +++ b/test/suites/proj9.test.ts @@ -43,18 +43,9 @@ suite('proj9 - Extension Test Suite', () => { test('proj9.1 - ${workspaceFolder}/ablunit.json file exists', async () => { await runAllTests() - const workspaceFolder = workspace.workspaceFolders![0].uri - const ablunitJson = Uri.joinPath(workspaceFolder, 'ablunit.json') - const resultsXml = Uri.joinPath(workspaceFolder, 'results.xml') - const resultsJson = Uri.joinPath(workspaceFolder, 'results.json') - - assert.fileExists(ablunitJson) - assert.fileExists(resultsXml) - assert.fileExists(resultsJson) - - assert.equal(await getTestCount(resultsJson, 'pass'), 7, 'passed test count') - assert.equal(await getTestCount(resultsJson, 'fail'), 0, 'failed test count') - assert.equal(await getTestCount(resultsJson, 'error'), 0, 'error test count') + assert.tests.passed(14) + assert.tests.failed(0) + assert.tests.errored(0) }) test('proj9.2 - second profile passes (project)', async () => { diff --git a/test_projects/proj9/openedge-project.json b/test_projects/proj9/openedge-project.json index 8f2189e0..a7b5c954 100644 --- a/test_projects/proj9/openedge-project.json +++ b/test_projects/proj9/openedge-project.json @@ -1,7 +1,7 @@ { "name": "proj9", "buildPath": [ - { "type": "source", "path": "src", "excludes": "emptyClass.cls"}, + { "type": "source", "path": "src", "excludes": "emptyClass.cls,dirA/dir1/testInDir2.p"}, { "type": "source", "path": "test" } ], "numThreads": 1, diff --git a/test_projects/proj9/src/dirA/dir1/testInDir2.p b/test_projects/proj9/src/dirA/dir1/testInDir2.p deleted file mode 100644 index e650f9f0..00000000 --- a/test_projects/proj9/src/dirA/dir1/testInDir2.p +++ /dev/null @@ -1,23 +0,0 @@ -using OpenEdge.Core.Assert. -block-level on error undo, throw. - -@Test. -procedure test_proc : - Assert:Equals(1,1). -end procedure. - -@Test. -procedure dbAliasTest1 : - message "customerTest". - for first dbalias.customer no-lock where dbalias.customer.name begins "b": - message dbalias.customer.name. - end. -end procedure. - -@Test. -procedure dbAliasTest2 : - message "customerTest". - for last third.customer no-lock where third.customer.name begins "b": - message third.customer.name. - end. -end procedure. From e120cc16b6ea3b548cecac3bed149928d54a3283 Mon Sep 17 00:00:00 2001 From: kenherring Date: Tue, 3 Dec 2024 08:49:49 -0500 Subject: [PATCH 6/8] minor code style --- .vscode/settings.json | 2 +- esbuild.js | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index b0169e52..c447086a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -26,7 +26,7 @@ }, "files.exclude": { "./dist/**": true, - // "**/*{.js,.test.js}": { "when": "$(basename).ts" } + "**/.worktrees": true, }, "files.watcherExclude": { ".git/objects": true, diff --git a/esbuild.js b/esbuild.js index d53563ab..0a0f94e8 100644 --- a/esbuild.js +++ b/esbuild.js @@ -5,7 +5,7 @@ const watch = process.argv.includes('--watch') let logtag = '[build]' if (watch) { - logtag = '[watch]' + logtag = '[watch]' } async function main () { @@ -26,7 +26,7 @@ async function main () { ] }) if (watch) { - console.log(logtag + ' watching...') + console.log(logtag + ' watching...') await ctx.watch() } else { await ctx.rebuild() @@ -47,11 +47,11 @@ const esbuildProblemMatcherPlugin = { build.onEnd(result => { result.errors.forEach(({ text, location }) => { console.error(`✘ [ERROR] ${text}`) - if (location) { - console.error(` ${location.file}:${location.line}:${location.column}:`) - } else { - console.error(' location unknown') - } + if (location) { + console.error(` ${location.file}:${location.line}:${location.column}:`) + } else { + console.error(' location unknown') + } }) console.log(logtag + ' build finished') }) From 7fbd2ed36d111e7fa70c191f1fa27053a7a9b6f9 Mon Sep 17 00:00:00 2001 From: kenherring Date: Fri, 6 Dec 2024 05:58:14 -0500 Subject: [PATCH 7/8] incremental --- .eslintrc.json | 1 + .gitignore | 2 + .vscode/settings.json | 2 +- package-lock.json | 17 ++- package.json | 1 + src/ABLPromsgs.ts | 20 +-- src/ABLPropath.ts | 4 +- src/ABLResults.ts | 61 +++------ src/ABLUnitConfigWriter.ts | 2 +- src/extension.ts | 188 +++++++++++++-------------- src/parse/ProfileParser.ts | 4 +- src/parse/ResultsParser.ts | 7 +- src/parse/SourceParser.ts | 6 +- src/parse/TestParserCommon.ts | 36 +++-- src/parse/config/CoreOptions.ts | 3 +- src/testTree.ts | 19 ++- test/openedgeAblCommands.ts | 2 +- test/parse/TestProfileParser.test.ts | 6 +- test/suites/DebugLines.test.ts | 2 +- test/suites/proj0.test.ts | 113 +++++++--------- test/suites/proj7A.test.ts | 4 - test/suites/proj7B.test.ts | 14 +- test/testCommon.ts | 48 +++---- 23 files changed, 273 insertions(+), 289 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 2ccc387a..43b381c2 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -69,6 +69,7 @@ "warn", { "vars": "all", "args": "none", + "argsIgnorePattern": "^_", "ignoreRestSiblings": false } ], diff --git a/.gitignore b/.gitignore index 7a60c24d..d7010009 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,5 @@ test_projects/proj7_load_performance/src/classes # Dowloaded test files test_projects/proj7_load_performance/v*.tar.gz test_projects/proj7_load_performance/src/ADE-* + +.worktrees/ diff --git a/.vscode/settings.json b/.vscode/settings.json index 67fdf800..b4208be3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -26,7 +26,7 @@ }, "files.exclude": { "./dist/**": true, - "**/.worktrees": true, + "**/.worktrees": true }, "files.watcherExclude": { ".git/objects": true, diff --git a/package-lock.json b/package-lock.json index 21a9d315..49cbccdb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@types/mocha": "^9.1.1", "@types/node": "20.X", "@types/vscode": "^1.94.0", + "@types/xml2js": "^0.4.14", "@typescript-eslint/eslint-plugin": "^8.8.1", "@typescript-eslint/parser": "^8.8.1", "@vscode/test-cli": "^0.0.10", @@ -1599,6 +1600,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/xml2js": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz", + "integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.8.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.1.tgz", @@ -2865,9 +2876,9 @@ "license": "MIT" }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 98cb475f..9aa7578f 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "@types/mocha": "^9.1.1", "@types/node": "20.X", "@types/vscode": "^1.94.0", + "@types/xml2js": "^0.4.14", "@typescript-eslint/eslint-plugin": "^8.8.1", "@typescript-eslint/parser": "^8.8.1", "@vscode/test-cli": "^0.0.10", diff --git a/src/ABLPromsgs.ts b/src/ABLPromsgs.ts index 994ee67c..45c73a7a 100644 --- a/src/ABLPromsgs.ts +++ b/src/ABLPromsgs.ts @@ -39,8 +39,8 @@ export class ABLPromsgs { async loadPromsgFile (msgfile: Uri) { const lines = await workspace.fs.readFile(msgfile).then((buffer) => { return Buffer.from(buffer).toString('utf8').split('\n') - }, (err) => { - throw new Error('Cannot read promsgs file \'' + msgfile + '\', err=' + err) + }, (e: unknown) => { + throw new Error('Cannot read promsgs file \'' + msgfile + '\', err=' + e) }) @@ -93,10 +93,10 @@ export class ABLPromsgs { }).then(() => { log.info('promsgs loaded from DLC') return - }, (err) => { - throw new Error('Cannot read promsgs directory \'' + promsgDir + '\', err=' + err) + }, (e: unknown) => { + throw new Error('Cannot read promsgs directory \'' + promsgDir + '\', err=' + e) }) - // }, (err) => { + // }, (e: unknown) => { // log.info('Cannot find DLC directory \'' + this.dlc.uri.fsPath + '"') // throw new Error('Cannot find DLC directory \'' + this.dlc.uri.fsPath + '", err=' + err) // }) @@ -107,8 +107,8 @@ export class ABLPromsgs { await workspace.fs.readFile(cacheUri).then((buffer) => { this.promsgs = JSON.parse(Buffer.from(buffer).toString('utf8')) as IPromsg[] return - }, (err) => { - throw new Error('Cannot read promsgs file \'' + cacheUri.fsPath + '\', err=' + err) + }, (e: unknown) => { + throw new Error('Cannot read promsgs file \'' + cacheUri.fsPath + '\', err=' + e) }) } @@ -121,8 +121,8 @@ export class ABLPromsgs { return workspace.fs.writeFile(cacheUri, Buffer.from(JSON.stringify(this.promsgs, null, 2))).then(() => { log.info('saved promsgs cache successfully \'' + cacheUri.fsPath + '\'') return - }, (err) => { - throw new Error('error writing promsgs cache file: ' + err) + }, (e: unknown) => { + throw new Error('error writing promsgs cache file: ' + e) }) } @@ -152,7 +152,7 @@ export function getPromsgText (text: string) { } }) return stackString - } catch (e) { + } catch (_e) { return text } } diff --git a/src/ABLPropath.ts b/src/ABLPropath.ts index 09473673..a7052031 100644 --- a/src/ABLPropath.ts +++ b/src/ABLPropath.ts @@ -1,9 +1,7 @@ import { Uri, workspace, WorkspaceFolder } from 'vscode' -import { getActiveProfile, getOpenEdgeProfileConfig, IBuildPathEntry, IProjectJson } from './parse/OpenedgeProjectParser' +import { getOpenEdgeProfileConfig, IBuildPathEntry, IProjectJson } from './parse/OpenedgeProjectParser' import { isRelativePath } from './ABLUnitCommon' import { log } from './ChannelLogger' -import { ablunitConfig } from 'ABLUnitConfigWriter' -import { parseRunProfiles, RunConfig } from 'parse/TestProfileParser' interface IPropathEntry { uri: Uri diff --git a/src/ABLResults.ts b/src/ABLResults.ts index a2faf6d3..eddfdc9d 100644 --- a/src/ABLResults.ts +++ b/src/ABLResults.ts @@ -6,7 +6,7 @@ import { FileType, MarkdownString, TestItem, TestItemCollection, TestMessage, Te TestRunProfileKind} from 'vscode' import { ABLUnitConfig } from './ABLUnitConfigWriter' import { ABLResultsParser, ITestCaseFailure, ITestCase, ITestSuite } from './parse/ResultsParser' -import { ABLTestSuite, ABLTestDir, ABLTestCase, testData} from './testTree' +import { ABLTestSuite, ABLTestDir, ABLTestCase, testData, ABLTestData} from './testTree' import { parseCallstack } from './parse/CallStackParser' import { ABLProfile, ABLProfileJson, IModule } from './parse/ProfileParser' import { ABLDebugLines } from './ABLDebugLines' @@ -15,34 +15,8 @@ import { PropathParser } from './ABLPropath' import { log } from './ChannelLogger' import { ABLUnitRuntimeError, RunStatus, TimeoutError, ablunitRun } from './ABLUnitRun' import { getDLC, IDlc } from './parse/OpenedgeProjectParser' - -export interface ITestFile { - folder?: undefined - test: string - cases?: string[] -} -export interface ITestFolder { - folder: string - test?: undefined - cases?: undefined -} - -export type ITestObj = ITestFile | ITestFolder - -export interface IABLUnitJson { - options: { - output: { - location: string // results.xml directory - filename: string // .xml - format: 'xml' - } - quitOnEnd: boolean - writeLog: boolean - showErrorMessage: boolean - throwError: boolean - } - tests: ITestObj[] -} +import { Duration } from './ABLUnitCommon' +import { ITestObj } from 'parse/config/CoreOptions' export class ABLResults implements Disposable { workspaceFolder: WorkspaceFolder @@ -55,6 +29,7 @@ export class ABLResults implements Disposable { tests: TestItem[] = [] topLevelTests: ITestObj[] = [] testQueue: ITestObj[] = [] + testData = new WeakMap() skippedTests: TestItem[] = [] propath?: PropathParser debugLines?: ABLDebugLines @@ -104,6 +79,10 @@ export class ABLResults implements Disposable { log.info('STATUS: ' + status) } + setTestData (testData: WeakMap) { + this.testData = testData + } + start () { log.info('[start] workspaceFolder=' + this.workspaceFolder.uri.fsPath) this.cfg.setup(this.workspaceFolder, this.request) @@ -135,12 +114,12 @@ export class ABLResults implements Disposable { return Promise.all(prom).then(() => { log.info('done creating config files for start') return - }, (err) => { + }, (err: unknown) => { log.error('ABLResults.start() did not complete promises. err=' + err) }) } - async addTest (test: TestItem, options: TestRun, isTopLevel: boolean) { + async addTest (test: TestItem, data: ABLTestData, options: TestRun, isTopLevel: boolean) { if (!test.uri) { log.error('test.uri is undefined (test.label = ' + test.label + ')', options) return @@ -158,8 +137,6 @@ export class ABLResults implements Disposable { this.testData.set(test, data) let testCase: string | undefined = undefined - log.info('100 ' + test.id) - const data = testData.get(test) if (!data) { log.error('could not find test data for TestItem.id=' + test.id) return @@ -177,7 +154,7 @@ export class ABLResults implements Disposable { if (data instanceof ABLTestDir) { log.info('directory ' + testRel + ' not found in propath, adding children') for (const [ , child ] of test.children) { - await this.addTest(child, options, isTopLevel) + await this.addTest(child, data, options, isTopLevel) } return } else { @@ -317,10 +294,10 @@ export class ABLResults implements Disposable { throw new Error('No results found in ' + this.cfg.ablunitConfig.optionsUri.filenameUri.fsPath + '\r\n') } return true - }, (err) => { + }, (e: unknown) => { this.setStatus(RunStatus.Error, 'parsing results') - log.error('Error parsing results from ' + this.cfg.ablunitConfig.optionsUri.filenameUri.fsPath + '. err=' + err, options) - throw new Error('Error parsing results from ' + this.cfg.ablunitConfig.optionsUri.filenameUri.fsPath + '\r\nerr=' + err) + log.error('Error parsing results from ' + this.cfg.ablunitConfig.optionsUri.filenameUri.fsPath + '. err=' + e, options) + throw new Error('Error parsing results from ' + this.cfg.ablunitConfig.optionsUri.filenameUri.fsPath + '\r\nerr=' + e) }) if (this.request.profile?.kind === TestRunProfileKind.Coverage && this.cfg.ablunitConfig.profiler.enabled && this.cfg.ablunitConfig.profiler.coverage) { @@ -329,10 +306,10 @@ export class ABLResults implements Disposable { await this.parseProfile().then(() => { log.info('parsing profiler data complete ' + parseTime.toString()) return true - }, (err) => { + }, (e: unknown) => { this.setStatus(RunStatus.Error, 'profiler data') - log.error('Error parsing profiler data from ' + this.cfg.ablunitConfig.profFilenameUri.fsPath + '. err=' + err, options) - throw new Error('Error parsing profiler data from ' + workspace.asRelativePath(this.cfg.ablunitConfig.profFilenameUri) + '\r\nerr=' + err) + log.error('Error parsing profiler data from ' + this.cfg.ablunitConfig.profFilenameUri.fsPath + '. err=' + e, options) + throw new Error('Error parsing profiler data from ' + workspace.asRelativePath(this.cfg.ablunitConfig.profFilenameUri) + '\r\nerr=' + e) }) } @@ -554,8 +531,8 @@ export class ABLResults implements Disposable { .then(() => { log.debug('assignProfileResults complete (time=' + (Number(new Date()) - Number(startTime)) + ')') return - }, (err) => { - throw new Error('assignProfileResults error: ' + err) + }, (e: unknown) => { + throw new Error('assignProfileResults error: ' + e) }) } diff --git a/src/ABLUnitConfigWriter.ts b/src/ABLUnitConfigWriter.ts index 9f8707c9..21c4e2ed 100644 --- a/src/ABLUnitConfigWriter.ts +++ b/src/ABLUnitConfigWriter.ts @@ -51,7 +51,7 @@ export class ABLUnitConfig { createAblunitJson (_uri: Uri, cfg: CoreOptions, testQueue: ITestObj[]) { if (!this.ablunitConfig.config_uri) { - throw new Error('Output location no defined!') + throw new Error('Output location not defined!') } log.info('creating ablunit.json: \'' + this.ablunitConfig.config_uri.fsPath + '\'') const promarr: PromiseLike[] = [] diff --git a/src/extension.ts b/src/extension.ts index 983b3139..a22f3851 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -23,7 +23,7 @@ import { log } from './ChannelLogger' import { getContentFromFilesystem } from './parse/TestParserCommon' import { ABLTestCase, ABLTestClass, ABLTestData, ABLTestDir, ABLTestFile, ABLTestProgram, ABLTestSuite, resultData, testData } from './testTree' import { minimatch } from 'minimatch' -import { ABLUnitRuntimeError } from 'ABLUnitRun' +import { ABLUnitRuntimeError, TimeoutError } from 'ABLUnitRun' import { basename } from 'path' import { PropathParser } from 'ABLPropath' @@ -34,6 +34,7 @@ export interface IExtensionTestReferences { } let recentResults: ABLResults[] = [] +let recentError: Error | undefined = undefined const propathMap = new Map() export async function activate (context: ExtensionContext) { @@ -66,25 +67,12 @@ export async function activate (context: ExtensionContext) { context.subscriptions.push( commands.registerCommand('_ablunit.openCallStackItem', openCallStackItem), - workspace.onDidChangeConfiguration(e => { updateConfiguration(e) }), - - workspace.onDidOpenTextDocument(e => { - log.info('updateNodeForDocument e.fileName=' + e.fileName) - return updateNodeForDocument(e, 'didOpen').then(() => { - log.info('updateNodeForDocument complete') - log.info('updateNodeForDocument complete e.' + e.uri.fsPath) - log.trace('updateNodeForDocument complete for ' + e.uri) - return - }, (e: unknown) => { - log.error('failed updateNodeForDocument onDidTextDocument! err=' + e) - }) - log.info('updateNodeForDocument complete') - // return new Disposable(() => { - // }) - }) - // watcher.onDidCreate(uri => { createOrUpdateFile(controller, uri) }) - // watcher.onDidChange(uri => { createOrUpdateFile(controller, uri) }) - // watcher.onDidDelete(uri => { controller.items.delete(uri.fsPath) }) + workspace.onDidChangeConfiguration(e => { return updateConfiguration(e) }), + workspace.onDidOpenTextDocument(e => { log.info('workspace.onDidOpenTextDocument'); return createOrUpdateFile(ctrl, e.uri, true) }), + workspace.onDidChangeTextDocument(e => { return didChangeTextDocument(e, ctrl) }), + workspace.onDidCreateFiles(e => { log.info('workspace.onDidCreate ' + e.files[0].fsPath); return createOrUpdateFile(ctrl, e, true) }), + workspace.onDidDeleteFiles(e => { log.info('workspace.onDidDelete ' + e.files[0].fsPath); return deleteFiles(ctrl, e.files) }), + // ...startWatchingWorkspace(ctrl), ) @@ -158,8 +146,8 @@ export async function activate (context: ExtensionContext) { return window.showTextDocument(Uri.joinPath(workspaceFolder.uri, '.vscode', 'ablunit-test-profile.json')).then(() => { log.info('Opened .vscode/ablunit-test-profile.json') return - }, (err) => { - log.error('Failed to open .vscode/ablunit-test-profile.json! err=' + err) + }, (e: unknown) => { + log.error('Failed to open .vscode/ablunit-test-profile.json! err=' + e) return }) } @@ -389,7 +377,7 @@ export async function activate (context: ExtensionContext) { res.push(r) } log.info('210') - await r.addTest(test, run, topLevelTests.includes(test) || data instanceof ABLTestDir) + await r.addTest(test, data, run, topLevelTests.includes(test) || data instanceof ABLTestDir) log.info('211') } @@ -464,8 +452,8 @@ export async function activate (context: ExtensionContext) { return commands.executeCommand('testing.refreshTests').then(() => { log.trace('tests tree successfully refreshed on workspace startup') return - }, (err) => { - log.error('failed to refresh test tree. err=' + err) + }, (e: unknown) => { + log.error('failed to refresh test tree. err=' + e) }) } return Promise.resolve() @@ -479,7 +467,7 @@ export async function activate (context: ExtensionContext) { const data = testData.get(item) if (data instanceof ABLTestFile) { - return data.updateFromDisk(ctrl, item).then(() => { return }, (err) => { throw err }) + return data.updateFromDisk(ctrl, item).then(() => { return }, (e: unknown) => { throw e }) } return Promise.resolve() } @@ -514,7 +502,7 @@ export async function activate (context: ExtensionContext) { .then(() => { log.info('tests tree successfully refreshed on configuration change') return - }, (e) => { + }, (e: unknown) => { throw e }) } @@ -549,26 +537,20 @@ let contextLogUri: Uri async function updateNode (uri: Uri, ctrl: TestController) { log.trace('updateNode uri=' + uri.fsPath) - if(uri.scheme !== 'file' || isFileExcluded(uri, getExcludePatterns())) { return new Promise(() => { return false }) } + if(uri.scheme !== 'file' || isFileExcluded(uri, getWorkspaceTestPatterns()[1])) { + return Promise.resolve(false) + } - log.info('600') - const { item, data } = await getOrCreateFile(ctrl, uri) - log.info('601') + const { item, data } = getOrCreateFile(ctrl, uri) if(!item || !data) { - log.info('602') return new Promise(() => { return false }) } - log.info('603') ctrl.invalidateTestResults(item) - log.info('604 uri=' + uri.fsPath) await getContentFromFilesystem(uri).then((contents) => { - log.info('605') data.updateFromContents(ctrl, contents, item) - log.info('606') return - }, (e) => { - log.info('607 e=' + e) + }, (e: unknown) => { throw e }) } @@ -628,7 +610,7 @@ function getExistingTestItem (controller: TestController, uri: Uri) { return undefined } -async function getOrCreateFile (controller: TestController, uri: Uri, excludePatterns?: RelativePattern[]) { +function getOrCreateFile (controller: TestController, uri: Uri, excludePatterns?: RelativePattern[]) { const existing = getExistingTestItem(controller, uri) if (excludePatterns && excludePatterns.length > 0 && isFileExcluded(uri, excludePatterns)) { @@ -659,16 +641,16 @@ async function getOrCreateFile (controller: TestController, uri: Uri, excludePat propathMap.set(wf.uri.fsPath, p) return { item: undefined, data: undefined } } - log.info('propath=' + JSON.stringify(p.getPropath())) - const pEntry = await p.search(uri) - log.info('pEntry=' + JSON.stringify(pEntry)) - if (!pEntry) { - log.info('no propath entry found for uri: ' + uri.fsPath) - if (existing) { - deleteTest(controller, existing) - } - return { item: undefined, data: undefined } - } + // log.info('propath=' + JSON.stringify(p.getPropath())) + // const pEntry = await p.search(uri) + // log.info('pEntry=' + JSON.stringify(pEntry)) + // if (!pEntry) { + // log.info('no propath entry found for uri: ' + uri.fsPath) + // if (existing) { + // deleteTest(controller, existing) + // } + // return { item: undefined, data: undefined } + // } if (existing) { @@ -702,7 +684,7 @@ async function getOrCreateFile (controller: TestController, uri: Uri, excludePat } else if (file.label.endsWith('.p')) { file.description = 'ABL Test Program' } - file.tags = [ new TestTag('runnable') ] + file.tags = [ new TestTag('runnable'), new TestTag('ABLTestFile') ] const parent = getOrCreateDirNodeForFile(controller, uri, data instanceof ABLTestSuite) if (parent) { @@ -727,7 +709,7 @@ function getWorkspaceFolderNode (controller: TestController, workspaceFolder: Wo wf.tags = [ new TestTag('runnable'), new TestTag('ABLTestDir') ] controller.items.add(wf) - testData.set(wf, new ABLTestDir('WorkspaceFolder', workspaceFolder.name, workspaceFolder.uri)) + testData.set(wf, new ABLTestDir('WorkspaceFolder', workspaceFolder.name, workspaceFolder.uri, 'ABLTestDir')) return wf } @@ -749,7 +731,7 @@ function getTestSuiteNode (controller: TestController, workspaceFolder: Workspac suiteGroup.canResolveChildren = false suiteGroup.description = 'ABLTestSuiteGroup' suiteGroup.tags = [ new TestTag('runnable'), new TestTag('ABLTestSuiteGroup') ] - testData.set(suiteGroup, new ABLTestDir('TestSuiteGroup', '[ABL Test Suites]', groupId)) + testData.set(suiteGroup, new ABLTestDir('TestSuiteGroup', '[ABL Test Suites]', groupId, 'ABLTestDir')) siblings.add(suiteGroup) return suiteGroup @@ -792,7 +774,7 @@ function getOrCreateDirNodeForFile (controller: TestController, file: Uri, isTes dirNode.description = 'ABLTestDir' dirNode.tags = [ new TestTag('runnable'), new TestTag('ABLTestDir') ] - const data = new ABLTestDir('ABLTestDir', path, dirNode.uri!) + const data = new ABLTestDir('ABLTestDir', path, dirNode.uri!, 'ABLTestDir') testData.set(dirNode, data) siblings.add(dirNode) parent = dirNode @@ -993,14 +975,54 @@ function findMatchingFiles (includePatterns: RelativePattern[], token: Cancellat .then((files) => { filelist.push(...files) return true - }, (e) => { throw e }) + }, (e: unknown) => { throw e }) proms.push(prom) checkCancellationToken() } return Promise.all(proms) .then(() => { return filelist - }, (e) => { throw e }) + }, (e: unknown) => { throw e }) +} + +// async function parseMatchingFiles (files: Uri[], controller: TestController, excludePatterns: RelativePattern[], token: CancellationToken, checkCancellationToken: () => void): Promise { +// const proms: PromiseLike[] = [] +// log.debug('parsing files... (count=' + files.length + ')') +// for (const file of files) { +// checkCancellationToken() + +// const { item, data } = getOrCreateFile(controller, file, excludePatterns) +// log.info('data.description=\'' + data?.description + '\'; item.id=' + item?.id) +// if (item && data instanceof ABLTestFile) { +// log.info('updating from disk') +// const prom = data.updateFromDisk(controller, item, token).then((foundTestCase) => { +// return foundTestCase +// }, (e) => { +// log.error('failed to update file from disk. err=' + e) +// return false +// }) +// proms.push(prom) +// } +// } +// const r = await Promise.all(proms).then(() => { return true }) +// return r +// } + +function removeDeletedFiles (ctrl: TestController) { + const items = gatherAllTestItems(ctrl.items) + const proms: PromiseLike[] = [] + for (const item of items) { + if (!item.uri) { continue } + const p = workspace.fs.stat(item.uri) + .then((s) => { + log.debug('file still exists, skipping delete (item.id=' + item.id + ')') + return + }, (e: unknown) => { + deleteTest(ctrl, item) + }) + proms.push(p) + } + return Promise.all(proms).then(() => { return true }) } function refreshTestTree (controller: TestController, token: CancellationToken): Promise { @@ -1040,28 +1062,20 @@ function refreshTestTree (controller: TestController, token: CancellationToken): log.debug('finding files...') - return findMatchingFiles(includePatterns, token, checkCancellationToken) + const prom1 = removeDeletedFiles(controller) + .then(() => { return findMatchingFiles(includePatterns, token, checkCancellationToken) }) .then((r) => { - const proms = [] for (const file of r) { checkCancellationToken() - const prom = getOrCreateFile(controller, file, excludePatterns).then(({item, data}) => { - if (!item) { - log.warn('could not create test item for file - item undefined: ' + file.fsPath) - } - if (!data) { - log.warn('could not create test item for file - data undefined: ' + file.fsPath) - } - return - }) - proms.push(prom) + const { item, data } = getOrCreateFile(controller, file, excludePatterns) + if (!item && !data) { + log.debug('could not create test item for file: ' + file.fsPath) + } } - return Promise.all(proms) - }) - .then((r) => { - log.info('return true (r=' + r + ')') + log.info('found matching files (r.length=' + r.length + ')') return true - }, (e) => { throw e }) + }) + return prom1 } function getControllerTestFileCount (controller: TestController) { @@ -1123,26 +1137,6 @@ function createOrUpdateFile (controller: TestController, e: Uri | FileCreateEven return Promise.all(proms).then(() => { return true }) } -// function startWatchingWorkspace (controller: TestController) { -// log.info('start watching workspace') -// const [ includePatterns, excludePatterns ] = getWorkspaceTestPatterns() -// log.debug('includePatterns=' + includePatterns.length + ', excludePatterns=' + excludePatterns.length) - -// const watchers: FileSystemWatcher[] = [] - -// // TODO - different patterns in different workspaces... - -// for (const includePattern of includePatterns) { -// log.info('creating watcher for: ' + includePattern.pattern) -// const watcher = workspace.createFileSystemWatcher(includePattern) -// watcher.onDidCreate(uri => { log.info('watcher.onDidCreate'); return createOrUpdateFile(controller, uri, true) }) -// watcher.onDidChange(uri => { log.info('watcher.onDidChange'); return createOrUpdateFile(controller, uri, true) }) -// watcher.onDidDelete(uri => { log.info('watcher.onDidDelete'); return deleteTest(controller, uri) }) -// watchers.push(watcher) -// } -// return watchers -// } - function openCallStackItem (traceUriStr: string) { const traceUri = Uri.file(traceUriStr.split('&')[0]) const traceLine = Number(traceUriStr.split('&')[1]) @@ -1154,7 +1148,7 @@ function openCallStackItem (traceUriStr: string) { log.info('decorating editor - openCallStackItem') editor.revealRange(range) return - }, (e) => { throw e }) + }, (e: unknown) => { throw e }) } function isFileIncluded (uri: Uri, includePatterns: RelativePattern[], excludePatterns: RelativePattern[]) { @@ -1201,8 +1195,8 @@ export async function doesDirExist (uri: Uri) { return true } return false - }, (err) => { - log.debug('caught: ' + err) + }, (e: unknown) => { + log.debug('caught: ' + e) return false }) return ret @@ -1214,8 +1208,8 @@ export async function doesFileExist (uri: Uri) { return true } return false - }, (err) => { - log.debug('caught: ' + err) + }, (e: unknown) => { + log.debug('caught: ' + e) return false }) return ret diff --git a/src/parse/ProfileParser.ts b/src/parse/ProfileParser.ts index c988e3c0..5632e57e 100644 --- a/src/parse/ProfileParser.ts +++ b/src/parse/ProfileParser.ts @@ -86,8 +86,8 @@ export class ABLProfile { return workspace.fs.writeFile(uri, Uint8Array.from(Buffer.from(JSON.stringify(data, null, 2)))).then(() => { log.info('wrote profile output json file: ' + workspace.asRelativePath(uri)) return - }, (err) => { - log.error('failed to write profile output json file ' + workspace.asRelativePath(uri) + ' - ' + err) + }, (e: unknown) => { + log.error('failed to write profile output json file ' + workspace.asRelativePath(uri) + ' - ' + e) }) } } diff --git a/src/parse/ResultsParser.ts b/src/parse/ResultsParser.ts index 33c4df84..1c82fe76 100644 --- a/src/parse/ResultsParser.ts +++ b/src/parse/ResultsParser.ts @@ -92,7 +92,8 @@ export class ABLResultsParser { parseXml (xmlData: string): string { let res: string | undefined - parseString(xmlData, function (e: Error | null, resultsRaw: unknown) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + parseString(xmlData, (e: Error | null, resultsRaw: unknown) => { if (e) { log.info('error parsing XML file: ' + e) throw e @@ -281,8 +282,8 @@ export class ABLResultsParser { return workspace.fs.writeFile(uri, Uint8Array.from(Buffer.from(JSON.stringify(data, null, 2)))).then(() => { log.info('wrote results json file: ' + uri.fsPath) return - }, (err) => { - log.error('failed to write profile output json file ' + uri.fsPath + ' - ' + err) + }, (e: unknown) => { + log.error('failed to write profile output json file ' + uri.fsPath + ' - ' + e) }) } } diff --git a/src/parse/SourceParser.ts b/src/parse/SourceParser.ts index c3987c38..fb45d4ee 100644 --- a/src/parse/SourceParser.ts +++ b/src/parse/SourceParser.ts @@ -32,9 +32,9 @@ function readXrefFile (xrefUri: Uri) { return workspace.fs.readFile(xrefUri).then((content) => { const str = Buffer.from(content.buffer).toString() return str - }, (err) => { + }, (e: unknown) => { log.trace('xref file not found \'' + xrefUri.fsPath + '\'') - log.trace(' -- err=' + err) + log.trace(' -- err=' + e) return undefined // don't rethrow, just return undefined because we don't want to stop processing }) } @@ -170,7 +170,7 @@ export const getSourceMapFromSource = (propath: PropathParser, debugSourceName: } try { debugLines = await importDebugLines(debugSourceName, fileinfo.uri, fileinfo.xrefUri) - } catch (e) { + } catch (_e: unknown) { log.warn('cannot read: ' + fileinfo.uri.fsPath) return undefined } diff --git a/src/parse/TestParserCommon.ts b/src/parse/TestParserCommon.ts index ac2e9fe2..9926c6c6 100644 --- a/src/parse/TestParserCommon.ts +++ b/src/parse/TestParserCommon.ts @@ -4,26 +4,34 @@ import { isRelativePath } from 'ABLUnitCommon' import * as fs from 'fs' const textDecoder = new TextDecoder('utf-8') -export function getContentFromFilesystem (uri: Uri) { - log.info('700') - const v = workspace.fs.readFile(uri) - .then((rawContent) => { - log.info('701') - return textDecoder.decode(rawContent) - }, (e) => { - log.info('799') - log.warn('Error providing tests for ' + uri.fsPath + ': ' + e) - return '' - }) - log.info('702 v=' + JSON.stringify(v)) - return v + +function toUri (uri: Uri | string): Uri { + if (uri instanceof Uri) { + return uri + } + const filename = uri + + if (!isRelativePath(uri)) { + return Uri.file(uri) + } + + if (!workspace.workspaceFolders || workspace.workspaceFolders.length === 0) { + throw new Error('No workspace folder found') + } + for (const wf of workspace.workspaceFolders) { + uri = Uri.joinPath(wf.uri, filename) + if (fs.statSync(uri.fsPath).isFile()) { + return uri + } + } + throw new Error('relative file not found in any workspace: ' + filename) } export function getContentFromFilesystem (uri: Uri | string) { uri = toUri(uri) return workspace.fs.readFile(uri) .then((rawContent) => { return textDecoder.decode(rawContent) }, - (e) => { throw e }) + (e: unknown) => { throw e }) } export function readLinesFromFile (uri: Uri | string) { diff --git a/src/parse/config/CoreOptions.ts b/src/parse/config/CoreOptions.ts index 442ce03e..f0bbcb40 100644 --- a/src/parse/config/CoreOptions.ts +++ b/src/parse/config/CoreOptions.ts @@ -1,5 +1,6 @@ export interface ITestObj { - test: string + folder?: string + test?: string cases?: string[] } diff --git a/src/testTree.ts b/src/testTree.ts index a4cb627a..842d777f 100644 --- a/src/testTree.ts +++ b/src/testTree.ts @@ -79,15 +79,21 @@ class TestTypeObj implements ITestType { this.label = label this.type = type } - } -export class ABLTestDir extends TestTypeObj { +export class ABLTestDir implements ITestType { + public type: TestReferenceType + public isFile = false + public didResolve = true + public runnable = true + public canResolveChildren = false + public description: string public relativePath: string + public label = '' - - constructor (desc: string, label: string, path: Uri | string) { - super(desc, label, 'ABLTestDir') + constructor (desc: string, label: string, path: Uri | string, type: TestReferenceType) { + this.description = desc + this.label = label if (path instanceof Uri) { this.relativePath = workspace.asRelativePath(path.fsPath, false) } else { @@ -98,6 +104,7 @@ export class ABLTestDir extends TestTypeObj { this.didResolve = true this.runnable = true this.canResolveChildren = false + this.type = type } } @@ -131,7 +138,7 @@ export class ABLTestFile extends TestTypeObj { item.error = undefined item.canResolveChildren = true return this.updateFromContents(controller, content, item) - }, (e) => { + }, (e: unknown) => { item.error = (e as Error).stack throw e }) diff --git a/test/openedgeAblCommands.ts b/test/openedgeAblCommands.ts index 6ba93a85..fc7c51fd 100644 --- a/test/openedgeAblCommands.ts +++ b/test/openedgeAblCommands.ts @@ -51,7 +51,7 @@ export function rebuildAblProject () { const rcodeCount = getRcodeCount() log.info('abl.project.rebuild command complete! (rcodeCount=' + rcodeCount + ')') return rcodeCount - }, (err) => { throw err }) + }, (e: unknown) => { throw e }) } export async function printLastLangServerError () { diff --git a/test/parse/TestProfileParser.test.ts b/test/parse/TestProfileParser.test.ts index 6017ab75..1fdf2874 100644 --- a/test/parse/TestProfileParser.test.ts +++ b/test/parse/TestProfileParser.test.ts @@ -10,9 +10,9 @@ function readValidationFile (filename: string) { // const data = Buffer.from(content.buffer).toString().trim().replace(/[\r\t\n]/g, '').replace(/\/\/.*/g, '').replace(/^$/g, '') const conf: IConfigurations = JSON.parse(data) as IConfigurations return conf.configurations - }, (err) => { - log.error('Reading validation file failed: ' + err) - throw err + }, (e: unknown) => { + log.error('Reading validation file failed: ' + e) + throw e }) } diff --git a/test/suites/DebugLines.test.ts b/test/suites/DebugLines.test.ts index 414844c6..b425d795 100644 --- a/test/suites/DebugLines.test.ts +++ b/test/suites/DebugLines.test.ts @@ -13,7 +13,7 @@ suite('debugLines - Debug Line Tests - insiders', () => { .then((rcodeCount) => { log.info('rcodeCount=' + rcodeCount) return rcodeCount - }, (e) => { + }, (e: unknown) => { log.error('rcode error: ' + e) throw e }) diff --git a/test/suites/proj0.test.ts b/test/suites/proj0.test.ts index 837c704e..d38c4eda 100644 --- a/test/suites/proj0.test.ts +++ b/test/suites/proj0.test.ts @@ -1,15 +1,14 @@ -import { Uri, commands, window, workspace, TestController } from 'vscode' -import { assert, deleteFile, getResults, getTestController, log, refreshTests, runAllTests, runAllTestsWithCoverage, sleep, suiteSetupCommon, toUri, updateTestProfile } from '../testCommon' +import { Uri, commands, window, workspace } from 'vscode' +import * as vscode from 'vscode' +import { assert, deleteFile, getResults, getTestControllerItemCount, getTestItem, log, refreshTests, runAllTests, runAllTestsWithCoverage, runTestAtLine, runTestsDuration, runTestsInFile, sleep2, suiteSetupCommon, toUri, updateConfig, updateTestProfile } from '../testCommon' import { ABLResultsParser } from 'parse/ResultsParser' -import { gatherAllTestItems } from 'extension' - -function getTestItem (ctrl: TestController, uri: Uri) { - const testItems = gatherAllTestItems(ctrl.items) - const testItem = testItems.find((item) => item.uri?.fsPath === uri.fsPath) - if (!testItem) { - throw new Error('cannot find TestItem for ' + uri.fsPath) - } - return testItem +import * as fs from 'fs' +import { TimeoutError } from 'ABLUnitRun' + +function createTempFile () { + const tempFile = toUri('UNIT_TEST.tmp') + return workspace.fs.writeFile(tempFile, Buffer.from('')) + .then(() => { return tempFile }) } suite('proj0 - Extension Test Suite', () => { @@ -86,61 +85,49 @@ suite('proj0 - Extension Test Suite', () => { assert.equal(0, executedLines.length, 'executed lines found for ' + workspace.asRelativePath(testFileUri) + '. should be empty') }) - test('proj0.5 - parse test class with expected error annotation', () => { - const testFileUri = toUri('src/threeTestMethods.cls') - return refreshTests() - .then(() => { return workspace.openTextDocument(testFileUri) }) - .then((e) => { - log.info('e=' + JSON.stringify(e)) - return sleep(100) - }) - .then(() => { return sleep(100) }) - .then(() => { return getTestController() }) - .then((ctrl) => { - const testItem = getTestItem(ctrl, testFileUri) - assert.equal(testItem.children.size, 3, 'testClassItem.children.size should be 3') - return - }) + test('proj0.05 - parse test class with expected error annotation', async () => { + const testClassItem = await commands.executeCommand('vscode.open', toUri('src/threeTestMethods.cls')) + .then(() => { return sleep2(250) }) + .then(() => { return getTestItem(toUri('src/threeTestMethods.cls')) }) + + if (!testClassItem) { + throw new Error('cannot find TestItem for src/threeTestMethods.cls') + } + + assert.equal(testClassItem.children.size, 3, 'testClassItem.children.size should be 3') }) - test('proj0.6 - parse test program with expected error annotation', () => { - const testFileUri = toUri('src/threeTestProcedures.p') - return refreshTests() - .then(() => { return workspace.openTextDocument(testFileUri) }) - .then((e) => { return sleep(100) }) - .then(() => { return sleep(100) }) - .then(() => { return getTestController() }) - .then((ctrl) => { - const testItem = getTestItem(ctrl, testFileUri) - assert.equal(testItem.children.size, 3, 'testClassItem.children.size should be 3') - return - }) + test('proj0.06 - parse test program with expected error annotation', async () => { + const testClassItem = await commands.executeCommand('vscode.open', toUri('src/threeTestProcedures.p')) + .then(() => { return sleep2(250) }) + .then(() => { return getTestItem(toUri('src/threeTestProcedures.p')) }) + + if (!testClassItem) { + throw new Error('cannot find TestItem for src/threeTestProcedures.p') + } + assert.equal(testClassItem.children.size, 3, 'testClassItem.children.size should be 3') }) - test('proj0.7 - parse test class with skip annotation', () => { - const testFileUri = toUri('src/ignoreMethod.cls') - return workspace.openTextDocument(testFileUri) - .then(() => { return refreshTests() }) - .then(() => { return sleep(100) }) - .then(() => { return sleep(100) }) - .then(() => { return getTestController() }) - .then((ctrl) => { - const testItem = getTestItem(ctrl, testFileUri) - assert.equal(testItem.children.size, 5, 'testClassItem.children.size should be 5') - return - }) + test('proj0.07 - parse test class with skip annotation', async () => { + const testClassItem = await commands.executeCommand('vscode.open', toUri('src/ignoreMethod.cls')) + .then(() => { return sleep2(250) }) + .then(() => { return getTestItem(toUri('src/ignoreMethod.cls')) }) + + if (!testClassItem) { + throw new Error('cannot find TestItem for src/ignoreMethod.cls') + } + assert.equal(testClassItem.children.size, 5, 'testClassItem.children.size should be 5') }) - test('proj0.8 - parse test procedure with skip annotation', () => { - const testFileUri = toUri('src/ignoreProcedure.p') - return workspace.openTextDocument(testFileUri) - .then(() => { return sleep(100) }) - .then(() => { return getTestController() }) - .then((ctrl) => { - const testItem = getTestItem(ctrl, testFileUri) - assert.equal(testItem.children.size, 5, 'testClassItem.children.size should be 5') - return - }) + test('proj0.08 - parse test procedure with skip annotation', async () => { + const testClassItem = await commands.executeCommand('vscode.open', toUri('src/ignoreProcedure.p')) + .then(() => { return sleep2(250) }) + .then(() => { return getTestItem(toUri('src/ignoreProcedure.p')) }) + + if (!testClassItem) { + throw new Error('cannot find TestItem for src/ignoreProcedure.p') + } + assert.equal(testClassItem.children.size, 5, 'testClassItem.children.size should be 5') }) test('proj0.9 - ABLResultsParser', () => { @@ -197,7 +184,7 @@ suite('proj0 - Extension Test Suite', () => { .then((r) => { log.info('opened file (r=' + r + ')') return sleep2(250) - }, (e) => { throw e }) + }, (e: unknown) => { throw e }) const startCount = await getTestItem(toUri('src/dirA/proj10.p')) .then((r) => { @@ -205,7 +192,7 @@ suite('proj0 - Extension Test Suite', () => { log.info('c.label=' + c.label + '; c.id=' + c.id) } return r.children.size - }, (e) => { throw e }) + }, (e:unknown) => { throw e }) // update test program @@ -222,7 +209,7 @@ suite('proj0 - Extension Test Suite', () => { log.info('c.label=' + c.label + '; c.id=' + c.id) } return r.children.size - }, (e) => { throw e }) + }, (e: unknown) => { throw e }) assert.equal(endCount - startCount, 2, 'test cases added != 2 (endCount=' + endCount + '; startCount=' + startCount + ')') }) diff --git a/test/suites/proj7A.test.ts b/test/suites/proj7A.test.ts index 9fedaded..8a87cacc 100644 --- a/test/suites/proj7A.test.ts +++ b/test/suites/proj7A.test.ts @@ -7,10 +7,6 @@ suite('proj7A - Extension Test Suite', () => { .then(() => { return beforeProj7() }) }) - test.skip('proj7A.1 - test count', async () => { - await runAllTests() - - test('proj7A.1 - test count', async () => { const duration = new Duration() await runAllTests().then(() => { diff --git a/test/suites/proj7B.test.ts b/test/suites/proj7B.test.ts index 56dfa4eb..972b74aa 100644 --- a/test/suites/proj7B.test.ts +++ b/test/suites/proj7B.test.ts @@ -37,9 +37,9 @@ suite('proj7B - Extension Test Suite', () => { await commands.executeCommand('testing.cancelTestRefresh').then(() => { log.info('testing.cancelTestRefresh completed') return - }, (err) => { - log.error('testing.cancelTestRefresh caught an exception. err=' + err) - throw err + }, (e: unknown) => { + log.error('testing.cancelTestRefresh caught an exception. err=' + e) + throw e }) log.info(' - elapsedCancelTime=' + startCancelTime.elapsed() + 'ms, elapsedRefreshTime=' + startRefreshTime.elapsed() + 'ms') assert.durationMoreThan(startCancelTime, minCancelTime) @@ -56,12 +56,12 @@ suite('proj7B - Extension Test Suite', () => { await refresh.then(() => { assert.fail('testing.refreshTests completed without throwing CancellationError') return - }, (err) => { - if (err instanceof CancellationError) { + }, (e: unknown) => { + if (e instanceof CancellationError) { log.info('testing.refreshTests threw CancellationError as expected') } else { - const e = err as Error - assert.equal(e.name, 'Canceled', 'testing.refreshTests threw unexpected error. Expected e.name="Canceled" err=' + err) + const err = e as Error + assert.equal(err.name, 'Canceled', 'testing.refreshTests threw unexpected error. Expected e.name="Canceled" err=' + err) } }) }) diff --git a/test/testCommon.ts b/test/testCommon.ts index a49866af..0a79f470 100644 --- a/test/testCommon.ts +++ b/test/testCommon.ts @@ -190,9 +190,9 @@ export function setFilesExcludePattern () { return filesConfig.update('exclude', files.exclude).then(() => { log.info('[updateFilesExcludePatterns] filesConfig.update success!') return true - }, (err) => { - log.error('[updateFilesExcludePatterns] filesConfig.update failed! err=' + err) - throw err + }, (e: unknown) => { + log.error('[updateFilesExcludePatterns] filesConfig.update failed! err=' + e) + throw e }) } @@ -223,7 +223,7 @@ export function installExtension (extname = 'riversidesoftware.openedge-abl-lsp' throw new Error('get after install failed (undefined)') } return true - }, (e) => { + }, (e: unknown) => { log.error('install failed e=' + e) return false }) @@ -324,7 +324,7 @@ async function waitForExtensionActive (extensionId = 'kherring.ablunit-test-runn .then(() => { log.info('activated? ' + extensionId) return extensions.getExtension(extensionId) - }, (err) => { throw new Error('failed to activate kherring.ablunit-test-runner: ' + err) }) + }, (e: unknown) => { throw new Error('failed to activate kherring.ablunit-test-runner: ' + e) }) log.info('post-activate (ext.isActive=' + ext?.isActive + ')') if (!ext) { throw new Error(extensionId + ' is not installed') } @@ -370,7 +370,7 @@ export async function awaitRCode (workspaceFolder: WorkspaceFolder, rcodeCountMi await commands.executeCommand('abl.project.rebuild').then(() => { log.info('abl.project.rebuild command complete!') return true - }, (e) => { + }, (e: unknown) => { log.error('[awaitRCode] abl.project.rebuild failed! err=' + e) return false }) @@ -577,7 +577,7 @@ export async function runAllTests (doRefresh = true, waitForResults = true, with .then((r) => { log.info(tag + 'command ' + testCommand +' complete! (r=' + r + ')') return sleep(250) - }, (e) => { throw e }) + }, (e: unknown) => { throw e }) .then(() => { log.info(tag + 'testing.runAll completed - start getResults()') if (!waitForResults) { return [] } @@ -590,9 +590,9 @@ export async function runAllTests (doRefresh = true, waitForResults = true, with return doesFileExist(fUri) } return false - }, (err) => { + }, (e: unknown) => { runAllTestsDuration?.stop() - throw new Error('testing.runAll failed: ' + err) + throw new Error('testing.runAll failed: ' + e) }) runAllTestsDuration.stop() log.info(tag + 'runAllTests complete (r=' + r + ')') @@ -613,13 +613,13 @@ export function runTestsInFile (filename: string, len = 1, coverage = false) { return commands.executeCommand('testing.coverageCurrentFile') } return commands.executeCommand('testing.runCurrentFile') - }, (e) => { + }, (e: unknown) => { throw e }) .then((r: unknown) => { runTestsDuration?.stop() return getResults(len) - }, (e) => { + }, (e: unknown) => { runTestsDuration?.stop() throw e }) @@ -645,7 +645,7 @@ export function runTestAtLine (filename: string, line: number, len = 1) { .then(() => { log.info('testing.runAtCursor complete') return - }, (e) => { throw e }) + }, (e: unknown) => { throw e }) } async function waitForRefreshComplete () { @@ -658,7 +658,7 @@ async function waitForRefreshComplete () { .then((r: unknown) => { log.info('isRefreshTestsComplete=' + r) return true - }, (e) => { + }, (e: unknown) => { log.info('isRefreshTestComplete error=' + e) return false }) @@ -678,9 +678,9 @@ export function refreshTests () { .then((r) => { log.info('testing.refreshTests completed! (r=' + r + ')') return true - }, (err) => { - log.info('testing.refreshTests caught an exception. err=' + err) - throw err + }, (e: unknown) => { + log.info('testing.refreshTests caught an exception. err=' + e) + throw e }) } @@ -704,7 +704,7 @@ export async function waitForTestRunStatus (waitForStatus: RunStatus) { return runData[0].status } return RunStatus.None - }, (e) => { + }, (e: unknown) => { log.info('could not get current run data: ' + e) return RunStatus.None }) @@ -794,7 +794,7 @@ export function updateConfig (key: string, value: unknown, configurationTarget?: } log.info('updating configuration section1=' + section2 + ', section2=' + section2 + ', key=' + key + ' value=' + JSON.stringify(value)) return workspaceConfig.update(section2, value, configurationTarget) - .then(() => true, (e) => { throw e }) + .then(() => true, (e: unknown) => { throw e }) } export async function updateTestProfile (key: string, value: string | string[] | boolean | number | object | undefined, workspaceUri?: Uri) { @@ -878,9 +878,9 @@ export function refreshData (resultsLen = 0) { return true } return false - }, (err) => { - log.error('failed to refresh test results: ' + err) - throw new Error('failed to refresh test results: ' + err) + }, (e: unknown) => { + log.error('failed to refresh test results: ' + e) + throw new Error('failed to refresh test results: ' + e) }) } @@ -907,7 +907,7 @@ export function getTestItem (uri: Uri) { const item = i as TestItem log.info('202 item.id=' + item.id) return item - }, (e) => { throw e }) + }, (e: unknown) => { throw e }) } function getType (item: TestItem | undefined) { @@ -988,8 +988,8 @@ export async function getCurrentRunData (len = 1, resLen = 0, tag?: string) { const retResults = await refreshData(resLen).then((r) => { log.debug('refresh success (r=' + r + '; currentRunData.length=' + currentRunData?.length + ')') return true - }, (err) => { - log.error('refresh failed: ' + err) + }, (e: unknown) => { + log.error('refresh failed: ' + e) return false }) From eadb2943d6e8b429bb41bad253aeed42facc0ab7 Mon Sep 17 00:00:00 2001 From: kenherring Date: Fri, 6 Dec 2024 06:37:08 -0500 Subject: [PATCH 8/8] cleanup --- test/suites/proj7A.test.ts | 2 ++ test_projects/DebugLines/openedge-project.json | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test/suites/proj7A.test.ts b/test/suites/proj7A.test.ts index 8a87cacc..f0454d4d 100644 --- a/test/suites/proj7A.test.ts +++ b/test/suites/proj7A.test.ts @@ -7,6 +7,8 @@ suite('proj7A - Extension Test Suite', () => { .then(() => { return beforeProj7() }) }) + + test('proj7A.1 - test count', async () => { const duration = new Duration() await runAllTests().then(() => { diff --git a/test_projects/DebugLines/openedge-project.json b/test_projects/DebugLines/openedge-project.json index bd5351dc..a5b88d5c 100644 --- a/test_projects/DebugLines/openedge-project.json +++ b/test_projects/DebugLines/openedge-project.json @@ -1,6 +1,5 @@ { "name": "DebugLines", - "oeversion": "12.2", "charset": "UTF-8", "dbConnections": [], "graphicalMode": false,