"""Exercise the Windows launcher without downloading or compiling Docker images.""" import json import shutil import subprocess from pathlib import Path import pytest POWERSHELL = shutil.which("powershell") REPO_ROOT = Path(__file__).resolve().parents[1] pytestmark = pytest.mark.skipif(not POWERSHELL, reason="Windows PowerShell is required") def run_launcher(tmp_path, arguments, image_architecture): docker_dir = tmp_path / "build" / "wheel" / "docker" docker_dir.mkdir(parents=True) shutil.copy2(REPO_ROOT / "build/wheel/docker/run.ps1", docker_dir) (docker_dir / "entrypoint.sh").write_text("#!/bin/bash\n", encoding="utf-8") parameters = {} iterator = iter(arguments) for argument in iterator: parameters[argument.lstrip("-")] = ( True if argument == "-SkipImageBuild" else next(iterator) ) (tmp_path / "arguments.json").write_text(json.dumps(parameters), encoding="utf-8") harness = tmp_path / "harness.ps1" harness.write_text( """ $ErrorActionPreference = 'Stop' $global:Calls = [System.Collections.Generic.List[object]]::new() function docker { $global:Calls.Add(@($args)) $global:LASTEXITCODE = 0 if ($args[0] -eq 'version') { return 'linux' } if ($args[0] -eq 'image') { return '__ARCH__' } } $decoded = Get-Content (Join-Path $PSScriptRoot 'arguments.json') -Raw | ConvertFrom-Json $parameters = @{} foreach ($property in $decoded.PSObject.Properties) { $parameters[$property.Name] = $property.Value } try { & (Join-Path $PSScriptRoot 'build/wheel/docker/run.ps1') @parameters } finally { ConvertTo-Json -InputObject @($global:Calls.ToArray()) -Depth 5 | Set-Content (Join-Path $PSScriptRoot 'calls.json') -Encoding UTF8 } """.replace("__ARCH__", image_architecture), encoding="utf-8", ) result = subprocess.run( [POWERSHELL, "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", str(harness)], capture_output=True, text=True, check=False, ) calls = json.loads((tmp_path / "calls.json").read_text(encoding="utf-8-sig")) return result, calls @pytest.mark.parametrize( "arguments,image_architecture,platform,architecture,image_name", [ ([], "amd64", "linux/amd64", "x86_64", "orisys-manylinux"), ( ["-Architecture", "aarch64"], "arm64", "linux/arm64", "aarch64", "orisys-manylinux-aarch64", ), ( ["-Architecture", "AARCH64"], "arm64", "linux/arm64", "aarch64", "orisys-manylinux-aarch64", ), ], ) def test_architecture_controls_build_and_run( tmp_path, arguments, image_architecture, platform, architecture, image_name ): result, calls = run_launcher(tmp_path, arguments, image_architecture) assert result.returncode == 0, result.stderr build = next(call for call in calls if call[0] == "build") run = next(call for call in calls if call[0] == "run") assert build[build.index("--platform") + 1] == platform assert build[build.index("--build-arg") + 1] == f"MANYLINUX_ARCH={architecture}" assert build[build.index("-t") + 1] == image_name assert run[run.index("--platform") + 1] == platform assert run[-1] == image_name @pytest.mark.parametrize( "image_architecture,success", [("arm64", True), ("amd64", False)] ) def test_reused_image_must_match_requested_architecture( tmp_path, image_architecture, success ): result, calls = run_launcher( tmp_path, [ "-Architecture", "aarch64", "-SkipImageBuild", "-ImageName", "custom-arm", "-PythonVersions", "3.10", ], image_architecture, ) assert not any(call[0] == "build" for call in calls) assert (result.returncode == 0) == success runs = [call for call in calls if call[0] == "run"] if success: assert runs[0][-1] == "custom-arm" assert "ORISYS_PYTHON_TAGS=cp310-cp310" in runs[0] else: assert not runs assert "architecture" in result.stderr.lower()