d36bb954d1
Baseline before backporting wp-data-optimizer v3.0.1-v3.4.6. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
316 lines
12 KiB
PHP
316 lines
12 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
|
||
use PHPUnit\Framework\TestCase;
|
||
|
||
/**
|
||
* Integration test: WPDO_Term_Stress_Tester (v2.13.0).
|
||
*
|
||
* Verifies the term stress tester contract:
|
||
* - State machine: start / get_state / get_progress / cancel
|
||
* - count_test_terms() matches slug prefix
|
||
* - cleanup() removes test terms + cascade
|
||
* - Input validation throws / errors correctly
|
||
* - Run benchmark structure
|
||
*
|
||
* Note: Realistic mode tests (wp_insert_term path) are exercised live in dev10
|
||
* smoke tests since the bootstrap wp_insert_term stub is intentionally minimal.
|
||
*/
|
||
class TermStressTesterTest extends TestCase {
|
||
|
||
private const TERMS = 'wp_itest_terms';
|
||
private const TERM_TAXONOMY = 'wp_itest_term_taxonomy';
|
||
private const TERMMETA = 'wp_itest_termmeta';
|
||
|
||
public static function setUpBeforeClass(): void {
|
||
global $wpdb;
|
||
|
||
if ( ! class_exists( 'WPDO_Term_Stress_Tester' ) ) {
|
||
require_once WPDO_PLUGIN_DIR . 'includes/class-tmdo-term-stress-tester.php';
|
||
}
|
||
|
||
$wpdb->terms = self::TERMS;
|
||
$wpdb->termmeta = self::TERMMETA;
|
||
|
||
$wpdb->query( 'DROP TABLE IF EXISTS `' . self::TERMS . '`' );
|
||
$wpdb->query(
|
||
'CREATE TABLE `' . self::TERMS . '` (
|
||
term_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||
name varchar(200) NOT NULL DEFAULT "",
|
||
slug varchar(200) NOT NULL DEFAULT "",
|
||
term_group bigint(10) NOT NULL DEFAULT 0,
|
||
PRIMARY KEY (term_id),
|
||
KEY slug (slug(191))
|
||
) DEFAULT CHARACTER SET utf8mb4'
|
||
);
|
||
|
||
$wpdb->query( 'DROP TABLE IF EXISTS `' . self::TERM_TAXONOMY . '`' );
|
||
$wpdb->query(
|
||
'CREATE TABLE `' . self::TERM_TAXONOMY . '` (
|
||
term_taxonomy_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||
term_id bigint(20) unsigned NOT NULL DEFAULT 0,
|
||
taxonomy varchar(32) NOT NULL DEFAULT "",
|
||
description longtext,
|
||
parent bigint(20) unsigned NOT NULL DEFAULT 0,
|
||
count bigint(20) NOT NULL DEFAULT 0,
|
||
PRIMARY KEY (term_taxonomy_id),
|
||
KEY taxonomy (taxonomy)
|
||
) DEFAULT CHARACTER SET utf8mb4'
|
||
);
|
||
|
||
$wpdb->query( 'CREATE TABLE IF NOT EXISTS `' . self::TERMMETA . '` (
|
||
meta_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||
term_id bigint(20) unsigned NOT NULL DEFAULT 0,
|
||
meta_key varchar(255) DEFAULT NULL,
|
||
meta_value longtext,
|
||
PRIMARY KEY (meta_id),
|
||
KEY term_id (term_id),
|
||
KEY meta_key (meta_key(191))
|
||
) DEFAULT CHARACTER SET utf8mb4' );
|
||
}
|
||
|
||
public static function tearDownAfterClass(): void {
|
||
global $wpdb;
|
||
foreach ( array( self::TERMS, self::TERM_TAXONOMY, self::TERMMETA ) as $tbl ) {
|
||
$wpdb->query( 'DROP TABLE IF EXISTS `' . $tbl . '`' );
|
||
}
|
||
}
|
||
|
||
protected function setUp(): void {
|
||
global $wpdb;
|
||
$wpdb->query( 'TRUNCATE TABLE `' . self::TERMS . '`' );
|
||
$wpdb->query( 'TRUNCATE TABLE `' . self::TERM_TAXONOMY . '`' );
|
||
$wpdb->query( 'TRUNCATE TABLE `' . self::TERMMETA . '`' );
|
||
// Reset state per test so each starts idle.
|
||
unset( $GLOBALS['_wp_options'][ WPDO_Term_Stress_Tester::OPT_STATE ] );
|
||
unset( $GLOBALS['_wp_transients'][ WPDO_Term_Stress_Tester::CANCEL_FLAG ] );
|
||
unset( $GLOBALS['_wp_transients']['wpdo_term_stress_pump_lock'] );
|
||
}
|
||
|
||
// ── create() (fast-path direct SQL) ──────────────────────────────────────
|
||
|
||
public function test_create_inserts_terms_into_taxonomy(): void {
|
||
$result = WPDO_Term_Stress_Tester::create( 'category', 5 );
|
||
|
||
$this->assertSame( 5, $result['created'] );
|
||
$this->assertSame( 'category', $result['taxonomy'] );
|
||
$this->assertNotNull( $result['first_id'] );
|
||
$this->assertNotNull( $result['last_id'] );
|
||
|
||
global $wpdb;
|
||
$count = (int) $wpdb->get_var( 'SELECT COUNT(*) FROM `' . self::TERMS . '`' );
|
||
$this->assertSame( 5, $count );
|
||
$tax_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `" . self::TERM_TAXONOMY . "` WHERE taxonomy = 'category'" );
|
||
$this->assertSame( 5, $tax_count );
|
||
}
|
||
|
||
public function test_create_uses_stress_slug_prefix(): void {
|
||
WPDO_Term_Stress_Tester::create( 'category', 3 );
|
||
|
||
global $wpdb;
|
||
$prefix_count = (int) $wpdb->get_var(
|
||
$wpdb->prepare(
|
||
"SELECT COUNT(*) FROM `" . self::TERMS . "` WHERE slug LIKE %s",
|
||
WPDO_Term_Stress_Tester::TEST_TERM_PREFIX . '%'
|
||
)
|
||
);
|
||
$this->assertSame( 3, $prefix_count );
|
||
}
|
||
|
||
public function test_create_seeds_termmeta_keys(): void {
|
||
WPDO_Term_Stress_Tester::create( 'category', 3 );
|
||
|
||
global $wpdb;
|
||
$total_meta = (int) $wpdb->get_var( 'SELECT COUNT(*) FROM `' . self::TERMMETA . '`' );
|
||
// 3 terms × 3 keys (hp_sort_order/hp_default/hp_icon) = 9
|
||
$this->assertSame( 9, $total_meta );
|
||
}
|
||
|
||
public function test_create_rejects_empty_taxonomy(): void {
|
||
$this->expectException( InvalidArgumentException::class );
|
||
WPDO_Term_Stress_Tester::create( '', 3 );
|
||
}
|
||
|
||
public function test_create_rejects_zero_count(): void {
|
||
$this->expectException( InvalidArgumentException::class );
|
||
WPDO_Term_Stress_Tester::create( 'category', 0 );
|
||
}
|
||
|
||
public function test_create_rejects_excessive_count(): void {
|
||
$this->expectException( InvalidArgumentException::class );
|
||
WPDO_Term_Stress_Tester::create( 'category', 100001 );
|
||
}
|
||
|
||
// ── count_test_terms() ────────────────────────────────────────────────────
|
||
|
||
public function test_count_test_terms_returns_zero_for_empty(): void {
|
||
$this->assertSame( 0, WPDO_Term_Stress_Tester::count_test_terms() );
|
||
}
|
||
|
||
public function test_count_test_terms_counts_only_stress_prefix(): void {
|
||
WPDO_Term_Stress_Tester::create( 'category', 4 );
|
||
|
||
global $wpdb;
|
||
$wpdb->insert( self::TERMS, array( 'name' => 'Real', 'slug' => 'real-term', 'term_group' => 0 ) );
|
||
|
||
$this->assertSame( 4, WPDO_Term_Stress_Tester::count_test_terms() );
|
||
}
|
||
|
||
// ── cleanup() ─────────────────────────────────────────────────────────────
|
||
|
||
public function test_cleanup_removes_test_terms_and_cascade(): void {
|
||
WPDO_Term_Stress_Tester::create( 'category', 5 );
|
||
$this->assertSame( 5, WPDO_Term_Stress_Tester::count_test_terms() );
|
||
|
||
$result = WPDO_Term_Stress_Tester::cleanup();
|
||
$this->assertSame( 5, $result['deleted_terms'] );
|
||
|
||
global $wpdb;
|
||
$this->assertSame( 0, (int) $wpdb->get_var( 'SELECT COUNT(*) FROM `' . self::TERMS . '`' ) );
|
||
$this->assertSame( 0, (int) $wpdb->get_var( 'SELECT COUNT(*) FROM `' . self::TERMMETA . '`' ) );
|
||
$this->assertSame( 0, (int) $wpdb->get_var( 'SELECT COUNT(*) FROM `' . self::TERM_TAXONOMY . '`' ) );
|
||
}
|
||
|
||
public function test_cleanup_preserves_non_stress_terms(): void {
|
||
global $wpdb;
|
||
$wpdb->insert( self::TERMS, array( 'name' => 'Real', 'slug' => 'real-term', 'term_group' => 0 ) );
|
||
WPDO_Term_Stress_Tester::create( 'category', 3 );
|
||
|
||
$result = WPDO_Term_Stress_Tester::cleanup();
|
||
$this->assertSame( 3, $result['deleted_terms'] );
|
||
|
||
$remaining = (int) $wpdb->get_var( 'SELECT COUNT(*) FROM `' . self::TERMS . '`' );
|
||
$this->assertSame( 1, $remaining );
|
||
}
|
||
|
||
public function test_cleanup_idempotent_on_empty(): void {
|
||
$first = WPDO_Term_Stress_Tester::cleanup();
|
||
$second = WPDO_Term_Stress_Tester::cleanup();
|
||
$this->assertSame( 0, $first['deleted_terms'] );
|
||
$this->assertSame( 0, $second['deleted_terms'] );
|
||
}
|
||
|
||
// ── State machine ────────────────────────────────────────────────────────
|
||
|
||
public function test_get_state_returns_empty_when_idle(): void {
|
||
$this->assertSame( array(), WPDO_Term_Stress_Tester::get_state() );
|
||
}
|
||
|
||
public function test_get_progress_returns_idle_when_no_state(): void {
|
||
$progress = WPDO_Term_Stress_Tester::get_progress( false );
|
||
$this->assertSame( 'idle', $progress['status'] );
|
||
}
|
||
|
||
public function test_start_persists_state_with_running_status(): void {
|
||
// Stub taxonomy_exists() — fall back to true via global flag.
|
||
$GLOBALS['_taxonomy_exists_override'] = true;
|
||
|
||
$result = WPDO_Term_Stress_Tester::start( 'category', 10, 'fast', 5 );
|
||
|
||
$this->assertTrue( $result['ok'], 'start should succeed' );
|
||
$state = $result['state'];
|
||
$this->assertSame( 'running', $state['status'] );
|
||
$this->assertSame( 'category', $state['taxonomy'] );
|
||
$this->assertSame( 'fast', $state['mode'] );
|
||
$this->assertSame( 10, $state['target'] );
|
||
$this->assertSame( 5, $state['batch_size'] );
|
||
|
||
unset( $GLOBALS['_taxonomy_exists_override'] );
|
||
}
|
||
|
||
public function test_start_rejects_unknown_taxonomy(): void {
|
||
$GLOBALS['_taxonomy_exists_override'] = false;
|
||
|
||
$result = WPDO_Term_Stress_Tester::start( 'never_exists', 10 );
|
||
$this->assertFalse( $result['ok'] );
|
||
$this->assertStringContainsString( 'unknown_taxonomy', $result['error'] );
|
||
|
||
unset( $GLOBALS['_taxonomy_exists_override'] );
|
||
}
|
||
|
||
public function test_start_rejects_invalid_mode(): void {
|
||
$GLOBALS['_taxonomy_exists_override'] = true;
|
||
$result = WPDO_Term_Stress_Tester::start( 'category', 10, 'turbo' );
|
||
$this->assertFalse( $result['ok'] );
|
||
$this->assertSame( 'invalid mode', $result['error'] );
|
||
unset( $GLOBALS['_taxonomy_exists_override'] );
|
||
}
|
||
|
||
public function test_start_rejects_concurrent_run(): void {
|
||
$GLOBALS['_taxonomy_exists_override'] = true;
|
||
WPDO_Term_Stress_Tester::start( 'category', 10 );
|
||
$result = WPDO_Term_Stress_Tester::start( 'category', 5 );
|
||
|
||
$this->assertFalse( $result['ok'] );
|
||
$this->assertSame( 'already_running', $result['error'] );
|
||
unset( $GLOBALS['_taxonomy_exists_override'] );
|
||
}
|
||
|
||
public function test_run_batch_advances_processed_count(): void {
|
||
$GLOBALS['_taxonomy_exists_override'] = true;
|
||
WPDO_Term_Stress_Tester::start( 'category', 6, 'fast', 3 );
|
||
|
||
WPDO_Term_Stress_Tester::run_batch();
|
||
$progress = WPDO_Term_Stress_Tester::get_progress( false );
|
||
$this->assertSame( 3, $progress['processed'] );
|
||
$this->assertSame( 1, $progress['batches_done'] );
|
||
$this->assertSame( 'running', $progress['status'] );
|
||
|
||
WPDO_Term_Stress_Tester::run_batch();
|
||
$progress = WPDO_Term_Stress_Tester::get_progress( false );
|
||
$this->assertSame( 6, $progress['processed'] );
|
||
$this->assertSame( 'completed', $progress['status'] );
|
||
unset( $GLOBALS['_taxonomy_exists_override'] );
|
||
}
|
||
|
||
public function test_cancel_marks_state_as_cancelled(): void {
|
||
$GLOBALS['_taxonomy_exists_override'] = true;
|
||
WPDO_Term_Stress_Tester::start( 'category', 100, 'fast', 50 );
|
||
|
||
$result = WPDO_Term_Stress_Tester::cancel();
|
||
$this->assertTrue( $result['ok'] );
|
||
$this->assertSame( 'cancelled', $result['state']['status'] );
|
||
|
||
// In-flight batch run after cancel must NOT bump status back to running.
|
||
WPDO_Term_Stress_Tester::run_batch();
|
||
$state = WPDO_Term_Stress_Tester::get_state();
|
||
$this->assertSame( 'cancelled', $state['status'] );
|
||
unset( $GLOBALS['_taxonomy_exists_override'] );
|
||
}
|
||
|
||
public function test_cancel_returns_no_active_job_when_idle(): void {
|
||
$result = WPDO_Term_Stress_Tester::cancel();
|
||
$this->assertTrue( $result['ok'] );
|
||
$this->assertSame( 'no_active_job', $result['message'] ?? '' );
|
||
}
|
||
|
||
public function test_get_progress_includes_pct_and_eta_keys(): void {
|
||
$GLOBALS['_taxonomy_exists_override'] = true;
|
||
WPDO_Term_Stress_Tester::start( 'category', 10, 'fast', 5 );
|
||
WPDO_Term_Stress_Tester::run_batch();
|
||
|
||
$progress = WPDO_Term_Stress_Tester::get_progress( false );
|
||
$this->assertArrayHasKey( 'pct', $progress );
|
||
$this->assertArrayHasKey( 'rate_per_sec', $progress );
|
||
$this->assertArrayHasKey( 'elapsed_sec', $progress );
|
||
$this->assertArrayHasKey( 'eta_sec', $progress );
|
||
$this->assertArrayHasKey( 'test_term_count', $progress );
|
||
$this->assertSame( 50.0, $progress['pct'] );
|
||
unset( $GLOBALS['_taxonomy_exists_override'] );
|
||
}
|
||
|
||
public function test_run_benchmark_returns_structured_payload(): void {
|
||
$GLOBALS['_taxonomy_exists_override'] = true;
|
||
WPDO_Term_Stress_Tester::start( 'category', 4, 'fast', 4 );
|
||
WPDO_Term_Stress_Tester::run_batch();
|
||
|
||
$state = WPDO_Term_Stress_Tester::get_state();
|
||
$this->assertSame( 'completed', $state['status'] );
|
||
$this->assertIsArray( $state['benchmark'] );
|
||
$this->assertArrayHasKey( 'write', $state['benchmark'] );
|
||
$this->assertArrayHasKey( 'db_sizes', $state['benchmark'] );
|
||
$this->assertSame( 'category', $state['benchmark']['taxonomy'] );
|
||
unset( $GLOBALS['_taxonomy_exists_override'] );
|
||
}
|
||
}
|