Alien-libhisto
view release on metacpan or search on metacpan
bundled/tools/scripts/test_container.py view on Meta::CPAN
},
"native-32bit": {
"description": "Host Native 32-bit x86 (-m32 multilib execution directly on Linux host)",
"image": None,
"platform": None,
"arch": "i686",
"endian": "little",
"bits": 32,
},
}
ALIASES = {
"alpine": "musl",
"big-endian": "s390x",
"bigendian": "s390x",
"32bit": "i386",
"x86": "i386",
"aarch64": "arm64",
"arm": "armv7",
"riscv": "riscv64",
"32bit-native": "native-32bit",
}
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"]
exec_container_cmd(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
except Exception as e:
log_error(f"Warning: Failed to auto-register binfmt handlers ({e}). Foreign architectures may fail if not pre-configured.")
def run_native_32bit(repo_root, jobs, build_type="Release", full=False):
"""Run native 32-bit test on host using -m32 flags."""
build_dir = repo_root / "build-x86_32"
log(f"Running Host Native 32-bit Build & Test in {build_dir}...")
cmake_args = [
"cmake",
f"-B{build_dir}",
f"-S{repo_root}",
f"-DCMAKE_BUILD_TYPE={build_type}",
"-DCMAKE_C_FLAGS=-m32",
"-DCMAKE_EXE_LINKER_FLAGS=-m32",
"-DCMAKE_SHARED_LINKER_FLAGS=-m32",
"-DCMAKE_MODULE_LINKER_FLAGS=-m32",
]
t0 = time.time()
try:
subprocess.run(cmake_args, check=True)
except subprocess.CalledProcessError as e:
log_error("Host 32-bit multilib build configuration failed.")
print("\nTroubleshooting 32-Bit Multilib:")
print(" Install multilib development packages on your host:")
print(" $ sudo apt-get install -y gcc-multilib libc6-dev-i386 # Debian/Ubuntu")
print(" $ sudo dnf install -y glibc-devel.i686 libgcc.i686 # Fedora/RHEL")
print(" $ sudo pacman -S lib32-glibc lib32-gcc-libs # Arch Linux")
print(" Alternatively, run 32-bit tests inside an isolated container:")
print(" $ make test-32bit\n")
raise e
subprocess.run(["cmake", "--build", str(build_dir), f"-j{jobs}"], check=True)
subprocess.run(["ctest", "--test-dir", str(build_dir), f"-j{jobs}", "--output-on-failure"], check=True)
elapsed = time.time() - t0
log_success(f"Host Native 32-bit Test PASSED in {elapsed:.2f}s")
return True
def run_container_target(repo_root, target_name, config, engine, jobs, build_type="Release", full=False):
"""Run build and test inside a container target with enhanced crash reporting."""
image = config["image"]
build_dir_name = f"build-container-{target_name}"
install_cmd = config["install_full_cmd"] if full else config["install_cmd"]
effective_jobs = jobs
if config.get("arch") and is_foreign_arch(config["arch"]):
max_emulated_jobs = 2 if config.get("arch") in ["s390x", "armv7l"] else 4
if jobs > max_emulated_jobs:
effective_jobs = max_emulated_jobs
log(f"Notice: Clamped parallel concurrency to -j{effective_jobs} for emulated target '{target_name}' (QEMU user-mode stability safeguard).")
extra_cmake_flags = config.get("cmake_flags", "")
cmake_config_cmd = f"cmake -B {build_dir_name} -S . -DCMAKE_BUILD_TYPE={build_type}"
if extra_cmake_flags:
cmake_config_cmd += f" {extra_cmake_flags}"
bundled/tools/scripts/test_container.py view on Meta::CPAN
)
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:
mark = "\033[1;32m[PASS]\033[0m" if sys.stdout.isatty() else "[PASS]"
else:
mark = "\033[1;31m[FAIL]\033[0m" if sys.stdout.isatty() else "[FAIL]"
all_passed = False
print(f" {mark} {t:<16} : {status}")
print("=" * 70)
print(f"Total time elapsed: {total_elapsed:.2f}s\n")
( run in 1.064 second using v1.01-cache-2.11-cpan-5c0b1e786e0 )