driver: remove implicit test binaries after execution (#31236)

This commit is contained in:
Alexander Medvednikov authored and GitHub committed 2026-10-27 10:16:31 +03:00
1 parent 3f41e4bfd7
commit 7ec48941f8
4 files changed
+157 -33

No files matched your search

+5
View File
@@ -43,6 +43,11 @@ and will exit with a 0 exit code, if they all had 0 failing assertions.
`v -stats file_test.v` - this will run the test_ functions, and show a
report about how much time it took to run each of them too.
Implicit test executables and their macOS debug symbols are removed after the test process exits,
including failed assertions, panics, and nonzero exits. This also applies with `-stats`, `-keepc`,
and `v run file_test.v`; `-keepc` still retains generated C. An explicit `-o` keeps the requested
output for later execution or debugging.
NB 2: You can also run many test files at once (in parallel, depending on
how many cores you have), with:
`v test folder` - this will run *all* `_test.v` files in `folder`,
@@ -0,0 +1,128 @@
import os
import v.cmdexec
fn binary_cleanup_root(name string) !string {
root := os.join_path(os.vtmp_dir(), 'test binary cleanup ${name} ${os.getpid()}')
os.mkdir_all(root)!
return root
}
fn binary_cleanup_source(body string) string {
return 'import os
fn test_cleanup() {
os.write_file("binary_path", os.executable())!
assert os.is_file(os.executable())
${body}
}
'
}
fn binary_cleanup_compile(root string, args []string) os.Result {
old_vtmp := os.getenv_opt('VTMP')
os.setenv('VTMP', root, true)
defer {
if value := old_vtmp {
os.setenv('VTMP', value, true)
} else {
os.unsetenv('VTMP')
}
}
vexe := if os.base(@VEXE) in ['v1_fallback', 'v1_fallback.exe'] {
os.join_path(os.dir(@VEXE), 'v' + $if windows { '.exe' } $else { '' })
} else {
@VEXE
}
cc := $if windows { 'gcc' } $else { 'cc' }
mut flags := ['-new-compiler', '-no-retry-compilation', '-memory-limit', '10G', '-cc', cc]
flags << args
return cmdexec.run_in(vexe, flags, root)
}
fn test_implicit_test_binaries_are_removed_after_success_and_failure() {
root := binary_cleanup_root('exit')!
defer {
os.rmdir_all(root) or {}
}
source := os.join_path(root, 'sample_test.v')
marker := os.join_path(root, 'binary_path')
for body in ['assert true', 'assert false', 'panic("cleanup panic")', 'exit(23)'] {
text := binary_cleanup_source(body)
os.write_file(source, text)!
result := binary_cleanup_compile(root, ['sample_test.v'])
if body == 'assert true' {
assert result.exit_code == 0, result.output
} else if body == 'exit(23)' {
assert result.exit_code == 23, result.output
} else {
assert result.exit_code != 0, result.output
}
binary := os.read_file(marker)!.trim_space()
assert !os.exists(binary), 'test binary remains after ${body}: ${binary}'
assert !os.exists(binary + '.dSYM')
assert os.read_file(source)! == text
os.rm(marker)!
}
}
fn test_implicit_test_binary_cleanup_with_run_stats_keepc_and_debug() {
root := binary_cleanup_root('flags')!
defer {
os.rmdir_all(root) or {}
}
source := os.join_path(root, 'sample_test.v')
marker := os.join_path(root, 'binary_path')
os.write_file(source, binary_cleanup_source('assert false'))!
for flags in [['-stats'], ['-keepc'], ['-g'], ['run'], ['-keepc', 'run'], ['crun']] {
c_files_before := os.ls(root)!.filter(it.ends_with('.tmp.c')).len
// A previous implicit test executable is still a compiler-owned artifact.
os.write_file(os.join_path(root, 'sample_test' + $if windows { '.exe' } $else { '' }),
'old test artifact')!
mut args := flags.clone()
args << 'sample_test.v'
result := binary_cleanup_compile(root, args)
assert result.exit_code != 0, result.output
binary := os.read_file(marker)!.trim_space()
assert !os.exists(binary), 'test binary remains with ${flags}: ${binary}'
assert !os.exists(binary + '.dSYM')
if '-keepc' in flags {
assert os.ls(root)!.filter(it.ends_with('.tmp.c')).len > c_files_before
}
os.rm(marker)!
}
}
fn test_explicit_test_outputs_are_preserved() {
root := binary_cleanup_root('explicit')!
defer {
os.rmdir_all(root) or {}
}
source := os.join_path(root, 'sample_test.v')
marker := os.join_path(root, 'binary_path')
binary := os.join_path(root, 'chosen output' + $if windows { '.exe' } $else { '' })
os.write_file(source, binary_cleanup_source('assert true'))!
build := binary_cleanup_compile(root, ['-skip-running', '-o', binary, source])
assert build.exit_code == 0, build.output
assert !os.exists(marker)
assert os.is_file(binary)
run := cmdexec.run_in(binary, [], root)
assert run.exit_code == 0, run.output
assert os.read_file(marker)!.trim_space() == os.real_path(binary)
os.rm(marker)!
os.write_file(source, binary_cleanup_source('assert false'))!
run_with_output := binary_cleanup_compile(root, ['-stats', '-o', binary, source])
assert run_with_output.exit_code != 0, run_with_output.output
assert os.read_file(marker)!.trim_space() == os.real_path(binary)
assert os.is_file(binary)
}
fn test_directory_test_command_removes_implicit_binary() {
root := binary_cleanup_root('directory')!
defer {
os.rmdir_all(root) or {}
}
os.write_file(os.join_path(root, 'sample_test.v'), binary_cleanup_source('assert true'))!
result := binary_cleanup_compile(root, ['test', root])
assert result.exit_code == 0, result.output
binary := os.read_file(os.join_path(root, 'binary_path'))!.trim_space()
assert !os.exists(binary), 'directory test binary remains: ${binary}'
}
+23 -32
View File
@@ -2220,14 +2220,20 @@ fn cleanup_c_build_dir(dir string) {
}
}
fn run_test_binary(bin_file string) int {
return run_binary(bin_file, []string{})
fn run_test_binary(bin_file string, keep_binary bool) int {
return run_binary(bin_file, []string{}, !keep_binary)
}
fn ignore_run_signal(_ os.Signal) {
}
fn run_binary(bin_file string, args []string) int {
fn run_binary(bin_file string, args []string, remove_after_run bool) int {
defer {
if remove_after_run {
os.rm(bin_file) or {}
v3_remove_macos_debug_symbols(bin_file)
}
}
// The build is published: the builds that wait to populate the cache need
// not wait for the program as well.
release_v3_cache_population_lock()
@@ -2316,7 +2322,7 @@ fn run_v_inline_source(args []string, option_index int, consumed int, source str
mut child_args := args[..option_index].clone()
child_args << ['run', temporary_source]
child_args << args[option_index + consumed..]
exit_code := run_binary(os.executable(), child_args)
exit_code := run_binary(os.executable(), child_args, false)
os.rm(temporary_source) or {
eprintln('cannot remove temporary inline source file: ${err.msg()}')
if exit_code == 0 {
@@ -12358,8 +12364,8 @@ pub fn run(args []string) {
exit(1)
}
binary_existed_before := os.exists(bin_file)
remove_binary_after_run := should_run && !is_crun && !is_direct_vsh && !explicit_output && !keep_c
&& !binary_existed_before
mut remove_binary_after_run := should_run && !explicit_output
&& (fastc_test_file || (!is_crun && !is_direct_vsh && !keep_c && !binary_existed_before))
// Decide which backend modules to compile into the output. Standalone compilers
// default to C; optional backends and their SSA dependencies can be skipped. This
@@ -12932,10 +12938,7 @@ pub fn run(args []string) {
}
}
if should_run {
run_result := run_binary(bin_file, run_args)
if remove_binary_after_run {
os.rm(bin_file) or {}
}
run_result := run_binary(bin_file, run_args, remove_binary_after_run)
if run_result != 0 {
exit(run_result)
}
@@ -13410,6 +13413,9 @@ pub fn run(args []string) {
exit(1)
}
test_files := test_input_files(user_files, backend, prefs.target)
if should_run && test_files.len > 0 && !explicit_output {
remove_binary_after_run = true
}
// A test binary keeps the harness counters (`__test_failures` and the
// setjmp buffer that `assert` longjmps out of) as `static` definitions inside
// the program body, so they live in whichever split unit the body lands in
@@ -13804,7 +13810,7 @@ pub fn run(args []string) {
}
if os.is_file(bin_file) && v3_crun_cache_matches(bin_file, crun_build_identity, input_file) {
clear_macos_v3_compiler_error_fallback(macos_v3_fallback_file)
run_result := run_binary(bin_file, run_args)
run_result := run_binary(bin_file, run_args, remove_binary_after_run)
if run_result != 0 {
exit(run_result)
}
@@ -13927,10 +13933,7 @@ pub fn run(args []string) {
b.step(if cache_with_tcc { 'tcc (cached)' } else { 'cc (cached)' })
clear_macos_v3_compiler_error_fallback(macos_v3_fallback_file)
if should_run {
run_result := run_binary(bin_file, run_args)
if remove_binary_after_run {
os.rm(bin_file) or {}
}
run_result := run_binary(bin_file, run_args, remove_binary_after_run)
if run_result != 0 {
exit(run_result)
}
@@ -15744,19 +15747,13 @@ pub fn run(args []string) {
b.step('link')
clear_macos_v3_compiler_error_fallback(macos_v3_fallback_file)
if should_run {
run_result := run_binary(bin_file, run_args)
if remove_binary_after_run {
os.rm(bin_file) or {}
if (is_debug || race) && target.os == 'macos' {
v3_remove_macos_debug_symbols(bin_file)
}
}
run_result := run_binary(bin_file, run_args, remove_binary_after_run)
if run_result != 0 {
exit(run_result)
}
b.step('run')
} else if test_files.len > 0 && (!explicit_output || is_checker_fixture || show_test_stats) {
test_result := run_test_binary(bin_file)
test_result := run_test_binary(bin_file, explicit_output)
if test_result != 0 {
exit(test_result)
}
@@ -17591,22 +17588,16 @@ Please install the corresponding development package/libraries and make sure the
// The C compiler is done. What was set up for `cl` must not reach the program that runs next.
msvc_restore_environment(msvc_saved_environment)
if should_run {
if (is_crun || is_direct_vsh) && !explicit_output {
if (is_crun || is_direct_vsh) && !explicit_output && !remove_binary_after_run {
write_v3_crun_cache_marker(bin_file, crun_build_identity) or {}
}
run_result := run_binary(bin_file, run_args)
if remove_binary_after_run {
os.rm(bin_file) or {}
if (is_debug || race) && target.os == 'macos' {
v3_remove_macos_debug_symbols(bin_file)
}
}
run_result := run_binary(bin_file, run_args, remove_binary_after_run)
if run_result != 0 {
exit(run_result)
}
b.step('run')
} else if test_files.len > 0 && (!explicit_output || is_checker_fixture || show_test_stats) {
test_result := run_test_binary(bin_file)
test_result := run_test_binary(bin_file, explicit_output)
if test_result != 0 {
exit(test_result)
}
+1 -1
View File
@@ -135,7 +135,7 @@ fn v3_keep_macos_debug_symbols(staged_binary string, bin_file string) {
}
// v3_remove_macos_debug_symbols removes the debug symbols of a macOS debug binary that
// `v run` deletes after running it.
// a run or test deletes after running it.
fn v3_remove_macos_debug_symbols(bin_file string) {
os.rmdir_all(bin_file + '.dSYM') or {}
}