Skip to content

Commit 41e55ba

Browse files
test: do not use threads
1 parent b4ebf34 commit 41e55ba

2 files changed

Lines changed: 48 additions & 120 deletions

File tree

Lib/test/test_free_threading/test_list.py

Lines changed: 0 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import threading
21
import unittest
32

43
from threading import Thread, Barrier
@@ -172,125 +171,6 @@ def size_function():
172171
pass
173172

174173

175-
def test_richcompare_stale_element_list1(self) -> None:
176-
# gh-148442: list_richcompare_impl must keep references to the
177-
# captured items for the final ordering comparison, not re-read the
178-
# list slots after the critical section may have been suspended.
179-
#
180-
# list1 = [x, 1], list2 = [0, 0] with x > 0.
181-
# During list1 > list2, list1[0] is mutated from x to 0.
182-
# The result must be True (x > 0), not False (0 > 0).
183-
184-
class Value:
185-
def __init__(self, v: int) -> None:
186-
self.v = v
187-
188-
def __eq__(self, other: object) -> bool:
189-
if isinstance(other, Value):
190-
return self.v == other.v
191-
return NotImplemented
192-
193-
def __gt__(self, other: object) -> bool:
194-
if isinstance(other, Value):
195-
return self.v > other.v
196-
return NotImplemented
197-
198-
def __hash__(self) -> int:
199-
return hash(self.v)
200-
201-
eq_started = threading.Event()
202-
swap_done = threading.Event()
203-
204-
class SignalingValue(Value):
205-
# Value whose __eq__ signals eq_started then waits for swap_done,
206-
# giving another thread the window to mutate the list slot.
207-
def __eq__(self, other: object) -> bool:
208-
eq_started.set()
209-
swap_done.wait()
210-
return super().__eq__(other)
211-
212-
x = SignalingValue(5) # list1[0]; x > 0
213-
list1: list[Value] = [x, Value(1)]
214-
list2: list[Value] = [Value(0), Value(0)]
215-
216-
result: list[bool] = []
217-
218-
def compare() -> None:
219-
result.append(list1 > list2)
220-
221-
def swap() -> None:
222-
eq_started.wait()
223-
list1[0] = Value(0) # replace x(5) with 0 -- must not change result
224-
swap_done.set()
225-
226-
t_cmp = Thread(target=compare)
227-
t_swp = Thread(target=swap)
228-
t_cmp.start()
229-
t_swp.start()
230-
t_cmp.join()
231-
t_swp.join()
232-
233-
# x(5) > Value(0) is True; old code would compare Value(0) > Value(0) -> False
234-
self.assertTrue(result[0])
235-
236-
def test_richcompare_stale_element_list2(self) -> None:
237-
# Same as test_richcompare_stale_element_list1 but list2[0] is mutated
238-
# to a value larger than x, which would flip the result under the old code.
239-
#
240-
# list1 = [x, 1], list2 = [0, 0] with x > 0.
241-
# During list1 > list2, list2[0] is mutated from 0 to 100 (> x).
242-
# The result must be True (x > 0), not False (x > 100).
243-
244-
class Value:
245-
def __init__(self, v: int) -> None:
246-
self.v = v
247-
248-
def __eq__(self, other: object) -> bool:
249-
if isinstance(other, Value):
250-
return self.v == other.v
251-
return NotImplemented
252-
253-
def __gt__(self, other: object) -> bool:
254-
if isinstance(other, Value):
255-
return self.v > other.v
256-
return NotImplemented
257-
258-
def __hash__(self) -> int:
259-
return hash(self.v)
260-
261-
eq_started = threading.Event()
262-
swap_done = threading.Event()
263-
264-
class SignalingValue(Value):
265-
def __eq__(self, other: object) -> bool:
266-
eq_started.set()
267-
swap_done.wait()
268-
return super().__eq__(other)
269-
270-
x = SignalingValue(5) # list1[0]; x > 0
271-
list1: list[Value] = [x, Value(1)]
272-
list2: list[Value] = [Value(0), Value(0)]
273-
274-
result: list[bool] = []
275-
276-
def compare() -> None:
277-
result.append(list1 > list2)
278-
279-
def swap() -> None:
280-
eq_started.wait()
281-
list2[0] = Value(100) # replace 0 with 100 (> x) -- must not change result
282-
swap_done.set()
283-
284-
t_cmp = Thread(target=compare)
285-
t_swp = Thread(target=swap)
286-
t_cmp.start()
287-
t_swp.start()
288-
t_cmp.join()
289-
t_swp.join()
290-
291-
# x(5) > Value(0) is True; old code would compare Value(5) > Value(100) -> False
292-
self.assertTrue(result[0])
293-
294174

295175
if __name__ == "__main__":
296176
unittest.main()

Lib/test/test_list.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,54 @@ def __lt__(self, other):
262262
with self.assertRaises(TypeError):
263263
a[0] < a
264264

265+
def test_richcompare_stale_element_list_vitem(self):
266+
# gh-148442: list_richcompare_impl must use the captured vitem for
267+
# the final ordering comparison, not re-read list1's slot after __eq__
268+
# may have mutated it.
269+
#
270+
# x.__eq__(0) puts AlwaysLT() into list1[0] and returns False.
271+
class AlwaysLT:
272+
def __eq__(self, other: object) -> bool:
273+
return False
274+
275+
def __gt__(self, other: object) -> bool:
276+
return False
277+
278+
class Mutating:
279+
def __eq__(self, other: object) -> bool:
280+
list1[0] = AlwaysLT()
281+
return False
282+
283+
def __gt__(self, other: object) -> bool:
284+
return True
285+
286+
list1 = [Mutating(), 0]
287+
list2 = [0, 0]
288+
self.assertTrue(list1 > list2)
289+
290+
def test_richcompare_stale_element_list_witem(self):
291+
# gh-148442: list_richcompare_impl must use the captured witem for
292+
# the final ordering comparison, not re-read list2's slot after __eq__
293+
# may have mutated it.
294+
#
295+
# x.__eq__(0) puts AlwaysGT() into list2[0] and returns False.
296+
class AlwaysGT:
297+
pass
298+
299+
class Mutating:
300+
def __eq__(self, other: object) -> bool:
301+
list2[0] = AlwaysGT()
302+
return False
303+
304+
def __gt__(self, other: object) -> bool:
305+
if isinstance(other, AlwaysGT):
306+
return False # pretend AlwaysGT beats us
307+
return True # beat everything else (including 0)
308+
309+
list1 = [Mutating(), 0]
310+
list2 = [0, 0]
311+
self.assertTrue(list1 > list2)
312+
265313
def test_list_index_modifing_operand(self):
266314
# See gh-120384
267315
class evil:

0 commit comments

Comments
 (0)