tools: let `v gret` make an easily visible diff.png image after regressions, and upload it too, to make CI failures easier to diagnose

master
Delyan Angelov 2022-06-10 15:38:50 +03:00
parent fcaf529228
commit b27b6b2047
No known key found for this signature in database
GPG Key ID: 66886C0F12D595ED
2 changed files with 54 additions and 82 deletions

View File

@ -21,10 +21,13 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Build local v - name: Build local v
run: make -j4 run: make
- uses: openrndr/setup-opengl@v1.1
- name: Setup dependencies - name: Setup dependencies
run: | run: |
# imagemagick : convert, mogrify
# xvfb : xvfb (installed by openrndr/setup-opengl@v1.1) # xvfb : xvfb (installed by openrndr/setup-opengl@v1.1)
# openimageio-tools : idiff # openimageio-tools : idiff
# libxcursor-dev libxi-dev : V gfx deps # libxcursor-dev libxi-dev : V gfx deps
@ -33,14 +36,9 @@ jobs:
sudo apt-get update sudo apt-get update
sudo apt-get install imagemagick openimageio-tools mesa-common-dev libxcursor-dev libxi-dev freeglut3-dev sudo apt-get install imagemagick openimageio-tools mesa-common-dev libxcursor-dev libxi-dev freeglut3-dev
wget https://raw.githubusercontent.com/tremby/imgur.sh/c98345d/imgur.sh wget https://raw.githubusercontent.com/tremby/imgur.sh/c98345d/imgur.sh
git clone https://github.com/Larpon/gg-regression-images gg-regression-images
chmod +x ./imgur.sh chmod +x ./imgur.sh
- uses: openrndr/setup-opengl@v1.1
- uses: actions/checkout@v2
with:
repository: Larpon/gg-regression-images
path: gg-regression-images
- name: Sample and compare - name: Sample and compare
id: compare id: compare
continue-on-error: true continue-on-error: true
@ -52,4 +50,5 @@ jobs:
if: steps.compare.outcome != 'success' if: steps.compare.outcome != 'success'
run: | run: |
./imgur.sh /tmp/fail.png ./imgur.sh /tmp/fail.png
./imgur.sh /tmp/diff.png
exit 1 exit 1

View File

@ -37,7 +37,7 @@ import flag
import toml import toml
const ( const (
tool_name = os.file_name(os.executable()) tool_name = 'vgret'
tool_version = '0.0.1' tool_version = '0.0.1'
tool_description = '\n Dump and/or compare rendered frames of `gg` based apps tool_description = '\n Dump and/or compare rendered frames of `gg` based apps
@ -57,7 +57,7 @@ Examples:
const ( const (
supported_hosts = ['linux'] supported_hosts = ['linux']
// External tool executables // External tool executables
v_exe = vexe() v_exe = os.getenv('VEXE')
idiff_exe = os.find_abs_path_of_executable('idiff') or { '' } idiff_exe = os.find_abs_path_of_executable('idiff') or { '' }
) )
@ -105,11 +105,27 @@ mut:
config Config config Config
} }
fn (opt Options) verbose_execute(cmd string) os.Result {
opt.verbose_eprintln('Running `$cmd`')
return os.execute(cmd)
}
fn (opt Options) verbose_eprintln(msg string) {
if opt.verbose {
eprintln(msg)
}
}
fn main() { fn main() {
if os.args.len == 1 { if runtime_os !in supported_hosts {
println('Usage: $tool_name PATH \n$tool_description\n$tool_name -h for more help...') eprintln('$tool_name is currently only supported on $supported_hosts hosts')
exit(1) exit(1)
} }
if os.args.len == 1 {
eprintln('Usage: $tool_name PATH \n$tool_description\n$tool_name -h for more help...')
exit(1)
}
mut fp := flag.new_flag_parser(os.args[1..]) mut fp := flag.new_flag_parser(os.args[1..])
fp.application(tool_name) fp.application(tool_name)
fp.version(tool_version) fp.version(tool_version)
@ -131,17 +147,17 @@ fn main() {
} }
toml_conf := fp.string('toml-config', `t`, default_toml, 'Path or string with TOML configuration') toml_conf := fp.string('toml-config', `t`, default_toml, 'Path or string with TOML configuration')
arg_paths := fp.finalize()?
ensure_env(opt) or { panic(err) }
arg_paths := fp.finalize() or { panic(err) }
if arg_paths.len == 0 { if arg_paths.len == 0 {
println(fp.usage()) println(fp.usage())
println('\nError missing arguments') println('\nError missing arguments')
exit(1) exit(1)
} }
if !os.exists(tmp_dir) {
os.mkdir_all(tmp_dir)?
}
opt.config = new_config(opt.root_path, toml_conf)? opt.config = new_config(opt.root_path, toml_conf)?
gen_in_path := arg_paths[0] gen_in_path := arg_paths[0]
@ -154,13 +170,15 @@ fn main() {
all_paths_in_use := [path, gen_in_path, target_path] all_paths_in_use := [path, gen_in_path, target_path]
for path_in_use in all_paths_in_use { for path_in_use in all_paths_in_use {
if !os.is_dir(path_in_use) { if !os.is_dir(path_in_use) {
panic('`$path_in_use` is not a directory') eprintln('`$path_in_use` is not a directory')
exit(1)
} }
} }
if path == target_path || gen_in_path == target_path || gen_in_path == path { if path == target_path || gen_in_path == target_path || gen_in_path == path {
panic('Compare paths can not be the same directory `$path`/`$target_path`/`$gen_in_path`') eprintln('Compare paths can not be the same directory `$path`/`$target_path`/`$gen_in_path`')
exit(1)
} }
compare_screenshots(opt, gen_in_path, target_path) or { panic(err) } compare_screenshots(opt, gen_in_path, target_path)?
} }
} }
@ -184,21 +202,15 @@ fn generate_screenshots(mut opt Options, output_path string) ? {
rel_out_path = file rel_out_path = file
} }
if opt.verbose { opt.verbose_eprintln('Compiling shaders (if needed) for `$file`')
eprintln('Compiling shaders (if needed) for `$file`') sh_result := opt.verbose_execute('${os.quoted_path(v_exe)} shader ${os.quoted_path(app_path)}')
}
sh_result := os.execute('${os.quoted_path(v_exe)} shader ${os.quoted_path(app_path)}')
if sh_result.exit_code != 0 { if sh_result.exit_code != 0 {
if opt.verbose { opt.verbose_eprintln('Skipping shader compile for `$file` v shader failed with:\n$sh_result.output')
eprintln('Skipping shader compile for `$file` v shader failed with:\n$sh_result.output')
}
continue continue
} }
if !os.exists(dst_path) { if !os.exists(dst_path) {
if opt.verbose { opt.verbose_eprintln('Creating output path `$dst_path`')
eprintln('Creating output path `$dst_path`')
}
os.mkdir_all(dst_path)? os.mkdir_all(dst_path)?
} }
@ -221,18 +233,13 @@ fn compare_screenshots(opt Options, output_path string, target_path string) ? {
mut warns := map[string]string{} mut warns := map[string]string{}
for app_config in opt.config.apps { for app_config in opt.config.apps {
screenshots := app_config.screenshots screenshots := app_config.screenshots
if opt.verbose { opt.verbose_eprintln('Comparing $screenshots.len screenshots in `$output_path` with `$target_path`')
eprintln('Comparing $screenshots.len screenshots in `$output_path` with `$target_path`')
}
for screenshot in screenshots { for screenshot in screenshots {
relative_screenshot := screenshot.all_after(output_path + os.path_separator) relative_screenshot := screenshot.all_after(output_path + os.path_separator)
src := screenshot src := screenshot
target := os.join_path(target_path, relative_screenshot) target := os.join_path(target_path, relative_screenshot)
opt.verbose_eprintln('Comparing `$src` with `$target` with $app_config.compare.method')
if opt.verbose {
eprintln('Comparing `$src` with `$target` with $app_config.compare.method')
}
if app_config.compare.method == 'idiff' { if app_config.compare.method == 'idiff' {
if idiff_exe == '' { if idiff_exe == '' {
@ -242,14 +249,9 @@ fn compare_screenshots(opt Options, output_path string, target_path string) ? {
'.diff.tif') '.diff.tif')
flags := app_config.compare.flags.join(' ') flags := app_config.compare.flags.join(' ')
diff_cmd := '${os.quoted_path(idiff_exe)} $flags -abs -od -o ${os.quoted_path(diff_file)} -abs ${os.quoted_path(src)} ${os.quoted_path(target)}' diff_cmd := '${os.quoted_path(idiff_exe)} $flags -abs -od -o ${os.quoted_path(diff_file)} -abs ${os.quoted_path(src)} ${os.quoted_path(target)}'
if opt.verbose { result := opt.verbose_execute(diff_cmd)
eprintln('Running: $diff_cmd') if result.exit_code == 0 {
} opt.verbose_eprintln('OUTPUT: \n$result.output')
result := os.execute(diff_cmd)
if opt.verbose && result.exit_code == 0 {
eprintln('OUTPUT: \n$result.output')
} }
if result.exit_code != 0 { if result.exit_code != 0 {
eprintln('OUTPUT: \n$result.output') eprintln('OUTPUT: \n$result.output')
@ -278,15 +280,19 @@ fn compare_screenshots(opt Options, output_path string, target_path string) ? {
} }
first := fails.keys()[0] first := fails.keys()[0]
fail_copy := os.join_path(os.temp_dir(), 'fail.' + first.all_after_last('.')) fail_copy := os.join_path(os.temp_dir(), 'fail.' + first.all_after_last('.'))
os.cp(first, fail_copy) or { panic(err) } os.cp(first, fail_copy)?
eprintln('First failed file `$first` is copied to `$fail_copy`') eprintln('First failed file `$first` is copied to `$fail_copy`')
diff_file := os.join_path(os.temp_dir(), os.file_name(first).all_before_last('.') + diff_file := os.join_path(os.temp_dir(), os.file_name(first).all_before_last('.') +
'.diff.tif') '.diff.tif')
diff_copy := os.join_path(os.temp_dir(), 'diff.tif') diff_copy := os.join_path(os.temp_dir(), 'diff.tif')
if os.is_file(diff_file) { if os.is_file(diff_file) {
os.cp(diff_file, diff_copy) or { panic(err) } os.cp(diff_file, diff_copy)?
eprintln('First failed diff file `$diff_file` is copied to `$diff_copy`') eprintln('First failed diff file `$diff_file` is copied to `$diff_copy`')
eprintln('Removing alpha channel from $diff_copy ...')
final_fail_result_file := os.join_path(os.temp_dir(), 'diff.png')
opt.verbose_execute('convert ${os.quoted_path(diff_copy)} -alpha off ${os.quoted_path(final_fail_result_file)}')
eprintln('Final diff file: `$final_fail_result_file`')
} }
exit(1) exit(1)
} }
@ -295,25 +301,16 @@ fn compare_screenshots(opt Options, output_path string, target_path string) ? {
fn take_screenshots(opt Options, app AppConfig) ?[]string { fn take_screenshots(opt Options, app AppConfig) ?[]string {
out_path := app.screenshots_path out_path := app.screenshots_path
if !opt.compare_only { if !opt.compare_only {
if opt.verbose { opt.verbose_eprintln('Taking screenshot(s) of `$app.path` to `$out_path`')
eprintln('Taking screenshot(s) of `$app.path` to `$out_path`')
}
if app.capture.method == 'gg_record' { if app.capture.method == 'gg_record' {
for k, v in app.capture.env { for k, v in app.capture.env {
rv := v.replace('\$OUT_PATH', out_path) rv := v.replace('\$OUT_PATH', out_path)
if opt.verbose { opt.verbose_eprintln('Setting ENV `$k` = $rv ...')
eprintln('Setting ENV `$k` = $rv ...')
}
os.setenv('$k', rv, true) os.setenv('$k', rv, true)
} }
mut flags := app.capture.flags.join(' ') mut flags := app.capture.flags.join(' ')
v_cmd := '${os.quoted_path(v_exe)} $flags -d gg_record run ${os.quoted_path(app.abs_path)}' result := opt.verbose_execute('${os.quoted_path(v_exe)} $flags -d gg_record run ${os.quoted_path(app.abs_path)}')
if opt.verbose {
eprintln('Running `$v_cmd`')
}
result := os.execute('$v_cmd')
if result.exit_code != 0 { if result.exit_code != 0 {
return error('Failed taking screenshot of `$app.abs_path`:\n$result.output') return error('Failed taking screenshot of `$app.abs_path`:\n$result.output')
} }
@ -329,30 +326,6 @@ fn take_screenshots(opt Options, app AppConfig) ?[]string {
return screenshots return screenshots
} }
// ensure_env returns nothing if everything is okay.
fn ensure_env(opt Options) ? {
if !os.exists(tmp_dir) {
os.mkdir_all(tmp_dir)?
}
if runtime_os !in supported_hosts {
return error('$tool_name is currently only supported on $supported_hosts hosts')
}
}
// vexe returns the absolute path to the V compiler.
fn vexe() string {
mut exe := os.getenv('VEXE')
if os.is_executable(exe) {
return os.real_path(exe)
}
possible_symlink := os.find_abs_path_of_executable('v') or { '' }
if os.is_executable(possible_symlink) {
exe = os.real_path(possible_symlink)
}
return exe
}
fn new_config(root_path string, toml_config string) ?Config { fn new_config(root_path string, toml_config string) ?Config {
doc := if os.is_file(toml_config) { doc := if os.is_file(toml_config) {
toml.parse_file(toml_config)? toml.parse_file(toml_config)?