vdoc: implement -unsafe-run-examples and -check-examples support, add tests and document them

This commit is contained in:
Delyan Angelov 2025-08-13 14:21:21 +03:00
parent ab80c767bd
commit 6538b624e1
No known key found for this signature in database
GPG key ID: 66886C0F12D595ED
6 changed files with 72 additions and 13 deletions

View file

@ -12,10 +12,17 @@ const vroot = os.dir(vexe)
const allowed_formats = ['md', 'markdown', 'json', 'text', 'ansi', 'html', 'htm']
enum RunExampleMode {
skip
run
check
}
struct Config {
mut:
pub_only bool = true
show_loc bool // for plaintext
show_time bool // show the total time spend generating content
is_color bool
is_multi bool
is_vlib bool
@ -31,7 +38,8 @@ mut:
input_path string
symbol_name string
platform doc.Platform
run_examples bool // `-run-examples` will run all `// Example: assert mod.abc() == y` comments in the processed modules
run_examples RunExampleMode // `-unsafe-run-examples` will run all `// Example: assert mod.abc() == y` comments in the processed modules.
// -check-examples will only check/validate that they compile, but without running any code.
// The options below are useful for generating a more stable HTML, that is easier to regression test:
html_only_contents bool // `-html-only-contents` will produce only the content of any given page, without styling tags etc.
html_no_vhash bool // `-html-no-vhash` will remove the version hash from the generated html
@ -61,7 +69,7 @@ fn main() {
}
vd.vprintln('Setting output type to "${cfg.output_type}"')
vd.generate_docs_from_file()
if cfg.run_examples {
if cfg.run_examples != .skip {
println('')
if vd.example_oks == 0 && vd.example_failures == 0 {
println(term.colorize(term.bright_yellow, 'Found NO examples.'))
@ -137,8 +145,19 @@ fn parse_arguments(args []string) Config {
cfg.platform = selected_platform
i++
}
'-time' {
cfg.show_time = true
}
'-check-examples' {
cfg.run_examples = .check
}
'-unsafe-run-examples' {
cfg.run_examples = .run
}
'-run-examples' {
cfg.run_examples = true
eprintln('WARNING: the `-run-examples` option is deprecated, and will be removed after 2025-11-13.')
eprintln(' Use `-unsafe-run-examples` instead of `-run-examples` .')
cfg.run_examples = .run
}
'-no-timestamp' {
cfg.no_timestamp = true

View file

@ -32,9 +32,14 @@ fn get_mod_name_by_file_path(file_path string) string {
}
fn (mut vd VDoc) run_examples(dn doc.DocNode) {
if dn.comments.len == 0 || !vd.cfg.run_examples {
if dn.comments.len == 0 || vd.cfg.run_examples == .skip {
return
}
voptions := match vd.cfg.run_examples {
.run { ' -g run ' }
.check { '-N -W -check' }
.skip { '' }
}
examples := dn.examples()
if examples.len == 0 {
return
@ -56,12 +61,13 @@ fn (mut vd VDoc) run_examples(dn doc.DocNode) {
import_clause := if mod_name in ['builtin', ''] { '' } else { 'import ${mod_name}\n' }
source := '${import_clause}fn main() {\n\t${code}\n}\n'
os.write_file(vsource_path, source) or { continue }
cmd := '${os.quoted_path(vexe)} -g run ${os.quoted_path(vsource_path)}'
cmd := '${os.quoted_path(vexe)} ${voptions} ${os.quoted_path(vsource_path)}'
res := os.execute(cmd)
if res.exit_code != 0 {
eprintln('${dn_to_location(dn)}:${term.ecolorize(term.red, 'error in documentation example')}')
eprintln('cmd: ${cmd}')
eprintln('result: ${res.output}')
eprintln(' cmd: ${cmd}')
eprintln(' result:')
eprintln(res.output)
failures++
continue
}

View file

@ -1,5 +1,5 @@
// abc just prints 'xyz'. The important thing however is the next line, that does an assertion,
// that should FAIL to be executed with `v doc -run-examples good.v`:
// that should FAIL to be executed with `v doc -unsafe-run-examples main.v`, and should compile with -check-examples:
// Example: assert 5 * 5 == 77
pub fn abc() {
println('xyz')

View file

@ -277,6 +277,13 @@ fn (vd &VDoc) emit_generate_err(err IError) {
}
fn (mut vd VDoc) generate_docs_from_file() {
sw := time.new_stopwatch()
defer {
if vd.cfg.show_time {
println('Generation took: ${sw.elapsed().milliseconds()} ms.')
}
}
cfg := vd.cfg
mut out := Output{
path: cfg.output_path

View file

@ -4,10 +4,34 @@ const vexe_path = @VEXE
const vexe = os.quoted_path(vexe_path)
const vroot = os.dir(vexe_path)
fn test_run_examples_good() {
fn testsuite_begin() {
os.setenv('VCOLORS', 'never', true)
os.chdir(vroot)!
cmd := '${vexe} doc -comments -run-examples cmd/tools/vdoc/testdata/run_examples_good/main.v'
}
fn test_check_examples_good() {
cmd := '${vexe} doc -comments -check-examples cmd/tools/vdoc/testdata/run_examples_good/main.v'
println('${@METHOD:30} running ${cmd} ...')
res := os.execute(cmd)
assert res.exit_code == 0
assert res.output.contains('module main'), res.output
assert res.output.contains('fn abc()'), res.output
assert res.output.contains("abc just prints 'xyz'"), res.output
assert res.output.contains('and should succeed'), res.output
assert res.output.contains('Example: assert 5 * 5 == 25'), res.output
}
fn test_check_examples_bad() {
cmd := '${vexe} doc -comments -check-examples cmd/tools/vdoc/testdata/run_examples_bad/main.v'
println('${@METHOD:30} running ${cmd} ...')
res := os.execute(cmd)
assert res.exit_code == 0
assert res.output.contains('module main'), res.output
assert res.output.contains('Example: assert 5 * 5 == 77'), res.output
}
fn test_run_examples_good() {
cmd := '${vexe} doc -comments -unsafe-run-examples cmd/tools/vdoc/testdata/run_examples_good/main.v'
println('${@METHOD:30} running ${cmd} ...')
res := os.execute(cmd)
assert res.exit_code == 0
@ -19,9 +43,7 @@ fn test_run_examples_good() {
}
fn test_run_examples_bad() {
os.setenv('VCOLORS', 'never', true)
os.chdir(vroot)!
cmd := '${vexe} doc -comments -run-examples cmd/tools/vdoc/testdata/run_examples_bad/main.v'
cmd := '${vexe} doc -comments -unsafe-run-examples cmd/tools/vdoc/testdata/run_examples_bad/main.v'
println('${@METHOD:30} running ${cmd} ...')
res := os.execute(cmd)
assert res.exit_code != 0