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