mirror of
https://github.com/openai/codex.git
synced 2026-09-20 12:47:38 +00:00
## Why Linux voice needs system ALSA plugins and enough buffering to accommodate PipeWire graph cycles without losing capture samples. Voice startup failures also need actionable diagnostics without exposing native error details. ## What changed - Build and bundle GNU voice helpers and runtimes with primary Linux musl release archives, and sign the archives. Keep Python wheels free of these libraries to preserve `manylinux_2_17` compatibility. - Discover ALSA plugins in fixed system directories and increase Linux capture and playback buffering to support larger PipeWire graph cycles. - Report voice failures by stage, preserve negotiation timeout classification, and discard native error sources. Suppress the misleading `requested` closure message after failure cleanup. - Add explicit Windows MSVC, pkgconf, and CMake toolchain configuration and preserve host architecture in native build environments. ## Testing Add coverage for Linux release assembly, ALSA plugin discovery, PipeWire capture and playback, classified startup failures, failure cleanup rendering, and Windows build environment handling. GitOrigin-RevId: d805eace96a669ce3a4489f12e2db6f68f9f7f53
214 lines
8.5 KiB
Python
214 lines
8.5 KiB
Python
"""Exercise declared tool selection and the platform-independent payload copy action."""
|
|
|
|
import copy
|
|
import errno
|
|
import json
|
|
from pathlib import Path
|
|
import tempfile
|
|
from types import SimpleNamespace
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
import bazel_windows
|
|
|
|
from bazel_copy import copy_payloads
|
|
from bazel_windows import selected_inputs
|
|
|
|
|
|
class WindowsInputsTests(unittest.TestCase):
|
|
def test_adapter_preserves_host_architecture_without_developer_path(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
config = Path(directory) / "action.json"
|
|
config.write_text("{}")
|
|
for architecture in ("AMD64", "ARM64"):
|
|
environment = {"PROCESSOR_ARCHITECTURE": architecture, "PATH": "unsafe"}
|
|
with (
|
|
self.subTest(architecture=architecture),
|
|
patch.object(
|
|
bazel_windows, "os", SimpleNamespace(environ=environment)
|
|
),
|
|
patch.object(
|
|
bazel_windows.sys, "argv", ["driver", "unknown", str(config)]
|
|
),
|
|
patch.object(
|
|
bazel_windows,
|
|
"selected_inputs",
|
|
return_value={"target": "unused"},
|
|
),
|
|
patch.object(
|
|
bazel_windows,
|
|
"build_environment",
|
|
return_value=({"PATH": "declared"}, {}),
|
|
),
|
|
):
|
|
with self.assertRaisesRegex(
|
|
ValueError, "unknown Windows native action"
|
|
):
|
|
bazel_windows.main()
|
|
self.assertEqual(
|
|
environment,
|
|
{
|
|
"PROCESSOR_ARCHITECTURE": architecture,
|
|
"PATH": "declared",
|
|
"HOME": environment["HOME"],
|
|
"USERPROFILE": environment["HOME"],
|
|
},
|
|
)
|
|
|
|
def setUp(self):
|
|
temporary = tempfile.TemporaryDirectory()
|
|
self.addCleanup(temporary.cleanup)
|
|
self.root = Path(temporary.name)
|
|
self.repository = self.root / "external/installed tools"
|
|
self.names = {
|
|
"shell": "cygwin/bin/bash.exe",
|
|
"make": "cygwin/bin/make.exe",
|
|
"cygpath": "cygwin/bin/cygpath.exe",
|
|
"automake": "cygwin/bin/automake-1.18",
|
|
"pkg_config": "pkgconf-image/PFiles64/pkgconf 3.0.6/pkgconf.exe",
|
|
}
|
|
for name in self.names.values():
|
|
path = self.repository / name
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_bytes(b"synthetic declared executable")
|
|
self.manifest = self.repository / "voice-tools.json"
|
|
self.metadata = {
|
|
"schemaVersion": 1,
|
|
"target": "aarch64-pc-windows-msvc",
|
|
"cygwinArchitecture": "x86_64",
|
|
"tools": self.names,
|
|
}
|
|
self.manifest.write_text(json.dumps(self.metadata))
|
|
self.config = {
|
|
"inputs": {
|
|
"schemaVersion": 1,
|
|
"target": "aarch64-pc-windows-msvc",
|
|
"systemRoot": "C:/Windows",
|
|
"tools": {
|
|
"cc": "external/msvc/cl.exe",
|
|
"pkg_config": str(
|
|
(self.repository / self.names["pkg_config"]).relative_to(
|
|
self.root
|
|
)
|
|
),
|
|
},
|
|
"INCLUDE": ["external/sdk/include"],
|
|
"LIB": ["external/sdk/lib"],
|
|
},
|
|
"manifest": str(self.manifest.relative_to(self.root)),
|
|
"installed_files": [
|
|
str((self.repository / name).relative_to(self.root))
|
|
for name in self.names.values()
|
|
],
|
|
}
|
|
|
|
def test_paths_are_anchored_before_recipe_changes_directory(self):
|
|
before = copy.deepcopy(self.config)
|
|
expected = {
|
|
**self.config["inputs"],
|
|
"tools": {
|
|
"cc": str(self.root / "external/msvc/cl.exe"),
|
|
**{
|
|
name: str(self.repository / path)
|
|
for name, path in self.names.items()
|
|
},
|
|
},
|
|
"INCLUDE": [str(self.root / "external/sdk/include")],
|
|
"LIB": [str(self.root / "external/sdk/lib")],
|
|
}
|
|
self.assertEqual(selected_inputs(self.config, self.root), expected)
|
|
self.assertEqual(self.config, before)
|
|
|
|
def test_manifest_cannot_select_a_file_omitted_from_action_inputs(self):
|
|
self.config["installed_files"].pop()
|
|
with self.assertRaisesRegex(ValueError, "selection differs: pkg_config"):
|
|
selected_inputs(self.config, self.root)
|
|
|
|
def test_manifest_cannot_escape_installed_tree(self):
|
|
outside = self.repository.parent / "outside.exe"
|
|
outside.write_bytes(b"not part of the installed support tree")
|
|
inside = self.repository / "cygwin/bin/other-bash.exe"
|
|
try:
|
|
inside.symlink_to(outside)
|
|
except OSError as error:
|
|
if error.errno not in (errno.EPERM, errno.EACCES):
|
|
raise
|
|
self.skipTest("creating symlinks requires OS permission")
|
|
self.config["installed_files"].append(str(inside.relative_to(self.root)))
|
|
self.metadata["tools"]["shell"] = str(inside.relative_to(self.repository))
|
|
self.manifest.write_text(json.dumps(self.metadata))
|
|
with self.assertRaisesRegex(ValueError, "selection differs: shell"):
|
|
selected_inputs(self.config, self.root)
|
|
|
|
def test_manifest_cannot_replace_the_build_script_executable(self):
|
|
alternative = self.repository / "pkgconf-image/another/pkgconf.exe"
|
|
alternative.parent.mkdir(parents=True)
|
|
alternative.write_bytes(b"different declared executable")
|
|
self.config["installed_files"].append(str(alternative.relative_to(self.root)))
|
|
self.metadata["tools"]["pkg_config"] = str(
|
|
alternative.relative_to(self.repository)
|
|
)
|
|
self.manifest.write_text(json.dumps(self.metadata))
|
|
with self.assertRaisesRegex(ValueError, "selection differs: pkg_config"):
|
|
selected_inputs(self.config, self.root)
|
|
|
|
def test_wrong_target_or_emulation_contract_is_rejected(self):
|
|
for key, value in (
|
|
("target", "x86_64-pc-windows-msvc"),
|
|
("cygwinArchitecture", "aarch64"),
|
|
):
|
|
with self.subTest(key=key):
|
|
self.manifest.write_text(json.dumps({**self.metadata, key: value}))
|
|
with self.assertRaisesRegex(ValueError, "selected target"):
|
|
selected_inputs(self.config, self.root)
|
|
|
|
def test_missing_declared_executable_does_not_fall_back_to_path(self):
|
|
(self.repository / self.names["shell"]).unlink()
|
|
with self.assertRaises(FileNotFoundError):
|
|
selected_inputs(self.config, self.root)
|
|
|
|
|
|
class LibraryCopiesTests(unittest.TestCase):
|
|
def test_import_library_and_dll_bytes_remain_distinct(self):
|
|
with tempfile.TemporaryDirectory() as temporary:
|
|
root = Path(temporary)
|
|
dll = root / "source.dll"
|
|
library = root / "source.lib"
|
|
dll.write_bytes(b"DLL payload")
|
|
library.write_bytes(b"import library payload")
|
|
locator = root / "package/lib/search-path"
|
|
copy_payloads(
|
|
locator,
|
|
[
|
|
dll,
|
|
root / "package/bin/audio.dll",
|
|
library,
|
|
root / "package/lib/audio.lib",
|
|
],
|
|
)
|
|
self.assertTrue(locator.is_dir())
|
|
self.assertEqual(
|
|
{
|
|
path.relative_to(root / "package").as_posix(): path.read_bytes()
|
|
for path in (root / "package").rglob("*")
|
|
if path.is_file()
|
|
},
|
|
{
|
|
"bin/audio.dll": b"DLL payload",
|
|
"lib/audio.lib": b"import library payload",
|
|
},
|
|
)
|
|
|
|
def test_missing_payload_fails_the_action(self):
|
|
with tempfile.TemporaryDirectory() as temporary:
|
|
root = Path(temporary)
|
|
with self.assertRaises(FileNotFoundError):
|
|
copy_payloads(
|
|
root / "lib/search-path",
|
|
[root / "missing.dll", root / "bin/audio.dll"],
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|