Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
9a6387c
chore(ci): align workflows with shared self-hosted runners
efraespada Oct 23, 2026
dbcb227
fix(ci): prepare Flutter in every Windows validation job
efraespada Oct 23, 2026
c5ac2c9
fix(ci): select Git Bash for native Windows Flutter setup
efraespada Oct 23, 2026
5845ed8
fix(ci): provision native Flutter architectures and verified Windows jq
efraespada Oct 23, 2026
675d28f
fix(ci): route modern Flutter and ARM Android emulators to Apple Silicon
efraespada Oct 23, 2026
daffb99
fix(ci): run Linux desktop integration tests in isolated virtual disp…
efraespada Oct 23, 2026
e3b8eef
fix(ci): isolate Windows Flutter from incomplete legacy tool cache
efraespada Oct 23, 2026
6b8cecf
fix(ci): prepare headless Linux and align Flutter with declared SDK r…
efraespada Oct 23, 2026
0238bbf
fix(ci): resolve complete missing Xvfb runtime dependencies in privat…
efraespada Oct 23, 2026
9b3977d
fix(ci): persist Flutter analyzer migration before publication valida…
efraespada Oct 23, 2026
5879061
fix(ci): request supported Android tools and retain Dart line coverag…
efraespada Oct 23, 2026
c46b05a
fix(ci): pair Chrome for Testing with its native driver and isolate t…
efraespada Oct 23, 2026
eaedec7
fix(ci): give each iOS job an isolated simulator and exact cleanup
efraespada Oct 23, 2026
9f28cf3
fix(ci): preserve the macOS Chrome bundle layout for WebDriver sessions
efraespada Oct 23, 2026
f400feb
fix(ci): isolate Android JDK profiles and bound Gradle processes per job
efraespada Oct 23, 2026
49cadc4
fix: keep Android emulator test command in its project directory
efraespada Oct 24, 2026
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
34 changes: 34 additions & 0 deletions .github/actions/prepare-android-integration/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Prepare isolated Android integration
description: Keep SDK, Flutter settings and bounded Gradle processes local to this job.
runs:
using: composite
steps:
- name: Prepare private Android build state and select the job JDK
shell: bash
run: |
set -euo pipefail
build_directory="$(mktemp -d "$RUNNER_TEMP/zstandard-android-build.XXXXXX")"
sdk_root="$build_directory/sdk"
gradle_root="$build_directory/gradle"
flutter_profile="$build_directory/flutter-profile"
mkdir -p "$sdk_root" "$gradle_root" "$flutter_profile" "$build_directory/avd" "$build_directory/android-user"
cat > "$gradle_root/gradle.properties" <<'PROPERTIES'
org.gradle.daemon=false
org.gradle.parallel=false
org.gradle.workers.max=2
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=1g -Dfile.encoding=UTF-8
kotlin.compiler.execution.strategy=in-process
PROPERTIES
{
echo "ANDROID_HOME=$sdk_root"
echo "ANDROID_SDK_ROOT=$sdk_root"
echo "ANDROID_AVD_HOME=$build_directory/avd"
echo "ANDROID_USER_HOME=$build_directory/android-user"
echo "ANDROID_EMULATOR_HOME=$build_directory/android-user"
echo "GRADLE_USER_HOME=$gradle_root"
echo "ZSTANDARD_FLUTTER_PROFILE=$flutter_profile"
} >> "$GITHUB_ENV"
# Flutter otherwise prefers the host Android Studio JBR over JAVA_HOME.
# Configure a private child profile; never rewrite the operator's config.
env HOME="$flutter_profile" XDG_CONFIG_HOME="$flutter_profile/config" \
flutter config --jdk-dir="$JAVA_HOME"
39 changes: 39 additions & 0 deletions .github/actions/prepare-linux-display/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Prepare Linux virtual display
description: Prepare Ubuntu Xvfb tools in the job directory without requiring sudo.
runs:
using: composite
steps:
- name: Prepare job-local Xvfb
shell: bash
run: |
set -euo pipefail
if command -v xvfb-run >/dev/null && command -v Xvfb >/dev/null && command -v xauth >/dev/null; then
exit 0
fi
# Ubuntu's X server invokes this compiler at an absolute system path.
# It is part of the runner's base X11/GTK desktop toolchain.
if [ ! -x /usr/bin/xkbcomp ] || [ ! -f /usr/share/X11/xkb/rules/evdev ]; then
echo 'The Ubuntu runner needs x11-xkb-utils and xkb-data in its base desktop toolchain.' >&2
exit 1
fi
display_root="$(mktemp -d "$RUNNER_TEMP/zstandard-xvfb.XXXXXX")"
mkdir -p "$display_root/apt/lists/partial" "$display_root/packages/partial"
cp /var/lib/dpkg/status "$display_root/apt/status"
# Resolve every missing runtime dependency using a private package state
# and cache. Download-only never installs packages or runs maintainer scripts.
apt_options=(-o "Dir::State::status=$display_root/apt/status" -o "Dir::State::Lists=$display_root/apt/lists" -o "Dir::Cache::archives=$display_root/packages")
apt-get "${apt_options[@]}" update
apt-get "${apt_options[@]}" --download-only --assume-yes --no-install-recommends install xvfb xauth
(
cd "$display_root/packages"
for package in ./*.deb; do
Comment thread
vypbot marked this conversation as resolved.
Resolved
dpkg-deb --extract "$package" "$display_root"
done
)
echo "$display_root/usr/bin" >> "$GITHUB_PATH"
if [ -d "$display_root/usr/lib" ]; then
library_directories="$(find "$display_root/usr/lib" -mindepth 1 -maxdepth 1 -type d -name '*-linux-gnu' | paste -sd : -)"
if [ -n "$library_directories" ]; then
echo "LD_LIBRARY_PATH=$library_directories${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" >> "$GITHUB_ENV"
fi
fi
10 changes: 10 additions & 0 deletions .github/actions/prepare-macos-coverage/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name: Prepare macOS coverage verification
description: Provide GPG for Codecov signature verification on the self-hosted macOS runner.
runs:
using: composite
steps:
- shell: bash
run: |
if ! command -v gpg >/dev/null 2>&1; then
brew install gnupg
fi
55 changes: 55 additions & 0 deletions .github/actions/setup-flutter-windows/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Prepare native Windows Flutter
description: Select Git Bash instead of the WSL launcher, then install the reviewed Flutter SDK.
inputs:
flutter-version:
description: Exact Flutter SDK version used by repository validation.
required: true
runs:
using: composite
steps:
- name: Select Git Bash for composite actions
shell: cmd
run: |
@echo off
if not exist "%ProgramFiles%\Git\bin\bash.exe" (
echo Expected the runner's Git for Windows Bash executable. 1>&2
exit /b 1
)
echo %ProgramFiles%\Git\bin>>"%GITHUB_PATH%"
- name: Prepare verified portable jq
shell: powershell
run: |
$ErrorActionPreference = 'Stop'
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$toolDirectory = Join-Path $env:RUNNER_TEMP 'zstandard-jq'
New-Item -ItemType Directory -Force -Path $toolDirectory | Out-Null
$executable = Join-Path $toolDirectory 'jq.exe'
Invoke-WebRequest -UseBasicParsing -Uri 'https://github.com/jqlang/jq/releases/download/jq-1.8.1/jq-windows-amd64.exe' -OutFile $executable
$expected = '23cb60a1354eed6bcc8d9b9735e8c7b388cd1fdcb75726b93bc299ef22dd9334'
if ((Get-FileHash -Algorithm SHA256 $executable).Hash.ToLowerInvariant() -ne $expected) {
throw 'The downloaded jq executable does not match the reviewed checksum.'
}
Add-Content -Path $env:GITHUB_PATH -Value $toolDirectory
- name: Recover interrupted repository SDK extraction
shell: powershell
env:
SDK_TOOL_CACHE: ${{ runner.tool_cache }}
SDK_VERSION: ${{ inputs.flutter-version }}
run: |
$ErrorActionPreference = 'Stop'
if ($env:SDK_VERSION -notmatch '^\d+\.\d+\.\d+$') {
throw 'Expected an exact stable Flutter release version.'
}
$sdkDirectory = Join-Path (Join-Path $env:SDK_TOOL_CACHE 'zstandard-flutter-v2') "stable-$env:SDK_VERSION-x64"
$sdkRoot = Join-Path $sdkDirectory 'flutter'
$requiredFiles = @('bin\flutter.bat', 'bin\cache\dart-sdk\bin\dart.exe', 'packages\flutter_tools\bin\flutter_tools.dart', 'packages\flutter\lib\widgets.dart')
if ((Test-Path -LiteralPath $sdkDirectory) -and ($requiredFiles | Where-Object { -not (Test-Path -LiteralPath (Join-Path $sdkRoot $_)) })) {
Write-Host 'Removing the incomplete repository-specific Flutter installation before preparing the SDK.'
Remove-Item -LiteralPath $sdkDirectory -Recurse -Force
}
- name: Prepare Flutter SDK
uses: subosito/flutter-action@v2
with:
channel: stable
flutter-version: ${{ inputs.flutter-version }}
cache-path: '${{ runner.tool_cache }}/zstandard-flutter-v2/:channel:-:version:-:arch:'
1 change: 1 addition & 0 deletions .github/scripts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__pycache__/
68 changes: 68 additions & 0 deletions .github/scripts/run-web-integration.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env bash
set -euo pipefail

driver_pid=''
browser_directory=''
cleanup() {
if [ -n "$driver_pid" ]; then
kill "$driver_pid" 2>/dev/null || true
wait "$driver_pid" 2>/dev/null || true
fi
if [ -n "$browser_directory" ]; then
rm -rf -- "$browser_directory"
fi
}
trap cleanup EXIT

# setup-chrome strips the .app suffix from its macOS cache directory. Chrome
# prints its version there but cannot create the instrumented WebDriver session.
# Keep the shared cache untouched and restore the bundle name in a private copy.
if [ "$(uname -s)" = Darwin ]; then
chrome_bundle="${CHROME_EXECUTABLE%/Contents/MacOS/*}"
if [ ! -d "$chrome_bundle/Contents" ]; then
echo 'Expected a macOS Chrome app bundle.' >&2
exit 1
fi
case "$chrome_bundle" in
*.app) ;;
*)
browser_directory="$(mktemp -d "$RUNNER_TEMP/zstandard-chrome.XXXXXX")"
/bin/cp -R "$chrome_bundle" "$browser_directory/Chrome for Testing.app"
CHROME_EXECUTABLE="$browser_directory/Chrome for Testing.app/Contents/MacOS/${CHROME_EXECUTABLE##*/}"
;;
esac
fi

driver_log="$(mktemp "$RUNNER_TEMP/zstandard-chromedriver.XXXXXX")"
"$CHROMEDRIVER_EXECUTABLE" --port=0 > "$driver_log" 2>&1 &
driver_pid=$!

driver_port=''
for attempt in {1..100}; do
driver_port="$(sed -nE 's/.*ChromeDriver was started successfully on port ([0-9]+).*/\1/p' "$driver_log" | tail -n 1)"
if [ -n "$driver_port" ]; then
break
fi
if ! kill -0 "$driver_pid" 2>/dev/null; then
cat "$driver_log" >&2
exit 1
fi
sleep 0.1
done
if [ -z "$driver_port" ]; then
cat "$driver_log" >&2
echo 'ChromeDriver did not become ready within 10 seconds.' >&2
exit 1
fi

result=0
flutter drive \
--driver=test_driver/integration_test.dart \
--target=integration_test/zstandard_web_integration_test.dart \
--driver-port="$driver_port" \
--chrome-binary="$CHROME_EXECUTABLE" \
-d web-server || result=$?
if [ "$result" -ne 0 ]; then
tail -n 50 "$driver_log" >&2
fi
exit "$result"
43 changes: 43 additions & 0 deletions .github/scripts/start_ios_simulator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Create a fresh CI simulator without reusing or stopping personal devices."""

import json
import os
import subprocess
import uuid


def start():
result = subprocess.run(
["xcrun", "simctl", "list", "devices", "available", "--json"],
check=True, capture_output=True, text=True, timeout=30,
)
candidates = []
for runtime, devices in json.loads(result.stdout).get("devices", {}).items():
if not runtime.startswith("com.apple.CoreSimulator.SimRuntime.iOS-"):
continue
for device in devices:
kind = device.get("deviceTypeIdentifier", "")
if device.get("isAvailable") and kind.startswith("com.apple.CoreSimulator.SimDeviceType.iPhone-"):
candidates.append((runtime, kind, device.get("name")))
if not candidates:
raise RuntimeError("Xcode needs an available iPhone simulator and its installed iOS runtime.")
preferred = os.environ.get("ZSTANDARD_IOS_DEVICE", "iPhone 16")
runtime, kind, _ = next((item for item in candidates if item[2] == preferred), candidates[0])
name = "zstandard-{}-{}-{}-{}".format(
os.environ["GITHUB_RUN_ID"], os.environ["GITHUB_RUN_ATTEMPT"],
os.environ["GITHUB_JOB"], uuid.uuid4().hex[:8],
)
created = subprocess.run(
["xcrun", "simctl", "create", name, kind, runtime],
check=True, capture_output=True, text=True, timeout=30,
)
device_id = str(uuid.UUID(created.stdout.strip())).upper()
# Record ownership before boot so an interrupted boot can still be cleaned up.
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output:
output.write(f"device-id={device_id}\n")
subprocess.run(["xcrun", "simctl", "boot", device_id], check=True, timeout=30)
subprocess.run(["xcrun", "simctl", "bootstatus", device_id, "-b"], check=True, timeout=120)


if __name__ == "__main__":
start()
53 changes: 53 additions & 0 deletions .github/scripts/tests/test_ios_simulator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import importlib.util
import json
import os
import pathlib
import subprocess
import tempfile
import unittest
from unittest.mock import Mock, patch


path = pathlib.Path(__file__).parents[1] / "start_ios_simulator.py"
spec = importlib.util.spec_from_file_location("start_ios_simulator", path)
simulator = importlib.util.module_from_spec(spec)
spec.loader.exec_module(simulator)
RUNTIME = "com.apple.CoreSimulator.SimRuntime.iOS-27-0"
KIND = "com.apple.CoreSimulator.SimDeviceType.iPhone-16"
EXISTING = "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA"
CREATED = "BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB"


class SimulatorTests(unittest.TestCase):
def prepare(self, folder):
return patch.dict(os.environ, {"GITHUB_RUN_ID": "12", "GITHUB_RUN_ATTEMPT": "1", "GITHUB_JOB": "ios", "GITHUB_OUTPUT": str(pathlib.Path(folder) / "output")})

@patch.object(simulator.subprocess, "run")
def test_booted_personal_device_is_only_used_as_type_metadata(self, command):
devices = {"devices": {RUNTIME: [{"name": "iPhone 16", "deviceTypeIdentifier": KIND, "isAvailable": True, "state": "Booted", "udid": EXISTING}]}}
command.side_effect = [Mock(stdout=json.dumps(devices)), Mock(stdout=CREATED), Mock(), Mock()]
with tempfile.TemporaryDirectory() as folder, self.prepare(folder):
simulator.start()
self.assertEqual((pathlib.Path(folder) / "output").read_text(), f"device-id={CREATED}\n")
self.assertEqual(command.call_args_list[1].args[0][-2:], [KIND, RUNTIME])
self.assertEqual(command.call_args_list[2].args[0], ["xcrun", "simctl", "boot", CREATED])
self.assertNotIn(EXISTING, str(command.call_args_list))

@patch.object(simulator.subprocess, "run", return_value=Mock(stdout='{"devices": {}}'))
def test_missing_ios_runtime_fails_before_creating_a_device(self, command):
with self.assertRaisesRegex(RuntimeError, "installed iOS runtime"):
simulator.start()
self.assertEqual(command.call_count, 1)

@patch.object(simulator.subprocess, "run")
def test_failed_boot_preserves_created_device_id_for_cleanup(self, command):
devices = {"devices": {RUNTIME: [{"name": "iPhone 16", "deviceTypeIdentifier": KIND, "isAvailable": True}]}}
command.side_effect = [Mock(stdout=json.dumps(devices)), Mock(stdout=CREATED), subprocess.CalledProcessError(1, "boot")]
with tempfile.TemporaryDirectory() as folder, self.prepare(folder):
with self.assertRaises(subprocess.CalledProcessError):
simulator.start()
self.assertEqual((pathlib.Path(folder) / "output").read_text(), f"device-id={CREATED}\n")


if __name__ == "__main__":
unittest.main()
32 changes: 24 additions & 8 deletions .github/workflows/copilot_credential_health.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ jobs:
verify-pat:
if: ${{ inputs.check_pat }}
name: Verify PAT
runs-on: ubuntu-latest
runs-on:
group: vypdev
labels: [self-hosted, codex, Linux]
timeout-minutes: 10
steps:
- name: Validate PAT without exposing its value
Expand All @@ -67,7 +69,9 @@ jobs:
verify-openai:
if: ${{ inputs.check_openai }}
name: Verify OPENAI_API_KEY
runs-on: ubuntu-latest
runs-on:
group: vypdev
labels: [self-hosted, codex, Linux]
timeout-minutes: 10
steps:
- name: Validate OpenAI key without exposing its value
Expand All @@ -82,7 +86,9 @@ jobs:
verify-anthropic:
if: ${{ inputs.check_anthropic }}
name: Verify ANTHROPIC_API_KEY
runs-on: ubuntu-latest
runs-on:
group: vypdev
labels: [self-hosted, codex, Linux]
timeout-minutes: 10
steps:
- name: Validate Anthropic key without exposing its value
Expand All @@ -97,7 +103,9 @@ jobs:
verify-google:
if: ${{ inputs.check_google }}
name: Verify GOOGLE_API_KEY
runs-on: ubuntu-latest
runs-on:
group: vypdev
labels: [self-hosted, codex, Linux]
timeout-minutes: 10
steps:
- name: Validate Google key without exposing its value
Expand All @@ -112,7 +120,9 @@ jobs:
verify-openrouter:
if: ${{ inputs.check_openrouter }}
name: Verify OPENROUTER_API_KEY
runs-on: ubuntu-latest
runs-on:
group: vypdev
labels: [self-hosted, codex, Linux]
timeout-minutes: 10
steps:
- name: Validate OpenRouter key without exposing its value
Expand All @@ -127,7 +137,9 @@ jobs:
verify-cursor:
if: ${{ inputs.check_cursor }}
name: Verify CURSOR_API_KEY
runs-on: ubuntu-latest
runs-on:
group: vypdev
labels: [self-hosted, codex, Linux]
timeout-minutes: 10
steps:
- name: Validate Cursor key without exposing its value
Expand All @@ -142,7 +154,9 @@ jobs:
verify-opencode:
if: ${{ inputs.check_opencode }}
name: Verify OPENCODE_API_KEY
runs-on: ubuntu-latest
runs-on:
group: vypdev
labels: [self-hosted, codex, Linux]
timeout-minutes: 10
steps:
- name: Validate OpenCode key without exposing its value
Expand All @@ -157,7 +171,9 @@ jobs:
verify-codex-api-key:
if: ${{ inputs.check_codex_api_key }}
name: Verify CODEX_API_KEY
runs-on: ubuntu-latest
runs-on:
group: vypdev
labels: [self-hosted, codex, Linux]
timeout-minutes: 10
steps:
- name: Validate Codex API key without exposing its value
Expand Down
Loading
Loading