Skip to content

Commit 7c4cf95

Browse files
committed
github/gen-matrix.py: add sanitize mode, exclude g++ debug+sanitize
Add 'sanitize' as a third value in MODES so all-pairs covers it across the compiler/standard/arch axes instead of pinning it to two explicit one-offs. The regular grid grows from 8 to 10 items; specials drop by two for a net +2 rows. Exclude (g++-{15,16}, debug) and (g++-{15,16}, sanitize) from the regular matrix via allpairspy's filter_func. gcc 15+ miscompiles structured bindings inside loops in coroutines: the hidden tuple's lifetime tracking is broken, so the destructor runs on uninitialized (ASan-poisoned, 0xBE) stack memory. In debug mode this surfaces as a pollable_fd_state LSan leak via reactor::do_accept's cross-shard forwarding path; in sanitize mode UBSan aborts the rpc client/server loops at startup. Drop EXCLUDED_PAIRS once https://gcc.gnu.org/bugzilla/show_bug.cgi?id=124584 is fixed (tracked in scylladb#3431) so g++ debug + sanitize come back into coverage. Also emit tests.yaml via ruamel.yaml instead of hand-rolled string formatting (PR review feedback): load the workflow, replace the strategy matrix include list in place by key path, and dump it back so the library handles scalar quoting while the rest of the file round-trips untouched. Drops the _yaml_val/_format_item/_NEEDS_QUOTE helpers and the BEGIN/END text-splice markers, and adds ruamel.yaml to the inline-script dependencies. Add the Apache license header to gen-matrix.py.
1 parent abefa0b commit 7c4cf95

2 files changed

Lines changed: 91 additions & 52 deletions

File tree

.github/workflows/gen-matrix.py

Lines changed: 83 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,53 @@
11
#!/usr/bin/env -S uv run --script
2+
#
3+
# This file is open source software, licensed to you under the terms
4+
# of the Apache License, Version 2.0 (the "License"). See the NOTICE file
5+
# distributed with this work for additional information regarding copyright
6+
# ownership. You may not use this file except in compliance with the License.
7+
#
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing,
13+
# software distributed under the License is distributed on an
14+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
# KIND, either express or implied. See the License for the
16+
# specific language governing permissions and limitations
17+
# under the License.
18+
#
19+
# Copyright (C) 2026 Redpanda Data.
20+
#
221
# /// script
322
# requires-python = ">=3.10"
423
# dependencies = [
524
# "allpairspy",
25+
# "ruamel.yaml",
626
# ]
727
# ///
828
"""
929
Generate the regular_test include: matrix for tests.yaml.
1030
11-
Uses all-pairs (pairwise) coverage over compiler x standard x mode for
12-
the regular builds, plus an explicit list of special-purpose jobs
13-
(dpdk, cxx-modules, fuzz). All-pairs guarantees that every pair of
14-
parameter values is exercised by at least one job, with far fewer
15-
combinations than the full cartesian product (12 vs 24 for our 4x2x3
16-
matrix). Everything outside the marked region of tests.yaml is left
17-
untouched.
31+
Uses all-pairs (pairwise) coverage over compiler x standard x mode x
32+
arch for the regular builds, plus an explicit list of special-purpose
33+
jobs (dpdk, cxx-modules, fuzz). All-pairs guarantees that every pair
34+
of parameter values is exercised by at least one job, with far fewer
35+
combinations than the full cartesian product. Excluded pairs (see
36+
EXCLUDED_PAIRS) drop out of regular coverage. The file is round-tripped
37+
through ruamel.yaml, replacing only the strategy matrix include list;
38+
everything else in tests.yaml is left untouched.
1839
1940
Usage:
2041
.github/workflows/gen-matrix.py
2142
git diff .github/workflows/tests.yaml
2243
"""
2344

24-
from __future__ import annotations
25-
2645
import pathlib
2746
from typing import Any
2847

2948
from allpairspy import AllPairs
49+
from ruamel.yaml import YAML
50+
from ruamel.yaml.comments import CommentedMap, CommentedSeq
3051

3152
HERE = pathlib.Path(__file__).parent
3253
TESTS_YAML = HERE / "tests.yaml"
@@ -42,12 +63,33 @@
4263
+ [f"g++-{v}" for v in GCC_VERSIONS]
4364
)
4465
STANDARDS: list[int] = [20, 23]
45-
MODES: list[str] = ["debug", "release"]
66+
MODES: list[str] = ["debug", "release", "sanitize"]
4667
ARCHS: list[str] = ["x86", "arm"]
4768

48-
# Markers that bracket the managed region inside tests.yaml.
49-
BEGIN_TAG = "# This include block is AUTOGENERATED"
50-
END_TAG = "# end of AUTOGENERATED part"
69+
# Compiler+mode combinations to skip in the regular all-pairs matrix.
70+
# gcc 15+ miscompiles structured bindings inside loops in coroutines
71+
# (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=124584), breaking g++
72+
# debug (LSan: pollable_fd_state leak via cross-shard accept) and g++
73+
# sanitize (UBSan abort on poisoned rcv_buf in rpc loops). Tracked in
74+
# scylladb/seastar#3431.
75+
EXCLUDED_PAIRS: list[tuple[str, str]] = [
76+
(f"g++-{v}", m) for v in GCC_VERSIONS for m in ("debug", "sanitize")
77+
]
78+
79+
80+
def _keep_row(row: list[Any]) -> bool:
81+
"""allpairspy filter_func: drop rows hitting an EXCLUDED_PAIRS entry.
82+
83+
Called with progressively longer prefixes during pair generation, so
84+
the check only fires once both compiler (row[0]) and mode (row[2])
85+
are present in the partial row.
86+
"""
87+
return not (len(row) >= 3 and (row[0], row[2]) in EXCLUDED_PAIRS)
88+
89+
# Key path to the strategy matrix map inside tests.yaml; we replace its
90+
# "include" list. The "# AUTOGENERATED" comment is preserved automatically
91+
# on round-trip.
92+
MATRIX_PATH = ("jobs", "regular_test", "strategy", "matrix")
5193

5294
# Special jobs merged into the same matrix so tests.yaml stays a single job.
5395
# Items only set enable-ccache when overriding the default (test.yaml treats
@@ -86,53 +128,50 @@
86128
},
87129
]
88130

89-
# Characters that require a value to be double-quoted in YAML flow style.
90-
_NEEDS_QUOTE: frozenset[str] = frozenset(' ,:{}"[]#')
131+
def _build_include(items: list[dict[str, Any]]) -> CommentedSeq:
132+
"""Build the matrix include list as a block sequence of flow mappings.
91133
92-
93-
def _yaml_val(v: object) -> str:
94-
if isinstance(v, bool):
95-
return "true" if v else "false"
96-
if isinstance(v, int):
97-
return str(v)
98-
s = str(v)
99-
if any(c in s for c in _NEEDS_QUOTE):
100-
return '"' + s.replace("\\", "\\\\").replace('"', '\\"') + '"'
101-
return s
102-
103-
104-
def _format_item(item: dict[str, Any], indent: str) -> str:
105-
parts = ", ".join(f"{k}: {_yaml_val(v)}" for k, v in item.items())
106-
return f"{indent} - {{{parts}}}\n"
134+
Each item becomes a flow-style mapping (one job per line) so the block
135+
stays compact and diff-friendly; ruamel.yaml handles scalar quoting.
136+
"""
137+
seq = CommentedSeq()
138+
for item in items:
139+
mapping = CommentedMap(item)
140+
mapping.fa.set_flow_style()
141+
seq.append(mapping)
142+
return seq
107143

108144

109145
def generate() -> list[dict[str, Any]]:
110146
regular: list[dict[str, Any]] = [
111147
{"compiler": c, "standard": s, "mode": m, "arch": a}
112-
for c, s, m, a in AllPairs([COMPILERS, STANDARDS, MODES, ARCHS])
148+
for c, s, m, a in AllPairs(
149+
[COMPILERS, STANDARDS, MODES, ARCHS],
150+
filter_func=_keep_row,
151+
)
113152
]
114153
return regular + SPECIAL_ITEMS
115154

116155

117156
def main() -> None:
118157
items = generate()
119-
lines: list[str] = TESTS_YAML.read_text().splitlines(keepends=True)
120158

121-
begin_idx = next(i for i, line in enumerate(lines) if BEGIN_TAG in line)
122-
end_idx = next(i for i, line in enumerate(lines) if END_TAG in line)
159+
yaml = YAML()
160+
yaml.preserve_quotes = True
161+
yaml.width = 1 << 20 # never wrap a flow mapping across lines
162+
yaml.indent(mapping=2, sequence=4, offset=2)
123163

124-
first = lines[begin_idx]
125-
indent = " " * (len(first) - len(first.lstrip()))
164+
data = yaml.load(TESTS_YAML)
165+
matrix = data
166+
for key in MATRIX_PATH:
167+
matrix = matrix[key]
126168

127-
new_lines: list[str] = [
128-
f"{indent}{BEGIN_TAG}, do not edit by hand\n",
129-
f"{indent}# instead edit + re-run .github/workflows/gen-matrix.py\n",
130-
f"{indent}include:\n",
131-
]
132-
for item in items:
133-
new_lines.append(_format_item(item, indent))
169+
# We only swap out the include list. The "# AUTOGENERATED, do not edit"
170+
# comment is attached to the include key, not the list, so it is
171+
# preserved automatically on round-trip.
172+
matrix["include"] = _build_include(items)
134173

135-
TESTS_YAML.write_text("".join(lines[:begin_idx] + new_lines + lines[end_idx:]))
174+
yaml.dump(data, TESTS_YAML)
136175
print(f"Updated {TESTS_YAML}: {len(items)} matrix items")
137176

138177

.github/workflows/tests.yaml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,17 @@ jobs:
2424
include:
2525
- {compiler: clang++-21, standard: 20, mode: debug, arch: x86}
2626
- {compiler: clang++-22, standard: 23, mode: release, arch: x86}
27-
- {compiler: g++-15, standard: 23, mode: debug, arch: arm}
27+
- {compiler: g++-15, standard: 23, mode: release, arch: arm}
2828
- {compiler: g++-16, standard: 20, mode: release, arch: arm}
29-
- {compiler: g++-16, standard: 23, mode: debug, arch: x86}
29+
- {compiler: g++-16, standard: 23, mode: release, arch: x86}
3030
- {compiler: g++-15, standard: 20, mode: release, arch: x86}
31-
- {compiler: clang++-22, standard: 20, mode: debug, arch: arm}
32-
- {compiler: clang++-21, standard: 23, mode: release, arch: arm}
31+
- {compiler: clang++-22, standard: 20, mode: sanitize, arch: arm}
32+
- {compiler: clang++-21, standard: 23, mode: sanitize, arch: x86}
33+
- {compiler: clang++-21, standard: 23, mode: debug, arch: arm}
3334
- {compiler: clang++-22, standard: 23, arch: x86, mode: dev}
34-
- {compiler: clang++-22, standard: 23, arch: x86, mode: release, enables: --enable-dpdk, options: "--cook dpdk --dpdk-machine corei7-avx", info: "dpdk, "}
35-
- {compiler: clang++-22, standard: 23, arch: x86, mode: debug, enables: --enable-cxx-modules, enable-ccache: false, info: "modules, "}
36-
- {compiler: clang++-22, standard: 23, arch: x86, mode: fuzz, test-args: "-- -R 'Seastar.fuzz.'"}
37-
# end of AUTOGENERATED part
35+
- {compiler: clang++-22, standard: 23, arch: x86, mode: release, enables: --enable-dpdk, options: --cook dpdk --dpdk-machine corei7-avx, info: 'dpdk, '}
36+
- {compiler: clang++-22, standard: 23, arch: x86, mode: debug, enables: --enable-cxx-modules, enable-ccache: false, info: 'modules, '}
37+
- {compiler: clang++-22, standard: 23, arch: x86, mode: fuzz, test-args: -- -R 'Seastar.fuzz.'}
3838
with:
3939
compiler: ${{ matrix.compiler }}
4040
standard: ${{ matrix.standard }}

0 commit comments

Comments
 (0)