Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Env: Download WordPress PHPUnit Into Container #41780

Merged
merged 17 commits into from
Jul 15, 2022
Merged
Show file tree
Hide file tree
Changes from 7 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
39 changes: 33 additions & 6 deletions packages/env/lib/build-docker-compose-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 }`
Expand All @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -208,6 +230,7 @@ module.exports = function buildDockerComposeConfig( config ) {
environment: {
...dbEnv.credentials,
...dbEnv.development,
WP_PHPUNIT__DIR: '/wordpress-phpunit',
},
volumes: developmentMounts,
},
Expand All @@ -218,6 +241,7 @@ module.exports = function buildDockerComposeConfig( config ) {
environment: {
...dbEnv.credentials,
...dbEnv.tests,
WP_PHPUNIT__DIR: '/wordpress-phpunit',
},
volumes: testsMounts,
},
Expand All @@ -229,6 +253,7 @@ module.exports = function buildDockerComposeConfig( config ) {
environment: {
...dbEnv.credentials,
...dbEnv.development,
WP_PHPUNIT__DIR: '/wordpress-phpunit',
},
},
'tests-cli': {
Expand All @@ -239,6 +264,7 @@ module.exports = function buildDockerComposeConfig( config ) {
environment: {
...dbEnv.credentials,
...dbEnv.tests,
WP_PHPUNIT__DIR: '/wordpress-phpunit',
},
},
composer: {
Expand All @@ -258,6 +284,7 @@ module.exports = function buildDockerComposeConfig( config ) {
LOCAL_DIR: 'html',
WP_PHPUNIT__TESTS_CONFIG:
'/var/www/html/phpunit-wp-config.php',
WP_PHPUNIT__DIR: '/wordpress-phpunit',
...dbEnv.credentials,
...dbEnv.tests,
},
Expand Down
12 changes: 6 additions & 6 deletions packages/env/lib/commands/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const { spawn } = require( 'child_process' );
const initConfig = require( '../init-config' );

/**
* @typedef {import('../config').Config} Config
* @typedef {import('../config').WPConfig} WPConfig
*/

/**
Expand Down Expand Up @@ -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 = [
Expand Down
22 changes: 21 additions & 1 deletion packages/env/lib/commands/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to run this for every wp-env install? I wonder if we should instead have a top-level config option for including phpunit. That said, if this isn't causing any meaningful performance impact, we can probably leave it as is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered this initially but instead decided to heavily optimize the develop checkout. It only downloads the phpunit files. I've also taken care to parallelize the download, so as far as performance goes, I've found this to be incredibly fast. It only downloads something like 21MB. For what it's worth, I'm pretty sure this is lighter than the phpunit container we're currently spinning up.

For me, this was a tradeoff between standardization and performance. Everyone who is using wp-env should be writing unit tests. All of these tests will likely require the PHPUnit files, so, I didn't see any good reasons not to include them by default.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good reason. The easier we can make unit tests to use, the better.

config,
{ development: wpVersions[ 0 ], tests: wpVersions[ 1 ] },
spinner,
debug
);
}

spinner.text = 'Starting WordPress.';
Expand Down
6 changes: 3 additions & 3 deletions packages/env/lib/download-sources.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@ 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
*/

/**
* 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 ) {
Expand Down
140 changes: 140 additions & 0 deletions packages/env/lib/download-wp-phpunit.js
Original file line number Diff line number Diff line change
@@ -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 );
}
Loading