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

Add a debug helper coverage logger module. #42620

Draft
wants to merge 5 commits into
base: trunk
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions projects/plugins/debug-helper/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/vendor/
/scripts/wp-config.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

Added a new module for coverage data logging.
132 changes: 132 additions & 0 deletions projects/plugins/debug-helper/modules/class-coverage-logger.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?php
/**
* The Coverage Logger file contains the class `Coverage_Logger` that logs coverage data.
*
* @package automattic/jetpack-debug-helper
*/

namespace Automattic\Jetpack\Debug_Helper;

/**
* Class Coverage_Logger
*
* Handles logging of coverage data that Jetpack plugins generate.
*
* @package Automattic\Jetpack\Debug_Helper
*/
class Coverage_Logger {

const RUNTIME_TABLE_NAME = 'jetpack_runtime_coverage_data';

/**
* XMLRPC_Logger constructor.
* Hooks the XML-RPC logging function into WordPress's init action.
*/
public function __construct() {
if ( function_exists( 'xdebug_start_code_coverage' ) ) {
$this->maybe_upsert_database();

xdebug_start_code_coverage();
add_action( 'shutdown', array( $this, 'log_coverage_results' ), 100000 );
}
}

/**
* Saves coverage results into a file.
*/
public function log_coverage_results() {
global $wpdb;

$coverage_data = xdebug_get_code_coverage();

$sql = sprintf( 'INSERT IGNORE INTO `%s` (path, line) VALUES ', $wpdb->prefix . self::RUNTIME_TABLE_NAME );

foreach ( $coverage_data as $file => $lines ) {
$path = substr( $file, strlen( ABSPATH ) );

if ( ! str_starts_with( $path, 'wp-content/plugins/jetpack' ) ) {
continue;
}

if ( false !== strpos( $path, '/vendor/' ) ) {
continue;
}

$path = substr( $path, strlen( 'wp-content/plugins/' ) );

$vendor_pos = strpos( $path, 'jetpack_vendor' );
if ( false !== $vendor_pos ) {
$path = substr( $path, $vendor_pos + strlen( 'jetpack_vendor/automattic/' ) );
}

foreach ( $lines as $line => $count ) {
for ( $i = 0; $i < $count; $i++ ) {
$sql .= $wpdb->prepare( '( %s, %d ),', $path, $line );
}
}
}

$wpdb->query( rtrim( $sql, ',' ) ); // phpcs:ignore WordPress.DB -- We are preparing the query before this.
}

/**
* Uses the dbDelta function to either update, create, or leave the existing
* database in peace.
*/
protected function maybe_upsert_database() {
global $wpdb;

$charset_collate = $wpdb->get_charset_collate();
$table_name = $wpdb->prefix . self::RUNTIME_TABLE_NAME;

$sql = array(
"CREATE TABLE $table_name (
id mediumint(9) NOT NULL AUTO_INCREMENT,
path varchar(255),
line int,
PRIMARY KEY (id),
UNIQUE KEY `unique_path` (path, line)
) $charset_collate;",
);

require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $sql );
}

/**
* Returns an SQL string for getting the coverage diff results.
*/
protected function get_coverage_diff() {
return '
SELECT
runtime.path AS file_path,
runtime.lines_covered AS runtime_coverage,
test.line AS test_coverage,
runtime.lines_covered - test.line AS coverage_difference
FROM
(
SELECT
path,
COUNT(DISTINCT line) AS lines_covered
FROM
wp_jetpack_runtime_coverage_data
GROUP BY
path
) AS runtime
INNER JOIN
(
SELECT
path,
line
FROM
wp_jetpack_test_coverage_data
) AS test
ON runtime.path = test.path
ORDER BY coverage_difference
';
}
}

if ( isset( $_COOKIE['jetpack_enable_coverage_logging'] ) ) {
new Coverage_Logger();

Check failure on line 131 in projects/plugins/debug-helper/modules/class-coverage-logger.php

View workflow job for this annotation

GitHub Actions / Static analysis

NOOPError PhanNoopNew Unused result of new object creation expression in new Coverage_Logger() (this may be called for the side effects of the non-empty constructor or destructor) FAQ on Phan issues: pdWQjU-Jb-p2
}
5 changes: 5 additions & 0 deletions projects/plugins/debug-helper/plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@
'name' => 'XMLRPC Logger',
'description' => 'Logs incoming XMLRPC requests into the debug.log file.',
),
'coverage-logger' => array(
'file' => 'class-coverage-logger.php',
'name' => 'Coverage Logger',
'description' => 'Logs coverage data.',
),
'xmlrpc-blocker' => array(
'file' => 'class-xmlrpc-blocker.php',
'name' => 'Broken XML-RPC',
Expand Down
86 changes: 86 additions & 0 deletions projects/plugins/debug-helper/scripts/load-coverage-data.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php // phpcs:ignore Squiz.Commenting
// phpcs:disable WordPress.DB

if ( ! file_exists( __DIR__ . '/wp-config.php' ) ) {
echo 'This script needs a WordPress config file for database configuration to work.' . PHP_EOL;
echo 'You can copy your WordPress configuration part where it defines database connection and table prefix.' . PHP_EOL;
echo 'Put them in a wp-config.php file in this folder, just don\'t forget to remove it when you\'re done.' . PHP_EOL;
exit( 1 );
}

require_once __DIR__ . '/wp-config.php';

$data = get_coverage_data();
$table = $table_prefix . 'jetpack_test_coverage_data'; // phpcs:ignore VariableAnalysis -- required from wp-config.php

Check failure on line 14 in projects/plugins/debug-helper/scripts/load-coverage-data.php

View workflow job for this annotation

GitHub Actions / Static analysis

UndefError PhanUndeclaredGlobalVariable Global variable $table_prefix is undeclared FAQ on Phan issues: pdWQjU-Jb-p2

mysqli_report( MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT );
$dbh = new mysqli( DB_HOST, DB_USER, DB_PASSWORD, DB_NAME );

Check failure on line 17 in projects/plugins/debug-helper/scripts/load-coverage-data.php

View workflow job for this annotation

GitHub Actions / Static analysis

UndefError PhanUndeclaredConstant Reference to undeclared constant \DB_HOST. This will cause a thrown Error in php 8.0+. FAQ on Phan issues: pdWQjU-Jb-p2

Check failure on line 17 in projects/plugins/debug-helper/scripts/load-coverage-data.php

View workflow job for this annotation

GitHub Actions / Static analysis

UndefError PhanUndeclaredConstant Reference to undeclared constant \DB_NAME. This will cause a thrown Error in php 8.0+. FAQ on Phan issues: pdWQjU-Jb-p2

Check failure on line 17 in projects/plugins/debug-helper/scripts/load-coverage-data.php

View workflow job for this annotation

GitHub Actions / Static analysis

UndefError PhanUndeclaredConstant Reference to undeclared constant \DB_PASSWORD. This will cause a thrown Error in php 8.0+. FAQ on Phan issues: pdWQjU-Jb-p2

Check failure on line 17 in projects/plugins/debug-helper/scripts/load-coverage-data.php

View workflow job for this annotation

GitHub Actions / Static analysis

UndefError PhanUndeclaredConstant Reference to undeclared constant \DB_USER. This will cause a thrown Error in php 8.0+. Suggestion: Did you mean \SI_USER FAQ on Phan issues: pdWQjU-Jb-p2

$create_table = sprintf(
'CREATE TABLE IF NOT EXISTS `%s` (
id int NOT NULL AUTO_INCREMENT,
path varchar(255),
line int,
PRIMARY KEY (id),
UNIQUE KEY `unique_path` (path, line)
)',
$table
);

$dbh->query( $create_table );

$sql = sprintf( 'INSERT IGNORE INTO `%s` (path, line) VALUES ', $table );

foreach ( $data as $file ) {
$sql .= sprintf( "( '%s', %d ),", $file['file'], $file['lines'] );
}

$dbh->query( rtrim( $sql, ',' ) ); // phpcs:ignore WordPress.DB -- We are preparing the query before this.

$dbh->close();

/**
* Returns an unserialized coverage object.
*
* @return Array coverage data
*/
function get_coverage_data() {

$coverage = array();

try {
$file = new SplFileObject( 'summary.tsv' );
} catch ( LogicException $exception ) {
die( 'SplFileObject : ' . $exception->getMessage() ); // phpcs:ignore WordPress.Security
}

while ( $file->valid() ) {
$line = $file->fgets();

list( $filename, $executable, $executed ) = explode( "\t", $line ); // phpcs:ignore VariableAnalysis

if ( str_starts_with( $filename, 'projects/js-packages' ) ) {
continue;
}

if (
str_starts_with( $filename, 'projects/packages' )
) {
$filename = 'jetpack-' . substr( $filename, strlen( 'projects/packages/' ) );
}

if ( str_starts_with( $filename, 'projects/plugins/jetpack/' ) ) {
$filename = substr( $filename, strlen( 'projects/plugins/' ) );
} elseif ( str_starts_with( $filename, 'projects/plugins' ) ) {
$filename = 'jetpack-' . substr( $filename, strlen( 'projects/plugins/' ) );
}

$coverage[] = array(
'file' => $filename,
'lines' => $executed,
);
}

$file = null;
return $coverage;
}
Loading