Testing LiterateTest.jl

using Test
using LiterateTest

This file tests LiterateTest.jl using plain Literate.jl-compatible source code.

Helper function

decode_output(output) = join((strip(ln, '|') for ln in split(output, "\n")), "\n")

@assert decode_output("""
    |a|
    |b|
    """) === """
    a
    b
    """

Removing ans = begin and end

input = """
    ans = begin
        1 + 2
    end
    """

output = decode_output("""
    |ans = begin # hide|
    |1 + 2|
    |end # hide|
    """)

@assert LiterateTest.preprocess(input) == output

Removing @testset

input = """
    @testset begin
        @test 1 + 2 == 3
    end
    """

output = """
    """

@assert LiterateTest.preprocess(input) == output

Above two patterns can be combined to write tests like this that are not visible in the Documenter.jl output:

ans = begin
    1 + 2  # only this line is visible in the documentation
end
@testset begin
    @test ans == 3
end
Test Summary: | Pass  Total
test set      |    1      1

Removing @test begin and end ...

input = """
    @test begin
        1 + 2
    end == 3
    """

output = """
    1 + 2
    """

@assert LiterateTest.preprocess(input) == output

When extracting the result of @test, it's useful to use global. However, global is not needed when @test begin ... end is removed. Thus, global is removed from the code as well:

input = """
    @test begin
        global y = 1 + 2
    end == 3
    y + 3
    """

output = """
    y = 1 + 2
    y + 3
    """

@assert LiterateTest.preprocess(input) == output

@test itself can be indented

input = """
    if VERSION >= v"1.4"
        @test begin
            code
        end == 1
    end
    """

output = """
    if VERSION >= v"1.4"
    code
    end
    """

@assert LiterateTest.preprocess(input) == output

Extracting $code from @evaltest "$code"

input = """
    @evaltest "1 + 2" begin
        @test ans == 3
        @test ans isa Int
    end
    """

output = """
    1 + 2
    """

@assert LiterateTest.preprocess(input) == output

Note that "$code" will be used as the @testset title when it's evaluated withtout the transformation.

@evaltest "1 + 2" begin
    @test ans == 3
    @test ans isa Int
end
Test Summary: | Pass  Total
1 + 2         |    2      2
Note

@evaltest "1 + 2" begin ... above is shown in this Documenter.jl output because this file is not processed by LiterateTest.preprocess.

When using $ in the code, raw"" can be useful:

input = raw"""
    x = 1
    @evaltest raw":(1 + $x)" begin
        @test ans == :(1 + $x)
    end
    """

output = raw"""
    x = 1
    :(1 + $x)
    """

@assert LiterateTest.preprocess(input) == output

Demo:

x = 1
@evaltest raw":(1 + $x)" begin
    @test ans == :(1 + $x)
end
Test Summary: | Pass  Total
:(1 + $x)     |    1      1

Showing error thrown from $code in @evaltest_throw "$code"

input = """
    @evaltest_throw \"\"\"error("msg")\"\"\" begin
        @test ans == ErrorException(1)
    end
    """

Text(LiterateTest.preprocess(input))
err = try # hide
error("msg")
catch _err; _err; end # hide
print(stdout, "ERROR: ") # hide
showerror(stdout, err) # hide
#-

It's a bit tricky to test this in Literate.jl:

output = decode_output("""
    |err = try # hide|
    |error("msg")|
    |catch _err; _err; end # hide|
    |print(stdout, "ERROR: ") # hide|
    |showerror(stdout, err) # hide|
    |#-|
    """)

@assert LiterateTest.preprocess(input) == output

Demo:

@evaltest_throw """error("msg")""" begin
    @test ans == ErrorException("msg")
end
Test Summary: | Pass  Total
error("msg")  |    2      2

Testing an exception from multi-line code with @testset_error

input = """
    @testset_error try
        if true
            error(1)
        end
    catch err
        @test err == ErrorException(1)
    end
    """

Text(LiterateTest.preprocess(input))
err = try # hide
if true
    error(1)
end
catch _err; _err; end # hide
print(stdout, "ERROR: ") # hide
showerror(stdout, err) # hide
#-
output = decode_output("""
    |err = try # hide|
    |if true|
    |    error(1)|
    |end|
    |catch _err; _err; end # hide|
    |print(stdout, "ERROR: ") # hide|
    |showerror(stdout, err) # hide|
    |#-|
    """)

Text(output)
err = try # hide
if true
    error(1)
end
catch _err; _err; end # hide
print(stdout, "ERROR: ") # hide
showerror(stdout, err) # hide
#-
@assert LiterateTest.preprocess(input) == output

Demo:

@testset_error "@testset_error" try
    if true
        error(1)
    end
catch err
    @test err == ErrorException("1")
end
Test Summary:  | Pass  Total
@testset_error |    1      1

De-indenting code in @dedent begin code end

input = """
    @dedent @time begin
        code
    end
    """

output = decode_output("""
    |@time begin # hide|
    |code|
    |end # hide|
    """)

@assert LiterateTest.preprocess(input) == output

Demo:

@dedent @time begin
    1 + 1
end
2

Expressions after end are ignored

input = """
    @dedent @assert begin
        code
    end == 1
    """

output = decode_output("""
    |@assert begin # hide|
    |code|
    |end == 1 # hide|
    """)

@assert LiterateTest.preprocess(input) == output

Demo:

@dedent @assert begin
    1 + 1
end == 2

@dedent works with assignments

input = """
    @dedent y1 = begin
        1 + 1
    end
    @dedent y2 = begin
        3 - 1
    end
    @test y1 == y2
    """

output = decode_output("""
    |y1 = begin # hide|
    |1 + 1|
    |end # hide|
    |y2 = begin # hide|
    |3 - 1|
    |end # hide|
    """)

@assert LiterateTest.preprocess(input) == output

Demo:

@dedent y1 = begin
    1 + 1
end
@dedent y2 = begin
    3 - 1
end
@test y1 == y2

@dedent itself can be indented

input = """
    if VERSION >= v"1.4"
        @dedent @assert begin
            code
        end == 1
    end
    """

output = decode_output("""
    |if VERSION >= v"1.4"|
    |@assert begin # hide|
    |code|
    |end == 1 # hide|
    |end|
    """)

@assert LiterateTest.preprocess(input) == output

Combine @testset_error, @dedent and @eval to test macro expansion

input = """
    @testset_error try
        @dedent @eval begin
            LiterateTest.@this_macro_does_not_exist
        end
    catch err
        msg = sprint(showerror, err)
        @test occursin("UndefVarError: @this_macro_does_not_exist not defined", msg)
    end
    """

Text(LiterateTest.preprocess(input))
err = try # hide
@eval begin # hide
LiterateTest.@this_macro_does_not_exist
end # hide
catch _err; _err; end # hide
print(stdout, "ERROR: ") # hide
showerror(stdout, err) # hide
#-

Note that all lines except LiterateTest.@this_macro_does_not_exist ends with # hide.

output = decode_output("""
    |err = try # hide|
    |@eval begin # hide|
    |LiterateTest.@this_macro_does_not_exist|
    |end # hide|
    |catch _err; _err; end # hide|
    |print(stdout, "ERROR: ") # hide|
    |showerror(stdout, err) # hide|
    |#-|
    """)

@assert LiterateTest.preprocess(input) == output

Demo:

@testset_error "@testset_error + @dedent + @eval" try
    @dedent @eval begin
        LiterateTest.@this_macro_does_not_exist
    end
catch err
    msg = sprint(showerror, err)
    @test occursin("UndefVarError: @this_macro_does_not_exist not defined", msg)
end
Test Summary:                    | Pass  Total
@testset_error + @dedent + @eval |    1      1

Removing JuliaFormatter.jl on/off toggles

The on/off comments for JuliaFormatter.jl are removed by LiterateTest.preprocess:

input = """
    a
    #! format: off
    b
    #! format: on
    c
    """

output = """
    a
    b
    c
    """

@assert LiterateTest.preprocess(input) == output

There is also LiterateTest.preprocess_juliaformatter that does only this preprocessing:

@assert LiterateTest.preprocess_juliaformatter(input) == output

This page was generated using Literate.jl.