From beb3924e116d145ff46ae4f857df922958d1f37e Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Fri, 17 Jul 2026 21:48:30 +0200 Subject: [PATCH 1/3] Support Julia 1.12 terminal input --- .github/workflows/CI.yml | 4 +-- Project.toml | 2 +- src/TerminalRegressionTests.jl | 62 +++++++++++++++++++++++++++------- test/runtests.jl | 16 +++++++++ 4 files changed, 69 insertions(+), 15 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 7a70a99..c46caf0 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -11,7 +11,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - channel: ['1.1', '1.2', '1.3', '1.4', '1.5', '1.6', '1.7', '1.8', '1.9', '1.10', '1.11'] + channel: ['1.1', '1.2', '1.3', '1.4', '1.5', '1.6', '1.7', '1.8', '1.9', '1.10', '1.11', '1.12', 'nightly'] os: [ubuntu-latest] steps: - uses: actions/checkout@v6 @@ -29,4 +29,4 @@ jobs: ${{ runner.os }}-test- ${{ runner.os }}- - uses: julia-actions/julia-buildpkg@v1 - - uses: julia-actions/julia-runtest@v1 \ No newline at end of file + - uses: julia-actions/julia-runtest@v1 diff --git a/Project.toml b/Project.toml index 28e67da..5e3a349 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "TerminalRegressionTests" uuid = "98bfdc55-cc95-5876-a49a-74609291cbe0" author = ["Keno Fischer "] -version = "0.2.2" +version = "0.2.3" [deps] DeepDiffs = "ab62b9b5-e342-54a8-a765-a90f495de1a6" diff --git a/src/TerminalRegressionTests.jl b/src/TerminalRegressionTests.jl index 8217db4..7a76f23 100644 --- a/src/TerminalRegressionTests.jl +++ b/src/TerminalRegressionTests.jl @@ -3,6 +3,32 @@ module TerminalRegressionTests using DeepDiffs import REPL + @static if VERSION >= v"1.12-" + const EmulatedCondition = Threads.Condition + + function wait_condition(condition) + lock(condition) + try + return wait(condition) + finally + unlock(condition) + end + end + + function notify_condition(condition) + lock(condition) + try + return notify(condition) + finally + unlock(condition) + end + end + else + const EmulatedCondition = Condition + wait_condition(condition) = wait(condition) + notify_condition(condition) = notify(condition) + end + function load_outputs(file) outputs = String[] decorators = String[] @@ -47,8 +73,8 @@ module TerminalRegressionTests pty::VT100.PTY terminal::VT100.ScreenEmulator waiting::Bool - step::Condition - filled::Condition + step::EmulatedCondition + filled::EmulatedCondition # Yield after every write, e.g. to test for flickering issues aggressive_yield::Bool function EmulatedTerminal() @@ -56,30 +82,42 @@ module TerminalRegressionTests new( IOBuffer(UInt8[]; read = true, write = true, append = true, truncate = true, maxsize = typemax(Int)), Base.TTY(pty.slave), pty, - pty.em, false, Condition(), Condition() + pty.em, false, EmulatedCondition(), EmulatedCondition() ) end end + if isdefined(REPL.LineEdit, :hascolor) + @eval REPL.LineEdit.hascolor(::EmulatedTerminal) = true + end function Base.wait(term::EmulatedTerminal) if !term.waiting || bytesavailable(term.input_buffer) != 0 - wait(term.step) + wait_condition(term.step) end end for T in (Vector{UInt8}, Array, AbstractArray, String, Symbol, Any, Char, UInt8) function Base.write(term::EmulatedTerminal,a::T) b = write(term.out_stream, a) if term.aggressive_yield - notify(term.step) + notify_condition(term.step) end return b end end Base.eof(term::EmulatedTerminal) = false + function Base.peek(term::EmulatedTerminal, ::Type{T}=UInt8) where {T} + if bytesavailable(term.input_buffer) == 0 + term.waiting = true + notify_condition(term.step) + wait_condition(term.filled) + end + term.waiting = false + peek(term.input_buffer, T) + end function Base.read(term::EmulatedTerminal, ::Type{Char}) if bytesavailable(term.input_buffer) == 0 term.waiting = true - notify(term.step) - wait(term.filled) + notify_condition(term.step) + wait_condition(term.filled) end term.waiting = false read(term.input_buffer, Char) @@ -87,8 +125,8 @@ module TerminalRegressionTests function Base.readuntil(term::EmulatedTerminal, delim::UInt8; kwargs...) if bytesavailable(term.input_buffer) == 0 term.waiting = true - notify(term.step) - wait(term.filled) + notify_condition(term.step) + wait_condition(term.filled) end term.waiting = false readuntil(term.input_buffer, delim; kwargs...) @@ -145,7 +183,7 @@ module TerminalRegressionTests function process_all_buffered(emuterm) # Since writes to the tty are asynchronous, there's an # inherent race condition between them being sent to the - # kernel and being available to epoll. We write a sentintel value + # kernel and being available to epoll. We write a sentinel value # here and wait for it to be read back. sentinel = Ref{UInt32}(0xffffffff) ccall(:write, Cvoid, (Cint, Ptr{UInt32}, Csize_t), emuterm.pty.slave, sentinel, sizeof(UInt32)) @@ -187,7 +225,7 @@ module TerminalRegressionTests @assert !eof(emuterm.pty.master) process_all_buffered(emuterm) cmp(emuterm.terminal, output, decorator) - print(emuterm.input_buffer, input); notify(emuterm.filled) + print(emuterm.input_buffer, input); notify_condition(emuterm.filled) end Base.notify(c) catch err @@ -222,7 +260,7 @@ module TerminalRegressionTests out = IOBuffer() decorator = IOBuffer() VT100.dump(out, decorator, emuterm.terminal) - print(emuterm.input_buffer, input); notify(emuterm.filled) + print(emuterm.input_buffer, input); notify_condition(emuterm.filled) out, decorator end open(outputpath, "w") do io diff --git a/test/runtests.jl b/test/runtests.jl index d35759e..b64d43a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,5 +1,21 @@ using TerminalRegressionTests using Test +import REPL + +@testset "EmulatedTerminal" begin + emuterm = TerminalRegressionTests.EmulatedTerminal() + if isdefined(REPL.LineEdit, :hascolor) + @test REPL.LineEdit.hascolor(emuterm) + end + peek_task = @async peek(emuterm) + wait(emuterm) + @test emuterm.waiting + print(emuterm.input_buffer, 'x') + TerminalRegressionTests.notify_condition(emuterm.filled) + @test fetch(peek_task) == UInt8('x') + @test read(emuterm, Char) == 'x' + finalize(emuterm.pty) +end TerminalRegressionTests.automated_test( joinpath(@__DIR__, "TRT.multiout"), From 6e0e75821f2a5065c824854933e890a374bdeba5 Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Fri, 17 Jul 2026 21:52:20 +0200 Subject: [PATCH 2/3] Gate peek regression test on Julia 1.5 --- test/runtests.jl | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index b64d43a..16e5513 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,19 +2,21 @@ using TerminalRegressionTests using Test import REPL -@testset "EmulatedTerminal" begin - emuterm = TerminalRegressionTests.EmulatedTerminal() - if isdefined(REPL.LineEdit, :hascolor) - @test REPL.LineEdit.hascolor(emuterm) +@static if VERSION >= v"1.5" + @testset "EmulatedTerminal" begin + emuterm = TerminalRegressionTests.EmulatedTerminal() + if isdefined(REPL.LineEdit, :hascolor) + @test REPL.LineEdit.hascolor(emuterm) + end + peek_task = @async peek(emuterm) + wait(emuterm) + @test emuterm.waiting + print(emuterm.input_buffer, 'x') + TerminalRegressionTests.notify_condition(emuterm.filled) + @test fetch(peek_task) == UInt8('x') + @test read(emuterm, Char) == 'x' + finalize(emuterm.pty) end - peek_task = @async peek(emuterm) - wait(emuterm) - @test emuterm.waiting - print(emuterm.input_buffer, 'x') - TerminalRegressionTests.notify_condition(emuterm.filled) - @test fetch(peek_task) == UInt8('x') - @test read(emuterm, Char) == 'x' - finalize(emuterm.pty) end TerminalRegressionTests.automated_test( From 7e4a910e9d9220bd6fa61c821cbdc64c84a263e6 Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Fri, 17 Jul 2026 21:53:04 +0200 Subject: [PATCH 3/3] Raise minimum Julia version to 1.10 --- .github/workflows/CI.yml | 2 +- Project.toml | 2 +- test/runtests.jl | 26 ++++++++++++-------------- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index c46caf0..2294d76 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -11,7 +11,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - channel: ['1.1', '1.2', '1.3', '1.4', '1.5', '1.6', '1.7', '1.8', '1.9', '1.10', '1.11', '1.12', 'nightly'] + channel: ['1.10', '1.11', '1.12', 'nightly'] os: [ubuntu-latest] steps: - uses: actions/checkout@v6 diff --git a/Project.toml b/Project.toml index 5e3a349..653aabb 100644 --- a/Project.toml +++ b/Project.toml @@ -14,7 +14,7 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [compat] DeepDiffs = "1" VT100 = "0.3" -julia = "1.1" +julia = "1.10" [targets] test = ["Test"] diff --git a/test/runtests.jl b/test/runtests.jl index 16e5513..b64d43a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,21 +2,19 @@ using TerminalRegressionTests using Test import REPL -@static if VERSION >= v"1.5" - @testset "EmulatedTerminal" begin - emuterm = TerminalRegressionTests.EmulatedTerminal() - if isdefined(REPL.LineEdit, :hascolor) - @test REPL.LineEdit.hascolor(emuterm) - end - peek_task = @async peek(emuterm) - wait(emuterm) - @test emuterm.waiting - print(emuterm.input_buffer, 'x') - TerminalRegressionTests.notify_condition(emuterm.filled) - @test fetch(peek_task) == UInt8('x') - @test read(emuterm, Char) == 'x' - finalize(emuterm.pty) +@testset "EmulatedTerminal" begin + emuterm = TerminalRegressionTests.EmulatedTerminal() + if isdefined(REPL.LineEdit, :hascolor) + @test REPL.LineEdit.hascolor(emuterm) end + peek_task = @async peek(emuterm) + wait(emuterm) + @test emuterm.waiting + print(emuterm.input_buffer, 'x') + TerminalRegressionTests.notify_condition(emuterm.filled) + @test fetch(peek_task) == UInt8('x') + @test read(emuterm, Char) == 'x' + finalize(emuterm.pty) end TerminalRegressionTests.automated_test(