Skip to content

Commit 80a9d27

Browse files
authored
tools: support vreduce timeout, vreduce custom run command (#24359)
1 parent 9364ac6 commit 80a9d27

1 file changed

Lines changed: 110 additions & 29 deletions

File tree

‎cmd/tools/vreduce.v‎

Lines changed: 110 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import os
2+
import v.vmod
23
import flag
4+
import time
35
import math
46
import log
57

@@ -8,8 +10,6 @@ const default_command = '${os.quoted_path(@VEXE)} -no-skip-unused' // Command us
810
const default_error_msg = 'C compilation error' // the pattern to reproduce
911
// Temporary files
1012
const tmp_folder = os.join_path(os.vtmp_dir(), 'vreduce')
11-
const tmp_reduced_code_file_name = '__v_reduced_code.v'
12-
const path = '${tmp_folder}/${tmp_reduced_code_file_name}'
1313

1414
fn main() {
1515
log.use_stdout()
@@ -20,8 +20,10 @@ fn main() {
2020
fp.version(version)
2121

2222
error_msg := fp.string('error_msg', `e`, default_error_msg, 'the error message you want to reproduce, default: \'${default_error_msg}\'')
23-
command := fp.string('command', `c`, default_command, 'the command used to try to reproduce the error, default: \'${default_command}\'')
24-
do_fmt := fp.bool('fmt', `w`, false, 'enable v fmt for the output (rpdc.v)')
23+
mut command := fp.string('command', `c`, default_command, 'the command used to try to reproduce the error, default: \'${default_command}\', will replace PATH with the path of the folder where it is run')
24+
copy_project := fp.bool('cp', `p`, false, 'if used v reduce will copy the whole folder of the project')
25+
timeout := fp.int('to', `t`, 0, 'sets a timeout for the command, default=0 : no timeout')
26+
do_fmt := fp.bool('fmt', `w`, false, 'enable v fmt for the output (rpdc_file_name.v)')
2527
file_paths := fp.finalize() or {
2628
eprintln(err)
2729
println(fp.usage())
@@ -33,7 +35,7 @@ fn main() {
3335
exit(0)
3436
}
3537

36-
file_path := file_paths[1]
38+
mut file_path := file_paths[1]
3739
if file_path == '' || !os.exists(file_path) {
3840
log.error('You need to specify a valid file to reduce.')
3941
if file_path != '' {
@@ -46,39 +48,107 @@ fn main() {
4648
log.info("Starting to reduce the file: '${file_path}'\n with command: `${command}`,\n trying to reproduce: `${error_msg}`")
4749

4850
if do_fmt {
49-
log.info('Will do `v fmt -w rpdc.v` after the reduction.')
51+
log.info('Will do `v fmt -w rpdc_file_name.v` after the reduction.')
5052
} else {
51-
log.info('Will NOT do `v fmt -w rpdc.v` (use the `--fmt` or `-w` flag to enable it)')
53+
log.info('Will NOT do `v fmt -w rpdc_file_name.v` (use the `--fmt` or `-w` flag to enable it)')
5254
}
5355

5456
content := os.read_file(file_path)!
55-
warn_on_false(string_reproduces(content, error_msg, command), 'string_reproduces',
56-
@LOCATION)
5757
show_code_stats(content, label: 'Original code size')
5858

59+
// copy project
60+
if os.exists(tmp_folder) {
61+
os.rmdir_all(tmp_folder)!
62+
}
63+
os.mkdir(tmp_folder)!
64+
if copy_project {
65+
mut vmod_cacher := vmod.new_mod_file_cacher()
66+
project_folder := vmod_cacher.get_by_file(file_path).vmod_folder
67+
os.cp_all('${project_folder}/.', tmp_folder + '/', true)!
68+
// the path of the target file from the project folder
69+
file_path = os.walk_ext(project_folder, os.file_name(file_path))[0] or {
70+
panic('File not found in the project folder')
71+
}
72+
file_path = file_path[project_folder.len + 1..] // will remove the / too
73+
}
74+
path := '${tmp_folder}/${file_path}'
75+
if command == default_command {
76+
command = '${default_command} ${path}'
77+
} else {
78+
command = command.replace('PATH', '${tmp_folder}/')
79+
}
80+
5981
// start tests
6082
tmp_code := create_code(parse(content))
61-
warn_on_false(string_reproduces(tmp_code, error_msg, command), 'string_reproduces',
62-
@LOCATION)
83+
warn_on_false(string_reproduces(tmp_code, error_msg, command, path, true, timeout),
84+
'string_reproduces', @LOCATION)
6385
show_code_stats(tmp_code, label: 'Code size without comments')
6486

6587
// reduce the code
66-
reduce_scope(content, error_msg, command, do_fmt)
88+
reduce_scope(content, error_msg, command, do_fmt, path, timeout)
89+
90+
// cleanse
91+
if os.exists(tmp_folder) {
92+
os.rmdir_all(tmp_folder)!
93+
}
6794
}
6895

6996
// Return true if the command ran on the file produces the pattern
70-
fn string_reproduces(file string, pattern string, command string) bool {
97+
fn string_reproduces(file string, pattern string, command string, path string, debug bool, timeout int) bool {
7198
if !os.exists(tmp_folder) {
7299
os.mkdir(tmp_folder) or { panic(err) }
73100
}
74101
os.write_file(path, file) or { panic(err) }
75-
res := os.execute(command + ' ' + path)
76-
if res.output.contains(pattern) {
102+
mut output := ''
103+
if timeout == 0 {
104+
res := os.execute(command)
105+
output = res.output
106+
} else {
107+
split := command.split(' ')
108+
mut prog := os.new_process(split[0])
109+
prog.set_args(split[1..])
110+
prog.set_redirect_stdio()
111+
prog.run()
112+
mut sw := time.new_stopwatch()
113+
sw.start()
114+
for prog.is_alive() {
115+
// check if there is any input from the user (it does not block, if there is not):
116+
time.sleep(1 * time.millisecond)
117+
mut b := true
118+
for b {
119+
b = false
120+
if oline := prog.pipe_read(.stdout) {
121+
if oline != '' {
122+
output += oline
123+
b = true
124+
}
125+
}
126+
if eline := prog.pipe_read(.stderr) {
127+
if eline != '' {
128+
output += eline
129+
b = true
130+
}
131+
}
132+
}
133+
if sw.elapsed().seconds() > f32(timeout) {
134+
if debug {
135+
println('Timeout')
136+
}
137+
return false
138+
}
139+
}
140+
prog.close()
141+
prog.wait()
142+
}
143+
if output.contains(pattern) {
77144
// println('reproduces')
78145
return true
79146
} else {
80147
// println('does not reproduce')
81-
// println(res.output)
148+
if debug {
149+
println(output)
150+
println('executed command: ${command}')
151+
}
82152
return false
83153
}
84154
}
@@ -225,8 +295,8 @@ fn create_code(sc Scope) string {
225295
return output_code
226296
}
227297

228-
// Reduces the code contained in the scope tree and writes the reduced code to `rpdc.v`
229-
fn reduce_scope(content string, error_msg string, command string, do_fmt bool) {
298+
// Reduces the code contained in the scope tree and writes the reduced code to `rpdc_file_name.v`
299+
fn reduce_scope(content string, error_msg string, command string, do_fmt bool, path string, timeout int) {
230300
mut sc := parse('') // will get filled in the start of the loop
231301
log.info('Cleaning the scopes')
232302
mut text_code := content
@@ -242,17 +312,21 @@ fn reduce_scope(content string, error_msg string, command string, do_fmt bool) {
242312
for i in 0 .. sc.children.len {
243313
stack << &sc.children[i]
244314
}
315+
mut item_nb := 0
245316
for stack.len > 0 { // traverse the tree and disable (ignore) scopes that are not needed for reproduction
246317
mut item := stack.pop()
318+
item_nb += 1
319+
eprint('\ritem n: ${item_nb}')
247320
if mut item is Scope {
248321
if !item.ignored {
249322
item.tmp_ignored = true // try to ignore it
250323
code := create_code(sc)
251324
item.tmp_ignored = false // dont need it anymore
252-
if string_reproduces(code, error_msg, command) {
325+
if string_reproduces(code, error_msg, command, path, false, timeout) {
253326
item.ignored = true
254327
modified_smth = true
255328
outer_modified_smth = true
329+
println('')
256330
show_code_stats(code)
257331
} else { // if can remove it, no need to go though it's children
258332
for i in 0 .. item.children.len {
@@ -263,6 +337,7 @@ fn reduce_scope(content string, error_msg string, command string, do_fmt bool) {
263337
}
264338
}
265339
}
340+
println('')
266341

267342
text_code = create_code(sc)
268343

@@ -296,8 +371,8 @@ fn reduce_scope(content string, error_msg string, command string, do_fmt bool) {
296371

297372
// Traverse the tree and prune the useless lines / line groups for the reproduction
298373
mut line_tree := *line_stack[0]
299-
warn_on_false(string_reproduces(create_code(line_tree), error_msg, command), 'string_reproduces',
300-
@LOCATION) // should be the same
374+
warn_on_false(string_reproduces(create_code(line_tree), error_msg, command, path,
375+
true, timeout), 'string_reproduces', @LOCATION) // should be the same
301376
log.info('Pruning the lines/line groups')
302377
modified_smth = true
303378
for modified_smth {
@@ -307,17 +382,21 @@ fn reduce_scope(content string, error_msg string, command string, do_fmt bool) {
307382
for i in 0 .. line_tree.children.len {
308383
stack << &line_tree.children[i]
309384
}
385+
mut item_nb := 0
310386
for stack.len > 0 { // traverse the binary tree (of the lines)
311387
mut item := stack.pop()
388+
item_nb += 1
389+
eprint('\ritem n: ${item_nb}')
312390
if mut item is Scope {
313391
if !item.ignored {
314392
item.tmp_ignored = true
315393
code := create_code(line_tree)
316394
item.tmp_ignored = false // dont need it anymore
317-
if string_reproduces(code, error_msg, command) {
395+
if string_reproduces(code, error_msg, command, path, false, timeout) {
318396
item.ignored = true
319397
modified_smth = true
320398
outer_modified_smth = true
399+
println('')
321400
show_code_stats(code)
322401
} else { // if can remove it, can remove it's children
323402
for i in 0 .. item.children.len {
@@ -328,18 +407,20 @@ fn reduce_scope(content string, error_msg string, command string, do_fmt bool) {
328407
}
329408
}
330409
}
410+
println('')
331411
text_code = create_code(line_tree)
332412
}
333413

334-
warn_on_false(string_reproduces(text_code, error_msg, command), 'string_reproduces',
335-
@LOCATION)
336-
os.write_file('rpdc.v', text_code) or { panic(err) }
414+
warn_on_false(string_reproduces(text_code, error_msg, command, path, true, timeout),
415+
'string_reproduces', @LOCATION)
416+
rpdc_file_path := 'rpdc_${os.file_name(path)#[..-2]}.v'
417+
os.write_file(rpdc_file_path, text_code) or { panic(err) }
337418
if do_fmt {
338-
os.execute('v fmt -w rpdc.v')
339-
final_content := os.read_file('rpdc.v') or { panic(err) }
419+
os.execute('v fmt -w ${rpdc_file_path}')
420+
final_content := os.read_file(rpdc_file_path) or { panic(err) }
340421
show_code_stats(final_content, label: 'Code size after formatting')
341422
}
342-
println('The reduced code is now in rpdc.v')
423+
println('The reduced code is now in ${rpdc_file_path}')
343424
}
344425

345426
@[params]
@@ -354,6 +435,6 @@ fn show_code_stats(code string, params ShowParams) {
354435

355436
fn warn_on_false(res bool, what string, loc string) {
356437
if !res {
357-
log.warn('${what} is false, at ${loc}')
438+
log.warn('${what} is false, at ${loc}; see output above')
358439
}
359440
}

0 commit comments

Comments
 (0)