Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions src/steps/upgrade/twopointoh/backend/phpunit-xml.ts
Original file line number Diff line number Diff line change
@@ -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
* <source>, 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 <source> now; coverage configuration as it
// existed in 9.x is gone.
updated = updated.replace(/<coverage[^>]*>/, '<source>').replace(/<\/coverage>/, '</source>');

// Test listeners were removed in PHPUnit 10 (Mockery integration works
// without one).
updated = updated.replace(/[\t ]*<listeners>[\S\s]*?<\/listeners>\r?\n?/, '');

return updated;
}
37 changes: 22 additions & 15 deletions src/steps/upgrade/twopointoh/backend/phpunit.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
123 changes: 123 additions & 0 deletions test/steps/upgrade/phpunit-config.test.ts
Original file line number Diff line number Diff line change
@@ -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 <coverage> 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 = `<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="true"
stopOnFailure="false"
>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">../src/</directory>
</include>
</coverage>
<testsuites>
<testsuite name="Flarum Integration Tests">
<directory suffix="Test.php">./integration</directory>
<exclude>./integration/tmp</exclude>
</testsuite>
</testsuites>
</phpunit>
`;

const oldUnit = `<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">../src/</directory>
</include>
</coverage>
<testsuites>
<testsuite name="Flarum Unit Tests">
<directory suffix="Test.php">./unit</directory>
</testsuite>
</testsuites>
<listeners>
<listener class="\\Mockery\\Adapter\\Phpunit\\TestListener" />
</listeners>
</phpunit>
`;

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('<source>');
expect(integration).toContain('</source>');
expect(integration).not.toContain('<coverage');
expect(integration).not.toContain('processUncoveredFiles');
// The include list survives inside <source>.
expect(integration).toMatch(/<source>[\S\s]*<directory suffix="\.php">\.\.\/src\/<\/directory>[\S\s]*<\/source>/);
});

test('removes the listeners block PHPUnit 10 dropped', () => {
expect(unit).not.toContain('<listeners>');
expect(unit).not.toContain('Mockery\\Adapter');
});

test('preserves the testsuites and per-file settings verbatim', () => {
expect(integration).toContain('<testsuite name="Flarum Integration Tests">');
expect(integration).toContain('<exclude>./integration/tmp</exclude>');
expect(integration).toContain('processIsolation="true"');
expect(unit).toContain('processIsolation="false"');
expect(unit).toContain('<testsuite name="Flarum Unit Tests">');
});

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);
});
});
Loading