Skip to content

Commit d63c21e

Browse files
authored
Merge branch 'main' into gh-156443-longobject
2 parents 5ff5f31 + 23180c5 commit d63c21e

20 files changed

Lines changed: 261 additions & 68 deletions

Lib/_pydatetime.py

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1570,10 +1570,11 @@ def _cmp(self, other, allow_mixed=False):
15701570
return 2 # arbitrary non-zero value
15711571
else:
15721572
raise TypeError("cannot compare naive and aware times")
1573-
myhhmm = self._hour * 60 + self._minute - myoff//timedelta(minutes=1)
1574-
othhmm = other._hour * 60 + other._minute - otoff//timedelta(minutes=1)
1575-
return _cmp((myhhmm, self._second, self._microsecond),
1576-
(othhmm, other._second, other._microsecond))
1573+
myus = (((self._hour * 60 + self._minute) * 60 + self._second) * 1000000
1574+
+ self._microsecond - myoff._to_microseconds())
1575+
otus = (((other._hour * 60 + other._minute) * 60 + other._second) * 1000000
1576+
+ other._microsecond - otoff._to_microseconds())
1577+
return _cmp(myus, otus)
15771578

15781579
def __hash__(self):
15791580
"""Hash."""
@@ -1583,17 +1584,13 @@ def __hash__(self):
15831584
else:
15841585
t = self
15851586
tzoff = t.utcoffset()
1586-
if not tzoff: # zero or None
1587+
if tzoff is None:
15871588
self._hashcode = hash(t._getstate()[0])
15881589
else:
1589-
h, m = divmod(timedelta(hours=self.hour, minutes=self.minute) - tzoff,
1590-
timedelta(hours=1))
1591-
assert not m % timedelta(minutes=1), "whole minute"
1592-
m //= timedelta(minutes=1)
1593-
if 0 <= h < 24:
1594-
self._hashcode = hash(time(h, m, self.second, self.microsecond))
1595-
else:
1596-
self._hashcode = hash((h, m, self.second, self.microsecond))
1590+
self._hashcode = hash(timedelta(hours=t.hour,
1591+
minutes=t.minute,
1592+
seconds=t.second,
1593+
microseconds=t.microsecond) - tzoff)
15971594
return self._hashcode
15981595

15991596
# Conversion to string

Lib/asyncio/proactor_events.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -766,8 +766,8 @@ async def _sock_sendfile_native(self, sock, file, offset, count):
766766
async def _sendfile_native(self, transp, file, offset, count):
767767
resume_reading = transp.is_reading()
768768
transp.pause_reading()
769-
await transp._make_empty_waiter()
770769
try:
770+
await transp._make_empty_waiter()
771771
return await self.sock_sendfile(transp._sock, file, offset, count,
772772
fallback=False)
773773
finally:

Lib/asyncio/selector_events.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -739,8 +739,8 @@ async def _sendfile_native(self, transp, file, offset, count):
739739
del self._transports[transp._sock_fd]
740740
resume_reading = transp.is_reading()
741741
transp.pause_reading()
742-
await transp._make_empty_waiter()
743742
try:
743+
await transp._make_empty_waiter()
744744
return await self.sock_sendfile(transp._sock, file, offset, count,
745745
fallback=False)
746746
finally:

Lib/difflib.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
'unified_diff', 'diff_bytes', 'HtmlDiff', 'Match']
3232

3333
from heapq import nlargest as _nlargest
34-
from collections import namedtuple as _namedtuple
34+
from collections import deque as _deque, namedtuple as _namedtuple
3535
from types import GenericAlias
3636
lazy from _colorize import can_colorize, get_theme
3737

@@ -1571,7 +1571,7 @@ def _line_pair_iterator():
15711571
is defined) does not need to be of module scope.
15721572
"""
15731573
line_iterator = _line_iterator()
1574-
fromlines,tolines=[],[]
1574+
fromlines, tolines = _deque(), _deque()
15751575
while True:
15761576
# Collecting lines of text until we have a from/to pair
15771577
while (len(fromlines)==0 or len(tolines)==0):
@@ -1584,8 +1584,8 @@ def _line_pair_iterator():
15841584
if to_line is not None:
15851585
tolines.append((to_line,found_diff))
15861586
# Once we have a pair, remove them from the collection and yield it
1587-
from_line, fromDiff = fromlines.pop(0)
1588-
to_line, to_diff = tolines.pop(0)
1587+
from_line, fromDiff = fromlines.popleft()
1588+
to_line, to_diff = tolines.popleft()
15891589
yield (from_line,to_line,fromDiff or to_diff)
15901590

15911591
# Handle case where user does not want context differencing, just yield

Lib/idlelib/idle_test/test_configdialog.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,8 @@ def test_fontlist_key(self):
150150
font = d.fontlist.get('active')
151151

152152
# Test Down key.
153-
fontlist.focus_force()
154153
fontlist.update()
154+
fontlist.focus_force()
155155
fontlist.event_generate('<Key-Down>')
156156
fontlist.event_generate('<KeyRelease-Down>')
157157

@@ -160,8 +160,8 @@ def test_fontlist_key(self):
160160
self.assertIn(d.font_name.get(), down_font.lower())
161161

162162
# Test Up key.
163-
fontlist.focus_force()
164163
fontlist.update()
164+
fontlist.focus_force()
165165
fontlist.event_generate('<Key-Up>')
166166
fontlist.event_generate('<KeyRelease-Up>')
167167

Lib/test/datetimetester.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4693,6 +4693,33 @@ def tzname(self, dt): return self.tz
46934693
Badtzname.tz = '\ud800'
46944694
self.assertEqual(t.strftime("%Z"), '\ud800')
46954695

4696+
def test_subminute_offset_equality(self):
4697+
t1 = self.theclass(12, tzinfo=timezone.utc)
4698+
t2 = self.theclass(12, 0, 1, tzinfo=timezone(timedelta(seconds=1)))
4699+
self.assertEqual(t1, t2)
4700+
t2 = self.theclass(12, 0, 0, 1, tzinfo=timezone(timedelta(microseconds=1)))
4701+
self.assertEqual(t1, t2)
4702+
t2 = self.theclass(11, 59, 59, 999999, tzinfo=timezone(timedelta(microseconds=-1)))
4703+
self.assertEqual(t1, t2)
4704+
4705+
def test_subminute_offset_ordering(self):
4706+
t1 = self.theclass(0, tzinfo=timezone.utc)
4707+
t2 = self.theclass(0, tzinfo=timezone(timedelta(microseconds=1)))
4708+
self.assertGreater(t1, t2)
4709+
4710+
t1 = self.theclass(13, 59, 59, 900000, tzinfo=timezone(timedelta(hours=2)))
4711+
t2 = self.theclass(14, tzinfo=timezone(timedelta(hours=2, microseconds=900000)))
4712+
self.assertGreater(t1, t2)
4713+
4714+
def test_subminute_offset_hash(self):
4715+
t1 = self.theclass(12, tzinfo=timezone.utc)
4716+
t2 = self.theclass(12, 0, 1, tzinfo=timezone(timedelta(seconds=1)))
4717+
self.assertEqual(hash(t1), hash(t2))
4718+
t2 = self.theclass(12, 0, 0, 1, tzinfo=timezone(timedelta(microseconds=1)))
4719+
self.assertEqual(hash(t1), hash(t2))
4720+
t2 = self.theclass(11, 59, 59, 999999, tzinfo=timezone(timedelta(microseconds=-1)))
4721+
self.assertEqual(hash(t1), hash(t2))
4722+
46964723
def test_hash_edge_cases(self):
46974724
# Offsets that overflow a basic time.
46984725
t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))

Lib/test/test_asyncio/test_sendfile.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,47 @@ def test_sendfile(self):
376376
self.assertEqual(srv_proto.data, self.DATA)
377377
self.assertEqual(self.file.tell(), len(self.DATA))
378378

379+
def test_sendfile_cancel_empty_waiter(self):
380+
for reading in (True, False):
381+
with self.subTest(reading=reading):
382+
srv_proto, cli_proto = self.prepare_sendfile()
383+
transport = cli_proto.transport
384+
if not reading:
385+
transport.pause_reading()
386+
waiter = self.loop.create_future()
387+
388+
def make_empty_waiter():
389+
transport._empty_waiter = waiter
390+
return waiter
391+
392+
with mock.patch.object(transport, '_make_empty_waiter',
393+
side_effect=make_empty_waiter):
394+
task = self.loop.create_task(
395+
self.loop.sendfile(transport, self.file))
396+
test_utils.run_briefly(self.loop)
397+
self.assertIs(transport._empty_waiter, waiter)
398+
self.assertFalse(waiter.done())
399+
self.assertFalse(transport.is_reading())
400+
task.cancel()
401+
with self.assertRaises(asyncio.CancelledError):
402+
self.run_loop(task)
403+
404+
try:
405+
self.assertIsNone(transport._empty_waiter)
406+
self.assertEqual(transport.is_reading(), reading)
407+
if isinstance(self.loop, asyncio.SelectorEventLoop):
408+
self.assertIs(
409+
self.loop._transports[transport._sock_fd],
410+
transport)
411+
finally:
412+
transport._reset_empty_waiter()
413+
414+
ret = self.run_loop(self.loop.sendfile(transport, self.file))
415+
transport.close()
416+
self.run_loop(srv_proto.done)
417+
self.assertEqual(ret, len(self.DATA))
418+
self.assertEqual(srv_proto.data, self.DATA)
419+
379420
def test_sendfile_force_fallback(self):
380421
srv_proto, cli_proto = self.prepare_sendfile()
381422

Lib/test/test_curses.py

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,9 @@ def test_complexstr(self):
796796
self.assertEqual(str(s[1:]), 'bc')
797797
self.assertEqual(str(s[::-1]), 'cbA')
798798
self.assertEqual(str(s + curses.complexstr(['Z'])), 'AbcZ')
799+
# Concatenating anything else raises instead of returning NotImplemented.
800+
self.assertRaises(TypeError, lambda: s + 'Z')
801+
self.assertRaises(TypeError, lambda: s + cc('Z'))
799802
# The empty complexstr.
800803
self.assertEqual(len(curses.complexstr([])), 0)
801804
self.assertEqual(str(curses.complexstr('')), '')
@@ -3383,6 +3386,23 @@ def test_close(self):
33833386
# close() is idempotent.
33843387
screen.close()
33853388

3389+
def test_close_then_write_with_attr_keeps_no_reference(self):
3390+
# A write with an *attr* argument on a detached window fails while
3391+
# setting the rendition, and has to release the bytes it converted.
3392+
s = self.make_pty()
3393+
screen = curses.newterm('xterm', s, s)
3394+
win = screen.stdscr
3395+
screen.close()
3396+
writes = [lambda b: win.addstr(b, curses.A_BOLD),
3397+
lambda b: win.addnstr(b, 4, curses.A_BOLD),
3398+
lambda b: win.insstr(b, curses.A_BOLD),
3399+
lambda b: win.insnstr(b, 4, curses.A_BOLD)]
3400+
data = b'x' * 8
3401+
nrefs = sys.getrefcount(data)
3402+
for write in writes:
3403+
self.assertRaises(curses.error, write, data)
3404+
self.assertEqual(sys.getrefcount(data), nrefs)
3405+
33863406
@requires_curses_func('panel')
33873407
def test_close_then_panel_replace(self):
33883408
# A detached window has no underlying curses window, so replace()
@@ -3479,10 +3499,10 @@ class SLKTests(NewtermTestBase):
34793499
# slk_init() must run before newterm()/initscr(), so each test sets up its
34803500
# own screen rather than reusing the one TestCurses builds in setUp().
34813501

3482-
def make_slk_screen(self, fmt=0):
3502+
def make_slk_screen(self, fmt=0, term='xterm'):
34833503
s = self.make_pty()
34843504
curses.slk_init(fmt)
3485-
return curses.newterm('xterm', s, s)
3505+
return curses.newterm(term, s, s)
34863506

34873507
def test_init_reserves_a_line(self):
34883508
# Every layout takes the bottom line for the labels; the index-line
@@ -3565,6 +3585,26 @@ def test_color(self):
35653585
curses.slk_attr_set(curses.A_BOLD, 0)
35663586
curses.slk_color(0)
35673587

3588+
def test_color_wide_pair(self):
3589+
# Drive a terminal with enough color pairs to reach past a short,
3590+
# rather than relying on whatever $TERM happens to be.
3591+
try:
3592+
self.make_slk_screen(term='xterm-256color')
3593+
except curses.error:
3594+
self.skipTest('no xterm-256color terminfo entry')
3595+
if not curses.has_colors():
3596+
self.skipTest('requires colors support')
3597+
curses.start_color()
3598+
if not (curses.has_extended_color_support()
3599+
and curses.COLOR_PAIRS > SHORT_MAX + 1):
3600+
self.skipTest('requires extended color support')
3601+
# A pair that does not fit in a short is still a valid pair here.
3602+
curses.slk_color(SHORT_MAX + 1)
3603+
# The low 16 bits of this are pair 5, but the pair itself is out of
3604+
# range, so it must raise instead of selecting pair 5.
3605+
self.assertRaises(curses.error, curses.slk_color,
3606+
curses.COLOR_PAIRS * 2 + 5)
3607+
35683608

35693609
@unittest.skipUnless(hasattr(curses, 'newterm'), 'requires curses.newterm()')
35703610
@unittest.skipIf(BROKEN_NEWTERM, 'ncurses < 6.5 mishandles repeated newterm()')

Lib/test/test_difflib.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,26 @@ def test_mdiff_catch_stop_iteration(self):
200200
[((1, '\x00-2\x01'), (1, '\x00+3\x01'), True)],
201201
)
202202

203+
def test_mdiff_lopsided_replace(self):
204+
self.assertEqual(
205+
list(difflib._mdiff(["a\n"] * 4, ["b\n"])),
206+
[
207+
((1, '\x00-a\n\x01'), (1, '\x00+b\n\x01'), True),
208+
((2, '\x00-a\n\x01'), ('', '\n'), True),
209+
((3, '\x00-a\n\x01'), ('', '\n'), True),
210+
((4, '\x00-a\n\x01'), ('', '\n'), True),
211+
],
212+
)
213+
self.assertEqual(
214+
list(difflib._mdiff(["a\n"], ["b\n"] * 4)),
215+
[
216+
((1, '\x00-a\n\x01'), (1, '\x00+b\n\x01'), True),
217+
(('', '\n'), (2, '\x00+b\n\x01'), True),
218+
(('', '\n'), (3, '\x00+b\n\x01'), True),
219+
(('', '\n'), (4, '\x00+b\n\x01'), True),
220+
],
221+
)
222+
203223

204224
patch914575_from1 = """
205225
1. Beautiful is beTTer than ugly.

Lib/test/test_tkinter/support.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ def require_mapped(self, widget, timeout=None):
6161
f'(timed out after {timeout:g}s)')
6262

6363

64+
class AbstractDialogTest(AbstractTkTest):
65+
# Tk delivers generated keyboard events to the focused window. Hide the
66+
# root window, otherwise the window manager can take the focus back from
67+
# the dialog (gh-154357).
68+
69+
def setUp(self):
70+
super().setUp()
71+
self.root.withdraw()
72+
73+
6474
class AbstractDefaultRootTest:
6575

6676
def setUp(self):
@@ -112,6 +122,7 @@ def wait_until_mapped(widget, timeout=None, *, full_size=False):
112122
timeout = support.LOOPBACK_TIMEOUT
113123
deadline = time.monotonic() + timeout
114124
widget.update_idletasks()
125+
reset = False
115126
while True:
116127
widget.update() # drain pending Map/Configure events
117128
if widget.winfo_ismapped():
@@ -123,6 +134,11 @@ def wait_until_mapped(widget, timeout=None, *, full_size=False):
123134
h_ok = widget.winfo_height() > 1
124135
if w_ok and h_ok:
125136
return True
137+
if full_size and not reset:
138+
# Tk no longer resizes the toplevel to fit its content if
139+
# the window manager has resized it. Undo this.
140+
widget.winfo_toplevel().wm_geometry('')
141+
reset = True
126142
if time.monotonic() >= deadline:
127143
return False
128144
time.sleep(0.01)

0 commit comments

Comments
 (0)