Skip to content

Commit 84dbb56

Browse files
committed
Support for REPL-simulated test cases
The implementation is based on the code from Documenter.jl. Allows to define test cases with REPL-simulated syntax: julia> 1 1 This is useful as allows to define new test cases by simply copy pasting real REPL sessions.
1 parent 9d00b17 commit 84dbb56

File tree

4 files changed

+300
-15
lines changed

4 files changed

+300
-15
lines changed

src/NarrativeTest.jl

+70-9
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ struct TestCase <: AbstractTestCase
125125
code::TextBlock
126126
pre::Union{TextBlock,Nothing}
127127
expect::Union{TextBlock,Nothing}
128+
repl::Bool
128129
end
129130

130131
location(test::TestCase) = test.loc
@@ -537,29 +538,36 @@ function parsemd!(stack::Vector{TextBlock})
537538
line = pop!(stack)
538539
if isfence(line)
539540
# Extract a fenced block.
541+
isrepl = false
540542
fenceloc = line.loc
541543
lang = strip(line.val[4:end])
542544
jlstack = TextBlock[]
543545
while !isempty(stack) && !isfence(stack[end])
544-
push!(jlstack, pop!(stack))
546+
block = pop!(stack)
547+
isrepl = isrepl || startswith(block.val, "julia>")
548+
push!(jlstack, block)
545549
end
546550
if isempty(stack)
547551
push!(suite, BrokenTestCase(fenceloc, "incomplete fenced code block"))
548552
else
549553
pop!(stack)
550554
if isempty(lang)
551555
reverse!(jlstack)
552-
append!(suite, parsejl!(jlstack))
556+
append!(suite, isrepl ? parsejlrepl!(jlstack) : parsejl!(jlstack))
553557
end
554558
end
555559
elseif isindent(line) && !isblank(line)
556560
# Extract an indented block.
557-
jlstack = TextBlock[unindent(line)]
561+
block = unindent(line)
562+
isrepl = startswith(block.val, "julia>")
563+
jlstack = TextBlock[block]
558564
while !isempty(stack) && (isindent(stack[end]) || isblank(stack[end]))
559-
push!(jlstack, unindent(pop!(stack)))
565+
block = unindent(pop!(stack))
566+
isrepl = isrepl || startswith(block.val, "julia> ")
567+
push!(jlstack, block)
560568
end
561569
reverse!(jlstack)
562-
append!(suite, parsejl!(jlstack))
570+
append!(suite, isrepl ? parsejlrepl!(jlstack) : parsejl!(jlstack))
563571
elseif isadmonition(line)
564572
# Skip an indented admonition block.
565573
while !isempty(stack) && (isindent(stack[end]) || isblank(stack[end]))
@@ -588,6 +596,54 @@ function parsejl!(stack::Vector{TextBlock})
588596
return suite
589597
end
590598

599+
const PROMPT_REGEX = r"^julia>(?: (.*))?$"
600+
const SOURCE_REGEX = r"^ (.*)$"
601+
602+
function parsejlrepl!(stack::Vector{TextBlock})
603+
reverse!(stack)
604+
suite = AbstractTestCase[]
605+
code, buf = nothing, IOBuffer()
606+
function addcase!(expect)
607+
case = !isempty(code.val) ?
608+
TestCase(code.loc, code, nothing, expect, true) :
609+
BrokenTestCase(code.loc, "missing test code")
610+
push!(suite, case)
611+
code = nothing
612+
end
613+
while true
614+
line = popfirst!(stack)
615+
prompt = match(PROMPT_REGEX, line.val)
616+
if prompt !== nothing
617+
prompt[1] !== nothing && println(buf, prompt[1])
618+
while !isempty(stack)
619+
source = match(SOURCE_REGEX, stack[1].val)
620+
source !== nothing || break
621+
println(buf, source[1])
622+
popfirst!(stack)
623+
end
624+
code !== nothing && addcase!(TextBlock(code.loc, ""))
625+
code = TextBlock(line.loc, consumebuf!(buf))
626+
else
627+
println(buf, rstrip(line.val))
628+
while !isempty(stack) && !occursin(PROMPT_REGEX, stack[1].val)
629+
println(buf, rstrip(popfirst!(stack).val))
630+
end
631+
expect = TextBlock(line.loc, rstrip(consumebuf!(buf)))
632+
code !== nothing && addcase!(expect)
633+
end
634+
if isempty(stack)
635+
code !== nothing && addcase!(TextBlock(code.loc, ""))
636+
break
637+
end
638+
end
639+
suite
640+
end
641+
642+
function consumebuf!(buf)
643+
n = bytesavailable(seekstart(buf))
644+
n > 0 ? String(take!(buf)) : ""
645+
end
646+
591647
# Extract a test case from Julia source.
592648

593649
function parsecase!(stack::Vector{TextBlock})
@@ -638,7 +694,7 @@ function parsecase!(stack::Vector{TextBlock})
638694
end
639695
end
640696
!isempty(code) || return BrokenTestCase(loc, "missing test code")
641-
return TestCase(loc, collapse(code), collapse(pre), collapse(expect))
697+
return TestCase(loc, collapse(code), collapse(pre), collapse(expect), false)
642698
end
643699

644700
# Run a single test case.
@@ -703,7 +759,11 @@ function runtest(test::TestCase; subs=common_subs(), mod=nothing)
703759
body = asexpr(test.code)
704760
ans = Core.eval(mod, body)
705761
if ans !== nothing && !no_output
706-
Base.invokelatest(show, io, ans)
762+
if test.repl
763+
Base.invokelatest(show, io, "text/plain", ans)
764+
else
765+
Base.invokelatest(show, io, ans)
766+
end
707767
end
708768
end
709769
catch exc
@@ -746,10 +806,11 @@ end
746806
runtest(test::BrokenTestCase; subs=common_subs(), mod=nothing) =
747807
Error(test)
748808

749-
runtest(loc, code; pre=nothing, expect=nothing, subs=common_subs(), mod=nothing) =
809+
runtest(loc, code; pre=nothing, expect=nothing, subs=common_subs(), mod=nothing, repl=false) =
750810
runtest(TestCase(loc, TextBlock(loc, code),
751811
pre !== nothing ? TextBlock(loc, pre) : nothing,
752-
expect !== nothing ? TextBlock(loc, expect) : nothing),
812+
expect !== nothing ? TextBlock(loc, expect) : nothing,
813+
repl),
753814
subs=subs, mod=mod)
754815

755816
# Convert expected output block to a regex.

0 commit comments

Comments
 (0)