Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ios-appstore-upload.yml
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,7 @@ jobs:
# job fail. Notes timeouts must not turn an accepted upload into a failure.
if: ${{ !cancelled() && needs.upload.result == 'success' && needs.upload.outputs.notes_artifact_id != '' }}
runs-on: ${{ github.repository_owner != 'manaflow-ai' && 'ubuntu-24.04' || vars.LINUX_RUNNER || 'blacksmith-4vcpu-ubuntu-2404' }}
timeout-minutes: 20
timeout-minutes: 40
continue-on-error: true
permissions:
contents: read
Expand Down
3 changes: 2 additions & 1 deletion ios/scripts/asc_assign_internal_testflight_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import urllib.request

API_BASE = "https://api.appstoreconnect.apple.com"
DEFAULT_PROCESSING_TIMEOUT_SECONDS = 1800


def _b64u(data: bytes) -> bytes:
Expand Down Expand Up @@ -266,7 +267,7 @@ def main() -> int:
parser.add_argument("--build-number", required=True, help="CFBundleVersion of the uploaded build")
parser.add_argument("--group-id", default=os.environ.get("CMUX_TESTFLIGHT_INTERNAL_GROUP_ID", ""))
parser.add_argument("--group-name", default=os.environ.get("CMUX_TESTFLIGHT_INTERNAL_GROUP_NAME", ""))
parser.add_argument("--timeout-seconds", type=int, default=900)
parser.add_argument("--timeout-seconds", type=int, default=DEFAULT_PROCESSING_TIMEOUT_SECONDS)
parser.add_argument("--poll-seconds", type=int, default=20)
parser.add_argument("--state-out", default=os.environ.get("CMUX_TESTFLIGHT_ASSIGN_STATE_OUT_FILE", ""))
args = parser.parse_args()
Expand Down
3 changes: 2 additions & 1 deletion ios/scripts/asc_set_testflight_notes.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import urllib.request

API_BASE = "https://api.appstoreconnect.apple.com"
DEFAULT_PROCESSING_TIMEOUT_SECONDS = 1800

# App Store Connect caps What to Test at 4000 characters. Truncate defensively so
# a long changelog block never makes the PATCH/POST fail with a validation error.
Expand Down Expand Up @@ -232,7 +233,7 @@ def main():
parser.add_argument("--build-number", required=True, help="CFBundleVersion of the uploaded build")
parser.add_argument("--notes-file", required=True, help="file containing the What to Test text")
parser.add_argument("--locale", default="en-US")
parser.add_argument("--timeout-seconds", type=int, default=900,
parser.add_argument("--timeout-seconds", type=int, default=DEFAULT_PROCESSING_TIMEOUT_SECONDS,
help="how long to wait for App Store Connect to ingest the build")
parser.add_argument("--poll-seconds", type=int, default=20)
args = parser.parse_args()
Expand Down
4 changes: 2 additions & 2 deletions ios/scripts/set-testflight-notes.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ set -euo pipefail
# [--audience internal|external] [--bundle-id dev.cmux.app.beta] \
# [--changelog <path>] [--locale en-US] \
# [--expect-marketing-version X.Y.Z] \
# [--notes "literal override text"] [--timeout-seconds 900]
# [--notes "literal override text"] [--timeout-seconds 1800]
#
# --expect-marketing-version asserts the changelog TOP entry's version equals the
# build's marketing version, so notes for the wrong version are never published
Expand All @@ -45,7 +45,7 @@ BUNDLE_ID="${IOS_BETA_BUNDLE_ID:-dev.cmux.app.beta}"
CHANGELOG="$IOS_DIR/CHANGELOG.md"
LOCALE="en-US"
NOTES_OVERRIDE=""
TIMEOUT_SECONDS="900"
TIMEOUT_SECONDS="${CMUX_TESTFLIGHT_PROCESSING_TIMEOUT_SECONDS:-1800}"
# When set, the TOP changelog version MUST equal this (the MARKETING_VERSION of
# the build being annotated). Guards against attaching e.g. 1.0.3 notes to a build
# archived as 1.0.0 when a cut forgets to bump the version. upload-testflight.sh
Expand Down
22 changes: 22 additions & 0 deletions tests/test_ios_testflight_notes.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@

REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SCRIPT = os.path.join(REPO_ROOT, "ios", "scripts", "generate-testflight-notes.sh")
ASSIGN_SCRIPT = os.path.join(REPO_ROOT, "ios", "scripts", "asc_assign_internal_testflight_group.py")
SET_NOTES_SCRIPT = os.path.join(REPO_ROOT, "ios", "scripts", "asc_set_testflight_notes.py")
SET_NOTES_SH = os.path.join(REPO_ROOT, "ios", "scripts", "set-testflight-notes.sh")
APPSTORE_WORKFLOW = os.path.join(REPO_ROOT, ".github", "workflows", "ios-appstore-upload.yml")

FAILURES = []

Expand Down Expand Up @@ -122,8 +126,26 @@ def test_deferred_notes():
_check(result.returncode == 0 and "warning:" in result.stderr, "notes timeout remains nonfatal after upload")


def test_processing_wait_defaults_cover_apple_ingest():
"""Internal TestFlight jobs tolerate Apple's delayed build visibility."""
assign_source = Path(ASSIGN_SCRIPT).read_text(encoding="utf-8")
notes_source = Path(SET_NOTES_SCRIPT).read_text(encoding="utf-8")
shell_source = Path(SET_NOTES_SH).read_text(encoding="utf-8")
workflow_source = Path(APPSTORE_WORKFLOW).read_text(encoding="utf-8")
notes_job = workflow_source.split(" set-testflight-notes:", 1)[1].split(" assign-internal:", 1)[0]
_check("DEFAULT_PROCESSING_TIMEOUT_SECONDS = 1800" in assign_source,
"internal group assignment waits 30 minutes by default")
_check("DEFAULT_PROCESSING_TIMEOUT_SECONDS = 1800" in notes_source,
"TestFlight notes wait 30 minutes by default")
_check('TIMEOUT_SECONDS="${CMUX_TESTFLIGHT_PROCESSING_TIMEOUT_SECONDS:-1800}"' in shell_source,
"shell notes setter shares the 30-minute processing default")
_check("timeout-minutes: 40" in notes_job,
"notes job remains alive for the full processing wait")


def main():
test_deferred_notes()
test_processing_wait_defaults_cover_apple_ingest()
_check(os.access(SCRIPT, os.X_OK), "generator script is executable")

with tempfile.TemporaryDirectory() as repo:
Expand Down
Loading