From d16485c29ee2e64067136eec44db9f6c1b184b41 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sun, 24 May 2020 17:45:53 +0300 Subject: [PATCH] live: make live_test.v more robust --- vlib/v/tests/live_test.v | 150 +++++++++++++++++++++++++++++++-------- 1 file changed, 122 insertions(+), 28 deletions(-) diff --git a/vlib/v/tests/live_test.v b/vlib/v/tests/live_test.v index d49c46e238..03969bbcbf 100755 --- a/vlib/v/tests/live_test.v +++ b/vlib/v/tests/live_test.v @@ -1,36 +1,111 @@ import os import time +/* +The goal of this test, is to simulate a developer, that has run a program, compiled with -live flag. + +It does so by writing a new generated program containing a [live] fn pmessage() string {...} function, +then runs the generated program at the start *in the background*, +waits some time, so that the program could run a few iterations, then modifies its source +(simulates a developer that has saved a new version of the program source), +then it waits some more, modifies it again and saves it once more. + +On each modification, the running program, should detect that its source code has changed, +and recompile a shared library, which it then it should load, and thus modify its own +behavior at runtime (the pmessage function). + +If everything works fine, the output of the generated program would have changed at least 1-2 times, +which then is detected by the test program (the histogram checks). + +Since this test program is sensitive to coordination (or lack of) of several processes, +it tries to sidestep the coordination issue by polling the file system for the existance +of files, ORIGINAL.txt ... STOP.txt , which are appended to by the generated program. + +NB: That approach of monitoring the state of the running generated program, is clearly not ideal, +but sidesteps the issue of coordinating processes through IPC or stdin/stdout in hopefully +not very flaky way. + +TODO: Cleanup this when/if v has better process control/communication primitives. +*/ + const ( vexe = os.getenv('VEXE') + tmp_file = os.join_path(os.temp_dir(), 'generated_live_program.tmp.v') source_file = os.join_path(os.temp_dir(), 'generated_live_program.v') + genexe_file = os.join_path(os.temp_dir(), 'generated_live_program') output_file = os.join_path(os.temp_dir(), 'generated_live_program.output.txt') + res_original_file = os.join_path(os.temp_dir(), 'ORIGINAL.txt') + res_changed_file = os.join_path(os.temp_dir(), 'CHANGED.txt') + res_another_file = os.join_path(os.temp_dir(), 'ANOTHER.txt') + res_stop_file = os.join_path(os.temp_dir(), 'STOP.txt') live_program_source = " module main -import time -[live] -fn pmessage() { - println('ORIGINAL') +import time +import os +import live + +fn append_to_file(fname, s string) { + f := os.open_append(fname) or { + println('>>>> could not open file \$fname for appending, err: \$err ') + return + } + f.writeln('\$s') + //info := live.info() + //f.writeln('>>> reloads: \${info.reloads} | ok reloads: \${info.reloads_ok}') + f.flush() + f.close() +} + +fn myprintln(s string) { + append_to_file('$output_file', s) + println(s) + os.flush() } +[live] +fn pmessage() string { + s := 'ORIGINAL' + myprintln(s) + return s +} + +const ( + delay = 5 +) + fn main() { - println('START') + mut info := live.info() + info.recheck_period_ms = 5 + myprintln('START') + myprintln('DATE: ' + time.now().str()) pmessage() - time.sleep_ms(10) pmessage() - for i := 0; i<3*100; i++ { - pmessage() - time.sleep_ms(10) + // NB: 1000 * 5 = maximum of ~5s runtime + for i:=0; i<1000; i++ { + s := pmessage() + append_to_file(os.resource_abs_path(s + '.txt'), s) + time.sleep_ms(delay) + if s == 'STOP' { + break + } } pmessage() - time.sleep_ms(10) pmessage() - println('END') + myprintln('DATE: ' + time.now().str()) + myprintln('END') } " ) +fn atomic_write_source( source string ){ + // NB: here wrtiting is done in 2 steps, since os.write_file can take some time, + // during which the file will be modified, but it will still be not completely written. + // The os.mv after that, guarantees that the reloader will see a complete valid V program. + os.write_file(tmp_file, source) + os.mv(tmp_file, source_file ) +} + // fn testsuite_begin() { if os.user_os() !in ['linux', 'solaris'] && os.getenv('FORCE_LIVE_TEST').len == 0 { @@ -39,16 +114,15 @@ fn testsuite_begin() { eprintln('You can still do it by setting FORCE_LIVE_TEST=1 .') exit(0) } - os.write_file(source_file, live_program_source) + for f in [ tmp_file, source_file, output_file, res_original_file, res_changed_file, res_another_file, res_stop_file] { + os.rm(f) + } + atomic_write_source( live_program_source ) } fn testsuite_end() { - os.rm(source_file) eprintln('source: $source_file') eprintln('output: $output_file') - $if !windows { - os.system('cat $output_file | sort | uniq -c | sort -n') - } println('---------------------------------------------------------------------------') output_lines := os.read_lines(output_file) or { return @@ -58,31 +132,51 @@ fn testsuite_end() { histogram[line] = histogram[line] + 1 } for k, v in histogram { - println('> found ${k} $v times.') + println('> found ${v:5d} times: ${k}') } println('---------------------------------------------------------------------------') assert histogram['START'] > 0 - assert histogram['END'] > 0 assert histogram['ORIGINAL'] > 0 assert histogram['CHANGED'] + histogram['ANOTHER'] > 0 + //assert histogram['END'] > 0 } fn change_source(new string) { - time.sleep_ms(250) + time.sleep_ms(100) eprintln('> change ORIGINAL to: $new') - os.write_file(source_file, live_program_source.replace('ORIGINAL', new)) - time.sleep_ms(1000) - eprintln('> done.') + atomic_write_source( live_program_source.replace('ORIGINAL', new) ) + wait_for_file(new) +} + +fn wait_for_file(new string){ + time.sleep_ms(100) + expected_file := os.join_path(os.temp_dir(), new + '.txt') + for i:=0 ; i <= 400 ; i++ { + if i % 25 == 0 { + eprintln(' checking ${i:-10d} for $expected_file ...') + } + if os.exists( expected_file ) { + assert true + eprintln('> done.') + time.sleep_ms(100) + break + } + time.sleep_ms(5) + } } // fn test_live_program_can_be_compiled() { - cmd := '$vexe -live run $source_file > $output_file &' - eprintln('Compiling and running with: $cmd') + eprintln('Compiling...') + os.system('$vexe -live -o $genexe_file $source_file') + // + cmd := '$genexe_file > /dev/null &' + eprintln('Running with: $cmd') res := os.system(cmd) - eprintln('... running in the background') - time.sleep_ms(1500) assert res == 0 + time.sleep_ms(1000) + eprintln('... running in the background') + wait_for_file('ORIGINAL') } fn test_live_program_can_be_changed_1() { @@ -95,7 +189,7 @@ fn test_live_program_can_be_changed_2() { assert true } -fn test_live_program_has_ended() { - time.sleep_ms(3500) +fn test_live_program_can_be_changed_3() { + change_source('STOP') assert true }