#!/usr/bin/env python3 """Verify feature modules and real Redis in sequential resource-limited containers.""" import argparse from datetime import datetime, timezone import hashlib import json from pathlib import Path import shutil import signal import subprocess import tempfile import time import uuid def capture(command): return subprocess.check_output(command, text=True, timeout=15).strip() def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--image", default="golang:1.25") parser.add_argument("--redis-image", default="redis:7.4-alpine") parser.add_argument("--advanced", action="store_true", help="also verify shared state, durable queue, S3 and MySQL migrations") parser.add_argument("--s3-image", default="quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z") parser.add_argument("--mysql-image", default="m.daocloud.io/docker.io/library/mysql:8.4") parser.add_argument("--goproxy", default="") parser.add_argument("--wall-timeout", type=int, default=900) parser.add_argument("--output", type=Path, default=Path("performance/modulesLinux.json")) args = parser.parse_args() if args.wall_timeout < 1: parser.error("wall timeout must be positive") docker = shutil.which("docker") if not docker: raise SystemExit("docker is required") root = Path(__file__).resolve().parent.parent running = capture([docker, "ps", "--format", "{{.ID}} {{.Names}}"]) if any("go-omni-" in line for line in running.splitlines()): raise SystemExit("another go-omni validation container is active") before = {line.split()[0] for line in running.splitlines()} output_path = args.output.resolve() output_path.parent.mkdir(parents=True, exist_ok=True) log_path = output_path.with_suffix(".txt") prefix = "go-omni-modules-" + uuid.uuid4().hex[:12] stages = ("build", "redis", "s3", "mysql") if args.advanced else ("build", "redis") names = [prefix + "-" + stage for stage in stages] paths = [root / "go.mod", root / "go.sum", root / "httpx/context.go", Path(__file__).resolve()] directories = ["cache", "httpc", "task", "middleware", "cmd/go-omni", "examples/modules"] if args.advanced: directories += ["auth", "config", "logx", "openapi", "telemetry", "resilience", "storage", "queue", "audit", "pagination", "database/migrate", "internal/storagefixture", "examples/platform"] for directory in directories: paths.extend(p for p in (root / directory).rglob("*") if p.is_file() and p.suffix in (".go", ".tmpl")) report = { "status": "running", "startedAt": datetime.now(timezone.utc).isoformat(), "testedCommit": capture(["git", "-C", str(root), "rev-parse", "HEAD"]), "workingTreeStatus": capture(["git", "-C", str(root), "status", "--porcelain"]), "sourceSha256": {str(p.relative_to(root)): hashlib.sha256(p.read_bytes()).hexdigest() for p in sorted(set(paths))}, "containerLimits": {"cpus": 2, "memoryBytes": 2147483648, "memoryAndSwapBytes": 2147483648, "pidsLimit": 256}, "wallTimeoutSeconds": args.wall_timeout, "imageIds": {}, "commands": [], "cleanedContainers": [], "notes": [ "Full repository race/vet/build plus independent generated-module CRUD in the compiler container.", "Real Redis uses a statically linked non-race test binary, loopback only, network=none and no published ports.", "At most one validation container; no pre-existing service or database is accessed.", ], } output_path.write_text(json.dumps(report, indent=2) + "\n") deadline = time.monotonic() + args.wall_timeout failure = None def interrupt(signum, frame): raise KeyboardInterrupt previous = {sig: signal.signal(sig, interrupt) for sig in (signal.SIGTERM, signal.SIGHUP)} with log_path.open("w") as log: def execute(command): remaining = deadline - time.monotonic() if remaining <= 0: raise TimeoutError("module verification wall timeout") report["commands"].append(command) subprocess.run(command, stdout=log, stderr=subprocess.STDOUT, check=True, timeout=remaining) log.flush() def container(name, arguments): try: execute([docker, "run", "--rm", "--name", name, "--cpus", "2", "--memory", "2048m", "--memory-swap", "2048m", "--pids-limit", "256", "--log-opt", "max-size=10m", "--log-opt", "max-file=1"] + arguments) finally: result = subprocess.run([docker, "rm", "--force", name], capture_output=True, text=True, timeout=15) if result.returncode and "No such container" not in result.stderr: raise RuntimeError("could not clean up " + name) report["cleanedContainers"].append(name) try: images = [args.image, args.redis_image] if args.advanced: images += [args.s3_image, args.mysql_image] for image in images: result = subprocess.run([docker, "image", "inspect", image], capture_output=True, timeout=15) if result.returncode: print("Pulling " + image, flush=True) execute([docker, "pull", image]) report["imageIds"][image] = capture([docker, "image", "inspect", image, "--format", "{{.Id}}"]) with tempfile.TemporaryDirectory(prefix="goOmniModules") as temporary: # The Redis stage runs as an unprivileged uid and must traverse # this directory to execute the read-only test binary. Path(temporary).chmod(0o755) print("Full Linux race/vet/build and generated-project CRUD; 2 CPUs, 2048 MiB, 256 pids", flush=True) arguments = ["-v", f"{root}:/workspace:ro", "-v", f"{temporary}:/output", "-w", "/workspace", "-e", "GOWORK=off", "-e", "GOMAXPROCS=2", "-e", "GO_OMNI_SCAFFOLD_INTEGRATION=1"] if args.goproxy: arguments += ["-e", "GOPROXY=" + args.goproxy] checks = "go version && go test -race -count=1 -timeout=240s ./... && go vet ./... && go build ./... && CGO_ENABLED=0 go test -c -o /output/redis.test ./cache/redis" if args.advanced: for binary, package in (("shared", "middleware/redis"), ("queue", "queue/redis"), ("s3", "storage/s3"), ("mysql", "database/migrate")): checks += f" && CGO_ENABLED=0 go test -c -o /output/{binary}.test ./{package}" arguments += [args.image, "sh", "-c", checks] container(names[0], arguments) print("Real Redis TTL/read/write/delete/failure checks; external network disabled", flush=True) redis_check = '''set -eu redis-server --version redis-server --bind 127.0.0.1 --port 6379 --save '' --appendonly no --maxmemory 32mb --daemonize yes --pidfile /tmp/redis.pid --logfile /tmp/redis.log --dir /tmp trap 'redis-cli -h 127.0.0.1 shutdown nosave >/dev/null 2>&1 || true' EXIT attempt=0 until redis-cli -h 127.0.0.1 ping >/dev/null 2>&1; do attempt=$((attempt + 1)) if [ "$attempt" -gt 50 ]; then cat /tmp/redis.log; exit 1; fi sleep 0.1 done /test/redis.test -test.v -test.run '^TestRedisIntegration$' -test.count=1 -test.timeout=30s ''' if args.advanced: redis_check += "/test/shared.test -test.v -test.count=1 -test.timeout=45s\n/test/queue.test -test.v -test.count=1 -test.timeout=45s\n" container(names[1], ["--network", "none", "--read-only", "--cap-drop", "ALL", "--security-opt", "no-new-privileges", "--user", "999:999", "--tmpfs", "/tmp:rw,nosuid,noexec,size=64m,mode=1777", "-v", f"{temporary}:/test:ro", "--entrypoint", "sh", "-e", "GOMAXPROCS=2", "-e", "GO_OMNI_REDIS_TEST_ADDR=127.0.0.1:6379", args.redis_image, "-c", redis_check]) if args.advanced: isolated = ["--network", "none", "--read-only", "--cap-drop", "ALL", "--security-opt", "no-new-privileges", "-v", f"{temporary}:/test:ro", "--entrypoint", "sh", "-e", "GOMAXPROCS=2"] print("Real S3 object operations and signature verification in isolated MinIO", flush=True) s3_check = '''set -eu minio --version minio server /data --address 127.0.0.1:9000 --console-address 127.0.0.1:9001 >/tmp/minio.log 2>&1 & server=$! trap 'kill "$server" 2>/dev/null || true; wait "$server" 2>/dev/null || true' EXIT /test/s3.test -test.v -test.run '^TestS3Integration$' -test.count=1 -test.timeout=45s || { cat /tmp/minio.log; exit 1; } ''' container(names[2], isolated + ["--user", "1000:1000", "--tmpfs", "/tmp:rw,nosuid,noexec,size=64m,mode=1777", "--tmpfs", "/data:rw,nosuid,noexec,size=128m,mode=1777", "-e", "MINIO_ROOT_USER=goOmniTest", "-e", "MINIO_ROOT_PASSWORD=goOmniTestSecret", "-e", "GO_OMNI_S3_TEST_ADDR=127.0.0.1:9000", args.s3_image, "-c", s3_check]) print("Real MySQL migrations, repeat apply, dirty-state blocking and SQL spans", flush=True) mysql_check = '''set -eu mysqld --version mkdir /tmp/mysql mysqld --no-defaults --initialize-insecure --datadir=/tmp/mysql --innodb-redo-log-capacity=67108864 >/tmp/init.log 2>&1 || { cat /tmp/init.log; exit 1; } mysqld --no-defaults --datadir=/tmp/mysql --socket=/tmp/mysql.sock --pid-file=/tmp/mysql.pid --bind-address=127.0.0.1 --port=3306 --mysqlx=OFF --skip-log-bin --skip-name-resolve --innodb-buffer-pool-size=134217728 --innodb-redo-log-capacity=67108864 --max-connections=8 --log-error=/tmp/mysql.log & server=$! trap 'kill "$server" 2>/dev/null || true; wait "$server" 2>/dev/null || true' EXIT attempt=0 until mysql --no-defaults --socket=/tmp/mysql.sock -uroot -e 'SELECT 1' >/dev/null 2>&1; do attempt=$((attempt + 1)); if [ "$attempt" -gt 200 ]; then cat /tmp/mysql.log; exit 1; fi; sleep 0.1 done mysql --no-defaults --socket=/tmp/mysql.sock -uroot -e 'CREATE DATABASE omni_test' /test/mysql.test -test.v -test.run '^TestMySQLIntegration$' -test.count=1 -test.timeout=60s ''' container(names[3], isolated + ["--user", "999:999", "--tmpfs", "/tmp:rw,nosuid,noexec,size=512m,mode=1777", "--tmpfs", "/var/lib/mysql:rw,nosuid,noexec,size=1m,mode=1777", "-e", "GO_OMNI_MIGRATE_TEST_DSN=root@unix(/tmp/mysql.sock)/omni_test?parseTime=true&timeout=2s&readTimeout=5s&writeTimeout=5s", args.mysql_image, "-c", mysql_check]) log.flush() content = log_path.read_text() if "--- PASS: TestRedisIntegration" not in content: raise RuntimeError("missing real Redis pass evidence") if args.advanced: for test in ("TestSharedRedisIntegration", "TestExpiredTargetBeyondCleanupBatch", "TestQueueRedisIntegration", "TestS3Integration", "TestMySQLIntegration"): if "--- PASS: " + test not in content: raise RuntimeError("missing integration pass evidence: " + test) report["status"] = "passed" except (Exception, KeyboardInterrupt) as error: failure = str(error) or type(error).__name__ report["status"], report["error"] = "failed", failure finally: for sig, handler in previous.items(): signal.signal(sig, handler) log.flush() report["logSha256"] = hashlib.sha256(log_path.read_bytes()).hexdigest() report["finishedAt"] = datetime.now(timezone.utc).isoformat() try: after = set(capture([docker, "ps", "--format", "{{.ID}}"]).split()) if not before.issubset(after): raise RuntimeError("a pre-existing container is no longer running") report["preExistingContainersStillRunning"] = len(before) if failure is None and set(names) != set(report["cleanedContainers"]): raise RuntimeError("container cleanup incomplete") except Exception as error: failure = str(error) report["status"], report["error"] = "failed", failure output_path.write_text(json.dumps(report, indent=2) + "\n") if failure: raise SystemExit(f"FAIL: {failure}; see {log_path}") print(f"PASS feature module acceptance; report: {output_path}", flush=True) if __name__ == "__main__": main()