diff --git a/tests/composable-caller-containment-smoke.php b/tests/composable-caller-containment-smoke.php index eae4b8aacf..a94c7bac6e 100644 --- a/tests/composable-caller-containment-smoke.php +++ b/tests/composable-caller-containment-smoke.php @@ -13,6 +13,30 @@ } define( 'ABSPATH', __DIR__ ); + +/** + * Whether a process is still running. + * + * An orphan reparented to a non-reaping subreaper (common under CI runners) + * stays a zombie after it exits, and signal 0 still succeeds on zombies. + * + * @param int $pid Process ID. + * @return bool + */ +function datamachine_compose_containment_is_running( int $pid ): bool { + if ( ! @posix_kill( $pid, 0 ) ) { + return false; + } + + $stat = @file_get_contents( "/proc/{$pid}/stat" ); + if ( false === $stat ) { + return true; + } + + // The state field follows the parenthesized command name. + $state = substr( (string) strrchr( $stat, ')' ), 2, 1 ); + return 'Z' !== $state && 'X' !== $state; +} require_once dirname( __DIR__ ) . '/inc/Cli/CallerLivenessMonitor.php'; use DataMachine\Cli\CallerLivenessMonitor; @@ -73,7 +97,7 @@ $deadline = microtime( true ) + 4.0; do { - if ( ! @posix_kill( $compose_pid, 0 ) && ! @posix_kill( $worker_pid, 0 ) ) { + if ( ! datamachine_compose_containment_is_running( $compose_pid ) && ! datamachine_compose_containment_is_running( $worker_pid ) ) { @unlink( $state_file ); echo "OK (compose process group terminated after caller death)\n"; exit( 0 ); diff --git a/tests/flow-formatter-loaded-settings-display-smoke.php b/tests/flow-formatter-loaded-settings-display-smoke.php index 21f7f9adea..ac17e225a5 100644 --- a/tests/flow-formatter-loaded-settings-display-smoke.php +++ b/tests/flow-formatter-loaded-settings-display-smoke.php @@ -35,33 +35,35 @@ function do_action( string $hook, ...$args ): void { } } - if ( ! function_exists( 'apply_filters' ) ) { - function apply_filters( string $hook, $value, ...$args ) { - if ( 'datamachine_step_types' === $hook ) { - return array( - 'fetch' => array( - 'uses_handler' => true, - 'multi_handler' => false, - 'show_settings_display' => true, - ), - 'publish' => array( - 'uses_handler' => true, - 'multi_handler' => true, - 'show_settings_display' => true, - ), - ); - } - - if ( 'datamachine_handler_settings' === $hook ) { - $handler_slug = $args[0] ?? ''; - return array( - $handler_slug => new FlowFormatterLoadedConfigSettings(), - ); - } - - return $value; + // The shared smoke stubs own apply_filters(); register this smoke's step + // types and handler settings through the stubbed filter registry. + add_filter( + 'datamachine_step_types', + static function () { + return array( + 'fetch' => array( + 'uses_handler' => true, + 'multi_handler' => false, + 'show_settings_display' => true, + ), + 'publish' => array( + 'uses_handler' => true, + 'multi_handler' => true, + 'show_settings_display' => true, + ), + ); } - } + ); + add_filter( + 'datamachine_handler_settings', + static function ( $value, $handler_slug = '' ) { + return array( + $handler_slug => new FlowFormatterLoadedConfigSettings(), + ); + }, + 10, + 2 + ); if ( ! function_exists( 'get_option' ) ) { function get_option( string $option, $default = false ) { diff --git a/tests/runtime-vocabulary-smoke.php b/tests/runtime-vocabulary-smoke.php index f8bf2223c6..b09b2a6179 100644 --- a/tests/runtime-vocabulary-smoke.php +++ b/tests/runtime-vocabulary-smoke.php @@ -79,6 +79,8 @@ function datamachine_runtime_vocab_is_allowed_file( string $relative_path ): boo 'tests/ai-concurrency-release-wakeup-smoke.php', 'tests/agent-daily-memory-pipeline-scope-smoke.php', 'tests/eslint-source-inventory-smoke.sh', + 'tests/cli-global-options-smoke.php', + 'tests/email-multi-inbox-forward-smoke.php', 'tests/phpunit-file-isolation-smoke.php', 'tests/typed-artifact-handler-smoke.php', 'tests/fuzz/README.md', @@ -94,6 +96,28 @@ function datamachine_runtime_vocab_is_allowed_file( string $relative_path ): boo return str_starts_with( $relative_path, '.github/workflows/' ); } +/** + * Git-tracked first-party paths, or null when git is unavailable. + * + * Runners drop untracked files into the checkout (for example `.homeboy-bin/` + * and shard plans). Those belong to the host, not to first-party source. + * + * @param string $root Repository root. + * @return array|null + */ +function datamachine_runtime_vocab_tracked_files( string $root ): ?array { + $output = array(); + $status = 1; + exec( 'git -C ' . escapeshellarg( $root ) . ' ls-files 2>/dev/null', $output, $status ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.system_calls_exec -- CLI-only smoke reads the git index. + if ( 0 !== $status || array() === $output ) { + return null; + } + + return array_fill_keys( $output, true ); +} + +$tracked_files = datamachine_runtime_vocab_tracked_files( $root ); + $host_specific_patterns = array( 'wp-site-generator' => '/wp-site-generator/i', 'wpsg' => '/\bwpsg\b/i', @@ -125,6 +149,11 @@ function ( SplFileInfo $file ) use ( $root ): bool { continue; } + // Vendored Agents API tests are scanned on purpose; other untracked files are runner output. + if ( null !== $tracked_files && ! isset( $tracked_files[ $relative_path ] ) && ! str_starts_with( $relative_path, 'vendor/' ) ) { + continue; + } + $contents = file_get_contents( $path ); if ( false === $contents ) { continue;