mirror of
https://github.com/feder-cr/invisible_playwright.git
synced 2026-06-13 08:55:12 +02:00
ci: $0 5-target GitHub Actions release pipeline + wrapper macOS/arm64 support
release.yml builds linux-x64/arm64 + win-x64 (cross) on free Linux runners and macos-arm64/x64 on native Mac runners; packages per the wrapper contract (juggler-gated so binaries are Playwright-drivable, issue-#14 symlink-safe via cp -aL), validate_release.py gate, ad-hoc macOS codesign, DRAFT publish. constants.py: arm64 + darwin ARCHIVE_NAME + BINARY_ENTRY_REL (Firefox.app). download.py: macOS post-extract xattr quarantine strip. BINARY_VERSION unchanged (firefox-8); the juggler-fixed firefox-9 is a separate release cut + pin bump.
This commit is contained in:
parent
215b8801d7
commit
eec373a719
5 changed files with 365 additions and 16 deletions
307
.github/workflows/release.yml
vendored
Normal file
307
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# release.yml — build all 5 patched-Firefox targets at $0 and publish them as
|
||||
# DRAFT GitHub Release assets, named per the wrapper contract (constants.ARCHIVE_NAME).
|
||||
# DRAFT on purpose: a human runs the realness gate and only THEN un-drafts + bumps
|
||||
# BINARY_VERSION. Nothing auto-ships (issue #14 lesson).
|
||||
#
|
||||
# PACKAGING (issue #14: dangling symlinks broke 265 downloads — never again):
|
||||
# Linux → cp -aL (dereference ALL symlinks into real files) + rm dev tools +
|
||||
# strip + sanitize + tar at ROOT, then validate_release.py as a HARD
|
||||
# in-pipeline gate (the exact battle-tested script from the source repo).
|
||||
# Win → mach package; zip the CONTENTS of dist/firefox (clean tree, NOT
|
||||
# dist/bin) so firefox.exe sits at the zip ROOT. Runtime-gated on a real
|
||||
# windows-latest runner (headless screenshot).
|
||||
# macOS → mach package; ad-hoc codesign the .app; PRESERVE its internal relative
|
||||
# symlinks (a .app legitimately has them — cp -aL would break it); verify
|
||||
# every symlink is relative+internal; tar the bundle. --version self-gate.
|
||||
#
|
||||
# Trigger: push a tag `firefox-N`, or run manually. Hybrid runners, all free.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
name: release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ['firefox-*']
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
source_ref:
|
||||
description: 'invisible_firefox ref to build'
|
||||
default: 'stealth/150'
|
||||
release_tag:
|
||||
description: 'release tag to publish the draft under (e.g. firefox-9)'
|
||||
required: true
|
||||
|
||||
env:
|
||||
SOURCE_REPO: feder-cr/invisible_firefox
|
||||
SOURCE_REF: ${{ github.event.inputs.source_ref || 'stealth/150' }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: build-${{ matrix.leg }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 350
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- leg: linux-x86_64
|
||||
runner: ubuntu-24.04
|
||||
family: linux
|
||||
target: ''
|
||||
rust_target: x86_64-unknown-linux-gnu
|
||||
win_disables: 'no'
|
||||
extra_pkgs: ''
|
||||
asset: firefox-150.0.1-stealth-linux-x86_64.tar.gz
|
||||
- leg: linux-arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
family: linux
|
||||
target: ''
|
||||
rust_target: aarch64-unknown-linux-gnu
|
||||
win_disables: 'no'
|
||||
extra_pkgs: ''
|
||||
asset: firefox-150.0.1-stealth-linux-arm64.tar.gz
|
||||
- leg: win-x86_64
|
||||
runner: ubuntu-24.04
|
||||
family: win
|
||||
target: x86_64-pc-windows-msvc
|
||||
rust_target: x86_64-pc-windows-msvc
|
||||
win_disables: 'yes'
|
||||
extra_pkgs: 'msitools p7zip-full zip'
|
||||
asset: firefox-150.0.1-stealth-win-x86_64.zip
|
||||
- leg: macos-arm64
|
||||
runner: macos-15
|
||||
family: mac
|
||||
target: aarch64-apple-darwin
|
||||
rust_target: aarch64-apple-darwin
|
||||
win_disables: 'no'
|
||||
extra_pkgs: ''
|
||||
asset: firefox-150.0.1-stealth-macos-arm64.tar.gz
|
||||
- leg: macos-x86_64
|
||||
runner: macos-15-intel
|
||||
family: mac
|
||||
target: x86_64-apple-darwin
|
||||
rust_target: x86_64-apple-darwin
|
||||
win_disables: 'no'
|
||||
extra_pkgs: ''
|
||||
asset: firefox-150.0.1-stealth-macos-x86_64.tar.gz
|
||||
steps:
|
||||
- name: Free disk + 16G swap (Linux runners)
|
||||
if: matrix.family != 'mac'
|
||||
run: |
|
||||
sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android \
|
||||
/usr/local/share/boost "${AGENT_TOOLSDIRECTORY:-/opt/hostedtoolcache}" 2>/dev/null || true
|
||||
sudo fallocate -l 16G /swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile || true
|
||||
|
||||
- name: Checkout patched Firefox source
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ${{ env.SOURCE_REPO }}
|
||||
ref: ${{ env.SOURCE_REF }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with: { python-version: '3.11' }
|
||||
|
||||
- name: Install Linux build tools
|
||||
if: matrix.family != 'mac'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y util-linux binutils ${{ matrix.extra_pkgs }}
|
||||
|
||||
- name: Select Xcode 26.2 + export SDK path (macOS)
|
||||
if: matrix.family == 'mac'
|
||||
run: |
|
||||
sudo xcode-select -s /Applications/Xcode_26.2.app
|
||||
SDKP="$(xcrun --show-sdk-path)"
|
||||
echo "SDK_PATH=$SDKP" >> "$GITHUB_ENV"
|
||||
echo "macOS SDK $(xcrun --sdk macosx --show-sdk-version) at $SDKP"
|
||||
|
||||
- name: Add Rust target
|
||||
run: rustup target add ${{ matrix.rust_target }} || true
|
||||
|
||||
- name: Extend the repo .mozconfig (NO mold; +target/SDK as needed)
|
||||
run: |
|
||||
test -f .mozconfig || { echo "ERROR: no .mozconfig in source"; exit 1; }
|
||||
rm -f mozconfig
|
||||
{
|
||||
echo ""
|
||||
echo "# --- release CI levers for ${{ matrix.leg }} (mold intentionally OFF — it segfaults libxul) ---"
|
||||
echo "ac_add_options --disable-debug-symbols"
|
||||
} >> .mozconfig
|
||||
if [ -n "${{ matrix.target }}" ]; then echo "ac_add_options --target=${{ matrix.target }}" >> .mozconfig; fi
|
||||
if [ "${{ matrix.family }}" = "mac" ]; then echo "ac_add_options --with-macos-sdk=$SDK_PATH" >> .mozconfig; fi
|
||||
if [ "${{ matrix.win_disables }}" = "yes" ]; then
|
||||
{ echo "ac_add_options --disable-default-browser-agent";
|
||||
echo "ac_add_options --disable-maintenance-service";
|
||||
echo "ac_add_options --disable-update-agent"; } >> .mozconfig
|
||||
fi
|
||||
if [ "${{ matrix.family }}" = "mac" ]; then NCPU=$(sysctl -n hw.ncpu); else NCPU=4; fi
|
||||
{ echo "mk_add_options MOZ_PARALLEL_BUILD=$NCPU";
|
||||
echo "mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-rel"; } >> .mozconfig
|
||||
echo "----- final .mozconfig -----"; cat .mozconfig
|
||||
|
||||
- name: Build
|
||||
run: ./mach build
|
||||
|
||||
# ── LINUX: dereference symlinks (issue #14) + strip + sanitize + tar@root + GATE
|
||||
- name: Package + validate (Linux)
|
||||
if: matrix.family == 'linux'
|
||||
run: |
|
||||
set -e
|
||||
DIST=obj-rel/dist/bin
|
||||
STAGING=staging
|
||||
rm -rf "$STAGING"; mkdir -p "$STAGING" out
|
||||
cp -aL "$DIST/." "$STAGING/" # -L: dereference ALL symlinks into real files
|
||||
N=$(find "$STAGING" -type l | wc -l)
|
||||
[ "$N" -eq 0 ] || { echo "ERROR: $N symlinks remain after cp -aL"; exit 1; }
|
||||
for t in xpcshell certutil pk12util rapl; do rm -f "$STAGING/$t"; done
|
||||
# JUGGLER GATE: the binary is undrivable by Playwright without it (see 70-known-bugs)
|
||||
{ [ -e "$STAGING/chrome/juggler.manifest" ] && [ -d "$STAGING/chrome/juggler" ]; } \
|
||||
|| { echo "ERROR: juggler missing from package (chrome/juggler) — Playwright can't drive it"; exit 1; }
|
||||
echo "juggler GATE OK (loose chrome/juggler present)"
|
||||
find "$STAGING" -type f \
|
||||
\( -name '*.so' -o -name firefox -o -name firefox-bin -o -name plugin-container \
|
||||
-o -name pingsender -o -name glxtest -o -name vaapitest -o -name updater \) \
|
||||
-exec strip --strip-debug {} + 2>/dev/null || true
|
||||
STAGING="$STAGING" python3 scripts/linux_sanitize.py || true # no-op in CI (no /home/feder), defensive
|
||||
tar --owner=0 --group=0 --numeric-owner --mtime="2026-01-01 00:00:00 UTC" \
|
||||
-czf "out/${{ matrix.asset }}" -C "$STAGING" . # firefox at ROOT
|
||||
echo "=== HARD GATE: scripts/validate_release.py (the issue-#14 protector) ==="
|
||||
python3 scripts/validate_release.py --linux "out/${{ matrix.asset }}" --linux-only
|
||||
ls -la out/
|
||||
|
||||
# ── WINDOWS (cross): zip the CLEAN dist/firefox tree, firefox.exe at root
|
||||
- name: Package (Windows cross)
|
||||
if: matrix.family == 'win'
|
||||
run: |
|
||||
set -e
|
||||
./mach package || echo "mach package rc=$? (continuing to locate the app tree)"
|
||||
# Prefer the clean packaged tree; fall back to dist/bin if cross didn't produce dist/firefox
|
||||
if [ -f obj-rel/dist/firefox/firefox.exe ]; then WIN_APP=obj-rel/dist/firefox
|
||||
elif [ -f obj-rel/dist/bin/firefox.exe ]; then WIN_APP=obj-rel/dist/bin
|
||||
else echo "ERROR: firefox.exe not found in dist/firefox nor dist/bin"; exit 1; fi
|
||||
echo "packaging from: $WIN_APP"
|
||||
# JUGGLER GATE: omni.ja must carry juggler (dist/firefox) or loose chrome/ (dist/bin fallback)
|
||||
if [ -f "$WIN_APP/omni.ja" ]; then
|
||||
python3 -c "import zipfile,sys; sys.exit(0 if any('juggler' in n.lower() for n in zipfile.ZipFile('$WIN_APP/omni.ja').namelist()) else 1)" \
|
||||
|| { echo "ERROR: juggler missing from $WIN_APP/omni.ja — Playwright can't drive it"; exit 1; }
|
||||
elif [ ! -d "$WIN_APP/chrome/juggler" ]; then
|
||||
echo "ERROR: juggler missing from $WIN_APP (no omni.ja juggler, no loose chrome/juggler)"; exit 1
|
||||
fi
|
||||
echo "juggler GATE OK (win)"
|
||||
mkdir -p out
|
||||
( cd "$WIN_APP" && zip -qr "$GITHUB_WORKSPACE/out/${{ matrix.asset }}" . ) # firefox.exe at zip ROOT
|
||||
ls -la out/
|
||||
|
||||
# ── macOS: package .app, ad-hoc sign, verify relative-internal symlinks, --version gate, tar
|
||||
- name: Package + validate (macOS)
|
||||
if: matrix.family == 'mac'
|
||||
run: |
|
||||
set -e
|
||||
./mach package
|
||||
APP="$(find obj-rel/dist -maxdepth 2 -name '*.app' -type d | head -1)"
|
||||
[ -n "$APP" ] || { echo "ERROR: no .app produced"; exit 1; }
|
||||
echo "built app: $APP"
|
||||
# JUGGLER GATE: the .app's omni.ja must carry juggler (else Playwright can't drive it)
|
||||
python3 -c "import zipfile,sys,glob; jas=glob.glob('$APP/Contents/Resources/omni.ja')+glob.glob('$APP/Contents/Resources/browser/omni.ja'); sys.exit(0 if jas and any(any('juggler' in n.lower() for n in zipfile.ZipFile(j).namelist()) for j in jas) else 1)" \
|
||||
|| { echo "ERROR: juggler missing from .app omni.ja — Playwright can't drive it"; exit 1; }
|
||||
echo "juggler GATE OK (mac)"
|
||||
codesign --force --deep --sign - --timestamp=none "$APP"
|
||||
codesign --verify --deep --strict --verbose=2 "$APP"
|
||||
echo "=== --version GATE ==="
|
||||
"$APP/Contents/MacOS/firefox" --version
|
||||
echo "=== critical files present ==="
|
||||
for need in "Contents/MacOS/firefox" "Contents/Info.plist"; do
|
||||
[ -e "$APP/$need" ] || { echo "ERROR: missing $need"; exit 1; }
|
||||
done
|
||||
echo "=== verify NO absolute symlinks in the .app (relative-internal ones are fine) ==="
|
||||
BAD="$(find "$APP" -type l -print0 | xargs -0 -I{} sh -c 't=$(readlink "{}"); case "$t" in /*) echo "{} -> $t";; esac')"
|
||||
[ -z "$BAD" ] || { echo "ERROR: absolute symlinks in .app (break on user machines):"; echo "$BAD" | head -5; exit 1; }
|
||||
echo "mac .app OK: critical files present, no absolute symlinks"
|
||||
STABLE="$(dirname "$APP")/Firefox.app"
|
||||
[ "$APP" = "$STABLE" ] || mv "$APP" "$STABLE"
|
||||
mkdir -p out
|
||||
tar -czf "out/${{ matrix.asset }}" -C "$(dirname "$STABLE")" Firefox.app # preserves internal symlinks
|
||||
ls -la out/
|
||||
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: asset-${{ matrix.leg }}
|
||||
path: out/${{ matrix.asset }}
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
# Windows binary is cross-built on Linux → gate it on a real Windows runner.
|
||||
gate-windows:
|
||||
name: gate-windows
|
||||
needs: build
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Download win asset
|
||||
uses: actions/download-artifact@v4
|
||||
with: { name: asset-win-x86_64, path: art }
|
||||
- name: Extract + structure + headless render gate
|
||||
shell: pwsh
|
||||
run: |
|
||||
$zip = Get-ChildItem art -Filter *.zip | Select-Object -First 1
|
||||
Expand-Archive $zip.FullName -DestinationPath ff -Force
|
||||
foreach ($f in 'firefox.exe','application.ini','dependentlibs.list') {
|
||||
if (-not (Test-Path (Join-Path ff $f))) { throw "missing critical file: $f" }
|
||||
}
|
||||
$exe = Join-Path (Resolve-Path ff) 'firefox.exe'
|
||||
Remove-Item shot.png -ErrorAction SilentlyContinue
|
||||
$p = Start-Process -FilePath $exe -ArgumentList '--headless','--no-remote','--profile','prof','--screenshot',"$PWD\shot.png",'https://example.com' -PassThru -NoNewWindow
|
||||
if (-not $p.WaitForExit(90000)) { $p.Kill(); throw 'win gate TIMEOUT' }
|
||||
Start-Sleep 1
|
||||
if (-not (Test-Path shot.png) -or (Get-Item shot.png).Length -lt 3000) { throw 'win gate: no/empty screenshot' }
|
||||
Write-Output "win gate OK: firefox.exe runs + renders ($((Get-Item shot.png).Length) bytes)"
|
||||
- uses: actions/upload-artifact@v4
|
||||
with: { name: gate-win-screenshot, path: shot.png, if-no-files-found: warn, retention-days: 7 }
|
||||
|
||||
publish:
|
||||
name: publish-draft-release
|
||||
needs: [build, gate-windows]
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Download all build assets
|
||||
uses: actions/download-artifact@v4
|
||||
with: { pattern: asset-*, path: dl, merge-multiple: true }
|
||||
- name: Generate checksums.txt
|
||||
run: |
|
||||
cd dl; ls -la
|
||||
# explicit glob — never include checksums.txt itself (the `*`-includes-itself trap)
|
||||
sha256sum firefox-150.0.1-stealth-* > checksums.txt
|
||||
echo "----- checksums.txt -----"; cat checksums.txt
|
||||
- name: Resolve release tag
|
||||
id: tag
|
||||
run: |
|
||||
TAG="${{ github.event.inputs.release_tag }}"
|
||||
[ -z "$TAG" ] && TAG="${GITHUB_REF_NAME}"
|
||||
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
||||
echo "publishing DRAFT release for tag: $TAG"
|
||||
- name: Create DRAFT release with all assets
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ steps.tag.outputs.tag }}
|
||||
name: invisible_firefox (150.0.1) rev ${{ steps.tag.outputs.tag }}
|
||||
draft: true
|
||||
prerelease: false
|
||||
files: |
|
||||
dl/*.tar.gz
|
||||
dl/*.zip
|
||||
dl/checksums.txt
|
||||
body: |
|
||||
Patched Firefox 150.0.1 — built on GitHub Actions ($0, no mold).
|
||||
Targets: linux-x86_64, linux-arm64, win-x86_64, macos-arm64, macos-x86_64.
|
||||
|
||||
DRAFT — do not publish until validate_release.py + realness gate pass on all archives.
|
||||
|
||||
macOS: ad-hoc signed (not notarized). After download run:
|
||||
xattr -dr com.apple.quarantine Firefox.app
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
@ -19,13 +19,15 @@ BINARY_BASENAME: str = f"firefox-{FIREFOX_UPSTREAM_VERSION}-stealth"
|
|||
def ARCHIVE_NAME(platform_key: str, machine: str) -> str:
|
||||
"""Return the platform-specific archive filename.
|
||||
|
||||
platform_key: sys.platform ("win32", "linux")
|
||||
machine: platform.machine() ("AMD64", "x86_64", ...)
|
||||
platform_key: sys.platform ("win32", "linux", "darwin")
|
||||
machine: platform.machine() ("AMD64", "x86_64", "arm64", "aarch64", ...)
|
||||
"""
|
||||
pk = platform_key.lower()
|
||||
m = machine.lower()
|
||||
if m in {"amd64", "x86_64"}:
|
||||
arch = "x86_64"
|
||||
elif m in {"arm64", "aarch64"}:
|
||||
arch = "arm64"
|
||||
else:
|
||||
raise NotImplementedError(f"unsupported arch: {machine}")
|
||||
|
||||
|
|
@ -33,13 +35,18 @@ def ARCHIVE_NAME(platform_key: str, machine: str) -> str:
|
|||
return f"{BINARY_BASENAME}-win-{arch}.zip"
|
||||
if pk == "linux":
|
||||
return f"{BINARY_BASENAME}-linux-{arch}.tar.gz"
|
||||
if pk == "darwin":
|
||||
return f"{BINARY_BASENAME}-macos-{arch}.tar.gz"
|
||||
raise NotImplementedError(f"unsupported platform: {platform_key}")
|
||||
|
||||
|
||||
# Binary entry point relative path inside the extracted archive root.
|
||||
# macOS ships the .app bundle (renamed to a stable "Firefox.app" by release.yml);
|
||||
# the wrapper execs the inner binary directly, which sidesteps Gatekeeper.
|
||||
BINARY_ENTRY_REL = {
|
||||
"win32": "firefox.exe",
|
||||
"linux": "firefox",
|
||||
"darwin": "Firefox.app/Contents/MacOS/firefox",
|
||||
}
|
||||
|
||||
# GitHub release URL template. The "TODO" owner is resolved at publication time.
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import os
|
|||
import platform
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import tempfile
|
||||
|
|
@ -120,6 +121,31 @@ def _extract(archive: Path, dst: Path) -> None:
|
|||
raise RuntimeError(f"unknown archive format: {archive}")
|
||||
|
||||
|
||||
def _post_extract_darwin(app_root: Path, entry: Path) -> None:
|
||||
"""Make an ad-hoc-signed .app launchable on macOS.
|
||||
|
||||
The .app is downloaded via requests (no Finder quarantine attached), but we
|
||||
strip com.apple.quarantine defensively and ensure the inner binary is
|
||||
executable. We exec the inner binary directly (not via LaunchServices), so
|
||||
Gatekeeper's first-launch prompt does not apply; the ad-hoc signature
|
||||
(applied in release.yml) is what lets the arm64 Mach-O run at all.
|
||||
"""
|
||||
app = app_root
|
||||
# walk up to the .app bundle dir if entry points inside it
|
||||
for parent in entry.parents:
|
||||
if parent.name.endswith(".app"):
|
||||
app = parent
|
||||
break
|
||||
try:
|
||||
subprocess.run(["xattr", "-dr", "com.apple.quarantine", str(app)], check=False)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
try:
|
||||
entry.chmod(0o755)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def ensure_binary(version: str = BINARY_VERSION) -> Path:
|
||||
"""Return a path to a runnable Firefox executable. Download if needed."""
|
||||
plat = sys.platform
|
||||
|
|
@ -154,6 +180,9 @@ def ensure_binary(version: str = BINARY_VERSION) -> Path:
|
|||
)
|
||||
_extract(archive_path, version_dir)
|
||||
|
||||
if plat == "darwin":
|
||||
_post_extract_darwin(version_dir, entry)
|
||||
|
||||
if not entry.exists():
|
||||
raise RuntimeError(f"binary not found after extraction: {entry}")
|
||||
return entry
|
||||
|
|
|
|||
|
|
@ -31,9 +31,16 @@ def test_archive_name_linux():
|
|||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_archive_name_unsupported_raises():
|
||||
def test_archive_name_macos_arm64():
|
||||
name = ARCHIVE_NAME("darwin", "arm64")
|
||||
assert name.endswith(".tar.gz")
|
||||
assert "macos-arm64" in name
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_archive_name_truly_unsupported_raises():
|
||||
with pytest.raises(NotImplementedError):
|
||||
ARCHIVE_NAME("darwin", "arm64")
|
||||
ARCHIVE_NAME("plan9", "x86_64")
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
|
|
@ -77,20 +84,18 @@ def test_archive_name_rejects_unsupported_arches(machine):
|
|||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.parametrize("machine", ["arm64", "aarch64"])
|
||||
def test_archive_name_arm64_not_yet_supported(machine):
|
||||
"""ARM64 is a frequent request (issue #6). Until binaries exist for it,
|
||||
ARCHIVE_NAME should hard-fail rather than silently degrade. If this test
|
||||
starts failing because someone shipped ARM64 builds, replace it with the
|
||||
positive case."""
|
||||
with pytest.raises(NotImplementedError):
|
||||
ARCHIVE_NAME("linux", machine)
|
||||
def test_archive_name_arm64_supported(machine):
|
||||
"""ARM64 is shipped now (issue #6): both Linux aarch64 and macOS arm64.
|
||||
ARCHIVE_NAME must map both machine spellings to the canonical -arm64 asset."""
|
||||
assert ARCHIVE_NAME("linux", machine) == "firefox-150.0.1-stealth-linux-arm64.tar.gz"
|
||||
assert ARCHIVE_NAME("darwin", machine) == "firefox-150.0.1-stealth-macos-arm64.tar.gz"
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.parametrize("platform_key", ["darwin", "freebsd", "cygwin", "openbsd"])
|
||||
@pytest.mark.parametrize("platform_key", ["freebsd", "cygwin", "openbsd"])
|
||||
def test_archive_name_rejects_unsupported_platforms(platform_key):
|
||||
"""Same logic — non-Linux/non-Windows platforms must raise, not silently
|
||||
pick one of the two."""
|
||||
"""win32/linux/darwin are supported; other platforms must raise, not
|
||||
silently pick one of the three."""
|
||||
with pytest.raises(NotImplementedError, match=platform_key):
|
||||
ARCHIVE_NAME(platform_key, "x86_64")
|
||||
|
||||
|
|
@ -104,7 +109,7 @@ def test_archive_name_rejects_unsupported_platforms(platform_key):
|
|||
def test_binary_entry_rel_covers_every_supported_platform():
|
||||
"""If ARCHIVE_NAME accepts a platform key, BINARY_ENTRY_REL must declare
|
||||
where the executable lives inside the archive for it."""
|
||||
for plat in ["win32", "linux"]:
|
||||
for plat in ["win32", "linux", "darwin"]:
|
||||
ARCHIVE_NAME(plat, "x86_64") # must not raise
|
||||
assert plat in BINARY_ENTRY_REL, (
|
||||
f"ARCHIVE_NAME accepts {plat!r} but BINARY_ENTRY_REL has no entry "
|
||||
|
|
@ -118,6 +123,7 @@ def test_binary_entry_rel_extension_matches_platform():
|
|||
assert BINARY_ENTRY_REL["win32"].endswith(".exe")
|
||||
assert not BINARY_ENTRY_REL["linux"].endswith(".exe")
|
||||
assert BINARY_ENTRY_REL["linux"] == "firefox"
|
||||
assert BINARY_ENTRY_REL["darwin"].endswith(".app/Contents/MacOS/firefox")
|
||||
|
||||
|
||||
# ---- RELEASE_URL_TEMPLATE shape ------------------------------------------- #
|
||||
|
|
|
|||
|
|
@ -418,7 +418,7 @@ def test_github_token_none_when_unset(monkeypatch):
|
|||
# Bonus coverage: unsupported platform raises NotImplementedError before any HTTP
|
||||
@pytest.mark.unit
|
||||
def test_ensure_binary_unsupported_platform_raises(monkeypatch):
|
||||
monkeypatch.setattr("sys.platform", "darwin")
|
||||
monkeypatch.setattr("sys.platform", "freebsd") # win32/linux/darwin are supported
|
||||
import platform
|
||||
monkeypatch.setattr(platform, "machine", lambda: "AMD64")
|
||||
with pytest.raises(NotImplementedError, match="unsupported platform"):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue