Skip to content

Commit 5dcb99f

Browse files
Michiel Jan Laurens de HoonMichiel Jan Laurens de Hoon
authored andcommitted
Adding test exposing the bug. The test mimics a user running Python interactively.
1 parent 6d0489a commit 5dcb99f

1 file changed

Lines changed: 87 additions & 0 deletions

File tree

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# test_tkinter_vwait_mainloop_stdin.py
2+
import unittest
3+
import subprocess
4+
import sys
5+
import time
6+
from test import support
7+
from test.support import import_helper
8+
9+
tkinter = import_helper.import_module("tkinter")
10+
11+
12+
@unittest.skipUnless(support.has_subprocess_support, "test requires subprocess")
13+
class TkVwaitMainloopStdinTest(unittest.TestCase):
14+
15+
def run_child(self):
16+
# Full Python script to execute in the child
17+
code = r"""
18+
import tkinter as tk
19+
import time
20+
21+
interp = tk.Tcl()
22+
23+
def do_vwait():
24+
start_wall = time.time()
25+
start_cpu = time.process_time()
26+
interp.eval("vwait myvar")
27+
end_cpu = time.process_time()
28+
end_wall = time.time()
29+
cpu_frac = (end_cpu - start_cpu) / (end_wall - start_wall)
30+
print(f"CPU fraction during vwait: {cpu_frac:.2f}", flush=True)
31+
32+
# Schedule vwait and release
33+
interp.after(100, do_vwait)
34+
interp.after(500, lambda: interp.setvar("myvar", "done"))
35+
# Schedule quit to stop mainloop
36+
interp.after(1000, interp.quit)
37+
38+
interp.mainloop()
39+
"""
40+
41+
# Start child in interactive mode, but use -c to execute code immediately
42+
proc = subprocess.Popen(
43+
[sys.executable, "-i", "-c", code],
44+
stdin=subprocess.PIPE,
45+
stdout=subprocess.PIPE,
46+
stderr=subprocess.PIPE,
47+
)
48+
return proc
49+
50+
def test_vwait_stdin_busy_loop(self):
51+
proc = self.run_child()
52+
53+
# Wait until vwait likely started
54+
time.sleep(0.15)
55+
56+
# Send input to stdin to trigger the bug
57+
proc.stdin.write(b"x\n")
58+
proc.stdin.flush()
59+
60+
# Ensure child exits cleanly
61+
proc.stdin.write(b"exit()\n")
62+
proc.stdin.flush()
63+
64+
stdout, stderr = proc.communicate()
65+
out = stdout.decode("utf-8", errors="replace")
66+
err = stderr.decode("utf-8", errors="replace")
67+
68+
if proc.returncode != 0:
69+
self.fail(f"Child exited with {proc.returncode}\nSTDOUT:\n{out}\nSTDERR:\n{err}")
70+
71+
# Extract CPU fraction printed by child
72+
cpu_frac = None
73+
for line in out.splitlines():
74+
if line.startswith("CPU fraction during vwait:"):
75+
cpu_frac = float(line.split(":")[1].strip())
76+
break
77+
78+
self.assertIsNotNone(cpu_frac, "CPU fraction not printed by child")
79+
80+
# Fail if CPU fraction is too high (indicative of busy-loop)
81+
self.assertLess(cpu_frac, 0.5,
82+
f"CPU usage too high during vwait with stdin input (fraction={cpu_frac:.2f})")
83+
84+
85+
if __name__ == "__main__":
86+
unittest.main()
87+

0 commit comments

Comments
 (0)