Files
codex/scripts/install/test_install_sh.py
T

452 lines
16 KiB
Python

#!/usr/bin/env python3
import hashlib
import io
import json
import os
from pathlib import Path
import subprocess
import tarfile
import tempfile
import textwrap
import unittest
INSTALL_SCRIPT = Path(__file__).with_name("install.sh")
VERSION = "0.142.5"
LEGACY_VERSION = "0.125.0"
class InstallShTest(unittest.TestCase):
def test_metadata_fetch_failure_is_not_reported_as_missing_assets(self) -> None:
result, requests = run_installer(VERSION, metadata_failure=True)
self.assertNotEqual(result.returncode, 0)
self.assertEqual(
requests,
[
"https://api.github.com/repos/openai/codex/releases/tags/"
f"rust-v{VERSION}"
],
)
self.assertIn(
f"Could not fetch GitHub release metadata for Codex {VERSION}",
result.stderr,
)
self.assertNotIn("Could not find Codex package", result.stderr)
def test_exact_release_fetches_metadata_once(self) -> None:
result, requests = run_installer(VERSION)
self.assertNotEqual(result.returncode, 0)
self.assertEqual(
requests,
[
"https://api.github.com/repos/openai/codex/releases/tags/"
f"rust-v{VERSION}",
"https://github.com/openai/codex/releases/download/"
f"rust-v{VERSION}/codex-package_SHA256SUMS",
],
)
self.assertIn(f"Resolved version: {VERSION}", result.stdout)
def test_latest_release_reuses_version_metadata(self) -> None:
result, requests = run_installer("latest")
self.assertNotEqual(result.returncode, 0)
self.assertEqual(
requests,
[
"https://api.github.com/repos/openai/codex/releases/latest",
"https://github.com/openai/codex/releases/download/"
f"rust-v{VERSION}/codex-package_SHA256SUMS",
],
)
self.assertIn(f"Resolved version: {VERSION}", result.stdout)
def test_releases_channel_unavailable_falls_back_to_github(self) -> None:
result, requests = run_installer(
"latest", use_releases=True, releases_mode="channel_failure"
)
self.assertNotEqual(result.returncode, 0)
self.assertEqual(
requests,
[
"https://releases.openai.com/codex/channels/latest",
"https://api.github.com/repos/openai/codex/releases/latest",
"https://github.com/openai/codex/releases/download/"
f"rust-v{VERSION}/codex-package_SHA256SUMS",
],
)
self.assertIn("falling back to GitHub Releases", result.stderr)
def test_releases_integrity_failure_does_not_fall_back(self) -> None:
result, requests = run_installer(
"latest", use_releases=True, releases_mode="corrupt_package"
)
self.assertNotEqual(result.returncode, 0)
package = f"codex-package-{current_target()}.tar.gz"
self.assertEqual(
requests,
[
"https://releases.openai.com/codex/channels/latest",
f"https://releases.openai.com/codex/releases/{VERSION}/codex-package_SHA256SUMS",
f"https://releases.openai.com/codex/releases/{VERSION}/{package}",
],
)
self.assertIn("checksum did not match", result.stderr)
def test_releases_latest_installs_verified_package(self) -> None:
result, requests = run_installer("latest", use_releases=True)
package = f"codex-package-{current_target()}.tar.gz"
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(
requests,
[
"https://releases.openai.com/codex/channels/latest",
f"https://releases.openai.com/codex/releases/{VERSION}/codex-package_SHA256SUMS",
f"https://releases.openai.com/codex/releases/{VERSION}/{package}",
],
)
def test_releases_latest_rejects_corrupt_checksum_manifest(self) -> None:
result, requests = run_installer(
"latest", use_releases=True, releases_mode="corrupt_manifest"
)
self.assertNotEqual(result.returncode, 0)
self.assertEqual(
requests,
[
"https://releases.openai.com/codex/channels/latest",
f"https://releases.openai.com/codex/releases/{VERSION}/codex-package_SHA256SUMS",
],
)
self.assertIn("checksum did not match", result.stderr)
def test_exact_releases_path_discovers_legacy_github_asset(self) -> None:
result, requests = run_installer(
LEGACY_VERSION, use_releases=True, releases_mode="legacy"
)
target = current_target()
package = f"codex-package-{target}.tar.gz"
legacy_package = f"codex-npm-{current_npm_tag()}-{LEGACY_VERSION}.tgz"
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(
requests,
[
f"https://releases.openai.com/codex/releases/{LEGACY_VERSION}/codex-package_SHA256SUMS",
f"https://releases.openai.com/codex/releases/{LEGACY_VERSION}/{package}",
"https://github.com/openai/codex/releases/download/"
f"rust-v{LEGACY_VERSION}/{package}",
"https://api.github.com/repos/openai/codex/releases/tags/"
f"rust-v{LEGACY_VERSION}",
"https://github.com/openai/codex/releases/download/"
f"rust-v{LEGACY_VERSION}/{legacy_package}",
],
)
self.assertIn("retrying from GitHub Releases", result.stderr)
self.assertIn("checking GitHub release metadata", result.stderr)
def run_installer(
release: str,
*,
metadata_failure: bool = False,
use_releases: bool = False,
releases_mode: str = "",
) -> tuple[subprocess.CompletedProcess[str], list[str]]:
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
bin_dir = root / "bin"
bin_dir.mkdir()
(root / "home").mkdir()
package_archive = root / "package.tar.gz"
legacy_archive = root / "legacy.tgz"
create_package_archive(package_archive, VERSION)
create_legacy_archive(legacy_archive, LEGACY_VERSION)
package_digest = sha256_file(package_archive)
legacy_digest = sha256_file(legacy_archive)
checksum_file = root / "codex-package_SHA256SUMS"
checksum_file.write_text(
checksum_manifest(package_digest),
encoding="utf-8",
)
checksum_digest = sha256_file(checksum_file)
request_log = root / "requests.log"
fake_curl = bin_dir / "curl"
fake_curl.write_text(
textwrap.dedent(
"""\
#!/bin/sh
url=""
output=""
next_is_output="false"
for arg in "$@"; do
case "$arg" in
https://*) url="$arg" ;;
esac
if [ "$next_is_output" = "true" ]; then
output="$arg"
next_is_output="false"
elif [ "$arg" = "-o" ]; then
next_is_output="true"
fi
done
printf '%s\n' "$url" >>"$CODEX_TEST_REQUEST_LOG"
case "$url" in
https://api.github.com/*)
if [ "$CODEX_TEST_METADATA_FAILURE" = "1" ]; then
echo "curl: (22) The requested URL returned error: 403" >&2
exit 22
fi
if [ "$CODEX_TEST_RELEASES_MODE" = "legacy" ]; then
printf '%s\n' "$CODEX_TEST_LEGACY_METADATA_JSON"
else
printf '%s\n' "$CODEX_TEST_GITHUB_METADATA_JSON"
fi
;;
https://releases.openai.com/codex/channels/latest)
if [ "$CODEX_TEST_RELEASES_MODE" = "channel_failure" ]; then
exit 22
fi
printf '%s\n' "$CODEX_TEST_LATEST_METADATA_JSON"
;;
https://releases.openai.com/codex/releases/*/codex-package_SHA256SUMS)
if [ "$CODEX_TEST_RELEASES_MODE" = "corrupt_manifest" ]; then
printf 'corrupt' >"$output"
else
cp "$CODEX_TEST_CHECKSUM_FILE" "$output"
fi
;;
https://releases.openai.com/codex/releases/*/codex-package-*.tar.gz)
if [ "$CODEX_TEST_RELEASES_MODE" = "corrupt_package" ]; then
printf 'corrupt' >"$output"
exit 0
fi
if [ "$CODEX_TEST_RELEASES_MODE" = "legacy" ]; then
exit 22
fi
cp "$CODEX_TEST_PACKAGE_ARCHIVE" "$output"
;;
https://github.com/openai/codex/releases/download/*/codex-package_SHA256SUMS)
exit 22
;;
https://github.com/openai/codex/releases/download/*/codex-package-*.tar.gz)
exit 22
;;
https://github.com/openai/codex/releases/download/*/codex-npm-*.tgz)
cp "$CODEX_TEST_LEGACY_ARCHIVE" "$output"
;;
*)
exit 22
;;
esac
"""
),
encoding="utf-8",
)
fake_curl.chmod(0o755)
env = os.environ.copy()
env.update(
{
"CODEX_HOME": str(root / "codex-home"),
"CODEX_INSTALL_DIR": str(root / "install-bin"),
"CODEX_NON_INTERACTIVE": "1",
"CODEX_RELEASE": release,
"CODEX_INSTALLER_USE_RELEASES_OPENAI_COM": (
"TRUE" if use_releases else "0"
),
"CODEX_TEST_CHECKSUM_FILE": str(checksum_file),
"CODEX_TEST_GITHUB_METADATA_JSON": release_metadata(
VERSION,
package_digest,
checksum_digest,
"https://github.com/openai/codex/releases/download",
),
"CODEX_TEST_LEGACY_ARCHIVE": str(legacy_archive),
"CODEX_TEST_LEGACY_METADATA_JSON": legacy_release_metadata(
LEGACY_VERSION,
legacy_digest,
),
"CODEX_TEST_METADATA_FAILURE": "1" if metadata_failure else "0",
"CODEX_TEST_PACKAGE_ARCHIVE": str(package_archive),
"CODEX_TEST_RELEASES_MODE": releases_mode,
"CODEX_TEST_REQUEST_LOG": str(request_log),
"CODEX_TEST_LATEST_METADATA_JSON": release_metadata(
VERSION,
package_digest,
checksum_digest,
"https://releases.openai.com/codex/releases",
),
"HOME": str(root / "home"),
"PATH": f"{bin_dir}:/usr/bin:/bin",
"SHELL": "/bin/sh",
}
)
result = subprocess.run(
["/bin/sh", str(INSTALL_SCRIPT)],
capture_output=True,
check=False,
env=env,
text=True,
)
requests = (
request_log.read_text(encoding="utf-8").splitlines()
if request_log.exists()
else []
)
return result, requests
def release_metadata(
version: str,
package_digest: str,
checksum_digest: str,
download_base_url: str,
) -> str:
release_path = f"rust-v{version}" if "github.com" in download_base_url else version
asset_base_url = f"{download_base_url}/{release_path}"
assets = [
{
"name": f"codex-package-{target}.tar.gz",
"digest": f"sha256:{package_digest}",
"browser_download_url": f"{asset_base_url}/codex-package-{target}.tar.gz",
}
for target in (
"aarch64-apple-darwin",
"x86_64-apple-darwin",
"aarch64-unknown-linux-musl",
"x86_64-unknown-linux-musl",
)
]
assets.append(
{
"name": "codex-package_SHA256SUMS",
"digest": f"sha256:{checksum_digest}",
"browser_download_url": f"{asset_base_url}/codex-package_SHA256SUMS",
}
)
return json.dumps(
{"tag_name": f"rust-v{version}", "assets": assets},
indent=2,
)
def legacy_release_metadata(version: str, digest: str) -> str:
asset = f"codex-npm-{current_npm_tag()}-{version}.tgz"
return json.dumps(
{
"tag_name": f"rust-v{version}",
"assets": [
{
"name": asset,
"digest": f"sha256:{digest}",
"browser_download_url": (
"https://github.com/openai/codex/releases/download/"
f"rust-v{version}/{asset}"
),
}
],
},
indent=2,
)
def current_target() -> str:
os_name = subprocess.run(
["uname", "-s"], capture_output=True, check=True, text=True
).stdout.strip()
architecture = subprocess.run(
["uname", "-m"], capture_output=True, check=True, text=True
).stdout.strip()
arch = "aarch64" if architecture in ("arm64", "aarch64") else "x86_64"
return (
f"{arch}-apple-darwin" if os_name == "Darwin" else f"{arch}-unknown-linux-musl"
)
def current_npm_tag() -> str:
target = current_target()
return {
"aarch64-apple-darwin": "darwin-arm64",
"x86_64-apple-darwin": "darwin-x64",
"aarch64-unknown-linux-musl": "linux-arm64",
"x86_64-unknown-linux-musl": "linux-x64",
}[target]
def checksum_manifest(package_digest: str) -> str:
return "".join(
f"{package_digest} codex-package-{target}.tar.gz\n"
for target in (
"aarch64-apple-darwin",
"x86_64-apple-darwin",
"aarch64-unknown-linux-musl",
"x86_64-unknown-linux-musl",
"aarch64-pc-windows-msvc",
"x86_64-pc-windows-msvc",
)
)
def create_package_archive(path: Path, version: str) -> None:
executable = f"#!/bin/sh\necho 'codex-cli {version}'\n".encode()
with tarfile.open(path, "w:gz") as archive:
add_archive_file(archive, "codex-package.json", b"{}\n")
add_archive_file(archive, "bin/codex", executable, mode=0o755)
add_archive_file(archive, "bin/codex-code-mode-host", executable, mode=0o755)
add_archive_file(archive, "codex-path/rg", executable, mode=0o755)
add_archive_file(archive, "codex-resources/bwrap", executable, mode=0o755)
def create_legacy_archive(path: Path, version: str) -> None:
target = current_target()
executable = f"#!/bin/sh\necho 'codex-cli {version}'\n".encode()
with tarfile.open(path, "w:gz") as archive:
add_archive_file(
archive,
f"package/vendor/{target}/codex/codex",
executable,
mode=0o755,
)
add_archive_file(
archive,
f"package/vendor/{target}/path/rg",
executable,
mode=0o755,
)
add_archive_file(
archive,
f"package/vendor/{target}/codex-resources/bwrap",
executable,
mode=0o755,
)
def add_archive_file(
archive: tarfile.TarFile,
name: str,
content: bytes,
*,
mode: int = 0o644,
) -> None:
info = tarfile.TarInfo(name)
info.size = len(content)
info.mode = mode
archive.addfile(info, io.BytesIO(content))
def sha256_file(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
if __name__ == "__main__":
unittest.main()