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'] const allowed_formats = ['md', 'markdown', 'json', 'text', 'ansi', 'html', 'htm']
enum RunExampleMode {
skip
run
check
}
struct Config { struct Config {
mut: mut:
pub_only bool = true pub_only bool = true
show_loc bool // for plaintext show_loc bool // for plaintext
show_time bool // show the total time spend generating content
is_color bool is_color bool
is_multi bool is_multi bool
is_vlib bool is_vlib bool
@ -31,7 +38,8 @@ mut:
input_path string input_path string
symbol_name string symbol_name string
platform doc.Platform 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: // 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_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 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.vprintln('Setting output type to "${cfg.output_type}"')
vd.generate_docs_from_file() vd.generate_docs_from_file()
if cfg.run_examples { if cfg.run_examples != .skip {
println('') println('')
if vd.example_oks == 0 && vd.example_failures == 0 { if vd.example_oks == 0 && vd.example_failures == 0 {
println(term.colorize(term.bright_yellow, 'Found NO examples.')) println(term.colorize(term.bright_yellow, 'Found NO examples.'))
@ -137,8 +145,19 @@ fn parse_arguments(args []string) Config {
cfg.platform = selected_platform cfg.platform = selected_platform
i++ i++
} }
'-time' {
cfg.show_time = true
}
'-check-examples' {
cfg.run_examples = .check
}
'-unsafe-run-examples' {
cfg.run_examples = .run
}
'-run-examples' { '-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' { '-no-timestamp' {
cfg.no_timestamp = true 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) { 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 return
} }
voptions := match vd.cfg.run_examples {
.run { ' -g run ' }
.check { '-N -W -check' }
.skip { '' }
}
examples := dn.examples() examples := dn.examples()
if examples.len == 0 { if examples.len == 0 {
return 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' } import_clause := if mod_name in ['builtin', ''] { '' } else { 'import ${mod_name}\n' }
source := '${import_clause}fn main() {\n\t${code}\n}\n' source := '${import_clause}fn main() {\n\t${code}\n}\n'
os.write_file(vsource_path, source) or { continue } 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) res := os.execute(cmd)
if res.exit_code != 0 { if res.exit_code != 0 {
eprintln('${dn_to_location(dn)}:${term.ecolorize(term.red, 'error in documentation example')}') eprintln('${dn_to_location(dn)}:${term.ecolorize(term.red, 'error in documentation example')}')
eprintln('cmd: ${cmd}') eprintln(' cmd: ${cmd}')
eprintln('result: ${res.output}') eprintln(' result:')
eprintln(res.output)
failures++ failures++
continue continue
} }

View file

@ -1,5 +1,5 @@
// abc just prints 'xyz'. The important thing however is the next line, that does an assertion, // 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 // Example: assert 5 * 5 == 77
pub fn abc() { pub fn abc() {
println('xyz') println('xyz')

View file

@ -277,6 +277,13 @@ fn (vd &VDoc) emit_generate_err(err IError) {
} }
fn (mut vd VDoc) generate_docs_from_file() { 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 cfg := vd.cfg
mut out := Output{ mut out := Output{
path: cfg.output_path path: cfg.output_path

View file

@ -4,10 +4,34 @@ const vexe_path = @VEXE
const vexe = os.quoted_path(vexe_path) const vexe = os.quoted_path(vexe_path)
const vroot = os.dir(vexe_path) const vroot = os.dir(vexe_path)
fn test_run_examples_good() { fn testsuite_begin() {
os.setenv('VCOLORS', 'never', true) os.setenv('VCOLORS', 'never', true)
os.chdir(vroot)! 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} ...') println('${@METHOD:30} running ${cmd} ...')
res := os.execute(cmd) res := os.execute(cmd)
assert res.exit_code == 0 assert res.exit_code == 0
@ -19,9 +43,7 @@ fn test_run_examples_good() {
} }
fn test_run_examples_bad() { fn test_run_examples_bad() {
os.setenv('VCOLORS', 'never', true) cmd := '${vexe} doc -comments -unsafe-run-examples cmd/tools/vdoc/testdata/run_examples_bad/main.v'
os.chdir(vroot)!
cmd := '${vexe} doc -comments -run-examples cmd/tools/vdoc/testdata/run_examples_bad/main.v'
println('${@METHOD:30} running ${cmd} ...') println('${@METHOD:30} running ${cmd} ...')
res := os.execute(cmd) res := os.execute(cmd)
assert res.exit_code != 0 assert res.exit_code != 0

View file

@ -29,6 +29,11 @@ Options:
-v Enables verbose logging. For debugging purposes. -v Enables verbose logging. For debugging purposes.
-no-timestamp Omits the timestamp in the output file. -no-timestamp Omits the timestamp in the output file.
-check-examples Find and check that all example comments are compilable,
but * without * running them.
-unsafe-run-examples Find and run all `// Example: assert 1 + 1 == 2` lines
in the comments.
For HTML mode: For HTML mode:
-inline-assets Embeds the contents of the CSS and JS assets into the -inline-assets Embeds the contents of the CSS and JS assets into the
webpage directly. webpage directly.