@@ -58,6 +58,10 @@ function wp_mkdir_p( string $path ): bool
5858 }
5959 }
6060
61+ if (! defined ('ARRAY_A ' ) ) {
62+ define ('ARRAY_A ' , 'ARRAY_A ' );
63+ }
64+
6165 if (! function_exists ('wp_json_encode ' ) ) {
6266 function wp_json_encode ( $ data , int $ flags = 0 )
6367 {
@@ -118,6 +122,21 @@ public function get_col( string $sql ): array
118122 return array_values (array_unique (array_filter ($ keys )));
119123 }
120124
125+ /**
126+ * @return array<string,mixed>|null
127+ */
128+ public function get_row ( string $ sql , string $ output ): ?array // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
129+ {
130+ $ rows = $ this ->matching_rows ($ sql );
131+ return empty ($ rows ) ? null : $ rows [count ($ rows ) - 1 ];
132+ }
133+
134+ public function query ( string $ sql ): int // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
135+ {
136+ $ this ->rows_affected = 0 ;
137+ return 0 ;
138+ }
139+
121140 public function prepare ( string $ query , mixed ...$ args ): string
122141 {
123142 foreach ( $ args as $ arg ) {
@@ -158,6 +177,8 @@ static function ( array $row ) use ( $sql, $now ): bool {
158177 }
159178 }
160179
180+ class DataMachineCode_Fake_WPDB extends Workspace_Mutation_Lock_Test_Wpdb {}
181+
161182 include __DIR__ . '/../inc/Workspace/WorkspaceLockStore.php ' ;
162183 include __DIR__ . '/../inc/Workspace/WorkspaceMutationLock.php ' ;
163184
@@ -195,10 +216,11 @@ function () use ( $tmp ) {
195216 $ assert (1 , (int ) $ status ['active ' ], 'held filesystem lock is visible in aggregate active count ' );
196217 $ assert (false , (bool ) $ status ['database ' ]['available ' ], 'DB lock store reports unavailable in pure-PHP smoke context ' );
197218
198- $ busy = \DataMachineCode \Workspace \WorkspaceMutationLock::acquire ($ tmp , 'demo ' , 0 );
199- $ assert (true , is_wp_error ($ busy ), 'same repo acquisition fails fast while held ' );
200- $ assert ('workspace_repo_busy ' , is_wp_error ($ busy ) ? $ busy ->get_error_code () : '' , 'busy failure uses DMC-shaped retryable code ' );
201- $ assert (true , is_wp_error ($ busy ) ? (bool ) ( $ busy ->get_error_data ()['retryable ' ] ?? false ) : false , 'busy failure is marked retryable ' );
219+ $ busy = \DataMachineCode \Workspace \WorkspaceMutationLock::acquire ($ tmp , 'demo ' , 0 );
220+ $ assert (true , is_wp_error ($ busy ), 'same repo acquisition fails fast while held ' );
221+ $ assert ('workspace_repo_busy ' , is_wp_error ($ busy ) ? $ busy ->get_error_code () : '' , 'busy failure uses DMC-shaped retryable code ' );
222+ $ assert (true , is_wp_error ($ busy ) ? (bool ) ( $ busy ->get_error_data ()['retryable ' ] ?? false ) : false , 'busy failure is marked retryable ' );
223+ $ assert ('worktree-demo ' , is_wp_error ($ busy ) ? (string ) ( $ busy ->get_error_data ()['lock_key ' ] ?? '' ) : '' , 'busy failure includes lock key ' );
202224
203225 $ other = \DataMachineCode \Workspace \WorkspaceMutationLock::acquire ($ tmp , 'other ' , 0 );
204226 $ assert (false , is_wp_error ($ other ), 'different repo acquisition is independent ' );
@@ -272,10 +294,37 @@ function () {
272294
273295 $ post_exception = \DataMachineCode \Workspace \WorkspaceMutationLock::acquire ($ tmp , 'demo ' , 0 );
274296 $ assert (false , is_wp_error ($ post_exception ), 'with_repo releases after callback exception ' );
275- if (! is_wp_error ($ post_exception ) ) {
276- $ post_exception ->release ();
277- }
297+ if (! is_wp_error ($ post_exception ) ) {
298+ $ post_exception ->release ();
299+ }
300+
301+ $ GLOBALS ['wpdb ' ] = new DataMachineCode_Fake_WPDB ();
302+ $ db_tmp = sys_get_temp_dir () . '/dmc-workspace-lock-db-smoke- ' . bin2hex (random_bytes (4 ));
303+ mkdir ($ db_tmp , 0755 , true );
304+ register_shutdown_function (
305+ function () use ( $ db_tmp ) {
306+ if (is_dir ($ db_tmp ) ) {
307+ exec ('rm -rf ' . escapeshellarg ($ db_tmp ));
308+ }
309+ }
310+ );
311+ $ db_first = \DataMachineCode \Workspace \WorkspaceMutationLock::acquire ($ db_tmp , 'demo ' , 1 );
312+ $ db_busy = \DataMachineCode \Workspace \WorkspaceMutationLock::acquire ($ db_tmp , 'demo ' , 0 );
313+ $ db_data = is_wp_error ($ db_busy ) ? $ db_busy ->get_error_data () : array ();
314+ $ assert (true , is_wp_error ($ db_busy ), 'DB-backed same repo acquisition fails fast while held ' );
315+ $ assert ('demo ' , (string ) ( $ db_data ['active_lock ' ]['scope ' ] ?? '' ), 'busy failure includes active DB lock scope ' );
316+ $ assert ('worktree-demo ' , (string ) ( $ db_data ['active_lock ' ]['lock_key ' ] ?? '' ), 'busy failure includes active DB lock key ' );
317+ $ assert (true , isset ($ db_data ['active_lock ' ]['owner ' ]), 'busy failure includes active DB lock owner ' );
318+ $ assert (true , isset ($ db_data ['active_lock ' ]['acquired_at ' ]), 'busy failure includes acquired timestamp ' );
319+ $ assert (true , isset ($ db_data ['active_lock ' ]['heartbeat_at ' ]), 'busy failure includes heartbeat timestamp ' );
320+ $ assert (true , isset ($ db_data ['active_lock ' ]['expires_at ' ]), 'busy failure includes expires timestamp ' );
321+ $ assert (true , isset ($ db_data ['active_lock ' ]['retry_after_seconds ' ]), 'busy failure includes retry-after seconds ' );
322+ $ assert (true , isset ($ db_data ['active_lock ' ]['age_seconds ' ]), 'busy failure includes age seconds ' );
323+ $ assert (true , isset ($ db_data ['active_lock ' ]['metadata ' ]['owner_context ' ]), 'busy failure includes owner context metadata ' );
324+ if (! is_wp_error ($ db_first ) ) {
325+ $ db_first ->release ();
326+ }
278327
279- echo "\nResult: " . ( $ total - $ failures ) . "/ {$ total } passed \n" ;
328+ echo "\nResult: " . ( $ total - $ failures ) . "/ {$ total } passed \n" ;
280329 exit ($ failures > 0 ? 1 : 0 );
281330}
0 commit comments