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

Behat code coverage support #234

Merged
merged 20 commits into from
Mar 6, 2025
Merged
Show file tree
Hide file tree
Changes from 11 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
9 changes: 9 additions & 0 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,13 @@
<rule ref="WordPress.NamingConventions.PrefixAllGlobals">
<exclude-pattern>*/utils/polyfills\.php$</exclude-pattern>
</rule>

<!-- This is a procedural stand-alone file that is never loaded in a WordPress context,
so this file does not have to comply with WP naming conventions. -->
<rule ref="WordPress.NamingConventions.PrefixAllGlobals">
<exclude-pattern>*/generate-coverage\.php$</exclude-pattern>
</rule>
<rule ref="WordPress.WP.GlobalVariablesOverride">
<exclude-pattern>*/generate-coverage\.php$</exclude-pattern>
</rule>
</ruleset>
75 changes: 67 additions & 8 deletions src/Context/FeatureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Behat\Testwork\Hook\Scope\AfterSuiteScope;
use Behat\Testwork\Hook\Scope\BeforeSuiteScope;
use Behat\Behat\Hook\Scope\AfterFeatureScope;
use Behat\Behat\Hook\Scope\BeforeFeatureScope;
use RuntimeException;
use WP_CLI\Process;
use WP_CLI\Utils;
Expand Down Expand Up @@ -110,6 +112,48 @@ class FeatureContext implements SnippetAcceptingContext {

private $mocked_requests = [];

/**
* The current feature.
*
* @var \Behat\Gherkin\Node\FeatureNode|null
*/
private static $feature;

/**
* The current scenario.
*
* @var \Behat\Gherkin\Node\ScenarioInterface|null
*/
private $scenario;

/**
* @BeforeFeature
*/
public static function store_feature( BeforeFeatureScope $scope ) {
self::$feature = $scope->getFeature();
}

/**
* @BeforeScenario
*/
public function store_scenario( BeforeScenarioScope $scope ) {
$this->scenario = $scope->getScenario();
}

/**
* @AfterScenario
*/
public function forget_scenario( AfterScenarioScope $scope ) {
$this->scenario = null;
}

/**
* @AfterFeature
*/
public static function forget_feature( AfterFeatureScope $scope ) {
self::$feature = null;
}

/**
* Get the path to the Composer vendor folder.
*
Expand Down Expand Up @@ -333,9 +377,9 @@ private static function get_behat_internal_variables() {
}

/**
* Download and extract a single copy of the sqlite-database-integration plugin
* for use in subsequent WordPress copies
*/
* Download and extract a single copy of the sqlite-database-integration plugin
* for use in subsequent WordPress copies
*/
private static function download_sqlite_plugin( $dir ) {
$download_url = 'https://downloads.wordpress.org/plugin/sqlite-database-integration.zip';
$download_location = $dir . '/sqlite-database-integration.zip';
Expand Down Expand Up @@ -370,9 +414,9 @@ private static function download_sqlite_plugin( $dir ) {
}

/**
* Given a WordPress installation with the sqlite-database-integration plugin,
* configure it to use SQLite as the database by placing the db.php dropin file
*/
* Given a WordPress installation with the sqlite-database-integration plugin,
* configure it to use SQLite as the database by placing the db.php dropin file
*/
private static function configure_sqlite( $dir ) {
$db_copy = $dir . '/wp-content/mu-plugins/sqlite-database-integration/db.copy';
$db_dropin = $dir . '/wp-content/db.php';
Expand Down Expand Up @@ -877,10 +921,25 @@ public function proc( $command, $assoc_args = [], $path = '' ) {
}

$env = self::get_process_env_variables();

if ( isset( $this->variables['SUITE_CACHE_DIR'] ) ) {
$env['WP_CLI_CACHE_DIR'] = $this->variables['SUITE_CACHE_DIR'];
}

if ( isset( $this->variables['PROJECT_DIR'] ) ) {
$env['BEHAT_PROJECT_DIR'] = $this->variables['PROJECT_DIR'];
}

if ( self::$feature ) {
$env['BEHAT_FEATURE_TITLE'] = self::$feature->getTitle();
}

if ( $this->scenario ) {
$env['BEHAT_SCENARIO_TITLE'] = $this->scenario->getTitle();
}

$env['WP_CLI_TEST_DBTYPE'] = self::$db_type;

if ( isset( $this->variables['RUN_DIR'] ) ) {
$cwd = "{$this->variables['RUN_DIR']}/{$path}";
} else {
Expand Down Expand Up @@ -1242,8 +1301,8 @@ private static function dir_diff_copy( $upd_dir, $src_dir, $cop_dir ) {
}
self::copy_dir( $upd_file, $cop_file );
} elseif ( ! copy( $upd_file, $cop_file ) ) {
$error = error_get_last();
throw new RuntimeException( sprintf( "Failed to copy '%s' to '%s': %s. " . __FILE__ . ':' . __LINE__, $upd_file, $cop_file, $error['message'] ) );
$error = error_get_last();
throw new RuntimeException( sprintf( "Failed to copy '%s' to '%s': %s. " . __FILE__ . ':' . __LINE__, $upd_file, $cop_file, $error['message'] ) );
}
} elseif ( is_dir( $upd_file ) ) {
self::dir_diff_copy( $upd_file, $src_file, $cop_file );
Expand Down
5 changes: 5 additions & 0 deletions src/Context/WhenStepDefinitions.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ public function when_i_launch_in_the_background( $cmd ) {
* @When /^I (run|try) `([^`]+)`$/
*/
public function when_i_run( $mode, $cmd ) {
Copy link
Member

Choose a reason for hiding this comment

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

This is not the only step that triggers a WP-CLI command. For proper coverage, we'll need to cover all instances that trigger WP-CLI commands, like (for example) the below when_i_run_from_a_subfolder() and when_i_run_the_previous_command_again(). There might be others as well...

Copy link
Member Author

Choose a reason for hiding this comment

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

It's only when_i_run_from_a_subfolder AFAICT, because when_i_run_the_previous_command_again just re-runs the previous command without any substitutions.

I don't think there are any others.

when_i_launch_in_the_background is used for launching wp server in the background, but it doesn't make sense to invoke coverage collection there. That means no code coverage for server-command but I think we can live with that .

$with_code_coverage = (string) getenv( 'WP_CLI_TEST_COVERAGE' );
if ( \in_array( $with_code_coverage, [ 'true', '1' ], true ) ) {
$cmd = preg_replace( '/(^wp )|( wp )|(\/wp )/', '$1$2$3--require={SRC_DIR}/utils/generate-coverage.php ', $cmd );
}

$cmd = $this->replace_variables( $cmd );
$this->result = $this->wpcli_tests_invoke_proc( $this->proc( $cmd ), $mode );
list( $this->result->stdout, $this->email_sends ) = $this->wpcli_tests_capture_email_sends( $this->result->stdout );
Expand Down
47 changes: 47 additions & 0 deletions utils/generate-coverage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Driver\Selector;
use SebastianBergmann\CodeCoverage\Filter;
use SebastianBergmann\CodeCoverage\Report\Clover;

$root_folder = realpath( dirname( __DIR__ ) );

if ( ! class_exists( 'SebastianBergmann\CodeCoverage\Filter' ) ) {
require "{$root_folder}/vendor/autoload.php";
}

$filter = new Filter();
$filter->includeDirectory( "{$root_folder}/includes" );
$filter->includeFiles( array( "{$root_folder}/plugin.php" ) );

$coverage = new CodeCoverage(
( new Selector() )->forLineCoverage( $filter ),
$filter
);

$feature = getenv( 'BEHAT_FEATURE_TITLE' );
$scenario = getenv( 'BEHAT_SCENARIO_TITLE' );
$name = "{$feature} - {$scenario}";

$coverage->start( $name );

register_shutdown_function(
static function () use ( $coverage, $feature, $scenario, $name ) {
$coverage->stop();

$project_dir = (string) getenv( 'BEHAT_PROJECT_DIR' );

$feature_suffix = preg_replace( '/[^a-z0-9]+/', '-', strtolower( $feature ) );
$scenario_suffix = preg_replace( '/[^a-z0-9]+/', '-', strtolower( $scenario ) );
$db_type = strtolower( getenv( 'WP_CLI_TEST_DBTYPE' ) );
$destination = "$project_dir/build/logs/$feature_suffix-$scenario_suffix-$db_type.xml";

$dir = dirname( $destination );
if ( ! file_exists( $dir ) ) {
mkdir( $dir, 0777, true /*recursive*/ );
}

( new Clover() )->process( $coverage, $destination, $name );
}
);