Skip to content

What kind of io is allowed for Pkg.test? #3430

Open
@ericphanson

Description

If you have a test environment that hasn't been precompiled, then as of #3281, running Pkg.test(; io) will call two commands like

run(pipeline(`...`, stdout=io))
run(pipeline(`...`, stdout=io))

with the same IO object. This doesn't work well for a lot of common IO objects like IOBuffer, Base.BufferStream, Base.PipeBuffer, etc, since they will have eof(stream) == true after the first command finishes, and some of them will error on the second command.

What happens for these IO types?

For IOBuffer and Base.PipeBuffer, this will show

┌ Warning: Process I/O error
│   exception =
│    ArgumentError: ensureroom failed, IOBuffer is not writeable
│    Stacktrace:
│     [1] ensureroom_slowpath(io::IOBuffer, nshort::UInt64)
│       @ Base ./iobuffer.jl:303
│     [2] ensureroom
│       @ ./iobuffer.jl:325 [inlined]
│     [3] unsafe_write(to::IOBuffer, p::Ptr{UInt8}, nb::UInt64)
│       @ Base ./iobuffer.jl:424
│     [4] unsafe_write
│       @ ./io.jl:685 [inlined]
│     [5] write
│       @ ./io.jl:708 [inlined]
│     [6] write(to::IOBuffer, from::Base.PipeEndpoint)
│       @ Base ./io.jl:755
│     [7] macro expansion
│       @ ./process.jl:300 [inlined]
│     [8] (::Base.var"#764#765"{IOBuffer, Bool, Base.PipeEndpoint, IOBuffer, Base.PipeEndpoint})()
│       @ Base ./task.jl:514
└ @ Base process.jl:302

For Base.BufferStream, it will "seem" to work, but you get strange behavior if you test it like this:

julia> io = Base.BufferStream()
BufferStream(bytes waiting=0, isopen=true)

julia> let
           t = @async begin
               for _ = 1:10
                   bytes = readavailable(io)
                   @show String(bytes)
                   @show eof(io)
               end
           end
           run(pipeline(`echo "hi"`, stdout=io))
           run(pipeline(`echo "bye"`, stdout=io))
       end
String(bytes) = "hi\n"
eof(io) = true
String(bytes) = ""
eof(io) = true
String(bytes) = ""
eof(io) = true
String(bytes) = "bye\n"
eof(io) = true
String(bytes) = ""
eof(io) = true
String(bytes) = ""
eof(io) = true
String(bytes) = ""
eof(io) = true
String(bytes) = ""
eof(io) = true
String(bytes) = ""
eof(io) = true
String(bytes) = ""
eof(io) = true
Process(`echo bye`, ProcessExited(0))

since it shows eof(io) == true but then still displays "bye\n" later.

However devnull and stdout work fine, which my guess is what was tested here. So is there a particular restriction on what kinds of IO are allowed? Or is the change in #3281 a regression?

The context for me is I wrote some code to try to parse Pkg.jl test results "live" during GitHub Actions CI runs to re-emit test failures as logs which can then show up as annotations in GitHub PRs. It's actually fairly handy when it works (annotations only show up in-line on the diff, so you have to have changed those tests), but this was broken by #3281: julia-actions/julia-runtest#76.

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions