Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions pyre/bench/synth/dict_update_source_mutation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""dict.update must notice source mutation from destination key equality."""


class MutatingKey:
def __hash__(self):
return 0

def __eq__(self, other_key):
source.clear()
return False


source = {1: 0, MutatingKey(): 0}
destination = {MutatingKey(): 0, 1: 1}

# 3.14 raises `RuntimeError: dict mutated during update`; older runtimes silently
# absorb the mutation instead. Accept either so the reference oracle agrees.
try:
destination.update(source)
except RuntimeError as error:
assert str(error) == "dict mutated during update", str(error)

print("ok")
24 changes: 24 additions & 0 deletions pyre/bench/synth/foriter_inplace_immutable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""FOR_ITER must not drop an item when immutable ``+=`` ends a hot body."""


class StatefulDecoder:
def __init__(self) -> None:
self.buffer = bytearray()

def process_word(self):
output = self.buffer.decode("ascii")
self.buffer = bytearray()
return output

def decode(self, data):
output = ""
for byte in data:
self.buffer.append(byte)
output += self.process_word()
return output


decoder = StatefulDecoder()
result = decoder.decode(b"abcd" * 2_000)
assert result == "abcd" * 2_000, (len(result), result[:8], result[-8:])
print(len(result), result[:4], result[-4:])
42 changes: 39 additions & 3 deletions pyre/extra_tests/snippets/builtin_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,13 +477,49 @@ def __eq__(self, other):
mutating_target -= {0}


# A colliding equality callback is a moving-GC collection point. Set update
# paths retain cached hashes from the operand, but must reload both copied key
# pointers from the shadow stack before they continue probing or insert/remove
# an entry.
import gc


class AllocatingSetKey:
def __init__(self, name):
self.name = name

def __hash__(self):
return 0

def __eq__(self, other):
# Equality may fill the nursery and therefore reach a normal moving-GC
# allocation safepoint while the set probe is holding copied keys.
self.allocations = [object() for _ in range(2000)]
return self is other


collecting_a = AllocatingSetKey("a")
collecting_b = AllocatingSetKey("b")
collecting_target = {collecting_a}
collecting_target.update({collecting_b})
assert len(collecting_target) == 2
assert any(item is collecting_a for item in collecting_target)
assert any(item is collecting_b for item in collecting_target)

collecting_target.difference_update({collecting_b})
assert len(collecting_target) == 1
assert next(iter(collecting_target)) is collecting_a

collecting_small = {collecting_a}
collecting_small.difference_update({collecting_b, object()})
assert len(collecting_small) == 1
assert next(iter(collecting_small)) is collecting_a


# test_set.py TestJointOps.test_free_after_iterating: exhausting a set
# iterator releases its source immediately. Layout-specific allocation must
# also register `__del__` on set/frozenset subclasses, just like PyPy's common
# objspace.allocate_instance path.
import gc


for _set_base in (set, frozenset):
_finalized = []

Expand Down
19 changes: 18 additions & 1 deletion pyre/extra_tests/snippets/stdlib_array.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from array import array
from array import _array_reconstructor, array
from io import BytesIO
from pickle import dumps, loads

from testutils import assert_raises
Expand Down Expand Up @@ -143,3 +144,19 @@ def write(self, chunk):
arr = array("b", range(128))
arr.tofile(_ReenteringWriter(arr))
assert len(arr) == 129

# CPython 3.14 reconstructs foreign-width integers into a native typecode
# with the same width and signedness instead of narrowing through the
# originally pickled C type.
rebuilt = _array_reconstructor(
array, "L", 6, b"\x01\x00\x00\x00\xff\xff\xff\xff"
)
assert rebuilt.typecode == "I"
assert rebuilt.tolist() == [1, 2**32 - 1]

# A short read that is not item-aligned is rejected by frombytes before the
# EOF check, and must not append the complete prefix.
partial = array("i")
with assert_raises(ValueError):
partial.fromfile(BytesIO(b"\x01\x00\x00\x00X"), 2)
assert partial == array("i")
18 changes: 17 additions & 1 deletion pyre/extra_tests/snippets/stdlib_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@

from testutils import assert_raises


# Python 3.14 exposes _checkClosed as a no-argument helper. In particular,
# an extra object must be rejected by argument parsing rather than interpreted
# as an unchecked string pointer.
_open_base = RawIOBase()
assert _open_base._checkClosed() is None
with assert_raises(TypeError):
_open_base._checkClosed(1)
_open_base.close()
with assert_raises(ValueError):
_open_base._checkClosed()

fi = FileIO("README.md")
assert isinstance(fi, RawIOBase)
assert issubclass(FileIO, RawIOBase)
Expand Down Expand Up @@ -63,10 +75,14 @@
with assert_raises(ValueError):
FileIO("README.md", closefd=False)

for bad_mode in ("", "rr", "rt", "rw", "rbb"):
for bad_mode in ("", "rr", "rt", "rw"):
with assert_raises(ValueError):
FileIO("README.md", bad_mode)

# Both PyPy's decode_mode and CPython 3.14 accept repeated binary markers.
with FileIO("README.md", "rbb") as fio:
assert fio.mode == "rb"


# Test that IOBase.isatty() raises ValueError when called on a closed file.
# Minimal subclass that inherits IOBase.isatty() without overriding it.
Expand Down
137 changes: 137 additions & 0 deletions pyre/extra_tests/snippets/stdlib_io_buffered.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import io

assert io.DEFAULT_BUFFER_SIZE == 128 * 1024


class ChunkRaw(io.RawIOBase):
def __init__(self, chunks):
super().__init__()
self.chunks = list(chunks)
self.position = 0
self.read_calls = 0

def readable(self):
return True

def seekable(self):
return True

def tell(self):
return self.position

def seek(self, offset, whence=0):
if whence == 0:
self.position = offset
elif whence == 1:
self.position += offset
else:
raise ValueError("unsupported test seek")
return self.position

def readinto(self, target):
self.read_calls += 1
if not self.chunks:
return 0
chunk = self.chunks.pop(0)
if chunk is None:
return None
count = min(len(target), len(chunk))
target[:count] = chunk[:count]
if count < len(chunk):
self.chunks.insert(0, chunk[count:])
self.position += count
return count


class SizingRaw(io.RawIOBase):
def __init__(self):
super().__init__()
self.request_size = None

def readable(self):
return True

def readinto(self, target):
self.request_size = len(target)
return 0


raw = SizingRaw()
assert io.BufferedReader(raw).read(1) == b""
assert raw.request_size == io.DEFAULT_BUFFER_SIZE


raw = ChunkRaw([b"abc", b"d", b"efg"])
reader = io.BufferedReader(raw, buffer_size=4)
assert reader.raw is raw
assert reader.readable() is True
assert reader.seekable() is True
assert reader.closed is False
assert reader.read(1) == b"a"
assert reader.read1(1) == b"b"
assert raw.read_calls == 1
assert reader.peek(10) == b"c"
assert raw.read_calls == 1

target = bytearray(b"xx")
assert reader.readinto(target) == 2
assert target == b"cd"
assert reader.read() == b"efg"
assert reader.read() == b""

raw = ChunkRaw([b"line 1\nline 2", b"\nend"])
reader = io.BufferedReader(raw, 8)
assert reader.readline() == b"line 1\n"
assert reader.readline() == b"line 2\n"
assert reader.readline() == b"end"

# A newline exactly at the raw chunk boundary must still terminate readline.
raw = ChunkRaw([b"abc\n", b"tail"])
reader = io.BufferedReader(raw, 4)
assert reader.readlines() == [b"abc\n", b"tail"]

# readinto1 drains readahead and permits one additional raw read.
raw = ChunkRaw([b"abc", b"def", b"ghi"])
reader = io.BufferedReader(raw, 4)
assert reader.read(1) == b"a"
target = bytearray(20)
assert reader.readinto1(target) == 5
assert target[:5] == b"bcdef"

raw = ChunkRaw([b"xyz"])
reader = io.BufferedReader(raw, 2)
assert reader.detach() is raw
for operation in (reader.read, reader.detach):
try:
operation()
except ValueError:
pass
else:
raise AssertionError("detached reader operation must fail")

raw = ChunkRaw([b"q"])
reader = io.BufferedReader(raw)
reader.close()
assert reader.closed is True
assert raw.closed is True
try:
reader.read()
except ValueError:
pass
else:
raise AssertionError("read on a closed BufferedReader must fail")

raw = ChunkRaw([b"context"])
with io.BufferedReader(raw) as reader:
assert reader.read() == b"context"
assert raw.closed is True


class ReaderSubclass(io.BufferedReader):
pass


raw = ChunkRaw([b"subclass"])
reader = ReaderSubclass(raw)
assert type(reader) is ReaderSubclass
assert reader.read() == b"subclass"
Loading
Loading