11import os
2+ import v.vmod
23import flag
4+ import time
35import math
46import log
57
@@ -8,8 +10,6 @@ const default_command = '${os.quoted_path(@VEXE)} -no-skip-unused' // Command us
810const default_error_msg = 'C compilation error' // the pattern to reproduce
911// Temporary files
1012const 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
1414fn 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 ('\r item 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 ('\r item 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
355436fn 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