From fc12027df74ab7a5c6ba5abfee9cd0b939408155 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sat, 30 May 2026 08:32:04 +0300 Subject: [PATCH 1/2] ci(ios-metal): enable Metal API validation in screenshot suite The #5103 stencil pixel-format mismatch shipped in 7.0.243-246 because no CI path exercised Apple's Metal validation layer. The mistake was a five-line oversight (a render pass missing a Stencil8 attachment while binding a pipeline that declared one), exactly the class of bug Metal validation flags instantly at setRenderPipelineState:. Catch this class going forward by: 1. Forwarding MTL_DEBUG_LAYER + MTL_DEBUG_LAYER_ERROR_MODE through run-ios-ui-tests.sh -> simctl launch when set in the caller's environment. The plumbing is gated by env-var presence so non-Metal local runs are unaffected. 2. Setting both vars in the build-ios-metal job (assert mode) so any validation error crashes the app and the suite's missed CN1SS:SUITE:FINISHED marker fails the step. The GL job is left untouched because validation only fires under the Metal layer. Static audit before flipping: scanned every MTLRenderPassDescriptor and setRenderPipelineState: call site in CN1Metalcompat.m, CN1MetalPipelineCache.m, and METALView.m. The post-#5103-fix code is consistent (all sites that bind a pipeline attach a Stencil8 texture; the framebufferOnly assertion noted at METALView.m:145 was already addressed by metalLayer.framebufferOnly=NO on line 149), so the first CI run on this branch should pass cleanly. If it doesn't, validation has surfaced a sibling latent bug, which is exactly the point. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/scripts-ios.yml | 10 ++++++++++ scripts/run-ios-ui-tests.sh | 20 +++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/.github/workflows/scripts-ios.yml b/.github/workflows/scripts-ios.yml index 3c8b0b6315..37d21d3026 100644 --- a/.github/workflows/scripts-ios.yml +++ b/.github/workflows/scripts-ios.yml @@ -420,6 +420,16 @@ jobs: CN1SS_REPORT_TITLE: 'iOS Metal screenshot updates' CN1SS_SUCCESS_MESSAGE: '✅ Native iOS Metal screenshot tests passed.' CN1SS_COMMENT_LOG_PREFIX: '[run-ios-device-tests-metal]' + # Enable Apple's Metal API Validation in the simulator. Catches + # render-pass / pipeline-state mismatches at the moment they + # happen (issue #5103: stencil pixel-format mismatch shipped in + # four consecutive 7.0.x releases because no test exercised the + # validation layer). assert mode crashes the app on the first + # validation error -- the missed CN1SS:SUITE:FINISHED marker + # then fails this step. run-ios-ui-tests.sh forwards both vars + # to the launched app via simctl --setenv. + MTL_DEBUG_LAYER: '1' + MTL_DEBUG_LAYER_ERROR_MODE: 'assert' run: | set -euo pipefail mkdir -p "${ARTIFACTS_DIR}" diff --git a/scripts/run-ios-ui-tests.sh b/scripts/run-ios-ui-tests.sh index 6d1400154b..b83522564a 100755 --- a/scripts/run-ios-ui-tests.sh +++ b/scripts/run-ios-ui-tests.sh @@ -625,13 +625,31 @@ APP_PROCESS_NAME="${WRAPPER_NAME%.app}" LAUNCH_LOG="$ARTIFACTS_DIR/simctl-launch.log" + # Thread Metal validation env vars (if set in the caller's environment) + # through to the launched app via simctl --setenv. Empty when neither + # MTL_DEBUG_LAYER nor MTL_DEBUG_LAYER_ERROR_MODE is set, so non-Metal + # local runs are unaffected. CI's Metal job sets both at the step level + # so iOS render-pass/pipeline-state mismatches (issue #5103) abort the + # app immediately instead of producing undefined behaviour off-CI. + LAUNCH_ENV_ARGS=() + if [ -n "${MTL_DEBUG_LAYER:-}" ]; then + LAUNCH_ENV_ARGS+=( "--setenv" "MTL_DEBUG_LAYER=${MTL_DEBUG_LAYER}" ) + ri_log "Forwarding MTL_DEBUG_LAYER=${MTL_DEBUG_LAYER} to simulator app" + fi + if [ -n "${MTL_DEBUG_LAYER_ERROR_MODE:-}" ]; then + LAUNCH_ENV_ARGS+=( "--setenv" "MTL_DEBUG_LAYER_ERROR_MODE=${MTL_DEBUG_LAYER_ERROR_MODE}" ) + ri_log "Forwarding MTL_DEBUG_LAYER_ERROR_MODE=${MTL_DEBUG_LAYER_ERROR_MODE} to simulator app" + fi + launch_simulator_app() { local target="$1" local attempt=1 local max_attempts=5 while true; do local output - if output="$(xcrun simctl launch "$target" "$BUNDLE_IDENTIFIER" 2>&1)"; then + # "${LAUNCH_ENV_ARGS[@]+"${LAUNCH_ENV_ARGS[@]}"}" is the bash-3.2-safe + # idiom for expanding an array that may be empty under set -u. + if output="$(xcrun simctl launch "${LAUNCH_ENV_ARGS[@]+"${LAUNCH_ENV_ARGS[@]}"}" "$target" "$BUNDLE_IDENTIFIER" 2>&1)"; then printf '%s\n' "$output" >> "$LAUNCH_LOG" return 0 fi From 37dd80f5316ad1146f67feec44fd4b47e70f37cc Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sat, 30 May 2026 09:42:49 +0300 Subject: [PATCH 2/2] ci(ios-metal): use SIMCTL_CHILD_ env-var forwarding, not --setenv simctl launch on Xcode 26 does not accept a --setenv flag (the help output for `xcrun simctl help launch` documents none); the run on the previous commit failed five retries with "Invalid device: --setenv" because simctl interpreted --setenv as a positional device argument. Apple's documented mechanism is to export SIMCTL_CHILD_= in the shell that invokes simctl, which the launch helper unwraps into = for the child process. Swap the LAUNCH_ENV_ARGS array plumbing for two `export` statements guarded by the same MTL_DEBUG_LAYER / MTL_DEBUG_LAYER_ERROR_MODE checks. The simctl launch line goes back to its original form. Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/run-ios-ui-tests.sh | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/scripts/run-ios-ui-tests.sh b/scripts/run-ios-ui-tests.sh index b83522564a..2f0a8f180a 100755 --- a/scripts/run-ios-ui-tests.sh +++ b/scripts/run-ios-ui-tests.sh @@ -626,19 +626,21 @@ APP_PROCESS_NAME="${WRAPPER_NAME%.app}" LAUNCH_LOG="$ARTIFACTS_DIR/simctl-launch.log" # Thread Metal validation env vars (if set in the caller's environment) - # through to the launched app via simctl --setenv. Empty when neither - # MTL_DEBUG_LAYER nor MTL_DEBUG_LAYER_ERROR_MODE is set, so non-Metal - # local runs are unaffected. CI's Metal job sets both at the step level - # so iOS render-pass/pipeline-state mismatches (issue #5103) abort the - # app immediately instead of producing undefined behaviour off-CI. - LAUNCH_ENV_ARGS=() + # through to the launched app. simctl on Xcode 26 does NOT take a + # --setenv flag (`xcrun simctl help launch` confirms); the documented + # mechanism is exporting SIMCTL_CHILD_= in the shell that + # invokes simctl, which the launch helper unwraps into = + # for the child. CI's Metal job sets MTL_DEBUG_LAYER / + # MTL_DEBUG_LAYER_ERROR_MODE at the step level so iOS render-pass / + # pipeline-state mismatches (issue #5103) abort the app immediately + # instead of producing undefined behaviour off-CI. if [ -n "${MTL_DEBUG_LAYER:-}" ]; then - LAUNCH_ENV_ARGS+=( "--setenv" "MTL_DEBUG_LAYER=${MTL_DEBUG_LAYER}" ) - ri_log "Forwarding MTL_DEBUG_LAYER=${MTL_DEBUG_LAYER} to simulator app" + export SIMCTL_CHILD_MTL_DEBUG_LAYER="${MTL_DEBUG_LAYER}" + ri_log "Forwarding MTL_DEBUG_LAYER=${MTL_DEBUG_LAYER} to simulator app (via SIMCTL_CHILD_)" fi if [ -n "${MTL_DEBUG_LAYER_ERROR_MODE:-}" ]; then - LAUNCH_ENV_ARGS+=( "--setenv" "MTL_DEBUG_LAYER_ERROR_MODE=${MTL_DEBUG_LAYER_ERROR_MODE}" ) - ri_log "Forwarding MTL_DEBUG_LAYER_ERROR_MODE=${MTL_DEBUG_LAYER_ERROR_MODE} to simulator app" + export SIMCTL_CHILD_MTL_DEBUG_LAYER_ERROR_MODE="${MTL_DEBUG_LAYER_ERROR_MODE}" + ri_log "Forwarding MTL_DEBUG_LAYER_ERROR_MODE=${MTL_DEBUG_LAYER_ERROR_MODE} to simulator app (via SIMCTL_CHILD_)" fi launch_simulator_app() { @@ -647,9 +649,7 @@ APP_PROCESS_NAME="${WRAPPER_NAME%.app}" local max_attempts=5 while true; do local output - # "${LAUNCH_ENV_ARGS[@]+"${LAUNCH_ENV_ARGS[@]}"}" is the bash-3.2-safe - # idiom for expanding an array that may be empty under set -u. - if output="$(xcrun simctl launch "${LAUNCH_ENV_ARGS[@]+"${LAUNCH_ENV_ARGS[@]}"}" "$target" "$BUNDLE_IDENTIFIER" 2>&1)"; then + if output="$(xcrun simctl launch "$target" "$BUNDLE_IDENTIFIER" 2>&1)"; then printf '%s\n' "$output" >> "$LAUNCH_LOG" return 0 fi