diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index 78cd049905825..c3554582b6b85 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -1183,7 +1183,7 @@ find_hist_file() = get(ENV, "JULIA_HISTORY", !isempty(DEPOT_PATH) ? joinpath(DEPOT_PATH[1], "logs", "repl_history.jl") : error("DEPOT_PATH is empty and ENV[\"JULIA_HISTORY\"] not set.")) -backend(r::AbstractREPL) = hasproperty(r, :backendref) ? r.backendref : nothing +backend(r::AbstractREPL) = hasproperty(r, :backendref) && isdefined(r, :backendref) ? r.backendref : nothing function eval_on_backend(ast, backend::REPLBackendRef) diff --git a/stdlib/REPL/src/Terminals.jl b/stdlib/REPL/src/Terminals.jl index 14ea6dd3dff77..71a287573ffbf 100644 --- a/stdlib/REPL/src/Terminals.jl +++ b/stdlib/REPL/src/Terminals.jl @@ -123,9 +123,18 @@ cmove_col(t::UnixTerminal, n) = (write(t.out_stream, '\r'); n > 1 && cmove_right if Sys.iswindows() function raw!(t::TTYTerminal,raw::Bool) if Base.ispty(t.in_stream) - run((raw ? `stty raw -echo onlcr -ocrnl opost` : `stty sane`), - t.in_stream, t.out_stream, t.err_stream) - true + try + run((raw ? `stty raw -echo onlcr -ocrnl opost` : `stty sane`), + t.in_stream, t.out_stream, t.err_stream) + true + catch ex + # Fall back to ccall if stty fails (e.g., in some CI environments) + if ex isa ProcessFailedException + ccall(:jl_tty_set_mode, Int32, (Ptr{Cvoid},Int32), t.in_stream.handle::Ptr{Cvoid}, raw) == 0 + else + rethrow() + end + end else ccall(:jl_tty_set_mode, Int32, (Ptr{Cvoid},Int32), t.in_stream.handle::Ptr{Cvoid}, raw) == 0 end diff --git a/stdlib/REPL/test/bad_history_startup.jl b/stdlib/REPL/test/bad_history_startup.jl new file mode 100644 index 0000000000000..09f7a00e1c31a --- /dev/null +++ b/stdlib/REPL/test/bad_history_startup.jl @@ -0,0 +1,74 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +# Test that interactive mode starts up without error when history file is bad + +using Test + +const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") +isdefined(Main, :FakePTYs) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "FakePTYs.jl")) +import .Main.FakePTYs: with_fake_pty + +@testset "Bad history file startup" begin + mktempdir() do tmpdir + # Create a bad history file + hist_file = joinpath(tmpdir, "repl_history.jl") + write(hist_file, "{ invalid json content\nmore bad content\n") + + julia_exe = Base.julia_cmd()[1] + + # Test interactive Julia startup with bad history file + with_fake_pty() do pts, ptm + # Set up environment with our bad history file + nENV = copy(ENV) + nENV["JULIA_HISTORY"] = hist_file + + # Start Julia in interactive mode + p = run(detach(setenv(`$julia_exe --startup-file=no --color=no -q`, nENV)), pts, pts, pts, wait=false) + Base.close_stdio(pts) + + # Read output until we get the prompt, which indicates successful startup + output = readuntil(ptm, "julia> ", keep=true) + # println("====== subprocess output ======") + # println(output) + # println("====== end subprocess output ======") + + # Test conditions: + # 1. We should see the invalid history file error + has_history_error = occursin("Invalid history file", output) || + occursin("Invalid character", output) + @test has_history_error + + # 2. We should NOT see UndefRefError (the bug being fixed) + has_undef_error = occursin("UndefRefError", output) + @test !has_undef_error + + # 3. We should see the "Disabling history file" message if the fix works + has_disable_message = occursin("Disabling history file for this session", output) + @test has_disable_message + + # Send exit command to clean shutdown + if isopen(ptm) + write(ptm, "exit()\n") + else + @warn "PTY master is already closed before sending exit command" + end + + # Read any remaining output until the process exits + try + read(ptm, String) + catch ex + # Handle platform-specific EOF behavior + if ex isa Base.IOError && ex.code == Base.UV_EIO + # This is expected on some platforms (e.g., Linux) + else + rethrow() + end + end + + # Wait for process to finish + wait(p) + + @test p.exitcode == 0 + end + end +end diff --git a/stdlib/REPL/test/runtests.jl b/stdlib/REPL/test/runtests.jl index d3eb6b9964981..9f3727485f81a 100644 --- a/stdlib/REPL/test/runtests.jl +++ b/stdlib/REPL/test/runtests.jl @@ -22,6 +22,9 @@ end module TerminalMenusTest include("TerminalMenus/runtests.jl") end +module BadHistoryStartupTest + include("bad_history_startup.jl") +end # Restore the original environment for k in keys(ENV)