diff --git a/packages/env/CHANGELOG.md b/packages/env/CHANGELOG.md
index 2043425127d67..082171ee01133 100644
--- a/packages/env/CHANGELOG.md
+++ b/packages/env/CHANGELOG.md
@@ -2,6 +2,14 @@
## Unreleased
+### Breaking Changes
+- Removed the `WP_PHPUNIT__TESTS_CONFIG` environment variable from the `phpunit` container. **This removes automatic support for the `wp-phpunit/wp-phpunit` Composer package. To continue using the package, set the following two environment variables in your `phpunit.xml` file or similar: `WP_TESTS_DIR=""` and `WP_PHPUNIT__TESTS_CONFIG="/wordpress-phpunit/wp-tests-config.php"`.**
+- Removed the generated `/var/www/html/phpunit-wp-config.php` file from the environment.
+
+### Enhancement
+- Read WordPress' version and include the corresponding PHPUnit test files in the environment.
+- Set the `WP_TESTS_DIR` environment variable in all containers to point at the PHPUnit test files.
+
## 4.8.0 (2022-06-01)
### Enhancement
- Removed the need for quotation marks when passing options to `wp-env run`.
diff --git a/packages/env/README.md b/packages/env/README.md
index 311db067fed9f..cc071ca2a07f6 100644
--- a/packages/env/README.md
+++ b/packages/env/README.md
@@ -188,6 +188,14 @@ wp-env start --debug
...
```
+## Using included WordPress PHPUnit test files
+
+Out of the box `wp-env` includes the [WordPress' PHPUnit test files](https://develop.svn.wordpress.org/trunk/tests/phpunit/) corresponding to the version of WordPress installed. There is an environment variable, `WP_TESTS_DIR`, which points to the location of these files within each container. By including these files in the environment, we remove the need for you to use a package or install and mount them yourself. If you do not want to use these files, you should ignore the `WP_TESTS_DIR` environment variable and load them from the location of your choosing.
+
+### Customizing the `wp-tests-config.php` file
+
+While we do provide a default `wp-tests-config.php` file within the environment, there may be cases where you want to use your own. WordPress provides a `WP_TESTS_CONFIG_FILE_PATH` constant that you can use to change the `wp-config.php` file used for testing. Set this to a desired path in your `bootstrap.php` file and the file you've chosen will be used instead of the one included in the environment.
+
## Using Xdebug
Xdebug is installed in the wp-env environment, but it is turned off by default. To enable Xdebug, you can use the `--xdebug` flag with the `wp-env start` command. Here is a reference to how the flag works:
diff --git a/packages/env/lib/build-docker-compose-config.js b/packages/env/lib/build-docker-compose-config.js
index 808d3b5dea294..1efe0e6035e8e 100644
--- a/packages/env/lib/build-docker-compose-config.js
+++ b/packages/env/lib/build-docker-compose-config.js
@@ -19,13 +19,18 @@ const { dbEnv } = require( './config' );
/**
* Gets the volume mounts for an individual service.
*
- * @param {WPServiceConfig} config The service config to get the mounts from.
- * @param {string} wordpressDefault The default internal path for the WordPress
- * source code (such as tests-wordpress).
+ * @param {string} workDirectoryPath The working directory for wp-env.
+ * @param {WPServiceConfig} config The service config to get the mounts from.
+ * @param {string} wordpressDefault The default internal path for the WordPress
+ * source code (such as tests-wordpress).
*
* @return {string[]} An array of volumes to mount in string format.
*/
-function getMounts( config, wordpressDefault = 'wordpress' ) {
+function getMounts(
+ workDirectoryPath,
+ config,
+ wordpressDefault = 'wordpress'
+) {
// Top-level WordPress directory mounts (like wp-content/themes)
const directoryMounts = Object.entries( config.mappings ).map(
( [ wpDir, source ] ) => `${ source.path }:/var/www/html/${ wpDir }`
@@ -45,9 +50,19 @@ function getMounts( config, wordpressDefault = 'wordpress' ) {
config.coreSource ? config.coreSource.path : wordpressDefault
}:/var/www/html`;
+ const corePHPUnitMount = `${ path.join(
+ workDirectoryPath,
+ wordpressDefault === 'wordpress'
+ ? 'WordPress-PHPUnit'
+ : 'tests-WordPress-PHPUnit',
+ 'tests',
+ 'phpunit'
+ ) }:/wordpress-phpunit`;
+
return [
...new Set( [
coreMount,
+ corePHPUnitMount,
...directoryMounts,
...pluginMounts,
...themeMounts,
@@ -64,8 +79,15 @@ function getMounts( config, wordpressDefault = 'wordpress' ) {
* @return {Object} A docker-compose config object, ready to serialize into YAML.
*/
module.exports = function buildDockerComposeConfig( config ) {
- const developmentMounts = getMounts( config.env.development );
- const testsMounts = getMounts( config.env.tests, 'tests-wordpress' );
+ const developmentMounts = getMounts(
+ config.workDirectoryPath,
+ config.env.development
+ );
+ const testsMounts = getMounts(
+ config.workDirectoryPath,
+ config.env.tests,
+ 'tests-wordpress'
+ );
// When both tests and development reference the same WP source, we need to
// ensure that tests pulls from a copy of the files so that it maintains
@@ -208,6 +230,7 @@ module.exports = function buildDockerComposeConfig( config ) {
environment: {
...dbEnv.credentials,
...dbEnv.development,
+ WP_TESTS_DIR: '/wordpress-phpunit',
},
volumes: developmentMounts,
},
@@ -218,6 +241,7 @@ module.exports = function buildDockerComposeConfig( config ) {
environment: {
...dbEnv.credentials,
...dbEnv.tests,
+ WP_TESTS_DIR: '/wordpress-phpunit',
},
volumes: testsMounts,
},
@@ -229,6 +253,7 @@ module.exports = function buildDockerComposeConfig( config ) {
environment: {
...dbEnv.credentials,
...dbEnv.development,
+ WP_TESTS_DIR: '/wordpress-phpunit',
},
},
'tests-cli': {
@@ -239,6 +264,7 @@ module.exports = function buildDockerComposeConfig( config ) {
environment: {
...dbEnv.credentials,
...dbEnv.tests,
+ WP_TESTS_DIR: '/wordpress-phpunit',
},
},
composer: {
@@ -256,8 +282,7 @@ module.exports = function buildDockerComposeConfig( config ) {
],
environment: {
LOCAL_DIR: 'html',
- WP_PHPUNIT__TESTS_CONFIG:
- '/var/www/html/phpunit-wp-config.php',
+ WP_TESTS_DIR: '/wordpress-phpunit',
...dbEnv.credentials,
...dbEnv.tests,
},
diff --git a/packages/env/lib/commands/run.js b/packages/env/lib/commands/run.js
index 3179a388e8540..f944d8de84fa5 100644
--- a/packages/env/lib/commands/run.js
+++ b/packages/env/lib/commands/run.js
@@ -9,7 +9,7 @@ const { spawn } = require( 'child_process' );
const initConfig = require( '../init-config' );
/**
- * @typedef {import('../config').Config} Config
+ * @typedef {import('../config').WPConfig} WPConfig
*/
/**
@@ -42,11 +42,11 @@ module.exports = async function run( { container, command, spinner, debug } ) {
/**
* Runs an arbitrary command on the given Docker container.
*
- * @param {Object} options
- * @param {string} options.container The Docker container to run the command on.
- * @param {string} options.command The command to run.
- * @param {Config} options.config The wp-env configuration.
- * @param {Object} options.spinner A CLI spinner which indicates progress.
+ * @param {Object} options
+ * @param {string} options.container The Docker container to run the command on.
+ * @param {string} options.command The command to run.
+ * @param {WPConfig} options.config The wp-env configuration.
+ * @param {Object} options.spinner A CLI spinner which indicates progress.
*/
function spawnCommandDirectly( { container, command, config, spinner } ) {
const composeCommand = [
diff --git a/packages/env/lib/commands/start.js b/packages/env/lib/commands/start.js
index 0cca813c1c7d3..066754c6e5114 100644
--- a/packages/env/lib/commands/start.js
+++ b/packages/env/lib/commands/start.js
@@ -21,16 +21,18 @@ const retry = require( '../retry' );
const stop = require( './stop' );
const initConfig = require( '../init-config' );
const downloadSources = require( '../download-sources' );
+const downloadWPPHPUnit = require( '../download-wp-phpunit' );
const {
checkDatabaseConnection,
configureWordPress,
setupWordPressDirectories,
+ readWordPressVersion,
} = require( '../wordpress' );
const { didCacheChange, setCache } = require( '../cache' );
const md5 = require( '../md5' );
/**
- * @typedef {import('../config').Config} Config
+ * @typedef {import('../config').WPConfig} WPConfig
*/
const CONFIG_CACHE_KEY = 'config_checksum';
@@ -127,7 +129,25 @@ module.exports = async function start( { spinner, debug, update, xdebug } ) {
] );
if ( shouldConfigureWp ) {
+ spinner.text = 'Setting up WordPress directories';
+
await setupWordPressDirectories( config );
+
+ // Use the WordPress versions to download the PHPUnit suite.
+ const wpVersions = await Promise.all( [
+ readWordPressVersion(
+ config.env.development.coreSource,
+ spinner,
+ debug
+ ),
+ readWordPressVersion( config.env.tests.coreSource, spinner, debug ),
+ ] );
+ await downloadWPPHPUnit(
+ config,
+ { development: wpVersions[ 0 ], tests: wpVersions[ 1 ] },
+ spinner,
+ debug
+ );
}
spinner.text = 'Starting WordPress.';
diff --git a/packages/env/lib/download-sources.js b/packages/env/lib/download-sources.js
index 537389317b058..aec47f23203d1 100644
--- a/packages/env/lib/download-sources.js
+++ b/packages/env/lib/download-sources.js
@@ -16,7 +16,7 @@ const extractZip = util.promisify( require( 'extract-zip' ) );
const rimraf = util.promisify( require( 'rimraf' ) );
/**
- * @typedef {import('./config').Config} Config
+ * @typedef {import('./config').WPConfig} WPConfig
* @typedef {import('./config').WPSource} WPSource
*/
@@ -24,8 +24,8 @@ const rimraf = util.promisify( require( 'rimraf' ) );
* Download each source for each environment. If the same source is used in
* multiple environments, it will only be downloaded once.
*
- * @param {Config} config The wp-env configuration object.
- * @param {Object} spinner The spinner object to show progress.
+ * @param {WPConfig} config The wp-env configuration object.
+ * @param {Object} spinner The spinner object to show progress.
* @return {Promise} Returns a promise which resolves when the downloads finish.
*/
module.exports = function downloadSources( config, spinner ) {
diff --git a/packages/env/lib/download-wp-phpunit.js b/packages/env/lib/download-wp-phpunit.js
new file mode 100644
index 0000000000000..923750d58b1d7
--- /dev/null
+++ b/packages/env/lib/download-wp-phpunit.js
@@ -0,0 +1,140 @@
+'use strict';
+/**
+ * External dependencies
+ */
+const SimpleGit = require( 'simple-git' );
+const fs = require( 'fs' );
+const path = require( 'path' );
+
+/**
+ * @typedef {import('./config').WPConfig} WPConfig
+ */
+
+/**
+ * Downloads the WordPress PHPUnit files for each environment into the appropriate directories.
+ *
+ * @param {WPConfig} config The wp-env config object.
+ * @param {Object} wpVersions The WordPress versions for each environment.
+ * @param {Object} spinner The spinner object to show progress.
+ * @param {boolean} debug Indicates whether or not debug mode is active.
+ * @return {Promise} Returns a promise which resolves when the downloads finish.
+ */
+module.exports = function downloadWPPHPUnit(
+ config,
+ wpVersions,
+ spinner,
+ debug
+) {
+ const progresses = {};
+ const getProgressSetter = ( id ) => ( progress ) => {
+ progresses[ id ] = progress;
+ spinner.text =
+ 'Downloading WordPress PHPUnit Suite.\n' +
+ Object.entries( progresses )
+ .map(
+ ( [ key, value ] ) =>
+ ` - ${ key }: ${ ( value * 100 ).toFixed( 0 ) }/100%`
+ )
+ .join( '\n' );
+ };
+
+ const promises = [];
+ for ( const env in config.env ) {
+ const wpVersion = wpVersions[ env ] ? wpVersions[ env ] : null;
+ const directory = path.join(
+ config.workDirectoryPath,
+ env === 'development'
+ ? 'WordPress-PHPUnit'
+ : 'tests-WordPress-PHPUnit'
+ );
+ promises.push(
+ downloadTestSuite( directory, wpVersion, {
+ onProgress: getProgressSetter,
+ spinner,
+ debug,
+ } )
+ );
+ }
+
+ return Promise.all( promises );
+};
+
+/**
+ * Downloads the PHPUnit tests for a given WordPress version into the appropriate directory.
+ *
+ * @param {string} directory The directory to place the PHPUnit tests in.
+ * @param {string} wpVersion The version of WordPress to install PHPUnit tests for. Trunk when empty.
+ * @param {Object} options
+ * @param {Function} options.onProgress A function called with download progress. Will be invoked with one argument: a number that ranges from 0 to 1 which indicates current download progress for this source.
+ * @param {Object} options.spinner A CLI spinner which indicates progress.
+ * @param {boolean} options.debug True if debug mode is enabled.
+ */
+async function downloadTestSuite(
+ directory,
+ wpVersion,
+ { onProgress, spinner, debug }
+) {
+ const log = debug
+ ? ( message ) => {
+ spinner.info( `SimpleGit: ${ message }` );
+ spinner.start();
+ }
+ : () => {};
+ onProgress( 0 );
+
+ const progressHandler = ( { progress } ) => {
+ onProgress( progress / 100 );
+ };
+
+ // Make sure that the version is in X.X.X format. This is required
+ // because WordPress/wordpress-develop uses X.X.X tags but
+ // WordPress uses X.X for non-patch releases.
+ if ( wpVersion && wpVersion.match( /^[0-9]+.[0-9]+$/ ) ) {
+ wpVersion += '.0';
+ }
+
+ log( 'Cloning or getting the PHPUnit suite from GitHub.' );
+ const git = SimpleGit( { progress: progressHandler } );
+
+ const isRepo =
+ fs.existsSync( directory ) &&
+ ( await git.cwd( directory ).checkIsRepo( 'root' ) );
+
+ if ( isRepo ) {
+ log( 'Repo already exists, using it.' );
+ } else {
+ await git.clone(
+ 'https://github.com/WordPress/wordpress-develop.git',
+ directory,
+ {
+ '--depth': '1',
+ '--no-checkout': null,
+ }
+ );
+ await git.cwd( directory );
+
+ // We use a sparse checkout to minimize the amount of data we need to download.
+ log( 'Enabling sparse checkout.' );
+ await git.raw( 'sparse-checkout', 'set', '--cone', 'tests/phpunit' );
+ }
+
+ // Figure out the ref that we need to checkout to get the correct version of the PHPUnit library.
+ // Alpha, Beta, and RC versions are bleeding edge and should pull from trunk.
+ let ref;
+ const fetchRaw = [];
+ if ( ! wpVersion || wpVersion.match( /-(?:alpha|beta|rc)/ ) ) {
+ ref = 'trunk';
+ fetchRaw.push( 'fetch', 'origin', ref, '--depth', '1' );
+ } else {
+ ref = `tags/${ wpVersion }`;
+ fetchRaw.push( 'fetch', 'origin', 'tag', wpVersion, '--depth', '1' );
+ }
+
+ log( `Fetching ${ ref }.` );
+ await git.raw( fetchRaw );
+
+ log( `Checking out ${ ref }.` );
+ await git.checkout( ref );
+
+ onProgress( 1 );
+}
diff --git a/packages/env/lib/wordpress.js b/packages/env/lib/wordpress.js
index 133524bd72bfe..62360b4c182e7 100644
--- a/packages/env/lib/wordpress.js
+++ b/packages/env/lib/wordpress.js
@@ -14,6 +14,7 @@ const copyDir = util.promisify( require( 'copy-dir' ) );
/**
* @typedef {import('./config').WPConfig} WPConfig
* @typedef {import('./config').WPServiceConfig} WPServiceConfig
+ * @typedef {import('./config').WPSource} WPSource
* @typedef {'development'|'tests'} WPEnvironment
* @typedef {'development'|'tests'|'all'} WPEnvironmentSelection
*/
@@ -101,25 +102,15 @@ async function configureWordPress( environment, config, spinner ) {
}
);
- /**
- * Since wp-phpunit loads wp-settings.php at the end of its wp-config.php
- * file, we need to avoid loading it too early in our own wp-config.php. If
- * we load it too early, then some things (like MULTISITE) will be defined
- * before wp-phpunit has a chance to configure them. To avoid this, create a
- * copy of wp-config.php for phpunit which doesn't require wp-settings.php.
- *
- * Note that This needs to be executed using `exec` on the wordpress service
- * so that file permissions work properly.
- *
- * This will be removed in the future. @see https://github.com/WordPress/gutenberg/issues/23171
- *
- */
+ // WordPress' PHPUnit suite expects a `wp-tests-config.php` in
+ // the directory that the test suite is contained within.
+ // Make sure ABSPATH points to the WordPress install.
await dockerCompose.exec(
environment === 'development' ? 'wordpress' : 'tests-wordpress',
[
'sh',
'-c',
- 'sed "/^require.*wp-settings.php/d" /var/www/html/wp-config.php > /var/www/html/phpunit-wp-config.php && chmod 777 /var/www/html/phpunit-wp-config.php',
+ `sed -e "/^require.*wp-settings.php/d" -e "s/define( 'ABSPATH', __DIR__ . '\\/' );/define( 'ABSPATH', '\\/var\\/www\\/html\\/' );\\n\\tdefine( 'WP_DEFAULT_THEME', 'default' );/" /var/www/html/wp-config.php > /wordpress-phpunit/wp-tests-config.php`,
],
{
config: config.dockerComposeConfigPath,
@@ -246,10 +237,49 @@ async function copyCoreFiles( fromPath, toPath ) {
} );
}
+/**
+ * Scans through a WordPress source to find the version of WordPress it contains.
+ *
+ * @param {WPSource} coreSource The WordPress source.
+ * @param {Object} spinner A CLI spinner which indicates progress.
+ * @param {boolean} debug Indicates whether or not the CLI is in debug mode.
+ * @return {string} The version of WordPress the source is for.
+ */
+async function readWordPressVersion( coreSource, spinner, debug ) {
+ // No source means they're using the bleeding edge.
+ if ( coreSource === null ) {
+ return null;
+ }
+
+ const versionFilePath = path.join(
+ coreSource.path,
+ 'wp-includes',
+ 'version.php'
+ );
+ const versionFile = await fs.readFile( versionFilePath, {
+ encoding: 'utf-8',
+ } );
+ const versionMatch = versionFile.match(
+ /\$wp_version = '([A-Za-z\-0-9.]+)'/
+ );
+ if ( ! versionMatch ) {
+ throw new Error( `Failed to find version in ${ versionFilePath }` );
+ }
+
+ if ( debug ) {
+ spinner.info(
+ `Found WordPress ${ versionMatch[ 1 ] } in ${ versionFilePath }.`
+ );
+ }
+
+ return versionMatch[ 1 ];
+}
+
module.exports = {
hasSameCoreSource,
checkDatabaseConnection,
configureWordPress,
resetDatabase,
setupWordPressDirectories,
+ readWordPressVersion,
};
diff --git a/packages/env/test/build-docker-compose-config.js b/packages/env/test/build-docker-compose-config.js
index 3b4ce2f5067cd..5927e188c9df0 100644
--- a/packages/env/test/build-docker-compose-config.js
+++ b/packages/env/test/build-docker-compose-config.js
@@ -26,11 +26,13 @@ describe( 'buildDockerComposeConfig', () => {
],
};
const dockerConfig = buildDockerComposeConfig( {
+ workDirectoryPath: '/path',
env: { development: envConfig, tests: envConfig },
} );
const { volumes } = dockerConfig.services.wordpress;
expect( volumes ).toEqual( [
'wordpress:/var/www/html', // WordPress root.
+ '/path/WordPress-PHPUnit/tests/phpunit:/wordpress-phpunit', // WordPress test library,
'/path/to/wp-plugins:/var/www/html/wp-content/plugins', // Mapped plugins root.
'/path/to/local/plugin:/var/www/html/wp-content/plugins/test-name', // Mapped plugin.
] );
@@ -52,6 +54,7 @@ describe( 'buildDockerComposeConfig', () => {
],
};
const dockerConfig = buildDockerComposeConfig( {
+ workDirectoryPath: '/path',
env: { development: envConfig, tests: envConfig },
} );
const devVolumes = dockerConfig.services.wordpress.volumes;
@@ -62,13 +65,20 @@ describe( 'buildDockerComposeConfig', () => {
const testsCliVolumes = dockerConfig.services[ 'tests-cli' ].volumes;
expect( testsVolumes ).toEqual( testsCliVolumes );
- const localSources = [
+ let localSources = [
'/path/to/wp-plugins:/var/www/html/wp-content/plugins',
+ '/path/WordPress-PHPUnit/tests/phpunit:/wordpress-phpunit',
'/path/to/local/plugin:/var/www/html/wp-content/plugins/test-name',
'/path/to/local/theme:/var/www/html/wp-content/themes/test-theme',
];
-
expect( devVolumes ).toEqual( expect.arrayContaining( localSources ) );
+
+ localSources = [
+ '/path/to/wp-plugins:/var/www/html/wp-content/plugins',
+ '/path/tests-WordPress-PHPUnit/tests/phpunit:/wordpress-phpunit',
+ '/path/to/local/plugin:/var/www/html/wp-content/plugins/test-name',
+ '/path/to/local/theme:/var/www/html/wp-content/themes/test-theme',
+ ];
expect( testsVolumes ).toEqual(
expect.arrayContaining( localSources )
);
@@ -84,10 +94,12 @@ describe( 'buildDockerComposeConfig', () => {
},
};
const dockerConfig = buildDockerComposeConfig( {
+ workDirectoryPath: '/path',
env: { development: envConfig, tests: envConfig },
} );
const expectedVolumes = [
'tests-wordpress:/var/www/html',
+ '/path/tests-WordPress-PHPUnit/tests/phpunit:/wordpress-phpunit',
'/path/to/wp-uploads:/var/www/html/wp-content/uploads',
];
expect( dockerConfig.services.phpunit.volumes ).toEqual(
@@ -105,10 +117,12 @@ describe( 'buildDockerComposeConfig', () => {
},
};
const dockerConfig = buildDockerComposeConfig( {
+ workDirectoryPath: '/path',
env: { development: envConfig, tests: CONFIG },
} );
const expectedVolumes = [
'tests-wordpress:/var/www/html',
+ '/path/tests-WordPress-PHPUnit/tests/phpunit:/wordpress-phpunit',
'phpunit-uploads:/var/www/html/wp-content/uploads',
];
expect( dockerConfig.services.phpunit.volumes ).toEqual(
@@ -121,6 +135,7 @@ describe( 'buildDockerComposeConfig', () => {
// local filesystem, so a volume should be created to contain core
// sources.
const dockerConfig = buildDockerComposeConfig( {
+ workDirectoryPath: '/path',
env: { development: CONFIG, tests: CONFIG },
} );
@@ -140,6 +155,7 @@ describe( 'buildDockerComposeConfig', () => {
};
const dockerConfig = buildDockerComposeConfig( {
+ workDirectoryPath: '/path',
env: { development: envConfig, tests: envConfig },
} );
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 220581708552d..4f822fe342b24 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -19,4 +19,8 @@
ms-required
+
+
+
+
diff --git a/phpunit/multisite.xml b/phpunit/multisite.xml
index 168d4cb9e1136..e4b78091c1324 100644
--- a/phpunit/multisite.xml
+++ b/phpunit/multisite.xml
@@ -16,4 +16,8 @@
ms-excluded
+
+
+
+