test(2meet-data-optimizer): add live test runner and failing shadow_read write suite
- Add tests/live: a `wp eval-file` runner (PASS/FAIL/XFAIL/XPASS/SKIP) with fixtures that override entity modes in process (pre_option) and read native / flat values with plain SQL, so WP core meta flows run for real - shadow_write suite reproduces the dev36 finding: in shadow_read, update_*_meta returns false and native meta is never written (3 XFAIL marked for PR-056); the dual_write control passes - Move the dev36 integration harness into tests/live/tools; outputs go to cwd / $TMDO_LIVE_OUT, site URL and path are env-configurable - Verify: wp --path=<dev36> eval-file tests/live/run.php shadow_write → 1 passed, 3 xfail; unit 658 OK, integration 480 OK, phpcs (phpcs.xml) 0, stan OK - PR: PR-052
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* Independent native-meta vs flat-table consistency checker (does not use TMDO read paths).
|
||||
* Usage: wp eval-file checker.php [entity]
|
||||
* Per registered group/key: native rows, flat non-null, missing-in-flat, value mismatches, extra-in-flat.
|
||||
*/
|
||||
global $wpdb;
|
||||
$only = $args[0] ?? '';
|
||||
$ids = array( 'post' => 'post_id', 'user' => 'user_id', 'term' => 'term_id', 'comment' => 'comment_id' );
|
||||
$metas = array( 'post' => $wpdb->postmeta, 'user' => $wpdb->usermeta, 'term' => $wpdb->termmeta, 'comment' => $wpdb->commentmeta );
|
||||
$tot = array( 'miss' => 0, 'diff' => 0, 'extra' => 0 );
|
||||
|
||||
printf( "%-8s %-24s %-28s %8s %8s %6s %6s %6s\n", 'entity', 'group', 'key', 'native', 'flat', 'miss', 'diff', 'extra' );
|
||||
foreach ( $ids as $e => $idcol ) {
|
||||
if ( $only && $only !== $e ) { continue; }
|
||||
$mt = $metas[ $e ];
|
||||
$mid = ( 'user' === $e ) ? 'umeta_id' : 'meta_id';
|
||||
foreach ( TMDO_Entity_Registry::get_groups_for_type( $e ) as $g ) {
|
||||
$table = $wpdb->prefix . 'wpdo_' . $e . '_' . $g;
|
||||
if ( ! $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) ) { continue; }
|
||||
$scope = ( 'post' === $e ) ? TMDO_Entity_Registry::get_group_post_types( $e, $g ) : array();
|
||||
$scope_sql = $scope ? " AND m.post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_type IN ('" . implode( "','", array_map( 'esc_sql', $scope ) ) . "'))" : '';
|
||||
foreach ( TMDO_Entity_Registry::get_group_keys( $e, $g ) as $key ) {
|
||||
$col = preg_replace( '/[^a-zA-Z0-9_]/', '', $key );
|
||||
// Latest native value per entity.
|
||||
$nat = $wpdb->prepare( "SELECT m.$idcol AS eid, m.meta_value AS v FROM $mt m JOIN (SELECT $idcol, MAX($mid) mid FROM $mt WHERE meta_key=%s GROUP BY $idcol) x ON x.mid=m.$mid WHERE 1=1 $scope_sql", $key );
|
||||
$n_native = (int) $wpdb->get_var( "SELECT COUNT(*) FROM ($nat) n" );
|
||||
$n_flat = (int) $wpdb->get_var( "SELECT COUNT(*) FROM $table WHERE `$col` IS NOT NULL" );
|
||||
$miss = (int) $wpdb->get_var( "SELECT COUNT(*) FROM ($nat) n LEFT JOIN $table f ON f.$idcol=n.eid WHERE f.$idcol IS NULL OR (f.`$col` IS NULL AND n.v <> '')" );
|
||||
// Value diff: numeric-aware loose compare.
|
||||
$diff = (int) $wpdb->get_var( "SELECT COUNT(*) FROM ($nat) n JOIN $table f ON f.$idcol=n.eid WHERE f.`$col` IS NOT NULL AND NOT (
|
||||
CAST(f.`$col` AS CHAR) = n.v
|
||||
OR (n.v REGEXP '^-?[0-9]+(\\\\.[0-9]+)?$' AND CAST(f.`$col` AS DECIMAL(30,6)) = CAST(n.v AS DECIMAL(30,6)))
|
||||
OR (n.v='' AND CAST(f.`$col` AS CHAR) IN ('','0'))
|
||||
)" );
|
||||
$extra = (int) $wpdb->get_var( "SELECT COUNT(*) FROM $table f WHERE f.`$col` IS NOT NULL AND NOT EXISTS (SELECT 1 FROM $mt m WHERE m.$idcol=f.$idcol AND m.meta_key='" . esc_sql( $key ) . "')" );
|
||||
if ( $n_native || $n_flat ) {
|
||||
printf( "%-8s %-24s %-28s %8d %8d %6d %6d %6d\n", $e, $g, $key, $n_native, $n_flat, $miss, $diff, $extra );
|
||||
}
|
||||
$tot['miss'] += $miss;
|
||||
$tot['diff'] += $diff;
|
||||
$tot['extra'] += $extra;
|
||||
}
|
||||
}
|
||||
}
|
||||
printf( "TOTAL miss=%d diff=%d extra=%d\n", $tot['miss'], $tot['diff'], $tot['extra'] );
|
||||
@@ -0,0 +1,12 @@
|
||||
-- Clone original hp_listing rows (ID < 20000) into copy k with ID offset k*20000.
|
||||
-- Bench-only: clones hp_listing rows into offset IDs. Never run on a production site.
|
||||
-- Usage: sed "s/__K__/<n>/" clone.sql | wp db query
|
||||
SET @k := __K__; SET @off := @k * 20000;
|
||||
INSERT INTO wp_posts (ID, post_author, post_date, post_date_gmt, post_content, post_title, post_excerpt, post_status, comment_status, ping_status, post_password, post_name, to_ping, pinged, post_modified, post_modified_gmt, post_content_filtered, post_parent, guid, menu_order, post_type, post_mime_type, comment_count)
|
||||
SELECT ID+@off, post_author, d, d, post_content, CONCAT(post_title,' c',@k), post_excerpt, post_status, comment_status, ping_status, post_password, CONCAT(post_name,'-c',@k), to_ping, pinged, d, d, post_content_filtered, post_parent, CONCAT(guid,'&c=',@k), menu_order, post_type, post_mime_type, comment_count
|
||||
FROM (SELECT *, DATE_SUB('2026-09-26 12:00:00', INTERVAL FLOOR(RAND()*63072000) SECOND) d FROM wp_posts WHERE post_type='hp_listing' AND ID < 20000) s;
|
||||
INSERT INTO wp_postmeta (post_id, meta_key, meta_value)
|
||||
SELECT m.post_id+@off, m.meta_key, CASE m.meta_key WHEN 'hp_price' THEN FLOOR(100+RAND()*49900) WHEN 'hp_hourly_rate' THEN FLOOR(10+RAND()*290) ELSE m.meta_value END
|
||||
FROM wp_postmeta m JOIN wp_posts p ON p.ID=m.post_id WHERE p.post_type='hp_listing' AND p.ID < 20000 AND m.meta_key NOT LIKE '\_transient%';
|
||||
INSERT INTO wp_term_relationships (object_id, term_taxonomy_id, term_order)
|
||||
SELECT r.object_id+@off, r.term_taxonomy_id, r.term_order FROM wp_term_relationships r JOIN wp_posts p ON p.ID=r.object_id WHERE p.post_type='hp_listing' AND p.ID < 20000;
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
# usage: fe.sh <label> — front-end HivePress/WC search probe, 5 runs each, median ms
|
||||
B="${TMDO_LIVE_URL:-https://dev36.2meet.pro}"
|
||||
URLS=(
|
||||
"/?post_type=hp_listing&s=&price[]=10000&price[]=20000"
|
||||
"/?post_type=hp_listing&s=&price[]=10000&price[]=20000&_sort=price__asc"
|
||||
"/?post_type=hp_listing&s=&hourly_rate[]=10&hourly_rate[]=50&_sort=hourly_rate__desc"
|
||||
"/?post_type=hp_listing&s=&_sort=rating__desc"
|
||||
"/?post_type=hp_listing&s=Taipei&price[]=1000&price[]=30000"
|
||||
"/?post_type=hp_vendor&s=&hourly_rate[]=100&hourly_rate[]=200"
|
||||
"/?post_type=hp_request&s="
|
||||
"/shop/?min_price=1000&max_price=5000"
|
||||
"/shop/?orderby=price-desc"
|
||||
)
|
||||
for u in "${URLS[@]}"; do
|
||||
ms=(); for i in 1 2 3 4 5; do h=$(curl -s -o /dev/null -D - -H "X-S36-Probe: 1" "$B$u"); ms+=($(echo "$h" | grep -i x-s36-ms | awk "{print \$2}" | tr -dc 0-9)); done
|
||||
med=$(printf "%s\n" "${ms[@]}" | sort -n | sed -n 3p)
|
||||
echo "$u|$(echo "$h" | grep -i x-s36-found | awk "{print \$2}" | tr -dc 0-9)|$(echo "$h" | grep -i x-s36-ids | cut -d' ' -f2 | tr -d '\r')|$(echo "$h" | grep -i x-s36-queries | awk "{print \$2}" | tr -dc 0-9)|$med"
|
||||
done
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
// Data-flow mutation test. Usage: wp eval-file mut.php <label>
|
||||
use HivePress\Models;
|
||||
add_filter( 'pre_http_request', function ( $r, $a, $u ) { return false !== strpos( $u, 'pixel.wp.com' ) ? array( 'headers' => array(), 'body' => '', 'response' => array( 'code' => 200, 'message' => 'OK' ), 'cookies' => array() ) : $r; }, 1, 3 );
|
||||
global $wpdb;
|
||||
$mode = TMDO_Mode_Manager::get( 'post' ) . '/' . TMDO_Mode_Manager::get( 'user' );
|
||||
function nat( $t, $id, $k ) { global $wpdb; $mt = $t === 'user' ? $wpdb->usermeta : $wpdb->postmeta; $c = $t === 'user' ? 'user_id' : 'post_id'; $v = $wpdb->get_col( $wpdb->prepare( "SELECT meta_value FROM $mt WHERE $c=%d AND meta_key=%s", $id, $k ) ); return $v ? implode( '|', $v ) : '∅'; }
|
||||
function flat( $t, $g, $id, $k ) { global $wpdb; $c = $t === 'user' ? 'user_id' : 'post_id'; $v = $wpdb->get_row( $wpdb->prepare( "SELECT `$k` v FROM {$wpdb->prefix}wpdo_{$t}_{$g} WHERE $c=%d", $id ) ); return $v ? ( $v->v ?? 'NULL' ) : 'NO-ROW'; }
|
||||
function row( $name, $vals ) { printf( "%-44s %s\n", $name, implode( ' ', array_map( fn( $k, $v ) => "$k=" . ( is_scalar( $v ) || is_null( $v ) ? var_export( $v, true ) : json_encode( $v ) ), array_keys( $vals ), $vals ) ) ); }
|
||||
function clear_caches( $id ) { wp_cache_flush(); clean_post_cache( $id ); }
|
||||
echo "== mode post/user = $mode\n";
|
||||
|
||||
// T1: HivePress model update of a listing price.
|
||||
$lid = (int) $wpdb->get_var( "SELECT ID FROM {$wpdb->posts} WHERE post_type='hp_listing' AND post_status='publish' ORDER BY ID DESC LIMIT 1" );
|
||||
$new = mt_rand( 60000, 69999 );
|
||||
update_post_meta( $lid, 'hp_price', $new ); // HP stores attributes via meta API
|
||||
clear_caches( $lid );
|
||||
$m = Models\Listing::query()->get_by_id( $lid );
|
||||
row( "T1 listing $lid hp_price:=$new", array( 'native' => nat( 'post', $lid, 'hp_price' ), 'flat' => flat( 'post', 'hp_listing_core', $lid, 'hp_price' ), 'get_meta' => get_post_meta( $lid, 'hp_price', true ), 'all_meta' => get_post_meta( $lid )['hp_price'][0] ?? '∅' ) );
|
||||
|
||||
// T2: HivePress model field (verified) via model save.
|
||||
$m->set_verified( ! $m->is_verified() )->save( array( 'verified' ) );
|
||||
clear_caches( $lid );
|
||||
$m2 = Models\Listing::query()->get_by_id( $lid );
|
||||
row( "T2 listing verified via HP model", array( 'native' => nat( 'post', $lid, 'hp_verified' ), 'flat' => flat( 'post', 'hp_listing_core', $lid, 'hp_verified' ), 'hp_model' => $m2->is_verified(), 'get_meta' => get_post_meta( $lid, 'hp_verified', true ) ) );
|
||||
|
||||
// T3: WC product price via CRUD.
|
||||
$pid = (int) $wpdb->get_var( "SELECT ID FROM {$wpdb->posts} WHERE post_type='product' AND post_title LIKE 'Product B1%' ORDER BY ID DESC LIMIT 1" );
|
||||
$p = wc_get_product( $pid ); $np = (string) mt_rand( 7000, 7999 ); $p->set_regular_price( $np ); $p->set_sale_price( '' ); $p->save();
|
||||
clear_caches( $pid );
|
||||
row( "T3 product $pid _regular_price:=$np", array( 'native' => nat( 'post', $pid, '_regular_price' ), 'flat' => flat( 'post', 'wc_product', $pid, '_regular_price' ), 'wc_read' => wc_get_product( $pid )->get_regular_price(), 'lookup' => $wpdb->get_var( "SELECT min_price FROM {$wpdb->prefix}wc_product_meta_lookup WHERE product_id=$pid" ) ) );
|
||||
|
||||
// T4: order reduces stock (WC direct SQL path).
|
||||
$before = wc_get_product( $pid )->get_stock_quantity();
|
||||
$o = wc_create_order(); $o->add_product( wc_get_product( $pid ), 2 ); $o->calculate_totals( false ); $o->set_status( 'processing' ); $o->save(); wc_reduce_stock_levels( $o->get_id() );
|
||||
clear_caches( $pid );
|
||||
row( "T4 order x2 on product $pid (stock $before)", array( 'native' => nat( 'post', $pid, '_stock' ), 'flat' => flat( 'post', 'wc_product', $pid, '_stock' ), 'get_meta' => get_post_meta( $pid, '_stock', true ), 'wc_read' => wc_get_product( $pid )->get_stock_quantity(), 'sales_native' => nat( 'post', $pid, 'total_sales' ), 'sales_flat' => flat( 'post', 'wc_product', $pid, 'total_sales' ) ) );
|
||||
|
||||
// T5: delete a meta key.
|
||||
update_post_meta( $lid, 'hp_featured', 1 ); delete_post_meta( $lid, 'hp_featured' ); clear_caches( $lid );
|
||||
row( "T5 delete_post_meta hp_featured", array( 'native' => nat( 'post', $lid, 'hp_featured' ), 'flat' => flat( 'post', 'hp_listing_core', $lid, 'hp_featured' ), 'get_meta' => var_export( get_post_meta( $lid, 'hp_featured', true ), true ), 'exists' => metadata_exists( 'post', $lid, 'hp_featured' ) ) );
|
||||
|
||||
// T6: user first_name via wp_update_user.
|
||||
$uid = (int) $wpdb->get_var( "SELECT ID FROM {$wpdb->users} WHERE user_login LIKE 's36v_B1p1_%' ORDER BY ID LIMIT 1" );
|
||||
$fn = 'Fn' . mt_rand( 100, 999 ); wp_update_user( array( 'ID' => $uid, 'first_name' => $fn ) ); wp_cache_flush();
|
||||
$u = get_userdata( $uid );
|
||||
row( "T6 user $uid first_name:=$fn", array( 'native' => nat( 'user', $uid, 'first_name' ), 'flat' => flat( 'user', 'core_profile', $uid, 'first_name' ), 'get_meta' => get_user_meta( $uid, 'first_name', true ), 'WP_User' => $u->first_name, 'wc_customer' => ( new WC_Customer( $uid ) )->get_first_name() ) );
|
||||
|
||||
// T7: WC customer billing city via CRUD.
|
||||
$c = new WC_Customer( $uid ); $city = 'City' . mt_rand( 10, 99 ); $c->set_billing_city( $city ); $c->save(); wp_cache_flush();
|
||||
row( "T7 WC_Customer billing_city:=$city", array( 'native' => nat( 'user', $uid, 'billing_city' ), 'flat' => flat( 'user', 'commerce', $uid, 'billing_city' ), 'get_meta' => get_user_meta( $uid, 'billing_city', true ), 'wc_customer' => ( new WC_Customer( $uid ) )->get_billing_city() ) );
|
||||
|
||||
// T8: delete a listing → flat row?
|
||||
$d = (int) $wpdb->get_var( "SELECT ID FROM {$wpdb->posts} WHERE post_type='hp_listing' AND post_status='draft' ORDER BY ID DESC LIMIT 1" );
|
||||
wp_delete_post( $d, true );
|
||||
row( "T8 wp_delete_post $d", array( 'posts' => (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->posts} WHERE ID=$d" ), 'native_meta' => (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE post_id=$d" ), 'flat_row' => flat( 'post', 'hp_listing_core', $d, 'post_id' ) ) );
|
||||
|
||||
// T9: HP review → listing rating recompute.
|
||||
$before = get_post_meta( $lid, 'hp_rating_count', true );
|
||||
$rv = ( new Models\Review() )->fill( array( 'text' => 'mut', 'rating' => 5, 'approved' => 1, 'author' => $uid, 'author__display_name' => 'x', 'author__email' => 'x@example.test', 'listing' => $lid ) ); $rv->save(); wp_cache_flush();
|
||||
row( "T9 add review (count was $before)", array( 'native' => nat( 'post', $lid, 'hp_rating_count' ), 'flat' => flat( 'post', 'hp_listing_core', $lid, 'hp_rating_count' ), 'hp_model' => Models\Listing::query()->get_by_id( $lid )->get_rating_count() ) );
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* Query correctness + latency harness. Runs identical WP_Query shapes and dumps
|
||||
* ids / found_posts / timings to JSON so two runs (native vs routed mode) can be diffed.
|
||||
* Usage: wp eval-file qbench.php <label> [iterations]
|
||||
*/
|
||||
global $wpdb;
|
||||
$label = $args[0] ?? 'run';
|
||||
$iters = (int) ( $args[1] ?? 5 );
|
||||
$out = getcwd() . "/qbench_{$label}.json";
|
||||
|
||||
// Deterministic parameters derived from data (same in every run).
|
||||
$vendor_ids = array_map( 'intval', $wpdb->get_col( "SELECT ID FROM {$wpdb->posts} WHERE post_type='hp_vendor' AND post_status='publish' ORDER BY ID LIMIT 3" ) );
|
||||
|
||||
$L = array( 'post_type' => 'hp_listing', 'post_status' => 'publish', 'posts_per_page' => 20 );
|
||||
$cases = array(
|
||||
'Q01 price BETWEEN (NUMERIC)' => $L + array( 'meta_query' => array( array( 'key' => 'hp_price', 'value' => array( 10000, 20000 ), 'compare' => 'BETWEEN', 'type' => 'NUMERIC' ) ) ),
|
||||
'Q02 price >= AND verified=1' => $L + array( 'meta_query' => array( 'relation' => 'AND', array( 'key' => 'hp_price', 'value' => 25000, 'compare' => '>=', 'type' => 'NUMERIC' ), array( 'key' => 'hp_verified', 'value' => '1' ) ) ),
|
||||
'Q03 rating >= 4.5 (DECIMAL)' => $L + array( 'meta_query' => array( array( 'key' => 'hp_rating', 'value' => '4.5', 'compare' => '>=', 'type' => 'DECIMAL(10,2)' ) ) ),
|
||||
'Q04 featured=1 OR verified=1' => $L + array( 'meta_query' => array( 'relation' => 'OR', array( 'key' => 'hp_featured', 'value' => '1' ), array( 'key' => 'hp_verified', 'value' => '1' ) ) ),
|
||||
'Q05 rate>=100 AND _thumbnail EXISTS'=> $L + array( 'meta_query' => array( array( 'key' => 'hp_hourly_rate', 'value' => 100, 'compare' => '>=', 'type' => 'NUMERIC' ), array( 'key' => '_thumbnail_id', 'compare' => 'EXISTS' ) ) ),
|
||||
'Q06 product _sku LIKE' => array( 'post_type' => 'product', 'post_status' => 'publish', 'posts_per_page' => 20, 'meta_query' => array( array( 'key' => '_sku', 'value' => 'S36-B1-1', 'compare' => 'LIKE' ) ) ),
|
||||
'Q07 orderby meta_value_num price' => $L + array( 'meta_key' => 'hp_price', 'orderby' => 'meta_value_num', 'order' => 'DESC' ),
|
||||
'Q08 multi post_type + hp_budget' => array( 'post_type' => array( 'hp_listing', 'hp_request' ), 'post_status' => 'publish', 'posts_per_page' => 20, 'meta_query' => array( array( 'key' => 'hp_budget', 'value' => 80000, 'compare' => '>=', 'type' => 'NUMERIC' ) ) ),
|
||||
'Q09 featured NOT EXISTS' => $L + array( 'meta_query' => array( array( 'key' => 'hp_featured', 'compare' => 'NOT EXISTS' ) ) ),
|
||||
'Q10 rating_count != 1' => $L + array( 'meta_query' => array( array( 'key' => 'hp_rating_count', 'value' => '1', 'compare' => '!=' ) ) ),
|
||||
'Q11 product _price BETWEEN+instock' => array( 'post_type' => 'product', 'post_status' => 'publish', 'posts_per_page' => 20, 'meta_query' => array( array( 'key' => '_price', 'value' => array( 1000, 5000 ), 'compare' => 'BETWEEN', 'type' => 'NUMERIC' ), array( 'key' => '_stock_status', 'value' => 'instock' ) ) ),
|
||||
'Q12 request budget > (NUMERIC)' => array( 'post_type' => 'hp_request', 'post_status' => 'publish', 'posts_per_page' => 20, 'meta_query' => array( array( 'key' => 'hp_budget', 'value' => 50000, 'compare' => '>', 'type' => 'NUMERIC' ) ) ),
|
||||
'Q13 rating_count IN (1,2)' => $L + array( 'meta_query' => array( array( 'key' => 'hp_rating_count', 'value' => array( 1, 2 ), 'compare' => 'IN' ) ) ),
|
||||
'Q16 price<1000 OR warranty=0 (mix)' => $L + array( 'meta_query' => array( 'relation' => 'OR', array( 'key' => 'hp_price', 'value' => 1000, 'compare' => '<', 'type' => 'NUMERIC' ), array( 'key' => 'hp_warranty', 'value' => '0' ) ) ),
|
||||
'Q14 price > (CHAR, no type)' => $L + array( 'meta_query' => array( array( 'key' => 'hp_price', 'value' => '9000', 'compare' => '>' ) ) ),
|
||||
'Q15 hourly_rate < 50 orderby price' => $L + array( 'meta_query' => array( 'rate' => array( 'key' => 'hp_hourly_rate', 'value' => 50, 'compare' => '<', 'type' => 'NUMERIC' ), 'price' => array( 'key' => 'hp_price', 'type' => 'NUMERIC', 'compare' => 'EXISTS' ) ), 'orderby' => 'price', 'order' => 'ASC' ),
|
||||
);
|
||||
|
||||
$res = array();
|
||||
foreach ( $cases as $name => $qa ) {
|
||||
$times = array();
|
||||
$ids = null;
|
||||
$found = null;
|
||||
$err = '';
|
||||
$sql = '';
|
||||
for ( $i = 0; $i < $iters; $i++ ) {
|
||||
wp_cache_flush_group( 'tmdo_found_rows' );
|
||||
$wpdb->last_error = '';
|
||||
$t = microtime( true );
|
||||
$q = new WP_Query( $qa + array( 'fields' => 'ids', 'cache_results' => false, 'suppress_filters' => false ) );
|
||||
$times[] = ( microtime( true ) - $t ) * 1000;
|
||||
$ids = array_map( 'intval', $q->posts );
|
||||
$found = (int) $q->found_posts;
|
||||
$sql = $q->request;
|
||||
if ( $wpdb->last_error ) { $err = $wpdb->last_error; }
|
||||
}
|
||||
sort( $times );
|
||||
$res[ $name ] = array(
|
||||
'found' => $found,
|
||||
'ids' => $ids,
|
||||
'p50' => round( $times[ (int) floor( count( $times ) / 2 ) ], 2 ),
|
||||
'max' => round( max( $times ), 2 ),
|
||||
'err' => $err,
|
||||
'sql' => preg_replace( '/\s+/', ' ', $sql ),
|
||||
);
|
||||
printf( "%-36s found=%-6d p50=%8.2fms %s\n", $name, $found, $res[ $name ]['p50'], $err ? "ERR: $err" : '' );
|
||||
}
|
||||
file_put_contents( $out, wp_json_encode( array( 'mode' => TMDO_Mode_Manager::get( 'post' ), 'res' => $res ), JSON_PRETTY_PRINT ) );
|
||||
echo "saved $out (post mode=" . TMDO_Mode_Manager::get( 'post' ) . ")\n";
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
// php qdiff.php a.json b.json — compare two qbench runs (a = native reference).
|
||||
$a = json_decode( file_get_contents( $argv[1] ), true ); $b = json_decode( file_get_contents( $argv[2] ), true );
|
||||
printf( "A=%s (%s) B=%s (%s)\n", basename( $argv[1] ), $a['mode'], basename( $argv[2] ), $b['mode'] );
|
||||
printf( "%-36s %8s %8s %6s %6s %9s %9s %7s\n", 'case', 'foundA', 'foundB', 'set', 'order', 'p50A', 'p50B', 'speedup' );
|
||||
foreach ( $a['res'] as $k => $ra ) {
|
||||
$rb = $b['res'][ $k ];
|
||||
$set = ( array_diff( $ra['ids'], $rb['ids'] ) || array_diff( $rb['ids'], $ra['ids'] ) ) ? 'DIFF' : 'same';
|
||||
$ord = $ra['ids'] === $rb['ids'] ? 'same' : 'DIFF';
|
||||
printf( "%-36s %8d %8d %6s %6s %9.2f %9.2f %6.1fx %s\n", $k, $ra['found'], $rb['found'], $set, $ord, $ra['p50'], $rb['p50'], $rb['p50'] > 0 ? $ra['p50'] / $rb['p50'] : 0, $rb['err'] ? 'ERR:' . substr( $rb['err'], 0, 60 ) : '' );
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
# usage: run_batch.sh <batch-tag> <scale-divisor>
|
||||
S="$(cd "$(dirname "$0")" && pwd)/seed.php"
|
||||
cd "${TMDO_LIVE_WP_PATH:-/var/www/Studio/wp-local-dev36}"
|
||||
B=$1; D=${2:-1}; OUT=${TMDO_LIVE_OUT:-/tmp}; LOG=$OUT/batch_$B.log
|
||||
run(){ wp eval-file $S "$@" 2>>$OUT/batch_${B}_err.log | grep SEED >> $LOG; }
|
||||
echo "== start $(date +%T)" >> $LOG
|
||||
run terms $((100/D)) ${B}
|
||||
for p in 1 2 3 4; do run vendors $((250/D)) ${B}p$p & done; wait
|
||||
for p in 1 2 3 4; do run listings $((1000/D)) ${B}p$p & done
|
||||
run requests $((500/D)) ${B} & run products $((1000/D)) ${B} & wait
|
||||
for p in 1 2; do run reviews $((2000/D)) ${B}p$p & done
|
||||
for p in 1 2; do run orders $((750/D)) ${B}p$p & done; wait
|
||||
echo "== end $(date +%T)" >> $LOG
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
/**
|
||||
* dev36 integration seeder — goes through the real APIs (HivePress models, WC CRUD,
|
||||
* wp_insert_user / update_*_meta) so every write passes the Hook Bus exactly like prod.
|
||||
*
|
||||
* Usage: wp eval-file seed.php <kind> <count> [tag]
|
||||
* kind: vendors | listings | requests | reviews | products | orders | terms
|
||||
*/
|
||||
|
||||
use HivePress\Models;
|
||||
|
||||
add_filter( 'pre_wp_mail', '__return_true' );
|
||||
// WC Tracks pings pixel.wp.com ~3x per product save (~180 ms each); irrelevant to the test.
|
||||
add_filter( 'pre_http_request', function ( $r, $a, $url ) { return false !== strpos( $url, 'pixel.wp.com' ) ? array( 'headers' => array(), 'body' => '', 'response' => array( 'code' => 200, 'message' => 'OK' ), 'cookies' => array() ) : $r; }, 1, 3 );
|
||||
wp_defer_term_counting( true );
|
||||
wp_defer_comment_counting( true );
|
||||
|
||||
$kind = $args[0] ?? '';
|
||||
$count = (int) ( $args[1] ?? 0 );
|
||||
$tag = $args[2] ?? 'b1';
|
||||
mt_srand( crc32( $kind . $tag ) );
|
||||
|
||||
$cities = array( 'Taipei', 'Taichung', 'Kaohsiung', 'Tainan', 'Hsinchu', 'Keelung' );
|
||||
|
||||
function s36_pick_ids( $post_type, $limit = 5000 ) {
|
||||
global $wpdb;
|
||||
return array_map( 'intval', $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_type=%s AND post_status='publish' ORDER BY RAND() LIMIT %d", $post_type, $limit ) ) );
|
||||
}
|
||||
|
||||
$t0 = microtime( true );
|
||||
$q0 = get_num_queries();
|
||||
$ok = 0;
|
||||
|
||||
switch ( $kind ) {
|
||||
case 'vendors':
|
||||
for ( $i = 0; $i < $count; $i++ ) {
|
||||
$login = "s36v_{$tag}_{$i}";
|
||||
$uid = wp_insert_user( array( 'user_login' => $login, 'user_email' => "$login@example.test", 'user_pass' => wp_generate_password(), 'role' => 'contributor', 'first_name' => "F$i", 'last_name' => "L$i", 'description' => "Vendor $i bio" ) );
|
||||
if ( is_wp_error( $uid ) ) { continue; }
|
||||
update_user_meta( $uid, 'billing_city', $cities[ $i % 6 ] );
|
||||
update_user_meta( $uid, 'billing_phone', '09' . mt_rand( 10000000, 99999999 ) );
|
||||
update_user_meta( $uid, 'facebook', "https://facebook.com/$login" );
|
||||
update_user_meta( $uid, 'login_count', mt_rand( 1, 300 ) );
|
||||
$v = ( new Models\Vendor() )->fill( array( 'name' => "Vendor $tag $i", 'description' => 'Seeded vendor', 'status' => 'publish', 'user' => $uid, 'verified' => (bool) mt_rand( 0, 1 ) ) );
|
||||
if ( $v->save() ) {
|
||||
update_post_meta( $v->get_id(), 'hp_hourly_rate', mt_rand( 10, 300 ) );
|
||||
update_post_meta( $v->get_id(), 'hp_rating', mt_rand( 10, 50 ) / 10 );
|
||||
$ok++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'listings':
|
||||
global $wpdb;
|
||||
$vendors = $wpdb->get_results( "SELECT ID, post_author FROM {$wpdb->posts} WHERE post_type='hp_vendor' AND post_status='publish'" );
|
||||
$cats = get_terms( array( 'taxonomy' => 'hp_listing_category', 'hide_empty' => false, 'fields' => 'ids' ) );
|
||||
for ( $i = 0; $i < $count; $i++ ) {
|
||||
$v = $vendors[ array_rand( $vendors ) ];
|
||||
$l = ( new Models\Listing() )->fill( array(
|
||||
'title' => "Listing $tag $i " . $cities[ $i % 6 ],
|
||||
'description' => str_repeat( 'Seeded listing body. ', 8 ),
|
||||
'status' => ( mt_rand( 1, 10 ) <= 9 ) ? 'publish' : 'draft',
|
||||
'user' => (int) $v->post_author,
|
||||
'vendor' => (int) $v->ID,
|
||||
'featured' => mt_rand( 1, 20 ) === 1,
|
||||
'verified' => mt_rand( 1, 3 ) === 1,
|
||||
'categories' => $cats ? array( $cats[ array_rand( $cats ) ] ) : array(),
|
||||
) );
|
||||
if ( $l->save() ) {
|
||||
$id = $l->get_id();
|
||||
update_post_meta( $id, 'hp_price', mt_rand( 100, 50000 ) );
|
||||
update_post_meta( $id, 'hp_hourly_rate', mt_rand( 10, 300 ) );
|
||||
update_post_meta( $id, 'hp_warranty', mt_rand( 0, 24 ) );
|
||||
$ok++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'requests':
|
||||
$users = get_users( array( 'fields' => 'ID', 'number' => 3000, 'orderby' => 'rand' ) );
|
||||
for ( $i = 0; $i < $count; $i++ ) {
|
||||
$r = ( new Models\Request() )->fill( array( 'title' => "Request $tag $i", 'description' => 'Need help', 'status' => 'publish', 'user' => (int) $users[ array_rand( $users ) ] ) );
|
||||
if ( $r->save() ) {
|
||||
update_post_meta( $r->get_id(), 'hp_budget', mt_rand( 500, 90000 ) );
|
||||
update_post_meta( $r->get_id(), 'hp_due_date', gmdate( 'Y-m-d', time() + mt_rand( 1, 90 ) * DAY_IN_SECONDS ) );
|
||||
$ok++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'reviews':
|
||||
$listings = s36_pick_ids( 'hp_listing' );
|
||||
$users = get_users( array( 'fields' => array( 'ID', 'display_name', 'user_email' ), 'number' => 3000, 'orderby' => 'rand' ) );
|
||||
for ( $i = 0; $i < $count; $i++ ) {
|
||||
$u = $users[ array_rand( $users ) ];
|
||||
$rv = ( new Models\Review() )->fill( array( 'text' => "Review $tag $i", 'rating' => mt_rand( 1, 5 ), 'approved' => 1, 'author' => (int) $u->ID, 'author__display_name' => $u->display_name, 'author__email' => $u->user_email, 'listing' => $listings[ array_rand( $listings ) ] ) );
|
||||
if ( $rv->save() ) { $ok++; }
|
||||
}
|
||||
break;
|
||||
|
||||
case 'terms':
|
||||
for ( $i = 0; $i < $count; $i++ ) {
|
||||
$t = wp_insert_term( "Cat $tag $i", 'hp_listing_category' );
|
||||
if ( ! is_wp_error( $t ) ) {
|
||||
update_term_meta( $t['term_id'], 'hp_sort_order', $i );
|
||||
update_term_meta( $t['term_id'], 'hp_icon', 'star' );
|
||||
$ok++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'products':
|
||||
$pcats = get_terms( array( 'taxonomy' => 'product_cat', 'hide_empty' => false, 'fields' => 'ids' ) );
|
||||
for ( $i = 0; $i < $count; $i++ ) {
|
||||
$p = new WC_Product_Simple();
|
||||
$p->set_name( "Product $tag $i" );
|
||||
$p->set_status( 'publish' );
|
||||
$reg = mt_rand( 100, 20000 );
|
||||
$p->set_regular_price( (string) $reg );
|
||||
if ( mt_rand( 1, 4 ) === 1 ) { $p->set_sale_price( (string) (int) ( $reg * 0.8 ) ); }
|
||||
$p->set_sku( "S36-$tag-$i" );
|
||||
$p->set_manage_stock( true );
|
||||
$p->set_stock_quantity( mt_rand( 0, 500 ) );
|
||||
if ( $pcats ) { $p->set_category_ids( array( $pcats[ array_rand( $pcats ) ] ) ); }
|
||||
if ( $p->save() ) { $ok++; }
|
||||
}
|
||||
break;
|
||||
|
||||
case 'orders':
|
||||
$products = wc_get_products( array( 'limit' => 2000, 'status' => 'publish', 'orderby' => 'rand', 'return' => 'ids' ) );
|
||||
$users = get_users( array( 'fields' => 'ID', 'number' => 3000, 'orderby' => 'rand' ) );
|
||||
$statuses = array( 'completed', 'completed', 'processing', 'on-hold', 'cancelled', 'refunded' );
|
||||
for ( $i = 0; $i < $count; $i++ ) {
|
||||
$o = wc_create_order( array( 'customer_id' => (int) $users[ array_rand( $users ) ] ) );
|
||||
if ( is_wp_error( $o ) ) { continue; }
|
||||
$n = mt_rand( 1, 3 );
|
||||
for ( $j = 0; $j < $n; $j++ ) { $o->add_product( wc_get_product( $products[ array_rand( $products ) ] ), mt_rand( 1, 3 ) ); }
|
||||
$o->set_billing_city( $cities[ $i % 6 ] );
|
||||
$o->set_billing_email( "buyer$i@example.test" );
|
||||
$o->calculate_totals( false );
|
||||
$o->set_status( $statuses[ array_rand( $statuses ) ] );
|
||||
$o->save();
|
||||
$ok++;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
WP_CLI::error( "unknown kind $kind" );
|
||||
}
|
||||
|
||||
wp_defer_term_counting( false );
|
||||
wp_defer_comment_counting( false );
|
||||
|
||||
$el = microtime( true ) - $t0;
|
||||
$q = get_num_queries() - $q0;
|
||||
WP_CLI::log( sprintf( 'SEED %s x%d ok=%d %.1fs %.2f ms/item %.1f queries/item mode(post=%s,user=%s,term=%s,comment=%s)',
|
||||
$kind, $count, $ok, $el, $ok ? $el * 1000 / $ok : 0, $ok ? $q / $ok : 0,
|
||||
...( class_exists( 'TMDO_Mode_Manager' ) ? array( TMDO_Mode_Manager::get( 'post' ), TMDO_Mode_Manager::get( 'user' ), TMDO_Mode_Manager::get( 'term' ), TMDO_Mode_Manager::get( 'comment' ) ) : array( 'NO-TMDO', '-', '-', '-' ) ) ) );
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
/**
|
||||
* TEMPORARY test probe for the 2meet-DO integration test (2026-09-26). Delete after testing.
|
||||
* Only active when the request carries header "X-S36-Probe: 1".
|
||||
*/
|
||||
if ( empty( $_SERVER['HTTP_X_S36_PROBE'] ) ) {
|
||||
return;
|
||||
}
|
||||
add_action(
|
||||
'template_redirect',
|
||||
function () {
|
||||
global $wp_query, $wpdb;
|
||||
$ids = wp_list_pluck( (array) $wp_query->posts, 'ID' );
|
||||
header( 'X-S36-Found: ' . (int) $wp_query->found_posts );
|
||||
header( 'X-S36-Ids: ' . implode( ',', array_map( 'intval', $ids ) ) );
|
||||
header( 'X-S36-Queries: ' . (int) $wpdb->num_queries );
|
||||
header( 'X-S36-Routed: ' . ( false !== strpos( (string) $wp_query->request, 'wpdo' ) ? 1 : 0 ) );
|
||||
header( 'X-S36-Sql: ' . substr( preg_replace( '/\s+/', ' ', (string) $wp_query->request ), 0, 1500 ) );
|
||||
header( 'X-S36-Ms: ' . round( ( microtime( true ) - $_SERVER['REQUEST_TIME_FLOAT'] ) * 1000 ) );
|
||||
},
|
||||
0
|
||||
);
|
||||
Reference in New Issue
Block a user