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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy