Skip to content

Commit 3742586

Browse files
AmbvIsTestingGithubStuffambv
authored andcommitted
Don't run curses compatibility tests when terminfo not present
1 parent 2a053c5 commit 3742586

File tree

2 files changed

+68
-84
lines changed

2 files changed

+68
-84
lines changed

Lib/_pyrepl/terminfo.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,8 @@ def _read_terminfo_file(terminal_name: str) -> bytes:
167167
"cud": b"\x1b[%p1%dB", # Move cursor down N rows
168168
"cuf": b"\x1b[%p1%dC", # Move cursor right N columns
169169
"cuu": b"\x1b[%p1%dA", # Move cursor up N rows
170-
"cub1": b"\x1b[D", # Move cursor left 1 column
171-
"cud1": b"\x1b[B", # Move cursor down 1 row
170+
"cub1": b"\x08", # Move cursor left 1 column
171+
"cud1": b"\n", # Move cursor down 1 row
172172
"cuf1": b"\x1b[C", # Move cursor right 1 column
173173
"cuu1": b"\x1b[A", # Move cursor up 1 row
174174
"cup": b"\x1b[%i%p1%d;%p2%dH", # Move cursor to row, column
@@ -180,10 +180,10 @@ def _read_terminfo_file(terminal_name: str) -> bytes:
180180
"dch": b"\x1b[%p1%dP", # Delete N characters
181181
"dch1": b"\x1b[P", # Delete 1 character
182182
"ich": b"\x1b[%p1%d@", # Insert N characters
183-
"ich1": b"\x1b[@", # Insert 1 character
183+
"ich1": b"", # Insert 1 character
184184
# Cursor visibility
185185
"civis": b"\x1b[?25l", # Make cursor invisible
186-
"cnorm": b"\x1b[?25h", # Make cursor normal (visible)
186+
"cnorm": b"\x1b[?12l\x1b[?25h", # Make cursor normal (visible)
187187
# Scrolling
188188
"ind": b"\n", # Scroll up one line
189189
"ri": b"\x1bM", # Scroll down one line
@@ -194,16 +194,16 @@ def _read_terminfo_file(terminal_name: str) -> bytes:
194194
"pad": b"",
195195
# Function keys and special keys
196196
"kdch1": b"\x1b[3~", # Delete key
197-
"kcud1": b"\x1b[B", # Down arrow
198-
"kend": b"\x1b[F", # End key
197+
"kcud1": b"\x1bOB", # Down arrow
198+
"kend": b"\x1bOF", # End key
199199
"kent": b"\x1bOM", # Enter key
200-
"khome": b"\x1b[H", # Home key
200+
"khome": b"\x1bOH", # Home key
201201
"kich1": b"\x1b[2~", # Insert key
202-
"kcub1": b"\x1b[D", # Left arrow
202+
"kcub1": b"\x1bOD", # Left arrow
203203
"knp": b"\x1b[6~", # Page down
204204
"kpp": b"\x1b[5~", # Page up
205-
"kcuf1": b"\x1b[C", # Right arrow
206-
"kcuu1": b"\x1b[A", # Up arrow
205+
"kcuf1": b"\x1bOC", # Right arrow
206+
"kcuu1": b"\x1bOA", # Up arrow
207207
# Function keys F1-F20
208208
"kf1": b"\x1bOP",
209209
"kf2": b"\x1bOQ",
@@ -217,14 +217,14 @@ def _read_terminfo_file(terminal_name: str) -> bytes:
217217
"kf10": b"\x1b[21~",
218218
"kf11": b"\x1b[23~",
219219
"kf12": b"\x1b[24~",
220-
"kf13": b"\x1b[25~",
221-
"kf14": b"\x1b[26~",
222-
"kf15": b"\x1b[28~",
223-
"kf16": b"\x1b[29~",
224-
"kf17": b"\x1b[31~",
225-
"kf18": b"\x1b[32~",
226-
"kf19": b"\x1b[33~",
227-
"kf20": b"\x1b[34~",
220+
"kf13": b"\x1b[1;2P",
221+
"kf14": b"\x1b[1;2Q",
222+
"kf15": b"\x1b[1;2R",
223+
"kf16": b"\x1b[1;2S",
224+
"kf17": b"\x1b[15;2~",
225+
"kf18": b"\x1b[17;2~",
226+
"kf19": b"\x1b[18;2~",
227+
"kf20": b"\x1b[19;2~",
228228
},
229229
# Dumb terminal - minimal capabilities
230230
"dumb": {

Lib/test/test_pyrepl/test_terminfo.py

Lines changed: 50 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ class TestCursesCompatibility(unittest.TestCase):
3333
$TERM in the same process, so we subprocess all `curses` tests to get correctly
3434
set up terminfo."""
3535

36-
def setUp(self):
36+
@classmethod
37+
def setUpClass(cls):
3738
if _curses is None:
3839
raise unittest.SkipTest(
3940
"`curses` capability provided to regrtest but `_curses` not importable"
@@ -42,6 +43,11 @@ def setUp(self):
4243
if not has_subprocess_support:
4344
raise unittest.SkipTest("test module requires subprocess")
4445

46+
# we need to ensure there's a terminfo database on the system and that
47+
# `infocmp` works
48+
cls.infocmp("dumb")
49+
50+
def setUp(self):
4551
self.original_term = os.environ.get("TERM", None)
4652

4753
def tearDown(self):
@@ -50,6 +56,34 @@ def tearDown(self):
5056
elif "TERM" in os.environ:
5157
del os.environ["TERM"]
5258

59+
@classmethod
60+
def infocmp(cls, term) -> list[str]:
61+
all_caps = []
62+
try:
63+
result = subprocess.run(
64+
["infocmp", "-l1", term],
65+
capture_output=True,
66+
text=True,
67+
check=True,
68+
)
69+
except Exception:
70+
raise unittest.SkipTest("calling `infocmp` failed on the system")
71+
72+
for line in result.stdout.splitlines():
73+
line = line.strip()
74+
if line.startswith("#"):
75+
if "terminfo" not in line and "termcap" in line:
76+
# PyREPL terminfo doesn't parse termcap databases
77+
raise unittest.SkipTest(
78+
"curses using termcap.db: no terminfo database on"
79+
" the system"
80+
)
81+
elif "=" in line:
82+
cap_name = line.split("=")[0]
83+
all_caps.append(cap_name)
84+
85+
return all_caps
86+
5387
def test_setupterm_basic(self):
5488
"""Test basic setupterm functionality."""
5589
# Test with explicit terminal type
@@ -79,7 +113,7 @@ def test_setupterm_basic(self):
79113

80114
# Set up with PyREPL curses
81115
try:
82-
terminfo.TermInfo(term)
116+
terminfo.TermInfo(term, fallback=False)
83117
pyrepl_success = True
84118
except Exception as e:
85119
pyrepl_success = False
@@ -120,7 +154,7 @@ def test_setupterm_none(self):
120154
std_success = ncurses_data["success"]
121155

122156
try:
123-
terminfo.TermInfo(None)
157+
terminfo.TermInfo(None, fallback=False)
124158
pyrepl_success = True
125159
except Exception:
126160
pyrepl_success = False
@@ -138,28 +172,7 @@ def test_tigetstr_common_capabilities(self):
138172
term = "xterm"
139173

140174
# Get ALL capabilities from infocmp
141-
all_caps = []
142-
try:
143-
result = subprocess.run(
144-
["infocmp", "-1", term],
145-
capture_output=True,
146-
text=True,
147-
check=True,
148-
)
149-
for line in result.stdout.splitlines():
150-
line = line.strip()
151-
if "=" in line and not line.startswith("#"):
152-
cap_name = line.split("=")[0]
153-
all_caps.append(cap_name)
154-
except:
155-
# If infocmp fails, at least test the critical ones
156-
# fmt: off
157-
all_caps = [
158-
"cup", "clear", "el", "cub1", "cuf1", "cuu1", "cud1", "bel",
159-
"ind", "ri", "civis", "cnorm", "smkx", "rmkx", "cub", "cuf",
160-
"cud", "cuu", "home", "hpa", "vpa", "cr", "nel", "ht"
161-
]
162-
# fmt: on
175+
all_caps = self.infocmp(term)
163176

164177
ncurses_code = dedent(
165178
f"""
@@ -176,7 +189,7 @@ def test_tigetstr_common_capabilities(self):
176189
results[cap] = -1
177190
else:
178191
results[cap] = list(val)
179-
except:
192+
except BaseException:
180193
results[cap] = "error"
181194
print(json.dumps(results))
182195
"""
@@ -193,7 +206,7 @@ def test_tigetstr_common_capabilities(self):
193206

194207
ncurses_data = json.loads(result.stdout)
195208

196-
ti = terminfo.TermInfo(term)
209+
ti = terminfo.TermInfo(term, fallback=False)
197210

198211
# Test every single capability
199212
for cap in all_caps:
@@ -255,7 +268,7 @@ def test_tigetstr_input_types(self):
255268
ncurses_data = json.loads(result.stdout)
256269

257270
# PyREPL setup
258-
ti = terminfo.TermInfo(term)
271+
ti = terminfo.TermInfo(term, fallback=False)
259272

260273
# PyREPL behavior with string
261274
try:
@@ -281,7 +294,7 @@ def test_tigetstr_input_types(self):
281294
def test_tparm_basic(self):
282295
"""Test basic tparm functionality."""
283296
term = "xterm"
284-
ti = terminfo.TermInfo(term)
297+
ti = terminfo.TermInfo(term, fallback=False)
285298

286299
# Test cursor positioning (cup)
287300
cup = ti.get("cup")
@@ -357,7 +370,7 @@ def test_tparm_basic(self):
357370
def test_tparm_multiple_params(self):
358371
"""Test tparm with capabilities using multiple parameters."""
359372
term = "xterm"
360-
ti = terminfo.TermInfo(term)
373+
ti = terminfo.TermInfo(term, fallback=False)
361374

362375
# Test capabilities that take parameters
363376
param_caps = {
@@ -472,7 +485,7 @@ def test_tparm_null_handling(self):
472485
ncurses_data = json.loads(result.stdout)
473486

474487
# PyREPL setup
475-
ti = terminfo.TermInfo(term)
488+
ti = terminfo.TermInfo(term, fallback=False)
476489

477490
# Test with None - both should raise TypeError
478491
if ncurses_data["raises_typeerror"]:
@@ -496,38 +509,9 @@ def test_special_terminals(self):
496509
]
497510

498511
# Get all string capabilities from ncurses
499-
all_caps = []
500-
try:
501-
# Get all capability names from infocmp
502-
result = subprocess.run(
503-
["infocmp", "-1", "xterm"],
504-
capture_output=True,
505-
text=True,
506-
check=True,
507-
)
508-
for line in result.stdout.splitlines():
509-
line = line.strip()
510-
if "=" in line:
511-
cap_name = line.split("=")[0]
512-
all_caps.append(cap_name)
513-
except:
514-
# Fall back to a core set if infocmp fails
515-
# fmt: off
516-
all_caps = [
517-
"cup", "clear", "el", "cub", "cuf", "cud", "cuu", "cub1",
518-
"cuf1", "cud1", "cuu1", "home", "bel", "ind", "ri", "nel", "cr",
519-
"ht", "hpa", "vpa", "dch", "dch1", "dl", "dl1", "ich", "ich1",
520-
"il", "il1", "sgr0", "smso", "rmso", "smul", "rmul", "bold",
521-
"rev", "blink", "dim", "smacs", "rmacs", "civis", "cnorm", "sc",
522-
"rc", "hts", "tbc", "ed", "kbs", "kcud1", "kcub1", "kcuf1",
523-
"kcuu1", "kdch1", "khome", "kend", "knp", "kpp", "kich1", "kf1",
524-
"kf2", "kf3", "kf4", "kf5", "kf6", "kf7", "kf8", "kf9", "kf10",
525-
"rmkx", "smkx"
526-
]
527-
# fmt: on
528-
529512
for term in special_terms:
530513
with self.subTest(term=term):
514+
all_caps = self.infocmp(term)
531515
ncurses_code = dedent(
532516
f"""
533517
import _curses
@@ -547,7 +531,7 @@ def test_special_terminals(self):
547531
else:
548532
# Convert bytes to list of ints for JSON
549533
results[cap] = list(val)
550-
except:
534+
except BaseException:
551535
results[cap] = "error"
552536
print(json.dumps(results))
553537
except Exception as e:
@@ -576,10 +560,10 @@ def test_special_terminals(self):
576560
if "error" in ncurses_data and len(ncurses_data) == 1:
577561
# ncurses failed to setup this terminal
578562
# PyREPL should still work with fallback
579-
ti = terminfo.TermInfo(term)
563+
ti = terminfo.TermInfo(term, fallback=True)
580564
continue
581565

582-
ti = terminfo.TermInfo(term)
566+
ti = terminfo.TermInfo(term, fallback=False)
583567

584568
# Compare all capabilities
585569
for cap in all_caps:
@@ -638,9 +622,9 @@ def test_terminfo_fallback(self):
638622

639623
# PyREPL should succeed with fallback
640624
try:
641-
ti = terminfo.TermInfo(fake_term)
625+
ti = terminfo.TermInfo(fake_term, fallback=True)
642626
pyrepl_ok = True
643-
except:
627+
except Exception:
644628
pyrepl_ok = False
645629

646630
self.assertTrue(

0 commit comments

Comments
 (0)