diff --git a/src/steps/upgrade/twopointoh/backend/phpunit-xml.ts b/src/steps/upgrade/twopointoh/backend/phpunit-xml.ts new file mode 100644 index 0000000..e6f7e3e --- /dev/null +++ b/src/steps/upgrade/twopointoh/backend/phpunit-xml.ts @@ -0,0 +1,38 @@ +/** + * Convert a PHPUnit 9-era config to the shape the CLI scaffolds for 2.x + * extensions: schema resolved from the vendored phpunit, the attributes + * PHPUnit 10 removed dropped or renamed, coverage's include list moved to + * , the Mockery listener block removed, and warning details enabled + * so flarum/testing's query guard is visible in the output. + */ +export function modernizePhpunitXml(file: string, code: string): string { + // The schema path is relative to the config file's own directory. + const prefix = '../'.repeat(file.split('/').length - 1); + let updated = code.replace(/xsi:noNamespaceSchemaLocation="[^"]*"/, `xsi:noNamespaceSchemaLocation="${prefix}vendor/phpunit/phpunit/phpunit.xsd"`); + + // Attributes removed in PHPUnit 10. + updated = updated.replace( + /[\t ]*(?:convert(?:Deprecations|Errors|Notices|Warnings)ToExceptions|forceCoversAnnotation|verbose|printerClass)="[^"]*"\r?\n/g, + '' + ); + updated = updated.replace(/backupStaticAttributes=/g, 'backupStaticProperties='); + + // New settings, inserted next to their neighbours in the scaffolded order. + if (!updated.includes('cacheDirectory=')) { + updated = updated.replace(/(\n([\t ]*)backupGlobals="[^"]*")/, '$1\n$2cacheDirectory=".phpunit.cache"'); + } + + if (!updated.includes('displayDetailsOnTestsThatTriggerWarnings=')) { + updated = updated.replace(/(\n([\t ]*)colors="[^"]*")/, '$1\n$2displayDetailsOnTestsThatTriggerWarnings="true"'); + } + + // The include list lives in now; coverage configuration as it + // existed in 9.x is gone. + updated = updated.replace(/]*>/, '').replace(/<\/coverage>/, ''); + + // Test listeners were removed in PHPUnit 10 (Mockery integration works + // without one). + updated = updated.replace(/[\t ]*[\S\s]*?<\/listeners>\r?\n?/, ''); + + return updated; +} diff --git a/src/steps/upgrade/twopointoh/backend/phpunit.ts b/src/steps/upgrade/twopointoh/backend/phpunit.ts index ff030a8..a8179ef 100644 --- a/src/steps/upgrade/twopointoh/backend/phpunit.ts +++ b/src/steps/upgrade/twopointoh/backend/phpunit.ts @@ -1,28 +1,35 @@ import { BaseUpgradeStep, GitCommit, Replacement } from '../base'; import chalk from 'chalk'; +import { modernizePhpunitXml } from './phpunit-xml'; export default class PhpUnit extends BaseUpgradeStep { type = 'PHPUnit 9 to 11 changes'; replacements(file: string): Replacement[] { - if (!file.endsWith('.php')) return []; - - return [ - (_file, code) => ({ - updated: this.php!.run('upgrade.2-0.phpunit', { file, code }).code, - }), - (file, code) => { - if (!file.endsWith('.xml')) return null; - - return { - updated: code.replace('xsi:noNamespaceSchemaLocation="([^"]+)"', 'xsi:noNamespaceSchemaLocation="../vendor/phpunit/phpunit/phpunit.xsd"'), - }; - }, - ]; + if (file.endsWith('.php')) { + return [ + (_file, code) => ({ + updated: this.php!.run('upgrade.2-0.phpunit', { file, code }).code, + }), + ]; + } + + // The XML rewrite previously sat behind the .php guard above (making it + // unreachable) and used String.replace with regex syntax in a string + // (matching nothing) — every upgraded extension kept its 9.3-era config. + if (/(^|\/)phpunit(\.[\w-]+)?\.xml$/.test(file)) { + return [ + (xmlFile, code) => ({ + updated: modernizePhpunitXml(xmlFile, code), + }), + ]; + } + + return []; } targets(): string[] { - return ['tests/**/*']; + return ['tests/**/*', 'phpunit.xml', 'phpunit.*.xml']; } gitCommit(): GitCommit { diff --git a/test/steps/upgrade/phpunit-config.test.ts b/test/steps/upgrade/phpunit-config.test.ts new file mode 100644 index 0000000..1a4c9c8 --- /dev/null +++ b/test/steps/upgrade/phpunit-config.test.ts @@ -0,0 +1,123 @@ +import { modernizePhpunitXml } from '../../../src/steps/upgrade/twopointoh/backend/phpunit-xml'; + +/** + * Extensions upgraded from 1.x carry PHPUnit 9.3-schema configs: attributes + * removed in PHPUnit 10+, the element, the Mockery listener block. + * PHPUnit 12 flags a deprecation on every run, warnings hide behind summary + * counts, and the version-pinned schema URL no longer matches the installed + * phpunit. These tests pin the conversion to the shape the CLI scaffolds. + */ + +const oldIntegration = ` + + + + ../src/ + + + + + ./integration + ./integration/tmp + + + +`; + +const oldUnit = ` + + + + ../src/ + + + + + ./unit + + + + + + +`; + +describe('modernizePhpunitXml', () => { + const integration = modernizePhpunitXml('tests/phpunit.integration.xml', oldIntegration); + const unit = modernizePhpunitXml('tests/phpunit.unit.xml', oldUnit); + + test('points the schema at the vendored phpunit, relative to the config', () => { + expect(integration).toContain('xsi:noNamespaceSchemaLocation="../vendor/phpunit/phpunit/phpunit.xsd"'); + expect(integration).not.toContain('schema.phpunit.de'); + }); + + test('a root-level config resolves the vendored schema without ../', () => { + const root = modernizePhpunitXml('phpunit.xml', oldIntegration); + expect(root).toContain('xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"'); + }); + + test('drops the attributes PHPUnit 10 removed', () => { + for (const gone of ['convertErrorsToExceptions', 'convertNoticesToExceptions', 'convertWarningsToExceptions', 'backupStaticAttributes']) { + expect(integration).not.toContain(gone); + } + }); + + test('renames backupStaticAttributes to backupStaticProperties, keeping the value', () => { + expect(integration).toContain('backupStaticProperties="false"'); + }); + + test('adds the cache directory and warning details display', () => { + expect(integration).toContain('cacheDirectory=".phpunit.cache"'); + expect(integration).toContain('displayDetailsOnTestsThatTriggerWarnings="true"'); + }); + + test('replaces the coverage element with source', () => { + expect(integration).toContain(''); + expect(integration).toContain(''); + expect(integration).not.toContain('. + expect(integration).toMatch(/[\S\s]*\.\.\/src\/<\/directory>[\S\s]*<\/source>/); + }); + + test('removes the listeners block PHPUnit 10 dropped', () => { + expect(unit).not.toContain(''); + expect(unit).not.toContain('Mockery\\Adapter'); + }); + + test('preserves the testsuites and per-file settings verbatim', () => { + expect(integration).toContain(''); + expect(integration).toContain('./integration/tmp'); + expect(integration).toContain('processIsolation="true"'); + expect(unit).toContain('processIsolation="false"'); + expect(unit).toContain(''); + }); + + test('is idempotent on an already-modern config', () => { + const once = modernizePhpunitXml('tests/phpunit.integration.xml', oldIntegration); + const twice = modernizePhpunitXml('tests/phpunit.integration.xml', once); + expect(twice).toBe(once); + }); +});