diff options
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/test_vendor_portable_pty.py | 103 | ||||
| -rw-r--r-- | scripts/windows_smoke_conpty_path.ps1 | 105 |
2 files changed, 208 insertions, 0 deletions
diff --git a/scripts/test_vendor_portable_pty.py b/scripts/test_vendor_portable_pty.py new file mode 100644 index 00000000..8a6d4ca1 --- /dev/null +++ b/scripts/test_vendor_portable_pty.py @@ -0,0 +1,103 @@ +from __future__ import annotations + +import json +import subprocess +import unittest +from pathlib import Path + + +class VendorPortablePtyTests(unittest.TestCase): + def test_vendored_tree_contains_required_upstream_files(self) -> None: + root = Path(__file__).resolve().parent.parent / "vendor" / "portable-pty" + required = [ + root / "Cargo.toml", + root / "LICENSE.md", + root / "src" / "lib.rs", + root / "src" / "win" / "psuedocon.rs", + ] + + missing = [str(path.relative_to(root)) for path in required if not path.exists()] + self.assertEqual(missing, []) + + def test_cargo_patch_points_at_vendored_tree(self) -> None: + project_root = Path(__file__).resolve().parent.parent + cargo_toml = (project_root / "Cargo.toml").read_text() + + self.assertIn('portable-pty = "=0.9.0"', cargo_toml) + self.assertIn("[patch.crates-io]", cargo_toml) + self.assertIn('portable-pty = { path = "vendor/portable-pty" }', cargo_toml) + + def test_cargo_metadata_resolves_portable_pty_to_vendored_tree(self) -> None: + project_root = Path(__file__).resolve().parent.parent + result = subprocess.run( + ["cargo", "metadata", "--locked", "--format-version", "1"], + cwd=project_root, + text=True, + capture_output=True, + ) + self.assertEqual( + result.returncode, + 0, + f"cargo metadata failed:\nstdout:\n{result.stdout}\nstderr:\n{result.stderr}", + ) + + metadata = json.loads(result.stdout) + packages = [ + package + for package in metadata["packages"] + if package["name"] == "portable-pty" and package["version"] == "0.9.0" + ] + self.assertEqual(len(packages), 1) + + manifest_path = Path(packages[0]["manifest_path"]).resolve() + expected = (project_root / "vendor" / "portable-pty" / "Cargo.toml").resolve() + self.assertEqual(manifest_path, expected) + + def test_local_vendor_patches_are_listed_in_patch_index(self) -> None: + project_root = Path(__file__).resolve().parent.parent + index = project_root / "vendor" / "portable-pty.patches.md" + patch_dir = project_root / "vendor" / "patches" / "portable-pty" + patches = sorted(patch_dir.glob("*.patch")) + + if not patches: + return + + self.assertTrue(index.exists()) + text = index.read_text() + missing = [ + str(path.relative_to(project_root)) + for path in patches + if str(path.relative_to(project_root)) not in text + ] + self.assertEqual(missing, []) + + def test_local_vendor_patches_are_applied_to_vendored_tree(self) -> None: + project_root = Path(__file__).resolve().parent.parent + patch_dir = project_root / "vendor" / "patches" / "portable-pty" + + for patch in sorted(patch_dir.glob("*.patch")): + result = subprocess.run( + ["git", "apply", "--check", "--reverse", str(patch.relative_to(project_root))], + cwd=project_root, + text=True, + capture_output=True, + ) + self.assertEqual( + result.returncode, + 0, + f"{patch.relative_to(project_root)} is not applied cleanly:\n" + f"stdout:\n{result.stdout}\n" + f"stderr:\n{result.stderr}", + ) + + def test_windows_conpty_loader_does_not_probe_path_conpty_dll(self) -> None: + project_root = Path(__file__).resolve().parent.parent + source = project_root / "vendor" / "portable-pty" / "src" / "win" / "psuedocon.rs" + text = source.read_text() + + self.assertIn('ConPtyFuncs::open(Path::new("kernel32.dll"))', text) + self.assertNotIn('Path::new("conpty.dll")', text) + + +if __name__ == "__main__": + unittest.main() diff --git a/scripts/windows_smoke_conpty_path.ps1 b/scripts/windows_smoke_conpty_path.ps1 new file mode 100644 index 00000000..d3fceb3f --- /dev/null +++ b/scripts/windows_smoke_conpty_path.ps1 @@ -0,0 +1,105 @@ +param( + [Parameter(Mandatory = $true)] + [string] $ExePath, + + [string] $Session = "ci-windows-$([guid]::NewGuid().ToString('N'))" +) + +$ErrorActionPreference = "Stop" + +function Invoke-Checked { + param([string] $Command, [string[]] $Arguments) + & $Command @Arguments + if ($LASTEXITCODE -ne 0) { + throw "command failed with exit code $LASTEXITCODE`: $Command $($Arguments -join ' ')" + } +} + +$exe = (Resolve-Path $ExePath).Path +$fakeDir = Join-Path ([System.IO.Path]::GetTempPath()) "herdr-fake-conpty-$([guid]::NewGuid().ToString('N'))" +New-Item -ItemType Directory -Force $fakeDir | Out-Null + +$fakeSource = Join-Path $fakeDir "fake_conpty.rs" +$fakeDll = Join-Path $fakeDir "conpty.dll" +@' +#![allow(non_snake_case)] + +use std::ffi::c_void; + +#[repr(C)] +pub struct COORD { + pub X: i16, + pub Y: i16, +} + +type HANDLE = *mut c_void; +type HRESULT = i32; + +#[no_mangle] +pub extern "system" fn CreatePseudoConsole( + _size: COORD, + _h_input: HANDLE, + _h_output: HANDLE, + _flags: u32, + _hpc: *mut HANDLE, +) -> HRESULT { + -2147467259 +} + +#[no_mangle] +pub extern "system" fn ResizePseudoConsole(_hpc: HANDLE, _size: COORD) -> HRESULT { + -2147467259 +} + +#[no_mangle] +pub extern "system" fn ClosePseudoConsole(_hpc: HANDLE) {} +'@ | Set-Content -NoNewline -Encoding utf8 $fakeSource + +Invoke-Checked rustc @("--crate-type", "cdylib", "--edition", "2021", $fakeSource, "-o", $fakeDll) + +$oldPath = $env:PATH +$oldSession = $env:HERDR_SESSION +$env:PATH = "$fakeDir;$oldPath" +$env:HERDR_SESSION = $Session + +$server = $null +try { + Invoke-Checked $exe @("--version") + & $exe --default-config | Out-Null + if ($LASTEXITCODE -ne 0) { + throw "command failed with exit code $LASTEXITCODE`: $exe --default-config" + } + + $server = Start-Process -FilePath $exe -ArgumentList "server" -PassThru -WindowStyle Hidden + $deadline = (Get-Date).AddSeconds(10) + do { + Start-Sleep -Milliseconds 250 + $status = & $exe status server 2>&1 + if ($LASTEXITCODE -eq 0 -and (($status -join "`n") -match "status: running")) { + break + } + } while ((Get-Date) -lt $deadline) + + if ((Get-Date) -ge $deadline) { + throw "server did not become ready" + } + + Invoke-Checked $exe @("agent", "start", "smoke", "--", $env:ComSpec, "/K", "dir") + Start-Sleep -Seconds 2 + $read = & $exe agent read smoke --source recent --lines 40 --format text + if ($LASTEXITCODE -ne 0) { + throw "command failed with exit code $LASTEXITCODE`: $exe agent read smoke" + } + $text = $read -join "`n" + if ($text -notmatch "File\(s\)" -or $text -notmatch "Dir\(s\)") { + throw "pane read did not include cmd.exe directory output: $text" + } +} finally { + if ($null -ne $server) { + & $exe server stop 2>$null + Wait-Process -Id $server.Id -Timeout 10 -ErrorAction SilentlyContinue + } + $env:PATH = $oldPath + $env:HERDR_SESSION = $oldSession + Remove-Item -Recurse -Force $fakeDir -ErrorAction SilentlyContinue +} |
