diff --git a/package-lock.json b/package-lock.json index 97d1c246..3d5cfea5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-phpunit", - "version": "3.6.7", + "version": "3.7.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vscode-phpunit", - "version": "3.6.7", + "version": "3.7.5", "hasInstallScript": true, "license": "MIT", "devDependencies": { @@ -25,7 +25,7 @@ "@vscode/vsce": "^3.3.2", "chai": "^5.2.0", "eslint": "^9.26.0", - "fast-xml-parser": "^5.2.2", + "fast-xml-parser": "^5.2.3", "glob": "^11.0.2", "minimatch": "^10.0.1", "mocha": "^11.2.2", @@ -5582,9 +5582,9 @@ "license": "BSD-3-Clause" }, "node_modules/fast-xml-parser": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.2.tgz", - "integrity": "sha512-ZaCmslH75Jkfowo/x44Uq8KT5SutC5BFxHmY61nmTXPccw11PVuIXKUqC2hembMkJ3nPwTkQESXiUlsKutCbMg==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.3.tgz", + "integrity": "sha512-OdCYfRqfpuLUFonTNjvd30rCBZUneHpSQkCqfaeWQ9qrKcl6XlWeDBNVwGb+INAIxRshuN2jF+BE0L6gbBO2mw==", "dev": true, "funding": [ { diff --git a/package.json b/package.json index 0f710a06..65aef6c5 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "displayName": "PHPUnit Test Explorer", "icon": "img/icon.png", "publisher": "recca0120", - "version": "3.7.4", + "version": "3.7.5", "private": true, "license": "MIT", "repository": { @@ -192,7 +192,7 @@ "@vscode/vsce": "^3.3.2", "chai": "^5.2.0", "eslint": "^9.26.0", - "fast-xml-parser": "^5.2.2", + "fast-xml-parser": "^5.2.3", "glob": "^11.0.2", "minimatch": "^10.0.1", "mocha": "^11.2.2", diff --git a/src/PHPUnit/ProblemMatcher/PHPUnitProblemMatcher.test.ts b/src/PHPUnit/ProblemMatcher/PHPUnitProblemMatcher.test.ts index eaab75e9..b95889ef 100644 --- a/src/PHPUnit/ProblemMatcher/PHPUnitProblemMatcher.test.ts +++ b/src/PHPUnit/ProblemMatcher/PHPUnitProblemMatcher.test.ts @@ -236,7 +236,33 @@ describe('PHPUnit ProblemMatcher Text', () => { ); }); - fit('fix PHPUnit 10 without details', () => { + it('fix PHPUnit10 testFailed', () => { + const contents = [ + `##teamcity[testSuiteStarted name='App\\Tests\\Ecommerce\\Offer\\Synchronizer\\PriceSynchronizerTest' locationHint='php_qn:///srv/app/tests/Ecommerce/Offer/Synchronizer/PriceSynchronizerTest.php::\\App\\Tests\\Ecommerce\\Offer\\Synchronizer\\PriceSynchronizerTest' flowId='5161']`, + `##teamcity[testFailed name='testProductNeedUpdateReturnsFalseWhenPriceSyncNotEnabled' message='Error: Class "App\\Ecommerce\\Offer\\Synchronizer\\PriceSynchronizer" not found' details='/srv/app/tests/Ecommerce/Offer/Synchronizer/PriceSynchronizerTest.php:28|n' duration='0' flowId='5161']`, + `##teamcity[testSuiteFinished name='App\\Tests\\Ecommerce\\Offer\\Synchronizer\\PriceSynchronizerTest' flowId='5161']`, + ]; + + problemMatcher.parse(contents[0]); + expect(problemMatcher.parse(contents[1])).toEqual( + expect.objectContaining({ + event: TeamcityEvent.testFailed, + name: 'testProductNeedUpdateReturnsFalseWhenPriceSyncNotEnabled', + // locationHint: 'php_qn:///srv/app/tests/Ecommerce/Offer/Synchronizer/PriceSynchronizerTest.php::\\App\\Tests\\Ecommerce\\Offer\\Synchronizer\\PriceSynchronizerTest::testProductNeedUpdateReturnsFalseWhenPriceSyncNotEnabled', + flowId: 5161, + id: '/srv/app/tests/Ecommerce/Offer/Synchronizer/PriceSynchronizerTest.php::testProductNeedUpdateReturnsFalseWhenPriceSyncNotEnabled', + file: '/srv/app/tests/Ecommerce/Offer/Synchronizer/PriceSynchronizerTest.php', + message: 'Error: Class "App\\Ecommerce\\Offer\\Synchronizer\\PriceSynchronizer" not found', + details: [{ + file: '/srv/app/tests/Ecommerce/Offer/Synchronizer/PriceSynchronizerTest.php', + line: 28, + }], + duration: 0, + }), + ); + }); + + it('fix PHPUnit10 testIgnored', () => { const contents = [ `##teamcity[testSuiteStarted name='Tests\\Feature\\ChatControllerTest' locationHint='php_qn:///var/www/html/tests/Feature/ChatControllerTest.php::\\Tests\\Feature\\ChatControllerTest' flowId='22946']`, `##teamcity[testIgnored name='test_permission' message='ChatControllerTest uses PlayerService' duration='0' flowId='22946']`, @@ -251,7 +277,7 @@ describe('PHPUnit ProblemMatcher Text', () => { expect.objectContaining({ event: TeamcityEvent.testIgnored, name: 'test_permission', - locationHint: 'php_qn:///var/www/html/tests/Feature/ChatControllerTest.php::test_permission', + locationHint: 'php_qn:///var/www/html/tests/Feature/ChatControllerTest.php::\\Tests\\Feature\\ChatControllerTest::test_permission', flowId: 22946, id: '/var/www/html/tests/Feature/ChatControllerTest.php::test_permission', file: '/var/www/html/tests/Feature/ChatControllerTest.php', @@ -268,7 +294,7 @@ describe('PHPUnit ProblemMatcher Text', () => { expect.objectContaining({ event: TeamcityEvent.testIgnored, name: 'test_grant_chat_token', - locationHint: 'php_qn:///var/www/html/tests/Feature/ChatControllerTest.php::test_grant_chat_token', + locationHint: 'php_qn:///var/www/html/tests/Feature/ChatControllerTest.php::\\Tests\\Feature\\ChatControllerTest::test_grant_chat_token', flowId: 22946, id: '/var/www/html/tests/Feature/ChatControllerTest.php::test_grant_chat_token', file: '/var/www/html/tests/Feature/ChatControllerTest.php', diff --git a/src/PHPUnit/Transformer/Fixer.ts b/src/PHPUnit/Transformer/Fixer.ts new file mode 100644 index 00000000..be4f16a0 --- /dev/null +++ b/src/PHPUnit/Transformer/Fixer.ts @@ -0,0 +1,121 @@ +import { TeamcityEvent, TestResult, TestStarted, TestSuiteStarted } from '../ProblemMatcher'; +import { capitalize } from '../utils'; + +export class PestV1Fixer { + static fixLocationHint(locationHint: string) { + return this.fixDataSet(/^tests\//.test(locationHint) ? locationHint : locationHint.substring(locationHint.lastIndexOf('tests/'))); + } + + static fixFlowId(results = new Map(), testResult?: TestResult) { + if (!testResult) { + return testResult; + } + + const events = [TeamcityEvent.testStarted, TeamcityEvent.testFailed, TeamcityEvent.testIgnored]; + if ('event' in testResult && !events.includes(testResult.event) || (testResult as any).flowId) { + return testResult; + } + + const result = Array.from(results.values()).reverse().find((result: TestResult) => { + if (testResult.event !== TeamcityEvent.testStarted) { + return result.event === TeamcityEvent.testStarted && (result as any).name === (testResult as any).name; + } + + const matched = (testResult as any).id?.match(/\((?.+)\)/); + + return matched && (result as any).id === matched.groups?.id.replace(/\\/g, '/') + 'Test'; + }); + + (testResult as any).flowId = (result as any)?.flowId; + + return testResult; + } + + private static fixDataSet(locationHint: string) { + const matched = locationHint.match(/(?.+)\swith\s\('(?.+)'\)/); + + return matched && matched.groups?.description + ? `${matched.groups.description} with data set "(\'${matched.groups.data}\')"` + : locationHint; + } +} + +export class Str { + static prefix = '__pest_evaluable_'; + + static evaluable(code: string) { + return this.prefix + code.replace(/_/g, '__').replace(/\s/g, '_').replace(/[^a-zA-Z0-9_\u0080-\uFFFF]/g, '_'); + } +} + +export class PestV2Fixer { + static fixId(location: string, name: string) { + return this.hasPrefix(name) ? name : location; + } + + static isEqualsPestV2DataSetId(result: TestResult, testItemId: string) { + if (!('id' in result) || !this.hasPrefix(result.id)) { + return false; + } + + let [classFQN, method] = testItemId.split('::'); + classFQN = capitalize(classFQN.replace(/\//g, '\\').replace(/\.php$/, '')); + + return [classFQN, this.methodName(method)].join('::') === result.id; + } + + private static hasPrefix(id?: string) { + return id && new RegExp(Str.prefix).test(id); + } + + static methodName(methodName: string) { + methodName = methodName.replace(/\{@\*}/g, '*/'); + const matched = methodName.match(/(?.*)\swith\sdata\sset\s(?.+)/); + let dataset = ''; + if (matched) { + methodName = matched.groups!.method; + dataset = matched.groups!.dataset.replace(/\|'/g, '\''); + } + + return Str.evaluable(methodName) + dataset; + } +} + +export class PHPUnitFixer { + static fixDetails(results = new Map(), testResult: TestResult & { + name: string, + locationHint?: string, + file?: string, + details?: Array<{ file: string, line: number }>, + }) { + if (testResult.details && testResult.file) { + return testResult; + } + + const result = Array.from(results.values()).reverse().find((result) => { + return [TeamcityEvent.testSuiteStarted, TeamcityEvent.testStarted].includes(result.event); + }) as (TestSuiteStarted | TestStarted | undefined); + + if (!result) { + return testResult; + } + + const file = result.file!; + if (!testResult.file) { + testResult.file = file; + } + + if (!testResult.details) { + testResult.details = [{ file: file, line: 1 }]; + } + + if (!testResult.locationHint) { + const locationHint = result.locationHint?.split('::').slice(0, 2).join('::'); + testResult.locationHint = [locationHint, testResult.name] + .filter(value => !!value) + .join('::'); + } + + return testResult; + } +} \ No newline at end of file diff --git a/src/PHPUnit/Transformer/PHPUnitTransformer.ts b/src/PHPUnit/Transformer/PHPUnitTransformer.ts index e31239c7..5646175a 100644 --- a/src/PHPUnit/Transformer/PHPUnitTransformer.ts +++ b/src/PHPUnit/Transformer/PHPUnitTransformer.ts @@ -1,47 +1,7 @@ -import { TeamcityEvent, TestResult, TestStarted, TestSuiteStarted } from '../ProblemMatcher'; import { TestDefinition, TestType } from '../types'; import { capitalize, snakeCase, titleCase } from '../utils'; import { Transformer } from './Transformer'; -export class PHPUnitFixer { - static fixDetails(results = new Map(), testResult: TestResult & { - name: string, - locationHint?: string, - file?: string, - details?: Array<{ file: string, line: number }>, - }) { - if (testResult.details && testResult.file) { - return testResult; - } - - const result = Array.from(results.values()).reverse().find((result) => { - return [TeamcityEvent.testSuiteStarted, TeamcityEvent.testStarted].includes(result.event); - }) as (TestSuiteStarted | TestStarted | undefined); - - if (!result) { - return testResult; - } - - const file = result.file!; - if (!testResult.file) { - testResult.file = file; - } - - if (!testResult.details) { - testResult.details = [{ file: file, line: 1 }]; - } - - if (!testResult.locationHint) { - const locationHint = result.locationHint?.split('::').slice(0, 1).join('::'); - testResult.locationHint = [locationHint, testResult.name] - .filter(value => !!value) - .join('::'); - } - - return testResult; - } -} - export class PHPUnitTransformer extends Transformer { uniqueId(testDefinition: Pick): string { let { type, classFQN } = testDefinition; diff --git a/src/PHPUnit/Transformer/PestTransFormer.test.ts b/src/PHPUnit/Transformer/PestTransFormer.test.ts index 3b30042b..e042954e 100644 --- a/src/PHPUnit/Transformer/PestTransFormer.test.ts +++ b/src/PHPUnit/Transformer/PestTransFormer.test.ts @@ -1,5 +1,6 @@ import { TestType } from '../types'; -import { PestTransformer, PestV2Fixer } from './PestTransformer'; +import { PestV2Fixer } from './Fixer'; +import { PestTransformer } from './PestTransformer'; describe('PestTransformer', () => { const transformer = new PestTransformer(); diff --git a/src/PHPUnit/Transformer/PestTransformer.ts b/src/PHPUnit/Transformer/PestTransformer.ts index e6c544e0..98f5592d 100644 --- a/src/PHPUnit/Transformer/PestTransformer.ts +++ b/src/PHPUnit/Transformer/PestTransformer.ts @@ -1,90 +1,9 @@ -import { TeamcityEvent, TestResult } from '../ProblemMatcher'; import { TestDefinition, TestType } from '../types'; -import { capitalize, uncapitalize } from '../utils'; +import { uncapitalize } from '../utils'; +import { PestV1Fixer, PestV2Fixer } from './Fixer'; import { PHPUnitTransformer } from './PHPUnitTransformer'; import { TransformerFactory } from './TransformerFactory'; - -export class Str { - static prefix = '__pest_evaluable_'; - - static evaluable(code: string) { - return this.prefix + code.replace(/_/g, '__').replace(/\s/g, '_').replace(/[^a-zA-Z0-9_\u0080-\uFFFF]/g, '_'); - } -} - -export class PestV1Fixer { - static fixLocationHint(locationHint: string) { - return this.fixDataSet(/^tests\//.test(locationHint) ? locationHint : locationHint.substring(locationHint.lastIndexOf('tests/'))); - } - - static fixFlowId(results = new Map(), testResult?: TestResult) { - if (!testResult) { - return testResult; - } - - const events = [TeamcityEvent.testStarted, TeamcityEvent.testFailed, TeamcityEvent.testIgnored]; - if ('event' in testResult && !events.includes(testResult.event) || (testResult as any).flowId) { - return testResult; - } - - const result = Array.from(results.values()).reverse().find((result: TestResult) => { - if (testResult.event !== TeamcityEvent.testStarted) { - return result.event === TeamcityEvent.testStarted && (result as any).name === (testResult as any).name; - } - - const matched = (testResult as any).id?.match(/\((?.+)\)/); - - return matched && (result as any).id === matched.groups?.id.replace(/\\/g, '/') + 'Test'; - }); - - (testResult as any).flowId = (result as any)?.flowId; - - return testResult; - } - - private static fixDataSet(locationHint: string) { - const matched = locationHint.match(/(?.+)\swith\s\('(?.+)'\)/); - - return matched && matched.groups?.description - ? `${matched.groups.description} with data set "(\'${matched.groups.data}\')"` - : locationHint; - } -} - -export class PestV2Fixer { - static fixId(location: string, name: string) { - return this.hasPrefix(name) ? name : location; - } - - static isEqualsPestV2DataSetId(result: TestResult, testItemId: string) { - if (!('id' in result) || !this.hasPrefix(result.id)) { - return false; - } - - let [classFQN, method] = testItemId.split('::'); - classFQN = capitalize(classFQN.replace(/\//g, '\\').replace(/\.php$/, '')); - - return [classFQN, this.methodName(method)].join('::') === result.id; - } - - private static hasPrefix(id?: string) { - return id && new RegExp(Str.prefix).test(id); - } - - static methodName(methodName: string) { - methodName = methodName.replace(/\{@\*}/g, '*/'); - const matched = methodName.match(/(?.*)\swith\sdata\sset\s(?.+)/); - let dataset = ''; - if (matched) { - methodName = matched.groups!.method; - dataset = matched.groups!.dataset.replace(/\|'/g, '\''); - } - - return Str.evaluable(methodName) + dataset; - } -} - export class PestTransformer extends PHPUnitTransformer { uniqueId(testDefinition: Pick): string { if (!TransformerFactory.isPest(testDefinition.classFQN!)) { diff --git a/src/PHPUnit/Transformer/index.ts b/src/PHPUnit/Transformer/index.ts index d7a41755..cb9b0d87 100644 --- a/src/PHPUnit/Transformer/index.ts +++ b/src/PHPUnit/Transformer/index.ts @@ -1,4 +1,5 @@ +export * from './Fixer'; export * from './TransformerFactory'; export * from './Transformer'; export * from './PHPUnitTransformer'; -export * from './PestTransformer'; +export * from './PestTransformer'; \ No newline at end of file