Alien-libhisto
view release on metacpan or search on metacpan
bundled/tools/scripts/test_container.py view on Meta::CPAN
ALL_LOCAL_TARGETS = ["hermetic", "musl", "s390x", "i386", "native-32bit", "arm64", "armv7", "riscv64"]
# Flag whether container execution requires "sg docker -c" group elevation
USE_SG_DOCKER = False
def log(msg, color="1;34"):
if sys.stdout.isatty():
print(f"\033[{color}m==>\033[0m \033[1m{msg}\033[0m")
else:
print(f"==> {msg}")
def log_success(msg):
log(msg, color="1;32")
def log_error(msg):
log(msg, color="1;31")
def exec_container_cmd(cmd, **kwargs):
"""Execute container command, applying sg docker elevation if necessary."""
if USE_SG_DOCKER and cmd[0] == "docker":
wrapped = ["sg", "docker", "-c", " ".join(shlex.quote(a) for a in cmd)]
return subprocess.run(wrapped, **kwargs)
return subprocess.run(cmd, **kwargs)
def test_engine_connectivity(engine):
"""Test if container engine binary exists and can communicate with daemon/socket."""
if not shutil.which(engine):
return False, f"'{engine}' binary not found in PATH", False
try:
res = subprocess.run(
[engine, "info"],
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE,
text=True,
timeout=10
)
if res.returncode == 0:
return True, "", False
if engine == "docker" and shutil.which("sg") and "permission denied" in res.stderr.lower():
sg_res = subprocess.run(
["sg", "docker", "-c", f"{engine} info"],
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE,
text=True,
timeout=10
)
if sg_res.returncode == 0:
return True, "", True
return False, res.stderr.strip(), False
except Exception as e:
return False, str(e), False
def detect_container_engine(preferred="auto"):
"""Detect an available and working container engine (docker or podman)."""
global USE_SG_DOCKER
candidates = ["docker", "podman"] if preferred == "auto" else [preferred]
errors = {}
for engine in candidates:
if shutil.which(engine):
ok, err, use_sg = test_engine_connectivity(engine)
if ok:
USE_SG_DOCKER = use_sg
return engine
errors[engine] = err
# If we reached here, no engine was fully working
print("\n" + "=" * 70)
log_error("No working container engine (Docker or Podman) accessible.")
print("=" * 70)
for engine, err in errors.items():
print(f" * {engine}: {err}")
print("\nTroubleshooting & Fixes:")
print(" 1. If using Docker: Ensure your user has permission to access the Docker daemon:")
print(" $ sudo usermod -aG docker $USER")
print(" (Log out and log back in, or run with 'newgrp docker')")
print(" 2. Or use Podman (rootless by default without daemon permissions):")
print(" $ sudo apt-get install -y podman # Debian/Ubuntu")
print(" $ sudo dnf install -y podman # Fedora/RHEL")
print("=" * 70 + "\n")
return None
def is_foreign_arch(target_arch):
"""Check if target architecture differs from host and requires QEMU emulation."""
host = platform.machine().lower()
if host in ["x86_64", "amd64"]:
return target_arch not in ["x86_64", "i386", "i686"]
if host in ["aarch64", "arm64"]:
return target_arch not in ["aarch64", "arm64", "armv7l"]
return target_arch != host
def ensure_binfmt_registered(engine, targets_to_run):
"""Ensure QEMU binfmt handlers are registered only if running foreign architectures."""
if platform.system() != "Linux":
return
foreign_targets = [
t for t in targets_to_run
if TARGETS[t].get("arch") and is_foreign_arch(TARGETS[t]["arch"])
]
if not foreign_targets:
return
binfmt_path = Path("/proc/sys/fs/binfmt_misc")
if binfmt_path.exists():
entries = list(binfmt_path.iterdir())
has_qemu = any("qemu-" in e.name for e in entries)
if has_qemu:
return
log("Registering QEMU user-mode binfmt handlers via multiarch/qemu-user-static...")
try:
cmd = [engine, "run", "--rm", "--privileged", "multiarch/qemu-user-static", "--reset", "-p", "yes"]
bundled/tools/scripts/test_container.py view on Meta::CPAN
parser = argparse.ArgumentParser(
description="libhisto Multi-Architecture & Portability Test Runner"
)
parser.add_argument(
"-t", "--target",
default="musl",
help="Target profile name, alias, 'all', or 'matrix' (default: musl)"
)
parser.add_argument(
"--engine",
choices=["auto", "docker", "podman"],
default="auto",
help="Container runtime engine (default: auto-detect)"
)
parser.add_argument(
"--full",
action="store_true",
help="Run full test suite (Core C + Python & Perl bindings) inside the target"
)
parser.add_argument(
"-j", "--jobs",
type=int,
default=os.cpu_count() or 4,
help="Number of parallel compilation jobs"
)
parser.add_argument(
"--build-type",
default="Release",
choices=["Release", "Debug"],
help="CMake build type (default: Release)"
)
parser.add_argument(
"--list",
action="store_true",
help="List available target architectures and profiles"
)
parser.add_argument(
"--clean",
action="store_true",
help="Clean up container build directories"
)
args = parser.parse_args()
repo_root = Path(__file__).resolve().parent.parent.parent
if args.list:
print("Available Portability & Multi-Architecture Targets:")
print("=" * 70)
for name, cfg in TARGETS.items():
print(f" * {name:<14} : {cfg['description']}")
print("\nAliases:")
for alias, target in ALIASES.items():
print(f" * {alias:<14} -> {target}")
print("\nAggregates:")
print(f" * {'all / matrix':<14} : {', '.join(ALL_LOCAL_TARGETS)}")
return 0
if args.clean:
log("Cleaning container build directories...")
for p in repo_root.glob("build-container-*"):
if p.is_dir():
shutil.rmtree(p, ignore_errors=True)
if p.is_dir():
engine = detect_container_engine()
if engine:
try:
exec_container_cmd([engine, "run", "--rm", "-v", f"{repo_root}:/workspace", "alpine:latest", "rm", "-rf", f"/workspace/{p.name}"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
except Exception:
pass
if (repo_root / "build-x86_32").is_dir():
shutil.rmtree(repo_root / "build-x86_32", ignore_errors=True)
log_success("Clean complete.")
return 0
# Resolve target
target_key = args.target.lower()
target_key = ALIASES.get(target_key, target_key)
if target_key in ["all", "matrix", "test-matrix-local"]:
targets_to_run = ALL_LOCAL_TARGETS
elif target_key in TARGETS:
targets_to_run = [target_key]
else:
log_error(f"Unknown target '{args.target}'. Run with --list to see available targets.")
return 1
needs_container = any(TARGETS[t]["image"] is not None for t in targets_to_run)
engine = None
if needs_container:
engine = detect_container_engine(args.engine)
if not engine:
return 1
ensure_binfmt_registered(engine, targets_to_run)
start_time = time.time()
results = {}
for t in targets_to_run:
cfg = TARGETS[t]
try:
if cfg["image"] is None:
ok = run_native_32bit(repo_root, args.jobs, args.build_type, args.full)
else:
ok = run_container_target(repo_root, t, cfg, engine, args.jobs, args.build_type, args.full)
results[t] = "PASSED"
except subprocess.CalledProcessError as e:
log_error(f"Target '{t}' FAILED with exit code {e.returncode}")
results[t] = f"FAILED (exit code {e.returncode})"
except Exception as e:
log_error(f"Target '{t}' FAILED: {e}")
results[t] = f"FAILED ({e})"
total_elapsed = time.time() - start_time
print("\n" + "=" * 70)
print(" PORTABILITY & MULTI-ARCHITECTURE TEST SUMMARY")
print("=" * 70)
all_passed = True
for t, status in results.items():
if "PASSED" in status:
( run in 1.331 second using v1.01-cache-2.11-cpan-1236ad508bc )