Skip to content

Commit ec55aa0

Browse files
committed
[1294] 修复宏环路检测漏检并使用 pynput 覆盖粘贴弹窗及继续编辑测试
1 parent dfd860e commit ec55aa0

5 files changed

Lines changed: 117 additions & 7 deletions

File tree

TeXmacs/tests/python/1294.py

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,25 @@ def activate_window():
165165
except Exception:
166166
pass
167167

168+
try:
169+
d = display.Display()
170+
root = d.screen().root
171+
NET_ACTIVE_WINDOW = d.intern_atom('_NET_ACTIVE_WINDOW')
172+
NET_CLIENT_LIST = d.intern_atom('_NET_CLIENT_LIST')
173+
prop = root.get_full_property(NET_CLIENT_LIST, Xatom.WINDOW)
174+
if prop:
175+
for wid in prop.value:
176+
win = d.create_resource_object('window', wid)
177+
c = str(win.get_wm_class() or "").lower()
178+
if "stem" in c or "mogan" in c:
179+
data = [2, X.CurrentTime, 0, 0, 0]
180+
ev = event.ClientMessage(window=wid, client_type=NET_ACTIVE_WINDOW, data=(32, data))
181+
root.send_event(ev, event_mask=X.SubstructureRedirectMask | X.SubstructureNotifyMask)
182+
d.flush()
183+
break
184+
except Exception:
185+
pass
186+
168187

169188
def is_window_ready():
170189
try:
@@ -276,16 +295,58 @@ def run_case(self, case_name, tex_content):
276295
print(" [!] Crashed during paste!")
277296
break
278297

298+
# 处理可能的错误弹窗并关闭
279299
if not crashed:
280-
print(" -> Pressing Enter...")
300+
time.sleep(0.5)
301+
print(" -> Closing possible error popup (Enter)...")
281302
self.kbd.tap(Key.enter)
303+
time.sleep(0.5)
304+
if not self.is_alive():
305+
crashed = True
306+
print(" [!] Crashed after dismissing popup!")
307+
308+
# 关闭弹窗后,继续编辑测试(防止损坏状态下继续操作引起崩溃)
309+
if not crashed:
310+
print(" -> Testing continue editing after paste...")
311+
activate_window()
312+
time.sleep(0.3)
313+
self.mouse.position = POS_DOC
314+
time.sleep(0.2)
315+
self.mouse.click(Button.left)
316+
time.sleep(0.3)
317+
318+
# 键入文本
319+
self.kbd.type("testing edit after paste ")
320+
time.sleep(0.3)
321+
# 回车触发分段排版
322+
self.kbd.tap(Key.enter)
323+
time.sleep(0.3)
324+
# 键入更多内容
325+
self.kbd.type("continue typing 12345")
326+
time.sleep(0.3)
327+
# 回车
328+
self.kbd.tap(Key.enter)
329+
time.sleep(0.3)
330+
# 退格删除
331+
for _ in range(8):
332+
self.kbd.tap(Key.backspace)
333+
time.sleep(0.05)
334+
# 光标移动
335+
self.kbd.tap(Key.up)
336+
time.sleep(0.1)
337+
self.kbd.tap(Key.down)
338+
time.sleep(0.1)
339+
self.kbd.type(" finished")
340+
time.sleep(0.5)
341+
342+
# 等待排版和可能的后台处理
282343
t_wait = 0.0
283-
while t_wait < 3.5:
344+
while t_wait < 2.5:
284345
time.sleep(0.2)
285346
t_wait += 0.2
286347
if not self.is_alive():
287348
crashed = True
288-
print(" [!] Crashed after Enter!")
349+
print(" [!] Crashed during continue editing!")
289350
break
290351

291352
status = "CRASH" if crashed else "PASS"

devel/1294.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,10 @@ xmake b parsetex_test && xmake r parsetex_test
7070
xmake r 0620
7171
xmake r 0631
7272

73-
# 2. GUI 自动化测试
73+
# 2. GUI 自动化测试(使用 pynput 驱动真实 GUI)
7474
python3 TeXmacs/tests/python/1294.py
75-
# 预期:1294_1.tex、1294_2.tex 和 1294_3.tex 均 PASS,进程不退出、无垃圾内容插入
75+
# 预期:1294_1.tex、1294_2.tex 和 1294_3.tex 均 PASS,
76+
# 覆盖完整流程:粘贴 -> 关闭错误弹窗 -> 继续键入/分段/删除/光标移动编辑 -> 进程均不崩溃退出
7677
```
7778

7879
### 手动测试方法
@@ -94,3 +95,12 @@ python3 TeXmacs/tests/python/1294.py
9495
`generic_to_tree (s, "latex-snippet")` → scheme `convert``parse-latex`(C++
9596
`parse_latex`)→ `latex->texmacs`(C++ `latex_to_tree`)。
9697
- GUI 手动验证(含崩溃用例 249 现象消失)由用户执行。
98+
99+
## 迭代改进记录
100+
101+
- 针对 `dfd860e0f` 中精简 `tree_calls_macro` 时误删 `L(t)``COMPOUND`/`APPLY` 匹配导致 `has_macro_cycle` 漏判宏环路的问题进行了修复:
102+
- `src/Plugins/Tex/fromtex_post.cpp`:恢复 `L(t)` 比较与 `COMPOUND`/`APPLY` 运算符提取,准确识别宏复合节点。
103+
- `src/Plugins/Tex/tex.hpp`:导出 `bool has_macro_cycle (tree t)` 接口。
104+
- `tests/Plugins/Tex/parsetex_test.cpp`:新增 `test_has_macro_cycle` 独立断言用例,覆盖自递归、带参自递归、相互递归与正常宏,杜绝漏检回归。
105+
- `TeXmacs/tests/python/1294.py`:明确采用 `pynput` 完善全流程自动化测试,在粘贴后主动关闭可能的错误弹窗,并模拟用户在文档中继续输入文本、回车分段排版、退格删除以及光标移动等连续编辑操作,验证全过程均不 crash。
106+

src/Plugins/Tex/fromtex_post.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2541,8 +2541,15 @@ latex_to_tree_body (tree t0) {
25412541

25422542
static bool
25432543
tree_calls_macro (tree t, string name) {
2544-
// 宏调用在树中即同名原子叶(compound/apply 的头也是原子子节点,由递归覆盖)
25452544
if (is_atomic (t)) return t->label == name;
2545+
string l= as_string (L (t));
2546+
if (l == name) return true;
2547+
if (is_func (t, COMPOUND) && N (t) > 0 && is_atomic (t[0]) &&
2548+
t[0]->label == name)
2549+
return true;
2550+
if (is_func (t, APPLY) && N (t) > 0 && is_atomic (t[0]) &&
2551+
t[0]->label == name)
2552+
return true;
25462553
for (int i= 0; i < N (t); i++)
25472554
if (tree_calls_macro (t[i], name)) return true;
25482555
return false;
@@ -2576,7 +2583,7 @@ dfs_macro_cycle (int u, const array<array<int>>& adj, array<int>& state) {
25762583
return false;
25772584
}
25782585

2579-
static bool
2586+
bool
25802587
has_macro_cycle (tree t) {
25812588
array<string> names;
25822589
array<tree> bodies;

src/Plugins/Tex/tex.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ tree parse_latex_document (string s, bool change= false, bool as_pic= false);
2121
tree latex_to_tree (tree t);
2222
tree latex_document_to_tree (string s, bool as_pic= false);
2323
tree latex_class_document_to_tree (string s);
24+
bool has_macro_cycle (tree t);
2425
string latex_verbarg_to_string (tree t);
2526
string get_latex_style (tree t);
2627
string string_arg (tree t, bool u= false);

tests/Plugins/Tex/parsetex_test.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ private slots:
3434
void test_crash_case_249 ();
3535
void test_crash_case_289 ();
3636
void test_crash_case_1294_3 ();
37+
void test_has_macro_cycle ();
3738
};
3839

3940
void
@@ -81,5 +82,35 @@ TestParseTex::test_crash_case_1294_3 () {
8182
check_crash_case ("1294_3.tex");
8283
}
8384

85+
void
86+
TestParseTex::test_has_macro_cycle () {
87+
// 1. 无参自递归: \def\foo{\foo} -> <assign|foo|<macro|<foo>>>
88+
tree self_rec=
89+
tuple (compound ("assign", "foo", compound ("macro", compound ("foo"))));
90+
QVERIFY (has_macro_cycle (self_rec));
91+
92+
// 2. 带参自递归: \def\foo#1{\foo{#1}} -> <assign|foo|<macro|x|<foo|<arg|x>>>>
93+
tree param_rec= tuple (compound (
94+
"assign", "foo",
95+
compound ("macro", "x", compound ("foo", compound ("arg", "x")))));
96+
QVERIFY (has_macro_cycle (param_rec));
97+
98+
// 3. 相互递归: foo 调 bar, bar 调 foo
99+
tree mutual_rec=
100+
tuple (compound ("assign", "foo", compound ("macro", compound ("bar"))),
101+
compound ("assign", "bar", compound ("macro", compound ("foo"))));
102+
QVERIFY (has_macro_cycle (mutual_rec));
103+
104+
// 4. 非递归正常宏: foo 调内置命令,无环
105+
tree normal_macro= tuple (compound (
106+
"assign", "foo",
107+
compound ("macro", "x", compound ("bold", compound ("arg", "x")))));
108+
QVERIFY (!has_macro_cycle (normal_macro));
109+
110+
// 5. 普通文档无宏定义
111+
tree no_macro= compound ("document", "Hello world");
112+
QVERIFY (!has_macro_cycle (no_macro));
113+
}
114+
84115
QTEST_MAIN (TestParseTex)
85116
#include "parsetex_test.moc"

0 commit comments

Comments
 (0)