Suppressor.jl icon indicating copy to clipboard operation
Suppressor.jl copied to clipboard

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

Open Suavesito-Olimpiada opened this issue 2 years ago • 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.

Suavesito-Olimpiada avatar Sep 22 '22 05:09 Suavesito-Olimpiada

Here is the asciinema https://asciinema.org/a/523250.

Suavesito-Olimpiada avatar Sep 22 '22 20:09 Suavesito-Olimpiada

Here is a little testing script, similar to https://github.com/JuliaDocs/IOCapture.jl/issues/16#issuecomment-1630698066:

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.

marcom avatar Jul 11 '23 13:07 marcom

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

Suavesito-Olimpiada avatar Jul 12 '23 21:07 Suavesito-Olimpiada