ci: fix macOS self-test failures and worker selection (#31196)

* ci: respect macOS self-test workers with fail-fast

* builtin: keep vgc thread-cache test allocation at runtime

* driver: align wasm array gate tests with string support

* tests: use selected C compiler for raw assembly fixture
This commit is contained in:
Alexander Medvednikov authored and GitHub committed 2026-10-26 16:58:04 +03:00
1 parent 6cce632507
commit f13fb293ad
6 files changed
+47 -22

No files matched your search

+4 -2
View File
@@ -69,8 +69,10 @@ its checkpoints, so the following invocation starts from the beginning.
The aggregate `ci` command sets `VTEST_FAIL_FAST=1` and `VJOBS=1`, overriding inherited
values. Test and example-build sessions stop at the first reported failure, with no
other files already running in parallel. The `self_tests` task retains this single-worker
setting; standalone GitHub Actions self-test tasks still use every available CPU.
other files already running in parallel. The `self_tests` task retains the explicit
`VJOBS` setting, including the workflow's three workers and the aggregate runner's
single worker. Without `VJOBS`, GitHub Actions self-tests use one worker when
`VTEST_FAIL_FAST=1`, or every available CPU otherwise.
`test-cleancode` also stops after a failed
vetting session instead of starting formatting checks. The failed task's checkpoint
is retained. Existing per-test retry and known-flaky-test policies are unchanged.
+5 -3
View File
@@ -64,10 +64,12 @@ fn self_tests() {
// test the release's own standard library instead of this repository's.
// Individual files still fall back to it when the default compiler cannot
// build them.
// Exercise default caching throughout the standard library suite. Use every
// core on the 7 GB runners unless the aggregate runner requires fail-fast.
// Exercise default caching throughout the standard library suite. Respect the
// workflow's worker count as well as the aggregate runner's serial setting.
if common.is_github_job {
vjobs := if os.getenv('VTEST_FAIL_FAST') == '1' { '1' } else { '${runtime.nr_cpus()}' }
vjobs := os.getenv_opt('VJOBS') or {
if os.getenv('VTEST_FAIL_FAST') == '1' { '1' } else { '${runtime.nr_cpus()}' }
}
exec_args(['env', 'VJOBS=' + '${vjobs}', 'v', '-no-memory-limit', '-silent', 'test-self',
'vlib'])
} else {
+18 -8
View File
@@ -39,8 +39,8 @@ if [ "$1" = doctor ]; then
fi
if [ "$1" = -no-memory-limit ]; then
[ "$2" = -silent ] && [ "$3" = test-self ] && [ "$4" = vlib ] || exit 98
[ "${VTEST_FAIL_FAST:-}" = 1 ] && [ "${VJOBS:-}" = 1 ] || exit 99
echo self-tests >> self.log
[ "${VJOBS:-}" = "$SELF_TEST_EXPECTED_JOBS" ] || exit 99
printf '%s:%s\n' "$VTEST_FAIL_FAST" "$VJOBS" >> self.log
exit 0
fi
[ "$1" = run ] && [ "$2" = ci/macos_ci.vsh ] || exit 90
@@ -134,12 +134,22 @@ run_ci 0 v_doctor
cmp "$work/saved" "$checkpoint"
[ "$(cat "$checkout/direct.log")" = doctor ] || fail 'individual task did not run'
# Execute the real self_tests task: its GitHub branch must retain the aggregate
# runner's serial fail-fast setting instead of replacing it with the CPU count.
(cd "$checkout" && GITHUB_JOB=clang-macos VTEST_FAIL_FAST=1 VJOBS=1 \
V_CI_VEXE="$fake_v" "$runner" self_tests) > "$work/output" 2>&1 || \
fail 'self_tests replaced the fail-fast worker count'
[ "$(cat "$checkout/self.log")" = self-tests ] || fail 'self_tests did not run'
# Execute the real self_tests task with both the aggregate runner's serial setting
# and the workflow's three workers. Fail-fast must not override an explicit VJOBS.
for fast in 0 1; do
for jobs in 1 3; do
(cd "$checkout" && GITHUB_JOB=clang-macos VTEST_FAIL_FAST="$fast" VJOBS="$jobs" \
SELF_TEST_EXPECTED_JOBS="$jobs" V_CI_VEXE="$fake_v" "$runner" self_tests) \
> "$work/output" 2>&1 || fail "self_tests replaced VJOBS=$jobs (fail-fast=$fast)"
done
done
printf '0:1\n0:3\n1:1\n1:3\n' > "$work/expected-self"
cmp "$work/expected-self" "$checkout/self.log" || fail 'self_tests did not run'
# Without an explicit worker count, fail-fast still defaults to a serial session.
(cd "$checkout" && unset VJOBS && GITHUB_JOB=clang-macos VTEST_FAIL_FAST=1 \
SELF_TEST_EXPECTED_JOBS=1 V_CI_VEXE="$fake_v" "$runner" self_tests) \
> "$work/output" 2>&1 || fail 'self_tests lost its serial fail-fast default'
# A second checkout must not consume the first checkout's saved progress.
first_checkout=$checkout
+7 -5
View File
@@ -7,16 +7,18 @@ import sync
fn C.vgc_get_cache_idx() int
fn cache_idx_after_allocating() int {
s := 'vgc'.repeat(40)
assert s.len == 120
// Keep the count dynamic so compile-time string folding cannot remove the allocation.
@[noinline]
fn cache_idx_after_allocating(count int) int {
s := 'vgc'.repeat(count)
assert s.len == 3 * count
return C.vgc_get_cache_idx()
}
// cache_idx_while_others_run keeps the thread alive until all threads have their cache: a
// thread that has exited gives its cache slot to the next one.
fn cache_idx_while_others_run(started &sync.WaitGroup, release chan bool) int {
idx := cache_idx_after_allocating()
idx := cache_idx_after_allocating(40)
mut wg := unsafe { started }
wg.done()
_ := <-release
@@ -38,7 +40,7 @@ fn allocate_strings(id int) int {
fn test_spawned_threads_get_their_own_vgc_cache() {
$if vgc ? {
main_idx := cache_idx_after_allocating()
main_idx := cache_idx_after_allocating(40)
assert main_idx >= 0
mut threads := []thread int{}
mut started := sync.new_waitgroup()
+5 -1
View File
@@ -98,10 +98,12 @@ fn test_all() {
no_closures_dir := 'vlib/v/tests/no_closures'
js_checker_tests := ['index_expr_implicit_int_downcast_err.vv',
'js_number_requires_explicit_cast.vv']
raw_asm_checker_tests := ['asm_raw_label_ok.vv']
disable_explicit_mutability_tests := ['disable_explicit_mutability.vv']
checker_tests := get_tests_in_dir(checker_dir, false).filter(!it.contains('with_check_option')
&& it !in js_checker_tests && it !in disable_explicit_mutability_tests)
&& it !in js_checker_tests && it !in raw_asm_checker_tests
&& it !in disable_explicit_mutability_tests)
parser_tests := get_tests_in_dir(parser_dir, false)
scanner_tests := get_tests_in_dir(scanner_dir, false)
global_tests := get_tests_in_dir(global_dir, false)
@@ -180,6 +182,8 @@ fn test_all() {
tasks.add('', parser_dir, '', '.out', parser_tests, false)
tasks.add('', checker_dir, '', '.out', checker_tests, false)
tasks.add('', checker_dir, '-b js', '.js.out', js_checker_tests, false)
// Keep raw assembly on the compiler selected for this test and its skip rules.
tasks.add('', checker_dir, '-cc ${@CCOMPILER}', '.out', raw_asm_checker_tests, false)
tasks.add('', scanner_dir, '', '.out', scanner_tests, false)
tasks.add('', checker_dir, '-enable-globals run', '.run.out', ['globals_error.vv'], false)
tasks.add('', global_run_dir, '-enable-globals run', '.run.out', global_run_tests, false)
+8 -3
View File
@@ -6,7 +6,7 @@ import v.parser
import v.pref
import v.types
// The wasm backend lowers dynamic arrays of primitive elements, so the
// The wasm backend lowers dynamic arrays of primitive and string elements, so the
// driver gate must admit them while still rejecting maps, structs, fixed
// arrays and nested arrays. A rejection here is a hard error before any wasm
// is emitted; an admission it should not make would miscompile silently
@@ -50,6 +50,11 @@ fn test_wasm_gate_admits_primitive_arrays() {
assert wasm_gate_check('module main\nfn main() {\n\tz := []int{len: 4, init: 7}\n\tprintln(z.len)\n}\n') == ''
}
fn test_wasm_gate_admits_string_arrays() {
assert wasm_gate_check("module main\nfn main() {\n\ta := ['one', 'two']\n\tprintln(a[0])\n}\n") == ''
assert wasm_gate_check("module main\nfn main() {\n\ta := []string{len: 2, init: 'v'}\n\tprintln(a.len)\n}\n") == ''
}
// Struct literals are admitted when every field has a lowering, which is what
// lets lesson pages with structs run client-side. The rejection direction is
// still pinned: a struct holding a map has no field-wise wasm lowering, so it
@@ -77,10 +82,10 @@ fn test_wasm_gate_rejects_maps_structs_fixed_nested() {
fn test_wasm_supported_array_elem() {
for t in ['int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'uint', 'f32', 'f64',
'bool', 'char', 'rune'] {
'bool', 'char', 'rune', 'string'] {
assert wasm_supported_array_elem(flat.source_type_id('[]' + t)), t
}
for t in ['', '[]', 'int', 'map[string]int', 'Point', '[2]int', '[][]int', '[]Point', '[]string',
for t in ['', '[]', 'int', 'map[string]int', 'Point', '[2]int', '[][]int', '[][]string', '[]Point',
' []int', '[]int ', '[] int', '[]builtin.int'] {
assert !wasm_supported_array_elem(flat.source_type_id(t)), t
}