diff --git a/CLAUDE.md b/CLAUDE.md index 0370f4855..323f7491f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -42,12 +42,32 @@ xcodebuild -project Palace.xcodeproj -scheme Palace \ xcodebuild -project Palace.xcodeproj -scheme Palace \ -destination 'platform=iOS Simulator,name=iPhone 16 Pro' test -# Run a single test class +# Run a single test class — SPOT CHECK ONLY, never "validation" (see rule below) xcodebuild -project Palace.xcodeproj -scheme Palace \ -destination 'platform=iOS Simulator,name=iPhone 16 Pro' \ -only-testing:PalaceTests/MyTestClass test ``` +**Local validation MUST run the same full suite CI runs — never a `-only-testing` +subset.** CI executes the whole `Palace` scheme across ALL test targets +(`PalaceTests` + `TenPrintCoverTests`) with `-test-iterations 3 +-retry-tests-on-failure` via `scripts/xcode-test-optimized.sh` (~7k executions). +Before claiming a change is verified / green: +- Run `scripts/xcode-test-optimized.sh` (CI parity) **or** `scripts/verify-pr.sh + --quick` (full-scheme single pass). A `-only-testing:` run is a scoped + spot-check for fast iteration/mutation/debugging — it is NEVER "the suite" and + must never be reported as a full or green pass. +- Confirm the run ended `** TEST SUCCEEDED **` with **no** `exceeded execution + time allowance` or `Restarting after … test timeout` lines. A timeout/restart + is a FAILURE even if the final assertion tally reads "0 failures." +- Read the top-level `Test Suite 'All tests'/'Selected tests'` rollup for the + count; never sum per-suite `Executed N` lines (they double/triple-count). + +Incident (PP-4542, 2026-06-09): a `-only-testing:PalaceTests` run was reported as +"full local suite 2359 / 0 failures, PRs verifiably correct." It was one bundle +(CI runs 7121) AND had actually hung + `** TEST FAILED **`. A subset run, itself +failed, cited as whole-suite green. Don't repeat it. + - Xcode 26, iOS 16.0+ deployment target (CI release path: `macos-26` + `xcode-version: '26'`) - Two targets: `Palace` (full DRM) and `Palace-noDRM` (open-source) - DRM builds run natively on Apple Silicon — Rosetta is no longer required diff --git a/Palace.xcodeproj/project.pbxproj b/Palace.xcodeproj/project.pbxproj index 5457f3a56..b5b64d27c 100644 --- a/Palace.xcodeproj/project.pbxproj +++ b/Palace.xcodeproj/project.pbxproj @@ -458,7 +458,6 @@ 5CDD263A7FFB392F96FA4F03 /* LegacySAMLAuthAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4F432B33DEBD1715E2687C7 /* LegacySAMLAuthAdapter.swift */; }; 5CEBAAD3ED3AF6889F45F38C /* UserAccountPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3332DED915E45913181C82D /* UserAccountPublisherTests.swift */; }; 5CEE9677D8514088B0155974 /* UserRetryTrackerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 978A163EF2834D9087A9CDEB /* UserRetryTrackerTests.swift */; }; - 5D0130219AE7A8767A2B5C3B /* TriageBotCore in Frameworks */ = {isa = PBXBuildFile; productRef = B6BB8DA6C2692FC1F126876A /* TriageBotCore */; }; 5D05015D55C6DE54BB6DB0DA /* TPPSignInBusinessLogicExtendedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DC9FC06EF6CCEED742C0D44 /* TPPSignInBusinessLogicExtendedTests.swift */; }; 5D1B142A22CC179F0006C964 /* TPPAlertUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D1B142922CC179F0006C964 /* TPPAlertUtils.swift */; }; 5D3A28CC22D3DA850042B3BD /* UserProfileDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D3A28CB22D3DA850042B3BD /* UserProfileDocument.swift */; }; @@ -3301,7 +3300,6 @@ 0725F3E330D15C718C170C94 /* PalaceCatalog in Frameworks */, 811E00F5F755273F726941CC /* PalaceAuth in Frameworks */, 89382FCA6DC82E0D3906130A /* PalaceReadingPosition in Frameworks */, - 5D0130219AE7A8767A2B5C3B /* TriageBotCore in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -6317,7 +6315,6 @@ 7081635582F472450D1A1B85 /* PalaceCatalog */, 3D8B27DA10A114B76EFB41A1 /* PalaceAuth */, 5F61B627DDE72CE091C181D5 /* PalaceReadingPosition */, - B6BB8DA6C2692FC1F126876A /* TriageBotCore */, ); productName = SimplyETests; productReference = 2D2B47721D08F807007F7764 /* PalaceTests.xctest */; @@ -9434,11 +9431,6 @@ package = EA43715A76E6B1D14299A3A1 /* XCLocalSwiftPackageReference "PalaceReadingPosition" */; productName = PalaceReadingPosition; }; - B6BB8DA6C2692FC1F126876A /* TriageBotCore */ = { - isa = XCSwiftPackageProductDependency; - package = DAAF04A6D259B174CFB2811E /* XCLocalSwiftPackageReference "PalaceTriageBot" */; - productName = TriageBotCore; - }; B7890FD5F70ADDBD798D7FC7 /* PalaceLogging */ = { isa = XCSwiftPackageProductDependency; package = AD4024ABF2AB1F4247E45BD3 /* XCLocalSwiftPackageReference "PalaceLogging" */; diff --git a/scripts/pre-push-test-gate.sh b/scripts/pre-push-test-gate.sh index 1c9f3d6e0..8fff371ce 100755 --- a/scripts/pre-push-test-gate.sh +++ b/scripts/pre-push-test-gate.sh @@ -165,6 +165,21 @@ else DEST_ARGS=(-destination 'platform=iOS Simulator,name=iPhone 16 Pro') fi +# When the push originates from a LINKED git worktree, the shared default +# DerivedData was resolved against a different checkout root, so xcodebuild's +# SPM graph points at a stale source-package path and package resolution dies +# with "the package manifest at '/Package.swift' doesn't exist" — a false gate +# failure (the worktree itself builds fine with an isolated DerivedData). Scope +# a per-checkout DerivedData + SourcePackages dir for worktree pushes only; the +# main checkout keeps its warm default cache so the gate stays fast there. +declare -a DERIVED_ARGS=() +if [[ "$(git rev-parse --git-common-dir 2>/dev/null)" != "$(git rev-parse --git-dir 2>/dev/null)" ]]; then + _repo_slug="$(printf '%s' "$REPO_DIR" | shasum 2>/dev/null | cut -c1-12)" + _dd_dir="${TMPDIR:-/tmp}/prepush-dd-${_repo_slug:-wt}" + DERIVED_ARGS=(-derivedDataPath "$_dd_dir" -clonedSourcePackagesDirPath "$_dd_dir/SourcePackages") + echo "[pre-push-test-gate] linked worktree detected — isolating DerivedData at $_dd_dir" >&2 +fi + declare -a ONLY_TESTING_ARGS=() for cls in "${CLASSES[@]}"; do ONLY_TESTING_ARGS+=("-only-testing:PalaceTests/$cls") @@ -189,10 +204,21 @@ run_with_timeout() { } # Quiet xcodebuild — we only care about pass/fail at the gate. -if run_with_timeout "$TIMEOUT_SECS" xcodebuild \ +# Clear the git hook environment before xcodebuild. git invokes pre-push +# hooks with GIT_DIR/GIT_WORK_TREE/GIT_INDEX_FILE exported; xcodebuild's SPM +# resolution shells out to git internally, and an inherited GIT_WORK_TREE +# makes the local-package path resolve to "/" — failing with "the package +# manifest at '/Package.swift' doesn't exist". This is THE reason a push from +# a linked worktree (or even the main checkout, under some git versions) hits +# that error while the same xcodebuild run from a plain shell succeeds. +# Unsetting these lets xcodebuild resolve packages against the project root. +if run_with_timeout "$TIMEOUT_SECS" \ + env -u GIT_DIR -u GIT_WORK_TREE -u GIT_INDEX_FILE -u GIT_PREFIX -u GIT_EXEC_PATH \ + xcodebuild \ -project Palace.xcodeproj \ -scheme Palace \ "${DEST_ARGS[@]}" \ + "${DERIVED_ARGS[@]}" \ "${ONLY_TESTING_ARGS[@]}" \ test \ -quiet >/tmp/pre-push-test-gate.log 2>&1; then diff --git a/scripts/xcode-test-optimized.sh b/scripts/xcode-test-optimized.sh index 08d4b7239..498e22bbf 100755 --- a/scripts/xcode-test-optimized.sh +++ b/scripts/xcode-test-optimized.sh @@ -76,6 +76,9 @@ if [ "${BUILD_CONTEXT:-}" == "ci" ]; then -enableCodeCoverage YES \ -retry-tests-on-failure \ -test-iterations 3 \ + -test-timeouts-enabled YES \ + -default-test-execution-time-allowance 120 \ + -maximum-test-execution-time-allowance 300 \ -parallel-testing-enabled NO \ CODE_SIGNING_REQUIRED=NO \ CODE_SIGNING_ALLOWED=NO \