tests: parallelize compiler_errors_test.v using channels & threads

pull/6086/head
Delyan Angelov 2020-08-07 16:44:49 +03:00
parent 34d03801de
commit 55b8cc1bb2
2 changed files with 133 additions and 49 deletions

View File

@ -3,7 +3,7 @@ module benchmark
import time import time
import term import term
const ( pub const (
b_ok = term.ok_message('OK ') b_ok = term.ok_message('OK ')
b_fail = term.fail_message('FAIL') b_fail = term.fail_message('FAIL')
b_skip = term.warn_message('SKIP') b_skip = term.warn_message('SKIP')
@ -111,8 +111,8 @@ pub fn (mut b Benchmark) measure(label string) i64 {
return res return res
} }
pub fn (b &Benchmark) step_message_with_label(label string, msg string) string { pub fn (b &Benchmark) step_message_with_label_and_duration(label string, msg string, sduration time.Duration) string {
timed_line := b.tdiff_in_ms(msg, b.step_timer.elapsed().microseconds()) timed_line := b.tdiff_in_ms(msg, sduration.microseconds())
if b.nexpected_steps > 1 { if b.nexpected_steps > 1 {
mut sprogress := '' mut sprogress := ''
if b.nexpected_steps < 10 { if b.nexpected_steps < 10 {
@ -137,6 +137,10 @@ pub fn (b &Benchmark) step_message_with_label(label string, msg string) string {
return '${label:-5s}${timed_line}' return '${label:-5s}${timed_line}'
} }
pub fn (b &Benchmark) step_message_with_label(label string, msg string) string {
return b.step_message_with_label_and_duration(label, msg, b.step_timer.elapsed())
}
pub fn (b &Benchmark) step_message(msg string) string { pub fn (b &Benchmark) step_message(msg string) string {
return b.step_message_with_label('', msg) return b.step_message_with_label('', msg)
} }

View File

@ -2,9 +2,25 @@ import os
import term import term
import v.util import v.util
import v.util.vtest import v.util.vtest
import time
import sync
import runtime
import benchmark
struct TaskDescription {
vexe string
dir string
voptions string
result_extension string
path string
mut:
is_error bool
expected string
found___ string
took time.Duration
}
fn test_all() { fn test_all() {
mut total_errors := 0
vexe := os.getenv('VEXE') vexe := os.getenv('VEXE')
vroot := os.dir(vexe) vroot := os.dir(vexe)
os.chdir(vroot) os.chdir(vroot)
@ -17,60 +33,115 @@ fn test_all() {
parser_dir := 'vlib/v/parser/tests' parser_dir := 'vlib/v/parser/tests'
parser_tests := get_tests_in_dir(parser_dir) parser_tests := get_tests_in_dir(parser_dir)
// -prod so that warns are errors // -prod so that warns are errors
total_errors += check_path(vexe, classic_dir, '-prod', '.out', classic_tests) mut tasks := []TaskDescription{}
total_errors += check_path(vexe, global_dir, '--enable-globals', '.out', global_tests) tasks << new_tasks(vexe, classic_dir, '-prod', '.out', classic_tests)
total_errors += check_path(vexe, classic_dir, '--enable-globals run', '.run.out', tasks << new_tasks(vexe, global_dir, '--enable-globals', '.out', global_tests)
['globals_error.vv']) tasks <<
total_errors += check_path(vexe, run_dir, 'run', '.run.out', run_tests) new_tasks(vexe, classic_dir, '--enable-globals run', '.run.out', ['globals_error.vv'])
total_errors += check_path(vexe, parser_dir, '-prod', '.out', parser_tests) tasks << new_tasks(vexe, run_dir, 'run', '.run.out', run_tests)
tasks << new_tasks(vexe, parser_dir, '-prod', '.out', parser_tests)
tasks.run()
total_errors := tasks.filter(it.is_error).len
assert total_errors == 0 assert total_errors == 0
} }
fn get_tests_in_dir(dir string) []string { fn new_tasks(vexe, dir, voptions, result_extension string, tests []string) []TaskDescription {
files := os.ls(dir) or {
panic(err)
}
mut tests := files.filter(it.ends_with('.vv'))
tests.sort()
return tests
}
fn check_path(vexe, dir, voptions, result_extension string, tests []string) int {
mut nb_fail := 0
paths := vtest.filter_vtest_only(tests, { paths := vtest.filter_vtest_only(tests, {
basepath: dir basepath: dir
}) })
mut res := []TaskDescription{}
for path in paths { for path in paths {
program := path.replace('.vv', '.v') res << TaskDescription{
print(path + ' ') vexe: vexe
os.cp(path, program) or { dir: dir
panic(err) voptions: voptions
} result_extension: result_extension
res := os.exec('$vexe $voptions $program') or { path: path
panic(err)
}
mut expected := os.read_file(program.replace('.v', '') + result_extension) or {
panic(err)
}
expected = clean_line_endings(expected)
found := clean_line_endings(res.output)
if expected != found {
println(term.red('FAIL'))
println('============')
println('expected:')
println(expected)
println('============')
println('found:')
println(found)
println('============\n')
diff_content(expected, found)
nb_fail++
} else {
println(term.green('OK'))
os.rm(program)
} }
} }
return nb_fail return res
}
// process an array of tasks in parallel, using no more than vjobs worker threads
fn (mut tasks []TaskDescription) run() {
vjobs := runtime.nr_jobs()
mut bench := benchmark.new_benchmark()
bench.set_total_expected_steps(tasks.len)
// TODO: close work channel instead of using sentinel items
task_sentinel := TaskDescription{
path: ''
}
mut work := sync.new_channel<TaskDescription>(tasks.len + vjobs)
mut results := sync.new_channel<TaskDescription>(tasks.len)
for i in 0 .. tasks.len {
work.push(&tasks[i])
}
for _ in 0 .. vjobs {
work.push(&task_sentinel)
go work_processor(mut work, mut results)
}
for _ in 0 .. tasks.len {
mut task := TaskDescription{}
results.pop(&task)
bench.step()
if task.is_error {
bench.fail()
eprintln(bench.step_message_with_label_and_duration(benchmark.b_fail, task.path,
task.took))
println('============')
println('expected:')
println(task.expected)
println('============')
println('found:')
println(task.found___)
println('============\n')
diff_content(task.expected, task.found___)
} else {
bench.ok()
eprintln(bench.step_message_with_label_and_duration(benchmark.b_ok, task.path,
task.took))
}
}
bench.stop()
eprintln(term.h_divider('-'))
eprintln(bench.total_message('all tests'))
}
// a single worker thread spends its time getting work from the `work` channel,
// processing the task, and then putting the task in the `results` channel
fn work_processor(mut work sync.Channel, mut results sync.Channel) {
for {
mut task := TaskDescription{}
work.pop(&task)
if task.path == '' {
break
}
sw := time.new_stopwatch({})
task.execute()
task.took = sw.elapsed()
results.push(&task)
}
}
// actual processing; NB: no output is done here at all
fn (mut task TaskDescription) execute() {
program := task.path.replace('.vv', '.v')
os.cp(task.path, program) or {
panic(err)
}
res := os.exec('$task.vexe $task.voptions $program') or {
panic(err)
}
mut expected := os.read_file(program.replace('.v', '') + task.result_extension) or {
panic(err)
}
task.expected = clean_line_endings(expected)
task.found___ = clean_line_endings(res.output)
if task.expected != task.found___ {
task.is_error = true
} else {
os.rm(program)
}
} }
fn clean_line_endings(s string) string { fn clean_line_endings(s string) string {
@ -90,3 +161,12 @@ fn diff_content(s1, s2 string) {
println(util.color_compare_strings(diff_cmd, s1, s2)) println(util.color_compare_strings(diff_cmd, s1, s2))
println('============\n') println('============\n')
} }
fn get_tests_in_dir(dir string) []string {
files := os.ls(dir) or {
panic(err)
}
mut tests := files.filter(it.ends_with('.vv'))
tests.sort()
return tests
}