From 462e4445e2f3a2413ddfd9463b03740134878d20 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Fri, 23 Oct 2026 18:46:26 -0700 Subject: [PATCH] test: skip catalog client images that cannot execute on the host CPU (#12014) New upstream releases build for x86-64-v3 (AVX2) - ClickHouse since 26.5, plus the DuckDB iceberg extension and pylance wheels - so on a runner whose CPU lacks AVX2 the container dies on SIGILL before the test reaches any SeaweedFS code. Probe each third-party image first and skip on the illegal-instruction exit instead of failing; other errors still surface normally. --- .../clickhouse_catalog_test.go | 5 +++ .../lifecycle/iceberg_lifecycle_test.go | 10 ++++++ .../lifecycle/lance_lifecycle_test.go | 3 ++ test/testutil/helpers.go | 31 +++++++++++++++++++ 4 files changed, 49 insertions(+) diff --git a/test/s3tables/catalog_clickhouse/clickhouse_catalog_test.go b/test/s3tables/catalog_clickhouse/clickhouse_catalog_test.go index 098fae43a5..b85629fed4 100644 --- a/test/s3tables/catalog_clickhouse/clickhouse_catalog_test.go +++ b/test/s3tables/catalog_clickhouse/clickhouse_catalog_test.go @@ -871,6 +871,11 @@ func requireClickHouseRuntime(t *testing.T) { if !hasDocker() { t.Skip("Docker not available, skipping ClickHouse integration test") } + // ClickHouse builds for x86-64-v3 (AVX2) since 26.5; on a CPU without it + // the binary dies on SIGILL inside the entrypoint before the server can + // come up. + testutil.RequireImageRunnable(t, + "--entrypoint", "clickhouse", clickhouseImage(), "local", "--query", "SELECT 1") } // hasDocker reports whether `docker version` can run, which we treat as a diff --git a/test/s3tables/lifecycle/iceberg_lifecycle_test.go b/test/s3tables/lifecycle/iceberg_lifecycle_test.go index 2c34d7b5ce..0eae42a575 100644 --- a/test/s3tables/lifecycle/iceberg_lifecycle_test.go +++ b/test/s3tables/lifecycle/iceberg_lifecycle_test.go @@ -70,6 +70,13 @@ func TestIcebergTableLifecycle(t *testing.T) { func testIcebergLifecycleWithDuckDB(t *testing.T) { env := shared + + // The write phase's first act is INSTALLing the iceberg extension, whose + // binary can itself target a newer x86-64 baseline than the host CPU has. + testutil.RequireImageRunnable(t, + "--entrypoint", "duckdb", duckdbImage, + "-c", "INSTALL iceberg; LOAD iceberg; SELECT 1;") + bucket := "lifecycle-duckdb-" + randomSuffix() namespace, table := "sales", "events" env.createTableBucket(t, bucket, "ICEBERG") @@ -109,6 +116,9 @@ func testIcebergLifecycleWithPyIceberg(t *testing.T) { namespace, table := "sales", "events" env.createTableBucket(t, bucket, "ICEBERG") buildClientImage(t, pyicebergImage, "Dockerfile.pyiceberg") + testutil.RequireImageRunnable(t, + "--entrypoint", "python3", pyicebergImage, + "-c", "import pyarrow; from pyiceberg.catalog import load_catalog") before := env.pyiceberg(t, "write", bucket, namespace, table) assertSeeded(t, before) diff --git a/test/s3tables/lifecycle/lance_lifecycle_test.go b/test/s3tables/lifecycle/lance_lifecycle_test.go index ea076eef55..31b5b6c173 100644 --- a/test/s3tables/lifecycle/lance_lifecycle_test.go +++ b/test/s3tables/lifecycle/lance_lifecycle_test.go @@ -35,6 +35,9 @@ func TestLanceTableLifecycle(t *testing.T) { namespace, table := "ml", "events" env.createTableBucket(t, bucket, "LANCE") buildClientImage(t, lanceImage, "Dockerfile.lance") + testutil.RequireImageRunnable(t, + "--entrypoint", "python3", lanceImage, + "-c", "import lance; import lance_namespace; import pyarrow") before := env.lance(t, "write", bucket, namespace, table) assertSeeded(t, before) diff --git a/test/testutil/helpers.go b/test/testutil/helpers.go index 268b35b29f..8d2d6b2806 100644 --- a/test/testutil/helpers.go +++ b/test/testutil/helpers.go @@ -2,12 +2,15 @@ package testutil import ( "context" + "errors" "fmt" "net" "net/http" "os" "os/exec" "path/filepath" + "strings" + "testing" "time" ) @@ -20,6 +23,34 @@ func HasDocker() bool { return cmd.Run() == nil } +// RequireImageRunnable runs a throwaway container with the given `docker run` +// arguments and skips the test when its process dies on SIGILL. Upstream +// images increasingly target x86-64-v3 (AVX2) - ClickHouse since 26.5 - so a +// host CPU without those instructions cannot run them at all, and the failure +// says nothing about the code under test. Any other outcome, whether a clean +// run, an ordinary non-zero exit, or a pull failure, is left for the test +// itself to report: the probe can only skip on the one failure the code +// cannot cause. +func RequireImageRunnable(t *testing.T, args ...string) { + t.Helper() + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + defer cancel() + cmd := exec.CommandContext(ctx, "docker", append([]string{"run", "--rm"}, args...)...) + out, err := cmd.CombinedOutput() + if err == nil { + return + } + var exitErr *exec.ExitError + // 132 = 128 + SIGILL: docker propagates the container process's death. + if errors.As(err, &exitErr) && + (exitErr.ExitCode() == 132 || + strings.Contains(string(out), "Illegal instruction")) { + t.Skipf("image cannot execute on this CPU (illegal instruction): docker run %s\n%s", + strings.Join(args, " "), out) + } +} + func FindBindIP() string { addrs, err := net.InterfaceAddrs() if err != nil {