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,30 @@ 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+ //
189+
190+ $ filter = new Filter ();
191+ $ coverage = new CodeCoverage (
192+ ( new Selector () )->forLineCoverage ( $ filter ),
193+ $ filter
194+ );
195+
196+ foreach ( new DirectoryIterator ( self ::$ behat_run_dir . '/build/logs ' ) as $ file ) {
197+ if ( ! $ file ->isFile () || 'cov ' !== $ file ->getExtension () ) {
198+ continue ;
199+ }
200+
201+ $ coverage ->merge ( include $ file ->getPathname () );
202+ unlink ( $ file ->getPathname () );
203+ }
204+
205+ ( new Clover () )->process ( $ coverage , self ::$ behat_run_dir . '/build/logs/behat-coverage.xml ' );
206+ }
207+
162208 /**
163209 * Get the path to the Composer vendor folder.
164210 *
@@ -298,7 +344,14 @@ private static function get_process_env_variables() {
298344 ];
299345
300346 $ with_code_coverage = (string ) getenv ( 'WP_CLI_TEST_COVERAGE ' );
347+
301348 if ( \in_array ( $ with_code_coverage , [ 'true ' , '1 ' ], true ) ) {
349+ $ has_coverage_driver = ( new Runtime () )->hasXdebug () || ( new Runtime () )->hasPCOV ();
350+
351+ if ( ! $ has_coverage_driver ) {
352+ throw new RuntimeException ( 'No coverage driver available. Re-run script with `--xdebug` flag, i.e. `composer behat -- --xdebug`. ' );
353+ }
354+
302355 $ coverage_require_file = self ::$ behat_run_dir . '/vendor/wp-cli/wp-cli-tests/utils/generate-coverage.php ' ;
303356 if ( ! file_exists ( $ coverage_require_file ) ) {
304357 // This file is not vendored inside the wp-cli-tests project
@@ -871,8 +924,8 @@ public function download_phar( $version = 'same' ) {
871924 );
872925
873926 $ this ->variables ['PHAR_PATH ' ] = $ this ->variables ['RUN_DIR ' ] . '/ '
874- . uniqid ( 'wp-cli-download- ' , true )
875- . '.phar ' ;
927+ . uniqid ( 'wp-cli-download- ' , true )
928+ . '.phar ' ;
876929
877930 Process::create (
878931 Utils \esc_cmd (
@@ -957,6 +1010,8 @@ public function proc( $command, $assoc_args = [], $path = '' ) {
9571010 $ env ['BEHAT_SCENARIO_TITLE ' ] = $ this ->scenario ->getTitle ();
9581011 }
9591012
1013+ $ env ['BEHAT_STEP_LINE ' ] = $ this ->step_line ;
1014+
9601015 $ env ['WP_CLI_TEST_DBTYPE ' ] = self ::$ db_type ;
9611016
9621017 if ( isset ( $ this ->variables ['RUN_DIR ' ] ) ) {
0 commit comments