vxx2 / vlib / v3 / test_all.vsh
275 lines · 247 sloc · 7.15 KB · 2b81918e62db9ab38787518603a7f93bee80868c
Raw
1#!/usr/bin/env -S v
2
3import os
4
5const total_steps = 7
6const temp_prefix = 'v3_test_all'
7const requested_vlib_tests = [
8 'vlib/builtin/string_test.v',
9 'vlib/math/math_test.v',
10 'vlib/builtin/array_test.v',
11 'vlib/math/complex/complex_test.v',
12 'vlib/builtin/map_test.v',
13 'vlib/crypto/hmac/hmac_test.v',
14 'vlib/crypto/sha3/sha3_test.v',
15 'vlib/time/time_test.v',
16 'vlib/os/process_test.v',
17 'vlib/os/file_test.v',
18 'vlib/arrays/arrays_test.v',
19]
20
21struct Config {
22 vexe string
23 script_dir string
24 repo_root string
25 tests_dir string
26 v3_src string
27 c99 bool
28 c99_flag string
29 host_backend string
30 host_os string
31 temp_prefix string
32}
33
34fn main() {
35 cfg := parse_config()
36 os.chdir(cfg.repo_root) or { fail('failed to enter ${cfg.repo_root}: ${err}') }
37
38 v3_bin := temp_path(cfg, 'v3')
39 hello_c_bin := temp_path(cfg, 'hello_c')
40 hello_arm_bin := temp_path(cfg, 'hello_arm64')
41 v4_arm_bin := temp_path(cfg, 'v4_arm64')
42 v3_lang_bin := temp_path(cfg, 'v3_lang')
43 v4_bin := temp_path(cfg, 'v4_chain')
44 v5_bin := temp_path(cfg, 'v5_chain')
45 v6_bin := temp_path(cfg, 'v6_chain')
46 cleanup_files([
47 v3_bin,
48 hello_c_bin,
49 hello_c_bin + '.c',
50 hello_arm_bin,
51 v4_arm_bin,
52 v3_lang_bin,
53 v3_lang_bin + '.c',
54 v4_bin,
55 v4_bin + '.c',
56 v5_bin,
57 v5_bin + '.c',
58 v6_bin,
59 v6_bin + '.c',
60 ])
61
62 section(1, 'V3 unit tests')
63 run('${q(cfg.vexe)} -silent test ${q(cfg.script_dir)}')
64
65 section(2, 'Build v3')
66 run('${q(cfg.vexe)} -o ${q(v3_bin)} ${q(cfg.v3_src)}')
67
68 section(3, 'Requested vlib tests')
69 for rel_path in requested_vlib_tests {
70 test_path := os.join_path(cfg.repo_root, rel_path)
71 test_bin := temp_path(rel_path.replace('/', '_').replace('.v', ''))
72 run('${q(v3_bin)} ${q(test_path)} -o ${q(test_bin)}')
73 run(q(test_bin))
74 cleanup_files([test_bin, test_bin + '.c'])
75 }
76
77 section(4, 'C backend hello world')
78 hello_v := os.join_path(cfg.tests_dir, 'hello.v')
79 run('${q(v3_bin)} ${cfg.c99_flag} ${q(hello_v)} -b c -o ${q(hello_c_bin)}')
80 run(q(hello_c_bin))
81 cleanup_files([hello_c_bin, hello_c_bin + '.c'])
82
83 section(5, 'ARM64 self-host hello world')
84 if cfg.c99 {
85 println(' Skipping ARM64 self-host in C99 mode (-c99 applies to the C backend)')
86 } else if cfg.host_backend == 'arm64' && cfg.host_os == 'macos' {
87 run('${q(v3_bin)} --no-parallel -selfhost -b arm64 -o ${q(v4_arm_bin)} ${q(cfg.v3_src)}')
88 run('${q(v4_arm_bin)} --no-parallel -b arm64 -o ${q(hello_arm_bin)} ${q(hello_v)}')
89 run(q(hello_arm_bin))
90 cleanup_files([v4_arm_bin, hello_arm_bin])
91 } else {
92 println(' Skipping ARM64 self-host on ${cfg.host_os}/${cfg.host_backend} host (Mach-O only)')
93 }
94
95 section(6, 'Self-host chain (v3->v4->v5->v6)')
96 println(' Building v4 from v3...')
97 run('${q(v3_bin)} ${cfg.c99_flag} --no-parallel -selfhost -o ${q(v4_bin)} ${q(cfg.v3_src)}')
98 println(' Building v5 from v4...')
99 run('${q(v4_bin)} ${cfg.c99_flag} --no-parallel -selfhost -o ${q(v5_bin)} ${q(cfg.v3_src)}')
100 println(' Building v6 from v5...')
101 run('${q(v5_bin)} ${cfg.c99_flag} --no-parallel -selfhost -o ${q(v6_bin)} ${q(cfg.v3_src)}')
102 converged_size := assert_same_file_bytes('v5/v6 generated C output', v5_bin + '.c', v6_bin +
103 '.c')
104 println(' v5.c=v6.c (${converged_size} bytes) - chain converged')
105 cleanup_files([v4_bin, v4_bin + '.c', v5_bin, v5_bin + '.c', v6_bin, v6_bin + '.c'])
106
107 section(7, 'Language feature parity')
108 lang_v := os.join_path(cfg.tests_dir, 'test_all_lang_features.v')
109 lang_out := os.join_path(cfg.tests_dir, 'test_all_lang_features.out')
110 run('${q(v3_bin)} ${cfg.c99_flag} ${q(lang_v)} -b c -o ${q(v3_lang_bin)}')
111 v3_c_out := run_output(cfg, q(v3_lang_bin))
112 expected_out := read_text_file(lang_out)
113 assert_same_text('language feature output', v3_c_out, expected_out)
114 println(' v3 C OK (${v3_c_out.split_into_lines().len} lines)')
115 println(' ARM64 coverage is the one-generation macOS self-host smoke test in step 4')
116 cleanup_files([v3_bin, v3_lang_bin, v3_lang_bin + '.c'])
117
118 println('')
119 println('=== ALL TESTS PASSED ===')
120}
121
122fn parse_config() Config {
123 c99 := parse_args()
124 script_dir := os.real_path(@DIR)
125 repo_root := os.real_path(os.join_path(script_dir, '..', '..'))
126 tests_dir := os.join_path(script_dir, 'tests')
127 vexe := absolute_path(@VEXE)
128 if !os.is_executable(vexe) {
129 fail('FAIL: V compiler not found: ${vexe}')
130 }
131 return Config{
132 vexe: vexe
133 script_dir: script_dir
134 repo_root: repo_root
135 tests_dir: tests_dir
136 v3_src: os.join_path(script_dir, 'v3.v')
137 c99: c99
138 c99_flag: if c99 { '-c99' } else { '' }
139 host_backend: native_backend_arch()
140 host_os: os.user_os()
141 temp_prefix: 'v3_test_all_${os.getpid()}'
142 }
143}
144
145fn parse_args() bool {
146 mut c99 := false
147 for arg in os.args[1..] {
148 match arg {
149 '-c99', '--c99' {
150 c99 = true
151 }
152 '-h', '--help' {
153 println('usage: test_all.vsh [-c99]')
154 exit(0)
155 }
156 else {
157 fail('unknown argument: ${arg}')
158 }
159 }
160 }
161 return c99
162}
163
164fn native_backend_arch() string {
165 machine := os.uname().machine.to_lower()
166 match machine {
167 'x86_64', 'amd64' {
168 return 'x64'
169 }
170 'aarch64', 'arm64' {
171 return 'arm64'
172 }
173 else {
174 return machine
175 }
176 }
177}
178
179fn temp_path(cfg Config, name string) string {
180 return os.join_path(os.temp_dir(), '${cfg.temp_prefix}_${name}')
181}
182
183fn absolute_path(path string) string {
184 if os.is_abs_path(path) {
185 return path
186 }
187 return os.join_path(os.getwd(), path)
188}
189
190fn section(step int, title string) {
191 if step > 1 {
192 println('')
193 }
194 println('=== ${step}/${total_steps}: ${title} ===')
195}
196
197fn run(cmd string) {
198 println('> ${cmd}')
199 code := os.system(cmd)
200 if code != 0 {
201 exit(code)
202 }
203}
204
205fn run_output(cfg Config, cmd string) string {
206 stdout_path := temp_path(cfg, 'stdout')
207 cleanup_files([stdout_path])
208 println('> ${cmd}')
209 code := os.system('${cmd} > ${q(stdout_path)}')
210 if code != 0 {
211 exit(code)
212 }
213 content := read_text_file(stdout_path)
214 cleanup_files([stdout_path])
215 return content
216}
217
218fn read_text_file(path string) string {
219 content := os.read_file(path) or {
220 fail('FAIL: failed to read ${path}: ${err}')
221 return ''
222 }
223 return content
224}
225
226fn read_binary_file(path string) []u8 {
227 content := os.read_bytes(path) or {
228 fail('FAIL: failed to read ${path}: ${err}')
229 return []u8{}
230 }
231 return content
232}
233
234fn assert_same_file_bytes(label string, left_path string, right_path string) int {
235 left := read_binary_file(left_path)
236 right := read_binary_file(right_path)
237 if left != right {
238 fail('FAIL: ${label} differs byte-for-byte (${left.len} bytes vs ${right.len} bytes)')
239 }
240 return left.len
241}
242
243fn assert_same_text(label string, actual string, expected string) {
244 if actual == expected {
245 return
246 }
247 actual_lines := actual.split_into_lines()
248 expected_lines := expected.split_into_lines()
249 min_lines := if actual_lines.len < expected_lines.len {
250 actual_lines.len
251 } else {
252 expected_lines.len
253 }
254 for i in 0 .. min_lines {
255 if actual_lines[i] != expected_lines[i] {
256 fail('FAIL: ${label} differs at line ${i + 1}: expected `${expected_lines[i]}`, got `${actual_lines[i]}`')
257 }
258 }
259 fail('FAIL: ${label} line count differs: expected ${expected_lines.len}, got ${actual_lines.len}')
260}
261
262fn cleanup_files(paths []string) {
263 for path in paths {
264 os.rm(path) or {}
265 }
266}
267
268fn q(path string) string {
269 return os.quoted_path(path)
270}
271
272fn fail(message string) {
273 eprintln(message)
274 exit(1)
275}
276