Skip to content

Commit 6c02a21

Browse files
Fix startup when history file is bad (JuliaLang#59418)
1 parent 99e0f78 commit 6c02a21

File tree

4 files changed

+90
-4
lines changed

4 files changed

+90
-4
lines changed

stdlib/REPL/src/REPL.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1183,7 +1183,7 @@ find_hist_file() = get(ENV, "JULIA_HISTORY",
11831183
!isempty(DEPOT_PATH) ? joinpath(DEPOT_PATH[1], "logs", "repl_history.jl") :
11841184
error("DEPOT_PATH is empty and ENV[\"JULIA_HISTORY\"] not set."))
11851185

1186-
backend(r::AbstractREPL) = hasproperty(r, :backendref) ? r.backendref : nothing
1186+
backend(r::AbstractREPL) = hasproperty(r, :backendref) && isdefined(r, :backendref) ? r.backendref : nothing
11871187

11881188

11891189
function eval_on_backend(ast, backend::REPLBackendRef)

stdlib/REPL/src/Terminals.jl

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,18 @@ cmove_col(t::UnixTerminal, n) = (write(t.out_stream, '\r'); n > 1 && cmove_right
123123
if Sys.iswindows()
124124
function raw!(t::TTYTerminal,raw::Bool)
125125
if Base.ispty(t.in_stream)
126-
run((raw ? `stty raw -echo onlcr -ocrnl opost` : `stty sane`),
127-
t.in_stream, t.out_stream, t.err_stream)
128-
true
126+
try
127+
run((raw ? `stty raw -echo onlcr -ocrnl opost` : `stty sane`),
128+
t.in_stream, t.out_stream, t.err_stream)
129+
true
130+
catch ex
131+
# Fall back to ccall if stty fails (e.g., in some CI environments)
132+
if ex isa ProcessFailedException
133+
ccall(:jl_tty_set_mode, Int32, (Ptr{Cvoid},Int32), t.in_stream.handle::Ptr{Cvoid}, raw) == 0
134+
else
135+
rethrow()
136+
end
137+
end
129138
else
130139
ccall(:jl_tty_set_mode, Int32, (Ptr{Cvoid},Int32), t.in_stream.handle::Ptr{Cvoid}, raw) == 0
131140
end
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# This file is a part of Julia. License is MIT: https://julialang.org/license
2+
3+
# Test that interactive mode starts up without error when history file is bad
4+
5+
using Test
6+
7+
const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test")
8+
isdefined(Main, :FakePTYs) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "FakePTYs.jl"))
9+
import .Main.FakePTYs: with_fake_pty
10+
11+
@testset "Bad history file startup" begin
12+
mktempdir() do tmpdir
13+
# Create a bad history file
14+
hist_file = joinpath(tmpdir, "repl_history.jl")
15+
write(hist_file, "{ invalid json content\nmore bad content\n")
16+
17+
julia_exe = Base.julia_cmd()[1]
18+
19+
# Test interactive Julia startup with bad history file
20+
with_fake_pty() do pts, ptm
21+
# Set up environment with our bad history file
22+
nENV = copy(ENV)
23+
nENV["JULIA_HISTORY"] = hist_file
24+
25+
# Start Julia in interactive mode
26+
p = run(detach(setenv(`$julia_exe --startup-file=no --color=no -q`, nENV)), pts, pts, pts, wait=false)
27+
Base.close_stdio(pts)
28+
29+
# Read output until we get the prompt, which indicates successful startup
30+
output = readuntil(ptm, "julia> ", keep=true)
31+
# println("====== subprocess output ======")
32+
# println(output)
33+
# println("====== end subprocess output ======")
34+
35+
# Test conditions:
36+
# 1. We should see the invalid history file error
37+
has_history_error = occursin("Invalid history file", output) ||
38+
occursin("Invalid character", output)
39+
@test has_history_error
40+
41+
# 2. We should NOT see UndefRefError (the bug being fixed)
42+
has_undef_error = occursin("UndefRefError", output)
43+
@test !has_undef_error
44+
45+
# 3. We should see the "Disabling history file" message if the fix works
46+
has_disable_message = occursin("Disabling history file for this session", output)
47+
@test has_disable_message
48+
49+
# Send exit command to clean shutdown
50+
if isopen(ptm)
51+
write(ptm, "exit()\n")
52+
else
53+
@warn "PTY master is already closed before sending exit command"
54+
end
55+
56+
# Read any remaining output until the process exits
57+
try
58+
read(ptm, String)
59+
catch ex
60+
# Handle platform-specific EOF behavior
61+
if ex isa Base.IOError && ex.code == Base.UV_EIO
62+
# This is expected on some platforms (e.g., Linux)
63+
else
64+
rethrow()
65+
end
66+
end
67+
68+
# Wait for process to finish
69+
wait(p)
70+
71+
@test p.exitcode == 0
72+
end
73+
end
74+
end

stdlib/REPL/test/runtests.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ end
2222
module TerminalMenusTest
2323
include("TerminalMenus/runtests.jl")
2424
end
25+
module BadHistoryStartupTest
26+
include("bad_history_startup.jl")
27+
end
2528

2629
# Restore the original environment
2730
for k in keys(ENV)

0 commit comments

Comments
 (0)