99use Behat \Testwork \Hook \Scope \BeforeSuiteScope ;
1010use Behat \Behat \Hook \Scope \AfterFeatureScope ;
1111use Behat \Behat \Hook \Scope \BeforeFeatureScope ;
12+ use Behat \Behat \Hook \Scope \BeforeStepScope ;
13+ use SebastianBergmann \CodeCoverage \Report \Clover ;
14+ use SebastianBergmann \CodeCoverage \Driver \Selector ;
15+ use SebastianBergmann \CodeCoverage \Filter ;
16+ use SebastianBergmann \CodeCoverage \CodeCoverage ;
17+ use SebastianBergmann \Environment \Runtime ;
1218use RuntimeException ;
19+ use DirectoryIterator ;
1320use WP_CLI \Process ;
1421use WP_CLI \Utils ;
1522use WP_CLI \WpOrgApi ;
@@ -131,6 +138,13 @@ class FeatureContext implements SnippetAcceptingContext {
131138 */
132139 private $ scenario ;
133140
141+ /**
142+ * Line of the current step.
143+ *
144+ * @var int
145+ */
146+ private $ step_line = 0 ;
147+
134148 /**
135149 * @BeforeFeature
136150 */
@@ -145,11 +159,19 @@ public function store_scenario( BeforeScenarioScope $scope ) {
145159 $ this ->scenario = $ scope ->getScenario ();
146160 }
147161
162+ /**
163+ * @BeforeStep
164+ */
165+ public function store_step ( BeforeStepScope $ scope ) {
166+ $ this ->step_line = $ scope ->getStep ()->getLine ();
167+ }
168+
148169 /**
149170 * @AfterScenario
150171 */
151172 public function forget_scenario ( AfterScenarioScope $ scope ) {
152- $ this ->scenario = null ;
173+ $ this ->step_line = 0 ;
174+ $ this ->scenario = null ;
153175 }
154176
155177 /**
@@ -159,6 +181,34 @@ public static function forget_feature( AfterFeatureScope $scope ) {
159181 self ::$ feature = null ;
160182 }
161183
184+ /**
185+ * @AfterSuite
186+ */
187+ public static function merge_coverage_reports () {
188+ $ with_code_coverage = (string ) getenv ( 'WP_CLI_TEST_COVERAGE ' );
189+
190+ if ( ! \in_array ( $ with_code_coverage , [ 'true ' , '1 ' ], true ) ) {
191+ return ;
192+ }
193+
194+ $ filter = new Filter ();
195+ $ coverage = new CodeCoverage (
196+ ( new Selector () )->forLineCoverage ( $ filter ),
197+ $ filter
198+ );
199+
200+ foreach ( new DirectoryIterator ( self ::$ behat_run_dir . '/build/logs ' ) as $ file ) {
201+ if ( ! $ file ->isFile () || 'cov ' !== $ file ->getExtension () ) {
202+ continue ;
203+ }
204+
205+ $ coverage ->merge ( include $ file ->getPathname () );
206+ unlink ( $ file ->getPathname () );
207+ }
208+
209+ ( new Clover () )->process ( $ coverage , self ::$ behat_run_dir . '/build/logs/behat-coverage.xml ' );
210+ }
211+
162212 /**
163213 * Get the path to the Composer vendor folder.
164214 *
@@ -298,7 +348,14 @@ private static function get_process_env_variables() {
298348 ];
299349
300350 $ with_code_coverage = (string ) getenv ( 'WP_CLI_TEST_COVERAGE ' );
351+
301352 if ( \in_array ( $ with_code_coverage , [ 'true ' , '1 ' ], true ) ) {
353+ $ has_coverage_driver = ( new Runtime () )->hasXdebug () || ( new Runtime () )->hasPCOV ();
354+
355+ if ( ! $ has_coverage_driver ) {
356+ throw new RuntimeException ( 'No coverage driver available. Re-run script with `--xdebug` flag, i.e. `composer behat -- --xdebug`. ' );
357+ }
358+
302359 $ coverage_require_file = self ::$ behat_run_dir . '/vendor/wp-cli/wp-cli-tests/utils/generate-coverage.php ' ;
303360 if ( ! file_exists ( $ coverage_require_file ) ) {
304361 // This file is not vendored inside the wp-cli-tests project
@@ -871,8 +928,8 @@ public function download_phar( $version = 'same' ) {
871928 );
872929
873930 $ this ->variables ['PHAR_PATH ' ] = $ this ->variables ['RUN_DIR ' ] . '/ '
874- . uniqid ( 'wp-cli-download- ' , true )
875- . '.phar ' ;
931+ . uniqid ( 'wp-cli-download- ' , true )
932+ . '.phar ' ;
876933
877934 Process::create (
878935 Utils \esc_cmd (
@@ -957,6 +1014,8 @@ public function proc( $command, $assoc_args = [], $path = '' ) {
9571014 $ env ['BEHAT_SCENARIO_TITLE ' ] = $ this ->scenario ->getTitle ();
9581015 }
9591016
1017+ $ env ['BEHAT_STEP_LINE ' ] = $ this ->step_line ;
1018+
9601019 $ env ['WP_CLI_TEST_DBTYPE ' ] = self ::$ db_type ;
9611020
9621021 if ( isset ( $ this ->variables ['RUN_DIR ' ] ) ) {
0 commit comments