Skip to content
Open
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
12 changes: 12 additions & 0 deletions ci.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,12 @@
local with_compiler = task_spec({
dynamic_imports +:: ["/compiler"],
}),
local unittest_args_gate(args) = task_spec({
tags:: "python-unittest",
environment +: {
GRAALPY_UNITTEST_ARGS: std.join(" ", args),
},
}),

// -----------------------------------------------------------------------------------------------------------------
//
Expand Down Expand Up @@ -166,6 +172,12 @@
"python-unittest-native-debug-build": gpgate + platform_spec(no_jobs) + native_debug_build_gate("python-unittest") + platform_spec({
"linux:amd64:jdk-latest" : tier3,
}),
"python-unittest-cached-interpreter": gpgate + unittest_args_gate(["--python.UncachedInterpreterThreshold=0"]) + platform_spec(no_jobs) + platform_spec({
"linux:amd64:jdk-latest" : tier3 + require(GPY_JVM_STANDALONE),
}),
"python-unittest-uncached-interpreter": gpgate + unittest_args_gate(["--python.ForceUncachedInterpreter=true"]) + platform_spec(no_jobs) + platform_spec({
"linux:amd64:jdk-latest" : tier3 + require(GPY_JVM_STANDALONE),
}),
"python-unittest-multi-context": gpgate + require(GPY_NATIVE_STANDALONE) + platform_spec(no_jobs) + platform_spec({
"linux:amd64:jdk-latest" : tier3,
"linux:aarch64:jdk-latest" : daily + t("02:00:00"),
Expand Down
48 changes: 48 additions & 0 deletions graalpython/com.oracle.graal.python.test/src/tests/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import sys
import tempfile
import unittest
import io

class MyIndexable(object):
def __init__(self, value):
Expand Down Expand Up @@ -121,6 +122,53 @@ def test_builtin_constants(self):
self.assertEqual(getattr(builtins, 'False'), False)
self.assertEqual(getattr(builtins, 'True'), True)

def test_missing_builtin_lookup_after_warmup(self):
def read_missing_builtin():
return definitely_missing_builtin_for_review

for _ in range(20):
with self.assertRaises(NameError):
read_missing_builtin()

def test_instance_attr_state_machine_after_warmup(self):
class PaddedFile:
def __init__(self, fileobj, prepend=b""):
self._buffer = prepend
self._length = len(prepend)
self.file = fileobj
self._read = 0

def read(self, size):
if self._read is None:
return self.file.read(size)
if self._read + size <= self._length:
read = self._read
self._read += size
return self._buffer[read:self._read]
read = self._read
self._read = None
return self._buffer[read:] + self.file.read(size - self._length + read)

def prepend(self, prepend=b""):
if self._read is None:
self._buffer = prepend
else:
self._read -= len(prepend)
return
self._length = len(self._buffer)
self._read = 0

def exercise():
padded = PaddedFile(io.BytesIO(b"cdef"), b"ab")
self.assertEqual(padded.read(1), b"a")
self.assertEqual(padded.read(3), b"bcd")
padded.prepend(b"Z")
self.assertEqual(padded.read(2), b"Ze")
self.assertEqual(padded.read(2), b"f")

for _ in range(20):
exercise()

def test_min(self):
self.assertEqual(min((), default=1, key="adsf"), 1)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,24 @@ def foo():
assert e.__traceback__.tb_next.tb_frame.f_back.f_code == test_backref_from_traceback.__code__


def test_backref_from_traceback_after_cached_transition():
def bar(should_raise):
if should_raise:
raise RuntimeError

def foo(should_raise):
bar(should_raise)

for _ in range(64): # we probably do not execute 64-times in uncached
foo(False)

try:
foo(True)
except Exception as e:
assert e.__traceback__.tb_next.tb_next.tb_frame.f_back.f_code == foo.__code__
assert e.__traceback__.tb_next.tb_frame.f_back.f_code == test_backref_from_traceback_after_cached_transition.__code__


def test_frame_from_another_thread():
import sys, threading
event1 = threading.Event()
Expand Down
16 changes: 13 additions & 3 deletions graalpython/com.oracle.graal.python.test/src/tests/test_mro.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2021, 2025, Oracle and/or its affiliates.
# Copyright (c) 2021, 2026, Oracle and/or its affiliates.
# Copyright (C) 1996-2020 Python Software Foundation
#
# Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
Expand Down Expand Up @@ -182,12 +182,20 @@ def __eq__(self, other):
eq_called.append(1)
X.__bases__ = (Base2,)

class Descr:
def __init__(self, value):
self.value = value
def __get__(self, obj, owner=None):
return self.value
def __set__(self, obj, value):
pass

class Base(object):
mykey = 'base 42'
mykey = Descr('base 42')
def __str__(self): return 'Base'

class Base2(object):
mykey = 'base2 42'
mykey = Descr('base2 42')
def __str__(self): return 'Base2'

X = type('X', (Base,), {MyKey(): 5})
Expand All @@ -202,6 +210,8 @@ def __str__(self): return 'Base2'
eq_called = []
X = type('X', (Base,), {MyKey(): 5})
xobj = X()
xobj_dict = object.__getattribute__(xobj, "__dict__")
xobj_dict['mykey'] = 'false lead'
assert str(xobj) == 'Base'
assert xobj.mykey == 'base 42'
assert eq_called == [1]
Expand Down
14 changes: 13 additions & 1 deletion graalpython/com.oracle.graal.python.test/src/tests/test_slot.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# The Universal Permissive License (UPL), Version 1.0
Expand Down Expand Up @@ -173,5 +173,17 @@ class C:
__slots__ = ('a', 'b')
self.assertRaises(AttributeError, setattr, C(), 'c', 42)

def test_write_attr_without_dict_after_warmup(self):
class C:
__slots__ = ()

obj = C()

def write_attr():
obj.x = 42

for _ in range(20):
self.assertRaises(AttributeError, write_attr)

if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,18 @@
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.object.Shape;
import com.oracle.truffle.api.profiles.InlinedBranchProfile;
import com.oracle.truffle.api.profiles.InlinedConditionProfile;
import com.oracle.truffle.api.strings.TruffleString;

/**
* This storage keeps a reference to the MRO when used for a type dict. Writing to this storage will
* cause the appropriate <it>attribute final</it> assumptions to be invalidated.
* <p/>
* This storage may wrap a {@link PythonObject}; in that case the storage and the object can be
* used interchangeably. If the storage is transformed to other storage, or for some other reason
* requires that all accesses are routed through the {@link DynamicObjectStorage}, then the
* {@link PythonObject}'s {@link Shape} flags must be updated to include
* {@link PythonObject#HAS_MATERIALIZED_DICT}.
*/
public final class DynamicObjectStorage extends HashingStorage {
public static final int SIZE_THRESHOLD = 100;
Expand Down Expand Up @@ -268,6 +273,10 @@ void setStringKey(TruffleString key, Object value, DynamicObject.PutNode putNode
putNode.execute(store, key, assertNoJavaString(value));
}

boolean setStringKeyIfPresent(TruffleString key, Object value, DynamicObject.PutNode putNode) {
return putNode.executeIfPresent(store, key, assertNoJavaString(value));
}

boolean shouldTransitionOnPut() {
// For now, we do not use SIZE_THRESHOLD condition to transition storages that wrap
// dictionaries retrieved via object's __dict__
Expand Down Expand Up @@ -332,8 +341,7 @@ public static DynamicObjectStorage copy(DynamicObjectStorage receiver,
public abstract static class DynamicObjectStorageSetStringKey extends SpecializedSetStringKey {
@Specialization
static void doIt(Node inliningTarget, HashingStorage self, TruffleString key, Object value,
@Cached DynamicObject.PutNode putNode,
@Cached InlinedBranchProfile invalidateMro) {
@Cached DynamicObject.PutNode putNode) {
((DynamicObjectStorage) self).setStringKey(key, value, putNode);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -605,11 +605,11 @@ static Object domStringKey(Frame frame, Node inliningTarget, DynamicObjectStorag
if (val == PNone.NO_VALUE) {
return null;
} else {
putNode.execute(store, key, PNone.NO_VALUE);
self.setStringKey(key, PNone.NO_VALUE, putNode);
return val;
}
} else {
return putNode.executeIfPresent(store, key, PNone.NO_VALUE);
return self.setStringKeyIfPresent(key, PNone.NO_VALUE, putNode);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public PythonModule(Object clazz, Shape instanceShape) {
setAttribute(T___SPEC__, PNone.NO_VALUE);
setAttribute(T___CACHED__, PNone.NO_VALUE);
setAttribute(T___FILE__, PNone.NO_VALUE);
GetOrCreateDictNode.executeUncached(this);
GetOrCreateDictNode.ensureModuleDict(this);
}

/**
Expand All @@ -105,7 +105,7 @@ private PythonModule(PythonLanguage lang, TruffleString moduleName) {
setAttribute(T___SPEC__, PNone.NONE);
setAttribute(T___CACHED__, PNone.NO_VALUE);
setAttribute(T___FILE__, PNone.NO_VALUE);
GetOrCreateDictNode.executeUncached(this);
GetOrCreateDictNode.ensureModuleDict(lang, this);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@
import com.oracle.truffle.api.nodes.UnexpectedResultException;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.object.HiddenKey;
import com.oracle.truffle.api.object.PropertyGetter;
import com.oracle.truffle.api.object.Shape;

public final class HiddenAttr {

Expand Down Expand Up @@ -127,6 +129,10 @@ boolean hasLongValue() {
this == METHOD_DEF_PTR;
}

public PropertyGetter createPropertyGetter(Shape shape) {
return shape.makePropertyGetter(key);
}

@Override
public String toString() {
return getName();
Expand Down Expand Up @@ -192,10 +198,7 @@ static void doPythonObjectDict(PythonObject self, HiddenAttr attr, Object value,
}

private static boolean isGenericDict(PythonObject self, Object value) {
if (value instanceof PDict dict && dict.getDictStorage() instanceof DynamicObjectStorage dynamicStorage) {
return dynamicStorage.getStore() != self;
}
return true;
return !(value instanceof PDict dict && dict.getDictStorage() instanceof DynamicObjectStorage dom && dom.getStore() == self);
}

@Specialization(guards = "attr != DICT || !isPythonObject(self)")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
import com.oracle.truffle.api.exception.AbstractTruffleException;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.UnexpectedResultException;
import com.oracle.truffle.api.object.Shape;
import com.oracle.truffle.api.profiles.InlinedBranchProfile;
import com.oracle.truffle.api.strings.TruffleString;
import com.oracle.truffle.api.strings.TruffleString.CodeRange;
Expand All @@ -127,6 +128,7 @@ public static boolean isNone(Object value) {
return value == PNone.NONE;
}

@Idempotent
public static boolean isNoValue(Object object) {
return object == PNone.NO_VALUE;
}
Expand Down Expand Up @@ -531,4 +533,8 @@ public static boolean hasBuiltinDictIter(Node inliningTarget, PDict dict, GetPyt
return isBuiltinDict(dict) || getSlots.execute(inliningTarget, getClassNode.execute(inliningTarget, dict)).tp_iter() == DictBuiltins.SLOTS.tp_iter();
}

@Idempotent
public static boolean hasMaterializedDict(Shape s) {
return (s.getFlags() & PythonObject.HAS_MATERIALIZED_DICT) != 0;
}
}
Loading
Loading