Files
codex/third_party/voice/test_cargo_ci.py
Benjamin Carlsson e02a32cd51 Wire native voice dependencies into full Rust CI (#58719)
## What changed

- Build native voice SDK and runtime artifacts with Bazel for Linux GNU, macOS, and Windows on x64 and ARM64, then configure Cargo jobs to consume them. Keep musl builds in a separate job without these inputs.
- Validate artifact receipts against the target, source commit, and source manifest; verify SDK file hashes and relocate pkg-config metadata. Package the runtime and plugins with nextest archives and restore `CODEX_TEST_VOICE_RUNTIME` when running shards.
- Prepare native Windows voice tools for argument-comment linting and install ARM64 DotSlash on Windows ARM64. Reduce archive disk usage, bound Linux ARM64 build concurrency, and correct the JUnit artifact path.
- Recognize TLS errors wrapped directly in `io::Error`. Make TLS fallback fixtures select the native backend explicitly, allow the MCP test server's non-loopback address, and isolate skill-root tests from ancestor Git markers. Install `git` in remote test containers and filter MCP discovery assertions to POST requests.

## Testing

Add tests for relocated voice inputs, Windows ARM64 import libraries with host-native pkgconf, and rejection of mismatched receipts or corrupt SDK files. Extend untrusted-certificate coverage to an explicitly selected rustls backend.

GitOrigin-RevId: da3ff690b94906edc1f8f60a3c7feda1bf66134a
2026-11-01 15:50:50 +00:00

159 lines
6.3 KiB
Python

"""Exercise how full Cargo CI consumes prepared native inputs."""
import json
import os
from pathlib import Path
import shlex
import shutil
import subprocess
import sys
import tempfile
import unittest
from unittest.mock import patch
from cargo_ci import environment, main, stage
from package_runtime import runtime_files
from runtime import PLUGINS, digest, required_library_paths
from sdk import MODULES
def write_files(root, contents):
records = []
for name, data in contents.items():
path = root / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(data)
path.chmod(0o444) # Bazel materializes these inputs read-only.
records.append({"path": name, "sha256": digest(path)})
return records
class CargoCiTests(unittest.TestCase):
def setUp(self):
temp = tempfile.TemporaryDirectory(prefix="voice cargo ")
self.addCleanup(temp.cleanup)
self.root = Path(temp.name).resolve()
self.sdk = self.root / "sdk"
self.runtime = self.root / "runtime"
self.output = self.root / "stage"
self.commit = "a" * 40
def inputs(self, target, alias, plugin):
canonical = required_library_paths(target)[0]
plugins = [plugin.format(name) for name in PLUGINS]
metadata = (
"prefix=/producer\nName: {name}\nDescription: fixture\nVersion: 1.28\n"
)
sdk = {alias: b"original", "lib/glib-2.0/include/glibconfig.h": b"header"}
for name in MODULES:
sdk[f"lib/pkgconfig/{name}.pc"] = metadata.format(name=name).encode()
if not target.endswith("msvc"):
sdk[canonical] = sdk[alias]
sdk_files = write_files(self.sdk, sdk)
libraries = write_files(
self.runtime, {name: b"prepared" for name in [canonical, *plugins]}
)
libraries[0]["sourceSha256"] = digest(self.sdk / alias)
identity = dict(
schemaVersion=1,
target=target,
sourceCommit=self.commit,
sourceManifestSha256=digest(Path(__file__).with_name("sources.json")),
)
(self.sdk / "sdk.json").write_text(json.dumps(dict(identity, files=sdk_files)))
receipt = dict(
identity, developmentOnly=True, libraries=libraries, plugins=plugins
)
(self.runtime / "runtime.json").write_text(json.dumps(receipt))
return canonical
def test_relocated_metadata_runtime_and_cargo_probe(self):
target = "aarch64-apple-darwin"
alias = "lib/libgio-2.0.dylib"
canonical = self.inputs(target, alias, "plugins/libgst{}.dylib")
stage(self.sdk, self.runtime, self.output, target, self.commit)
moved = self.root / "moved native inputs"
self.output.rename(moved)
native = moved / "runtime"
self.assertEqual(
(native / alias).read_bytes(), (self.runtime / canonical).read_bytes()
)
self.assertEqual(
runtime_files(native, target), runtime_files(self.runtime, target)
)
host = (
os.environ.get("VOICE_PKG_CONFIG")
or shutil.which("pkg-config")
or shutil.which("pkgconf")
)
if not host:
self.skipTest("host pkg-config is required to probe relocated metadata")
pkgdir = moved / "sdk/lib/pkgconfig"
probe_env = dict(os.environ, PKG_CONFIG_PATH="", PKG_CONFIG_LIBDIR=str(pkgdir))
result = subprocess.check_output(
[host, "--variable=prefix", "gio-2.0"], env=probe_env, text=True
)
# pkgconf can escape spaces while pkg-config can print the raw path.
raw = result.strip()
words = shlex.split(raw)
self.assertEqual(
Path(words[0] if len(words) == 1 else raw).resolve(), moved / "sdk"
)
env_file = self.root / "github env"
ci_env = dict(
GITHUB_SHA=self.commit, GITHUB_ENV=str(env_file), VOICE_PKG_CONFIG=host
)
argv = ["cargo_ci", "configure", "--root", str(moved), "--target", target]
with patch.dict(os.environ, ci_env), patch.object(sys, "argv", argv):
main()
written = env_file.read_text()
values = dict(line.split("=", 1) for line in written.splitlines())
self.assertEqual(
values["PKG_CONFIG_PATH_aarch64_apple_darwin"], str(pkgdir)
)
self.assertNotIn("PKG_CONFIG_LIBDIR_aarch64_apple_darwin", values)
(pkgdir / "gio-2.0.pc").unlink()
with self.assertRaises(subprocess.CalledProcessError):
main()
self.assertEqual(env_file.read_text(), written)
def test_arm64_import_library_and_dll_with_x64_host_pkgconf(self):
target = "aarch64-pc-windows-msvc"
alias = "lib/gio-2.0.lib"
canonical = self.inputs(target, alias, "bin/gst{}.dll")
stage(self.sdk, self.runtime, self.output, target, self.commit)
native = self.output / "runtime"
self.assertEqual(
(native / "bin/gio-2.0.lib").read_bytes(), (self.sdk / alias).read_bytes()
)
self.assertEqual(
(native / canonical).read_bytes(), (self.runtime / canonical).read_bytes()
)
tool = self.root / "x64 pkgconf.exe"
tool.touch()
with patch.dict(os.environ, GITHUB_SHA=self.commit):
values = environment(self.output, target, tool)
self.assertEqual(values["PKG_CONFIG_aarch64_pc_windows_msvc"], str(tool))
self.assertEqual(
values["SYSTEM_DEPS_GSTREAMER_1_0_SEARCH_NATIVE"], str(native / "bin")
)
def test_mismatched_receipt_and_corrupt_sdk_fail_staging(self):
target = "x86_64-unknown-linux-gnu"
alias = "lib/libgio-2.0.so"
self.inputs(target, alias, "lib/gstreamer-1.0/libgst{}.so")
for triple, commit in [
(target, "b" * 40),
("aarch64-unknown-linux-gnu", self.commit),
]:
with self.assertRaisesRegex(ValueError, "do not match"):
stage(self.sdk, self.runtime, self.output, triple, commit)
source = self.sdk / alias
source.chmod(0o644)
source.write_bytes(b"corrupt")
with self.assertRaisesRegex(ValueError, "receipt"):
stage(self.sdk, self.runtime, self.output, target, self.commit)
self.assertFalse(self.output.exists())