Skip to content

Support for REPL-simulated test cases #2

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
79 changes: 70 additions & 9 deletions src/NarrativeTest.jl
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ struct TestCase <: AbstractTestCase
code::TextBlock
pre::Union{TextBlock,Nothing}
expect::Union{TextBlock,Nothing}
repl::Bool
end

location(test::TestCase) = test.loc
Expand Down Expand Up @@ -537,29 +538,36 @@ function parsemd!(stack::Vector{TextBlock})
line = pop!(stack)
if isfence(line)
# Extract a fenced block.
isrepl = false
fenceloc = line.loc
lang = strip(line.val[4:end])
jlstack = TextBlock[]
while !isempty(stack) && !isfence(stack[end])
push!(jlstack, pop!(stack))
block = pop!(stack)
isrepl = isrepl || startswith(block.val, "julia>")
push!(jlstack, block)
end
if isempty(stack)
push!(suite, BrokenTestCase(fenceloc, "incomplete fenced code block"))
else
pop!(stack)
if isempty(lang)
reverse!(jlstack)
append!(suite, parsejl!(jlstack))
append!(suite, isrepl ? parsejlrepl!(jlstack) : parsejl!(jlstack))
end
end
elseif isindent(line) && !isblank(line)
# Extract an indented block.
jlstack = TextBlock[unindent(line)]
block = unindent(line)
isrepl = startswith(block.val, "julia>")
jlstack = TextBlock[block]
while !isempty(stack) && (isindent(stack[end]) || isblank(stack[end]))
push!(jlstack, unindent(pop!(stack)))
block = unindent(pop!(stack))
isrepl = isrepl || startswith(block.val, "julia>")
push!(jlstack, block)
end
reverse!(jlstack)
append!(suite, parsejl!(jlstack))
append!(suite, isrepl ? parsejlrepl!(jlstack) : parsejl!(jlstack))
elseif isadmonition(line)
# Skip an indented admonition block.
while !isempty(stack) && (isindent(stack[end]) || isblank(stack[end]))
Expand Down Expand Up @@ -588,6 +596,54 @@ function parsejl!(stack::Vector{TextBlock})
return suite
end

const PROMPT_REGEX = r"^julia>(?: (.*))?$"
const SOURCE_REGEX = r"^ (.*)$"

function parsejlrepl!(stack::Vector{TextBlock})
reverse!(stack)
suite = AbstractTestCase[]
code, buf = nothing, IOBuffer()
function addcase!(expect)
case = !isempty(code.val) ?
TestCase(code.loc, code, nothing, expect, true) :
BrokenTestCase(code.loc, "missing test code")
push!(suite, case)
code = nothing
end
while true
line = popfirst!(stack)
prompt = match(PROMPT_REGEX, line.val)
if prompt !== nothing
prompt[1] !== nothing && println(buf, prompt[1])
while !isempty(stack)
source = match(SOURCE_REGEX, stack[1].val)
source !== nothing || break
println(buf, source[1])
popfirst!(stack)
end
code !== nothing && addcase!(TextBlock(code.loc, ""))
code = TextBlock(line.loc, consumebuf!(buf))
else
println(buf, rstrip(line.val))
while !isempty(stack) && !occursin(PROMPT_REGEX, stack[1].val)
println(buf, rstrip(popfirst!(stack).val))
end
expect = TextBlock(line.loc, rstrip(consumebuf!(buf)))
code !== nothing && addcase!(expect)
end
if isempty(stack)
code !== nothing && addcase!(TextBlock(code.loc, ""))
break
end
end
suite
end

function consumebuf!(buf)
n = bytesavailable(seekstart(buf))
n > 0 ? String(take!(buf)) : ""
end

# Extract a test case from Julia source.

function parsecase!(stack::Vector{TextBlock})
Expand Down Expand Up @@ -638,7 +694,7 @@ function parsecase!(stack::Vector{TextBlock})
end
end
!isempty(code) || return BrokenTestCase(loc, "missing test code")
return TestCase(loc, collapse(code), collapse(pre), collapse(expect))
return TestCase(loc, collapse(code), collapse(pre), collapse(expect), false)
end

# Run a single test case.
Expand Down Expand Up @@ -703,7 +759,11 @@ function runtest(test::TestCase; subs=common_subs(), mod=nothing)
body = asexpr(test.code)
ans = Core.eval(mod, body)
if ans !== nothing && !no_output
Base.invokelatest(show, io, ans)
if test.repl
Base.invokelatest(show, io, "text/plain", ans)
else
Base.invokelatest(show, io, ans)
end
end
end
catch exc
Expand Down Expand Up @@ -746,10 +806,11 @@ end
runtest(test::BrokenTestCase; subs=common_subs(), mod=nothing) =
Error(test)

runtest(loc, code; pre=nothing, expect=nothing, subs=common_subs(), mod=nothing) =
runtest(loc, code; pre=nothing, expect=nothing, subs=common_subs(), mod=nothing, repl=false) =
runtest(TestCase(loc, TextBlock(loc, code),
pre !== nothing ? TextBlock(loc, pre) : nothing,
expect !== nothing ? TextBlock(loc, expect) : nothing),
expect !== nothing ? TextBlock(loc, expect) : nothing,
repl),
subs=subs, mod=mod)

# Convert expected output block to a regex.
Expand Down
Loading