Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a prefix when replacing for doctest=:fix #2378

Merged
merged 27 commits into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a6f9f89
Add failing test for issue 2303
lkastner Oct 19, 2023
7f685b8
First working attempt at fixing issue 2303
lkastner Nov 10, 2023
d16a138
More thorough testing triggered a bug in the prefix logic for suppres…
lkastner Dec 14, 2023
5844fe4
Merge branch 'master' into lk/issue/2303
lkastner Dec 20, 2023
302ab0e
Merge branch 'master' into lk/issue/2303
lkastner Jan 10, 2024
66385bd
Merge branch 'master' into lk/issue/2303
lkastner Jan 15, 2024
323a41c
Merge branch 'master' into lk/issue/2303
mortenpi Jan 25, 2024
41a9d75
Another 2303 testcase and fix
lkastner Jan 25, 2024
985fd08
Merge branch 'master' into lk/issue/2303
mortenpi Feb 3, 2024
a8a377e
Error output in Julia 1.6 looks slightly different
lkastner Feb 15, 2024
a20f8b7
Merge branch 'master' into lk/issue/2303
lkastner Feb 15, 2024
bd8702a
Merge branch 'master' into lk/issue/2303
mortenpi Feb 21, 2024
74f74b2
Different way of adapting to varying Julia versions and 32bit archite…
lkastner Feb 21, 2024
f264eaa
Merge branch 'lk/issue/2303' of github.com:lkastner/Documenter.jl int…
lkastner Feb 21, 2024
8b4febc
Merge branch 'master' into lk/issue/2303
lkastner Feb 28, 2024
0410547
Merge branch 'master' into lk/issue/2303
lkastner Feb 29, 2024
0211a0f
Update src/doctests.jl
mortenpi Mar 25, 2024
5e1a3be
Update src/doctests.jl
mortenpi Mar 25, 2024
b19bfd3
simplify the code a bit
mortenpi Mar 26, 2024
884934e
Merge remote-tracking branch 'origin/master' into lk/issue/2303
mortenpi Mar 26, 2024
070487a
add CHANGELOG
mortenpi Mar 26, 2024
e269504
fix tests on nightly, hopefully
mortenpi Mar 26, 2024
ef258fd
Fix for nightly
lkastner Apr 25, 2024
632a7d6
Merge remote-tracking branch 'origin/master' into lk/issue/2303
lkastner Apr 25, 2024
06fed2a
Update CHANGELOG.md
mortenpi May 1, 2024
9a45a90
Update CHANGELOG.md
mortenpi May 1, 2024
6b18253
Merge branch 'master' into lk/issue/2303
mortenpi May 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

* A fully qualified `@ref` link now resolves in `Main` as well, in addition to `CurrentModule`. For any package whose docstrings are included in the documentation, as long as that package is loaded in `make.jl`, fully qualified `@ref` links to docstrings in the package will work from anywhere. This simplifies, e.g., linking between docstrings for packages that use sub-modules. ([#2470])

### Fixed

* Doctest fixing functionality handles another edge case. ([#2303], [#2378])


## Version [v1.3.0] - 2024-03-01

Expand Down Expand Up @@ -1782,6 +1786,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#2288]: https://github.com/JuliaDocs/Documenter.jl/issues/2288
[#2293]: https://github.com/JuliaDocs/Documenter.jl/issues/2293
[#2300]: https://github.com/JuliaDocs/Documenter.jl/issues/2300
[#2303]: https://github.com/JuliaDocs/Documenter.jl/issues/2303
[#2306]: https://github.com/JuliaDocs/Documenter.jl/issues/2306
[#2307]: https://github.com/JuliaDocs/Documenter.jl/issues/2307
[#2308]: https://github.com/JuliaDocs/Documenter.jl/issues/2308
Expand All @@ -1803,6 +1808,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#2373]: https://github.com/JuliaDocs/Documenter.jl/issues/2373
[#2374]: https://github.com/JuliaDocs/Documenter.jl/issues/2374
[#2375]: https://github.com/JuliaDocs/Documenter.jl/issues/2375
[#2378]: https://github.com/JuliaDocs/Documenter.jl/issues/2378
[#2394]: https://github.com/JuliaDocs/Documenter.jl/issues/2394
[#2406]: https://github.com/JuliaDocs/Documenter.jl/issues/2406
[#2408]: https://github.com/JuliaDocs/Documenter.jl/issues/2408
Expand All @@ -1817,6 +1823,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#2459]: https://github.com/JuliaDocs/Documenter.jl/issues/2459
[#2460]: https://github.com/JuliaDocs/Documenter.jl/issues/2460
[#2461]: https://github.com/JuliaDocs/Documenter.jl/issues/2461
[#2470]: https://github.com/JuliaDocs/Documenter.jl/issues/2470
[JuliaLang/julia#36953]: https://github.com/JuliaLang/julia/issues/36953
[JuliaLang/julia#38054]: https://github.com/JuliaLang/julia/issues/38054
[JuliaLang/julia#39841]: https://github.com/JuliaLang/julia/issues/39841
Expand Down
85 changes: 65 additions & 20 deletions src/doctests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@
end
end

mutable struct MutablePrefix
content :: String
MutablePrefix() = new("")

Check warning on line 57 in src/doctests.jl

View check run for this annotation

Codecov / codecov/patch

src/doctests.jl#L57

Added line #L57 was not covered by tests
end

function _doctest(page::Documenter.Page, doc::Documenter.Document)
ctx = DocTestContext(page.source, doc) # FIXME
ctx.meta[:CurrentFile] = page.source
Expand Down Expand Up @@ -202,24 +207,31 @@
# Doctest evaluation.

mutable struct Result
block :: MutableMD2CodeBlock # The entire code block that is being tested.
input :: String # Part of `block.code` representing the current input.
output :: String # Part of `block.code` representing the current expected output.
file :: String # File in which the doctest is written. Either `.md` or `.jl`.
value :: Any # The value returned when evaluating `input`.
hide :: Bool # Semi-colon suppressing the output?
stdout :: IOBuffer # Redirected stdout/stderr gets sent here.
bt :: Vector # Backtrace when an error is thrown.
block :: MutableMD2CodeBlock # The entire code block that is being tested.
raw_input :: String # Part of `block.code` representing the current input.
input :: String # Part of `block.code` representing the current input
# without leading repl prompts and spaces.
output :: String # Part of `block.code` representing the current expected output.
file :: String # File in which the doctest is written. Either `.md` or `.jl`.
value :: Any # The value returned when evaluating `input`.
hide :: Bool # Semi-colon suppressing the output?
stdout :: IOBuffer # Redirected stdout/stderr gets sent here.
bt :: Vector # Backtrace when an error is thrown.

function Result(block, input, output, file)
new(block, input, rstrip(output, '\n'), file, nothing, false, IOBuffer())
new(block, input, input, rstrip(output, '\n'), file, nothing, false, IOBuffer())

Check warning on line 222 in src/doctests.jl

View check run for this annotation

Codecov / codecov/patch

src/doctests.jl#L222

Added line #L222 was not covered by tests
end
function Result(block, raw_input, input, output, file)
new(block, raw_input, input, rstrip(output, '\n'), file, nothing, false, IOBuffer())

Check warning on line 225 in src/doctests.jl

View check run for this annotation

Codecov / codecov/patch

src/doctests.jl#L224-L225

Added lines #L224 - L225 were not covered by tests
end
end


function eval_repl(block, sandbox, meta::Dict, doc::Documenter.Document, page)
src_lines = Documenter.find_block_in_file(block.code, meta[:CurrentFile])
for (input, output) in repl_splitter(block.code)
result = Result(block, input, output, meta[:CurrentFile])
(prefix, split) = repl_splitter(block.code)
for (raw_input, input, output) in split
result = Result(block, raw_input, input, output, meta[:CurrentFile])

Check warning on line 234 in src/doctests.jl

View check run for this annotation

Codecov / codecov/patch

src/doctests.jl#L232-L234

Added lines #L232 - L234 were not covered by tests
for (ex, str) in Documenter.parseblock(input, doc, page; keywords = false, raise=false)
# Input containing a semi-colon gets suppressed in the final output.
@debug "Evaluating REPL line from doctest at $(Documenter.locrepr(result.file, src_lines))" unparsed_string = str parsed_expression = ex
Expand All @@ -239,7 +251,7 @@
# don't evaluate further if there is a parse error
isa(ex, Expr) && ex.head === :error && break
end
checkresult(sandbox, result, meta, doc)
checkresult(sandbox, result, meta, doc; prefix)

Check warning on line 254 in src/doctests.jl

View check run for this annotation

Codecov / codecov/patch

src/doctests.jl#L254

Added line #L254 was not covered by tests
end
end

Expand Down Expand Up @@ -288,7 +300,7 @@
end

# Regex used here to replace gensym'd module names could probably use improvements.
function checkresult(sandbox::Module, result::Result, meta::Dict, doc::Documenter.Document)
function checkresult(sandbox::Module, result::Result, meta::Dict, doc::Documenter.Document; prefix::MutablePrefix=MutablePrefix())

Check warning on line 303 in src/doctests.jl

View check run for this annotation

Codecov / codecov/patch

src/doctests.jl#L303

Added line #L303 was not covered by tests
sandbox_name = nameof(sandbox)
mod_regex = Regex("(Main\\.)?(Symbol\\(\"$(sandbox_name)\"\\)|$(sandbox_name))[,.]")
mod_regex_nodot = Regex(("(Main\\.)?$(sandbox_name)"))
Expand All @@ -312,12 +324,19 @@
# to check that manually with `isempty`.
if isempty(head) || !startswith(filteredstr, filteredhead)
if doc.user.doctest === :fix
fix_doctest(result, str, doc)
fix_doctest(result, str, doc; prefix)

Check warning on line 327 in src/doctests.jl

View check run for this annotation

Codecov / codecov/patch

src/doctests.jl#L327

Added line #L327 was not covered by tests
else
report(result, str, doc)
@debug "Doctest metadata" meta
push!(doc.internal.errors, :doctest)
end
else
# Prefix was not modified, unless output was different.
prefix.content *= result.raw_input*"\n"
if str != ""
prefix.content *= str * "\n"

Check warning on line 337 in src/doctests.jl

View check run for this annotation

Codecov / codecov/patch

src/doctests.jl#L335-L337

Added lines #L335 - L337 were not covered by tests
end
prefix.content *= "\n"

Check warning on line 339 in src/doctests.jl

View check run for this annotation

Codecov / codecov/patch

src/doctests.jl#L339

Added line #L339 was not covered by tests
end
else
value = result.hide ? nothing : result.value # `;` hides output.
Expand All @@ -332,12 +351,19 @@
)
if filteredstr != filteredoutput
if doc.user.doctest === :fix
fix_doctest(result, str, doc)
fix_doctest(result, str, doc; prefix)

Check warning on line 354 in src/doctests.jl

View check run for this annotation

Codecov / codecov/patch

src/doctests.jl#L354

Added line #L354 was not covered by tests
else
report(result, str, doc)
@debug "Doctest metadata" meta
push!(doc.internal.errors, :doctest)
end
else
# Prefix was not modified, unless output was different.
prefix.content *= result.raw_input*"\n"
if str != ""
prefix.content *= str * "\n"

Check warning on line 364 in src/doctests.jl

View check run for this annotation

Codecov / codecov/patch

src/doctests.jl#L362-L364

Added lines #L362 - L364 were not covered by tests
end
prefix.content *= "\n"

Check warning on line 366 in src/doctests.jl

View check run for this annotation

Codecov / codecov/patch

src/doctests.jl#L366

Added line #L366 was not covered by tests
end
end
return nothing
Expand Down Expand Up @@ -448,7 +474,7 @@
""", diff, _file=result.file, _line=line)
end

function fix_doctest(result::Result, str, doc::Documenter.Document)
function fix_doctest(result::Result, str, doc::Documenter.Document; prefix::MutablePrefix=MutablePrefix())

Check warning on line 477 in src/doctests.jl

View check run for this annotation

Codecov / codecov/patch

src/doctests.jl#L477

Added line #L477 was not covered by tests
code = result.block.code
filename = Base.find_source_file(result.file)
# read the file containing the code block
Expand All @@ -470,7 +496,8 @@
write(io, content[1:prevind(content, first(codeidx))])
# next look for the particular input string in the given code block
# make a regex of the input that matches leading whitespace (for multiline input)
rinput = "\\h*" * replace(Documenter.regex_escape(result.input), "\\n" => "\\n\\h*")
composed = prefix.content * result.raw_input
rinput = replace(Documenter.regex_escape(composed), "\\n" => "\\n\\h*")

Check warning on line 500 in src/doctests.jl

View check run for this annotation

Codecov / codecov/patch

src/doctests.jl#L499-L500

Added lines #L499 - L500 were not covered by tests
r = Regex(rinput)
inputidx = findfirst(r, code)
if inputidx === nothing
Expand All @@ -480,6 +507,11 @@
# construct the new code-snippet (without indent)
# first part: everything up until the last index of the input string
newcode = code[1:last(inputidx)]
prefix.content = newcode * "\n"
if str != ""
prefix.content *= str * "\n"

Check warning on line 512 in src/doctests.jl

View check run for this annotation

Codecov / codecov/patch

src/doctests.jl#L510-L512

Added lines #L510 - L512 were not covered by tests
end
prefix.content *= "\n"

Check warning on line 514 in src/doctests.jl

View check run for this annotation

Codecov / codecov/patch

src/doctests.jl#L514

Added line #L514 was not covered by tests
isempty(result.output) && (newcode *= '\n') # issue #772
# second part: the rest, with the old output replaced with the new one
if result.output == ""
Expand All @@ -488,7 +520,11 @@
newcode *= str
newcode *= code[nextind(code, last(inputidx)):end]
else
newcode *= replace(code[nextind(code, last(inputidx)):end], result.output => str, count = 1)
if str == ""
newcode *= replace(code[nextind(code, last(inputidx)):end], result.output * "\n" => str, count = 1)

Check warning on line 524 in src/doctests.jl

View check run for this annotation

Codecov / codecov/patch

src/doctests.jl#L523-L524

Added lines #L523 - L524 were not covered by tests
else
newcode *= replace(code[nextind(code, last(inputidx)):end], result.output => str, count = 1)

Check warning on line 526 in src/doctests.jl

View check run for this annotation

Codecov / codecov/patch

src/doctests.jl#L526

Added line #L526 was not covered by tests
end
end
# replace internal code block with the non-indented new code, needed if we come back
# looking to replace output in the same code block later
Expand All @@ -511,31 +547,40 @@
function repl_splitter(code)
lines = split(string(code, "\n"), '\n')
input = String[]
raw_inputs = String[]

Check warning on line 550 in src/doctests.jl

View check run for this annotation

Codecov / codecov/patch

src/doctests.jl#L550

Added line #L550 was not covered by tests
output = String[]
prefix = MutablePrefix()

Check warning on line 552 in src/doctests.jl

View check run for this annotation

Codecov / codecov/patch

src/doctests.jl#L552

Added line #L552 was not covered by tests
buffer = IOBuffer() # temporary buffer for doctest inputs and outputs
raw_input_buffer = IOBuffer()

Check warning on line 554 in src/doctests.jl

View check run for this annotation

Codecov / codecov/patch

src/doctests.jl#L554

Added line #L554 was not covered by tests
found_first_prompt = false
while !isempty(lines)
line = popfirst!(lines)
prompt = match(PROMPT_REGEX, line)
# We allow comments before the first julia> prompt
!found_first_prompt && startswith(line, '#') && continue
if !found_first_prompt && startswith(line, '#')
prefix.content *= line * "\n"
continue

Check warning on line 562 in src/doctests.jl

View check run for this annotation

Codecov / codecov/patch

src/doctests.jl#L560-L562

Added lines #L560 - L562 were not covered by tests
end
if prompt === nothing
source = match(SOURCE_REGEX, line)
if source === nothing
savebuffer!(input, buffer)
savebuffer!(raw_inputs, raw_input_buffer)

Check warning on line 568 in src/doctests.jl

View check run for this annotation

Codecov / codecov/patch

src/doctests.jl#L568

Added line #L568 was not covered by tests
println(buffer, line)
takeuntil!(PROMPT_REGEX, buffer, lines)
else
println(buffer, source[1])
println(raw_input_buffer, line)

Check warning on line 573 in src/doctests.jl

View check run for this annotation

Codecov / codecov/patch

src/doctests.jl#L573

Added line #L573 was not covered by tests
end
else
found_first_prompt = true
savebuffer!(output, buffer)
println(buffer, prompt[1])
println(raw_input_buffer, line)

Check warning on line 579 in src/doctests.jl

View check run for this annotation

Codecov / codecov/patch

src/doctests.jl#L579

Added line #L579 was not covered by tests
end
end
savebuffer!(output, buffer)
zip(input, output)
return prefix, zip(raw_inputs, input, output)

Check warning on line 583 in src/doctests.jl

View check run for this annotation

Codecov / codecov/patch

src/doctests.jl#L583

Added line #L583 was not covered by tests
end

function savebuffer!(out, buf)
Expand Down
37 changes: 37 additions & 0 deletions test/doctests/fix/broken.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,40 @@ julia> 1 + 2
julia> 3 + 4
```
```jldoctest
julia> a = (1,2)
julia> a
```
```jldoctest
# Leading comment
julia> a
ERROR: UndefVarError: `a` not defined
julia> a = Int64[1,2]
2-element Vector{Int64}:
1
2
julia> b
julia> a
2-element Vector{Int64}:
2
julia> a;
1
julia> b;
julia> a = Int64[3,4];
julia> a
3
4
```
```jldoctest
julia> a = ("a", "b", "c");
julia> a
```
43 changes: 43 additions & 0 deletions test/doctests/fix/fixed.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,46 @@ julia> 1 + 2
julia> 3 + 4
7
```
```jldoctest
julia> a = (1,2)
(1, 2)

julia> a
(1, 2)
```
```jldoctest
# Leading comment
julia> a
ERROR: UndefVarError: `a` not defined

julia> a = Int64[1,2]
2-element Vector{Int64}:
1
2

julia> b
ERROR: UndefVarError: `b` not defined

julia> a
2-element Vector{Int64}:
1
2

julia> a;

julia> b;
ERROR: UndefVarError: `b` not defined

julia> a = Int64[3,4];

julia> a
2-element Vector{Int64}:
3
4
```
```jldoctest
julia> a = ("a", "b", "c");

julia> a
("a", "b", "c")
```
11 changes: 9 additions & 2 deletions test/doctests/fix/tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,15 @@ function test_doctest_fix(dir)
@debug "Running doctest/fix doctests with doctest=true"
@quietly makedocs(sitename="-", modules = [Foo], source = srcdir, build = builddir)

# also test that we obtain the expected output
@test normalize_line_endings(index_md) == normalize_line_endings(joinpath(@__DIR__, "fixed.md"))
# Load desired results and adapt to various Julia versions:
md_result = normalize_line_endings(joinpath(@__DIR__, "fixed.md"))
if VERSION < v"1.7"
# Error output is different in 1.6, so we adapt by having a separate correct file.
md_result = replace(md_result, r"UndefVarError: `([^`]*)` not defined" => s"UndefVarError: \1 not defined")
end

# test that we obtain the expected output
@test normalize_line_endings(index_md) == md_result
@test normalize_line_endings(src_jl) == normalize_line_endings(joinpath(@__DIR__, "fixed.jl"))
end

Expand Down
Loading