JuliaIO/Suppressor.jl

Suppressor.jl don't capture all `stdout` from `CBinding`ed function

Opened this issue · 3 comments

I'm making a package called LibTeXPrintf.jl in which I need to capture stdout to define some variables.

But @capture_out doesn't capture all the output unless I had run already a printing operation without capturing it. I'll upload a MWE and an asciinema once I have both.

marcom commented

Here is a little testing script, similar to JuliaDocs/IOCapture.jl#16 (comment):

Test script
using Suppressor, Test

nbytes = [10^0, 10^1, 10^2, 10^3, 10^4, 10^5, 10^6]

function test_print(str)
    out = @capture_out begin
        print(str)
    end
    # save in a bool to avoid printing long strings when test fails
    cap_output_equals_str = out == str
    @test cap_output_equals_str
end

function test_puts(str)
    retval = 0
    out = @capture_out begin
        retval = @ccall puts(str::Cstring)::Cint
    end
    @test retval == length(str) + 1
    # save in a bool to avoid printing long strings when test fails
    cap_output_equals_str = out == str * "\n"
    @test cap_output_equals_str
end

function test_puts_flush(str)
    retval = 0
    out = @capture_out begin
        retval = @ccall puts(str::Cstring)::Cint
        Libc.flush_cstdio()
    end
    @test retval == length(str) + 1
    # save in a bool to avoid printing long strings when test fails
    cap_output_equals_str = out == str * "\n"
    @test cap_output_equals_str
end

function run_testset(fn::Function)
    @testset verbose=true "$fn" begin
        for (i, n) in enumerate(nbytes)
            # print different letters for each test
            c = collect('A':'Z')[i]
            @testset "$n" begin
                fn(c^n)
            end
        end
    end
end

@testset "Suppressor cstdio" verbose=true begin
    run_testset(test_print)
    run_testset(test_puts)
    run_testset(test_puts_flush)
end
Test script output
Test Summary:     | Pass  Fail  Total  Time
Suppressor cstdio |   22    13     35  3.1s
  test_print      |    7            7  0.1s
    1             |    1            1  0.1s
    10            |    1            1  0.0s
    100           |    1            1  0.0s
    1000          |    1            1  0.0s
    10000         |    1            1  0.0s
    100000        |    1            1  0.0s
    1000000       |    1            1  0.0s
  test_puts       |    5     9     14  2.1s
    1             |    1     1      2  2.0s
    10            |    1     1      2  0.0s
    100           |    1     1      2  0.0s
    1000          |    1     1      2  0.0s
    10000         |    1     1      2  0.0s
    100000        |          2      2  0.0s
    1000000       |          2      2  0.0s
  test_puts_flush |   10     4     14  0.0s
    1             |    2            2  0.0s
    10            |    2            2  0.0s
    100           |    2            2  0.0s
    1000          |    2            2  0.0s
    10000         |    2            2  0.0s
    100000        |          2      2  0.0s
    1000000       |          2      2  0.0s

If you look at the errors, you can see that with Libc.flush_cstdio() it works for sizes 10k bytes and below, but fails for larger outputs.

From these results it seems to me that just adding a single Libc.flush_cstdio() as is done in #47 would help and work correctly for short cstdio outputs, but for longer outputs one would have to additionally keep on reading from the pipe so it doesn't fill up.

Interesting, I need to look at it to know how to correct it for good. Thanks for the test script.