runtime/object-builtins-test.cpp (963 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
#include "object-builtins.h"
#include "gtest/gtest.h"
#include "builtins.h"
#include "frame.h"
#include "ic.h"
#include "runtime.h"
#include "str-builtins.h"
#include "test-utils.h"
#include "type-builtins.h"
namespace py {
namespace testing {
using NoneBuiltinsTest = RuntimeFixture;
using ObjectBuiltinsTest = RuntimeFixture;
TEST_F(ObjectBuiltinsTest, DunderEqWithIdenticalObjectsReturnsTrue) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
result = object.__eq__(None, None)
)")
.isError());
HandleScope scope(thread_);
Object result(&scope, mainModuleAt(runtime_, "result"));
EXPECT_EQ(*result, Bool::trueObj());
}
TEST_F(ObjectBuiltinsTest,
DunderEqWithNonIdenticalObjectsReturnsNotImplemented) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
result = object.__eq__(object(), object())
)")
.isError());
HandleScope scope(thread_);
Object result(&scope, mainModuleAt(runtime_, "result"));
EXPECT_TRUE(result.isNotImplementedType());
}
TEST_F(ObjectBuiltinsTest, DunderGetattributeReturnsAttribute) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C: pass
i = C()
i.foo = 79
)")
.isError());
Object i(&scope, mainModuleAt(runtime_, "i"));
Object name(&scope, runtime_->newStrFromCStr("foo"));
EXPECT_TRUE(
isIntEqualsWord(runBuiltin(METH(object, __getattribute__), i, name), 79));
}
TEST_F(ObjectBuiltinsTest, DunderGetattributeWithNonStringNameRaisesTypeError) {
HandleScope scope(thread_);
Object object(&scope, NoneType::object());
Object name(&scope, runtime_->newInt(0));
EXPECT_TRUE(raisedWithStr(
runBuiltin(METH(object, __getattribute__), object, name),
LayoutId::kTypeError, "attribute name must be string, not 'int'"));
}
TEST_F(ObjectBuiltinsTest,
DunderGetattributeWithMissingAttributeRaisesAttributeError) {
HandleScope scope(thread_);
Object object(&scope, NoneType::object());
Object name(&scope, runtime_->newStrFromCStr("xxx"));
EXPECT_TRUE(raisedWithStr(
runBuiltin(METH(object, __getattribute__), object, name),
LayoutId::kAttributeError, "'NoneType' object has no attribute 'xxx'"));
}
TEST_F(ObjectBuiltinsTest, DunderSetattrSetsValue) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C: pass
i = C()
)")
.isError());
Object i(&scope, mainModuleAt(runtime_, "i"));
Object name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
Object value(&scope, runtime_->newInt(42));
EXPECT_TRUE(
runBuiltin(METH(object, __setattr__), i, name, value).isNoneType());
ASSERT_TRUE(i.isInstance());
Instance i_instance(&scope, *i);
EXPECT_TRUE(
isIntEqualsWord(instanceGetAttribute(thread_, i_instance, name), 42));
}
TEST_F(ObjectBuiltinsTest, DunderSetattrWithNonStringNameRaisesTypeError) {
HandleScope scope(thread_);
Object object(&scope, NoneType::object());
Object name(&scope, runtime_->newInt(0));
Object value(&scope, runtime_->newInt(1));
EXPECT_TRUE(raisedWithStr(
runBuiltin(METH(object, __setattr__), object, name, value),
LayoutId::kTypeError, "attribute name must be string, not 'int'"));
}
TEST_F(ObjectBuiltinsTest, DunderSetattrOnBuiltinTypeRaisesAttributeError) {
HandleScope scope(thread_);
Object object(&scope, NoneType::object());
Object name(&scope, runtime_->newStrFromCStr("foo"));
Object value(&scope, runtime_->newInt(1));
EXPECT_TRUE(raisedWithStr(
runBuiltin(METH(object, __setattr__), object, name, value),
LayoutId::kAttributeError, "'NoneType' object has no attribute 'foo'"));
}
TEST_F(ObjectBuiltinsTest,
DunderSizeofWithNonHeapObjectReturnsSizeofRawObject) {
HandleScope scope(thread_);
Object small_int(&scope, SmallInt::fromWord(6));
Object result(&scope, runBuiltin(METH(object, __sizeof__), small_int));
EXPECT_TRUE(isIntEqualsWord(*result, kPointerSize));
}
TEST_F(ObjectBuiltinsTest, DunderSizeofWithLargeStrReturnsSizeofHeapObject) {
HandleScope scope(thread_);
HeapObject large_str(&scope, runtime_->createLargeStr(40));
Object result(&scope, runBuiltin(METH(object, __sizeof__), large_str));
EXPECT_TRUE(isIntEqualsWord(*result, large_str.size()));
}
TEST_F(
ObjectBuiltinsTest,
DunderNeWithSelfImplementingDunderEqReturningNotImplementedReturnsNotImplemented) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
class Foo():
def __eq__(self, b): return NotImplemented
result = object.__ne__(Foo(), None)
)")
.isError());
EXPECT_TRUE(mainModuleAt(runtime_, "result").isNotImplementedType());
}
TEST_F(ObjectBuiltinsTest,
DunderNeWithSelfImplementingDunderEqReturningZeroReturnsTrue) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
class Foo():
def __eq__(self, b): return 0
result = object.__ne__(Foo(), None)
)")
.isError());
// 0 is converted to False, and flipped again for __ne__ from __eq__.
EXPECT_EQ(mainModuleAt(runtime_, "result"), Bool::trueObj());
}
TEST_F(ObjectBuiltinsTest,
DunderNeWithSelfImplementingDunderEqReturningOneReturnsFalse) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
class Foo():
def __eq__(self, b): return 1
result = object.__ne__(Foo(), None)
)")
.isError());
// 1 is converted to True, and flipped again for __ne__ from __eq__.
EXPECT_EQ(mainModuleAt(runtime_, "result"), Bool::falseObj());
}
TEST_F(ObjectBuiltinsTest,
DunderNeWithSelfImplementingDunderEqReturningFalseReturnsTrue) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
class Foo():
def __eq__(self, b): return False
result = object.__ne__(Foo(), None)
)")
.isError());
EXPECT_EQ(mainModuleAt(runtime_, "result"), Bool::trueObj());
}
TEST_F(ObjectBuiltinsTest,
DunderNeWithSelfImplementingDunderEqReturningTrueReturnsFalse) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
class Foo():
def __eq__(self, b): return True
result = object.__ne__(Foo(), None)
)")
.isError());
EXPECT_EQ(mainModuleAt(runtime_, "result"), Bool::falseObj());
}
TEST_F(ObjectBuiltinsTest, DunderStrReturnsDunderRepr) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
class Foo:
pass
f = Foo()
a = object.__str__(f)
b = object.__repr__(f)
)")
.isError());
HandleScope scope(thread_);
Object a(&scope, mainModuleAt(runtime_, "a"));
Object b(&scope, mainModuleAt(runtime_, "b"));
EXPECT_TRUE(isStrEquals(a, b));
}
TEST_F(ObjectBuiltinsTest, UserDefinedTypeInheritsDunderStr) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
class Foo:
pass
f = Foo()
a = object.__str__(f)
b = f.__str__()
)")
.isError());
HandleScope scope(thread_);
Object a(&scope, mainModuleAt(runtime_, "a"));
Object b(&scope, mainModuleAt(runtime_, "b"));
EXPECT_TRUE(isStrEquals(a, b));
}
TEST_F(ObjectBuiltinsTest,
DunderInitDoesNotRaiseIfNewIsDifferentButInitIsSame) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
class Foo:
def __new__(cls):
return object.__new__(cls)
Foo.__init__(Foo(), 1)
)")
.isError());
// It doesn't matter what the output is, just that it doesn't throw a
// TypeError.
}
TEST_F(ObjectBuiltinsTest, DunderInitWithNonInstanceIsOk) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
object.__init__(object)
)")
.isError());
// It doesn't matter what the output is, just that it doesn't throw a
// TypeError.
}
TEST_F(ObjectBuiltinsTest, DunderInitWithNoArgsRaisesTypeError) {
// Passing no args to object.__init__ should throw a type error.
EXPECT_TRUE(raisedWithStr(
runFromCStr(runtime_, R"(
object.__init__()
)"),
LayoutId::kTypeError,
"'object.__init__' takes min 1 positional arguments but 0 given"));
}
TEST_F(ObjectBuiltinsTest, DunderInitWithArgsRaisesTypeError) {
// Passing extra args to object.__init__, without overwriting __new__,
// should throw a type error.
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class Foo:
pass
Foo.__init__(Foo(), 1)
)"),
LayoutId::kTypeError,
"object.__init__() takes no parameters"));
}
TEST_F(ObjectBuiltinsTest, DunderInitWithNewAndInitRaisesTypeError) {
// Passing extra args to object.__init__, and overwriting only __init__,
// should throw a type error.
EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
class Foo:
def __init__(self):
object.__init__(self, 1)
Foo()
)"),
LayoutId::kTypeError,
"object.__init__() takes no parameters"));
}
TEST_F(NoneBuiltinsTest, NewReturnsNone) {
HandleScope scope(thread_);
Type type(&scope, runtime_->typeAt(LayoutId::kNoneType));
EXPECT_TRUE(runBuiltin(METH(NoneType, __new__), type).isNoneType());
}
TEST_F(NoneBuiltinsTest, NewWithExtraArgsRaisesTypeError) {
EXPECT_TRUE(
raised(runFromCStr(runtime_, "NoneType.__new__(NoneType, 1, 2, 3, 4, 5)"),
LayoutId::kTypeError));
}
TEST_F(NoneBuiltinsTest, DunderReprIsBoundMethod) {
ASSERT_FALSE(runFromCStr(runtime_, "a = None.__repr__").isError());
HandleScope scope(thread_);
Object a(&scope, mainModuleAt(runtime_, "a"));
EXPECT_TRUE(a.isBoundMethod());
}
TEST_F(NoneBuiltinsTest, DunderReprReturnsNone) {
ASSERT_FALSE(runFromCStr(runtime_, "a = None.__repr__()").isError());
HandleScope scope(thread_);
Object a(&scope, mainModuleAt(runtime_, "a"));
EXPECT_TRUE(isStrEqualsCStr(*a, "None"));
}
TEST_F(NoneBuiltinsTest, BuiltinBaseIsNone) {
HandleScope scope(thread_);
Type none_type(&scope, runtime_->typeAt(LayoutId::kNoneType));
EXPECT_EQ(none_type.builtinBase(), LayoutId::kNoneType);
}
TEST_F(ObjectBuiltinsTest,
InstanceDelAttrWithHiddenAttributeReturnsErrorNotFound) {
HandleScope scope(thread_);
LayoutId layout_id = LayoutId::kUserWarning;
Object previous_layout(&scope, runtime_->layoutAt(layout_id));
BuiltinAttribute attrs[] = {
{ID(__globals__), 0, AttributeFlags::kHidden},
};
Type type(&scope, addBuiltinType(thread_, ID(UserWarning), layout_id,
LayoutId::kObject, attrs,
/*size=*/kPointerSize,
/*basetype=*/true));
Layout layout(&scope, type.instanceLayout());
runtime_->layoutAtPut(layout_id, *layout);
Instance instance(&scope, runtime_->newInstance(layout));
Str attribute_name(&scope,
Runtime::internStrFromCStr(thread_, "__globals__"));
EXPECT_TRUE(
instanceDelAttr(thread_, instance, attribute_name).isErrorNotFound());
EXPECT_EQ(instance.layoutId(), layout.id());
runtime_->layoutAtPut(layout_id, *previous_layout);
}
TEST_F(ObjectBuiltinsTest,
InstanceDelAttrWithInObjectAttributeDeletesAttribute) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __init__(self):
self.foo = 42
instance = C()
)")
.isError());
HandleScope scope(thread_);
Instance instance(&scope, mainModuleAt(runtime_, "instance"));
Layout layout(&scope, runtime_->layoutOf(*instance));
Object name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
AttributeInfo info;
ASSERT_TRUE(Runtime::layoutFindAttribute(*layout, name, &info));
ASSERT_TRUE(info.isInObject());
EXPECT_TRUE(instanceDelAttr(thread_, instance, name).isNoneType());
EXPECT_TRUE(instanceGetAttribute(thread_, instance, name).isErrorNotFound());
EXPECT_TRUE(instanceDelAttr(thread_, instance, name).isErrorNotFound());
}
TEST_F(ObjectBuiltinsTest,
InstanceDelAttrWithTupleOverflowAttributeDeletesAttribute) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C: pass
instance = C()
instance.foo = 42
)")
.isError());
HandleScope scope(thread_);
Instance instance(&scope, mainModuleAt(runtime_, "instance"));
Layout layout(&scope, runtime_->layoutOf(*instance));
Object name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
AttributeInfo info;
ASSERT_TRUE(Runtime::layoutFindAttribute(*layout, name, &info));
ASSERT_TRUE(info.isOverflow());
EXPECT_TRUE(instanceDelAttr(thread_, instance, name).isNoneType());
EXPECT_TRUE(instanceGetAttribute(thread_, instance, name).isErrorNotFound());
EXPECT_TRUE(instanceDelAttr(thread_, instance, name).isErrorNotFound());
}
TEST_F(ObjectBuiltinsTest,
InstanceDelAttrWithNonexistentAttributeReturnsErrorNotFound) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C: pass
instance = C()
)")
.isError());
HandleScope scope(thread_);
Instance instance(&scope, mainModuleAt(runtime_, "instance"));
Object name(&scope, Runtime::internStrFromCStr(thread_, "does_not_exist"));
EXPECT_TRUE(instanceDelAttr(thread_, instance, name).isErrorNotFound());
}
TEST_F(ObjectBuiltinsTest,
InstanceDelAttrWithTupleOverflowAttributeKeepsOtherAttributes) {
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C: pass
instance = C()
instance.y = 2
instance.z = 3
)")
.isError());
HandleScope scope(thread_);
Instance instance(&scope, mainModuleAt(runtime_, "instance"));
Object y(&scope, Runtime::internStrFromCStr(thread_, "y"));
Object result(&scope, instanceDelAttr(thread_, instance, y));
EXPECT_TRUE(instanceGetAttribute(thread_, instance, y).isErrorNotFound());
EXPECT_TRUE(result.isNoneType());
Object z(&scope, Runtime::internStrFromCStr(thread_, "z"));
EXPECT_TRUE(isIntEqualsWord(instanceGetAttribute(thread_, instance, z), 3));
}
TEST_F(ObjectBuiltinsTest,
InstanceDelAttrWithReadonlyAttributeRaisesAttributeError) {
HandleScope scope(thread_);
LayoutId layout_id = LayoutId::kUserWarning;
Object previous_layout(&scope, runtime_->layoutAt(layout_id));
BuiltinAttribute attrs[] = {
{ID(__globals__), 0, AttributeFlags::kReadOnly},
};
Type type(&scope, addBuiltinType(thread_, ID(UserWarning), layout_id,
LayoutId::kObject, attrs,
/*size=*/kPointerSize,
/*basetype=*/true));
Layout layout(&scope, type.instanceLayout());
runtime_->layoutAtPut(layout_id, *layout);
Instance instance(&scope, runtime_->newInstance(layout));
Str attribute_name(&scope,
Runtime::internStrFromCStr(thread_, "__globals__"));
EXPECT_TRUE(raisedWithStr(instanceDelAttr(thread_, instance, attribute_name),
LayoutId::kAttributeError,
"'__globals__' attribute is read-only"));
EXPECT_EQ(instance.layoutId(), layout.id());
runtime_->layoutAtPut(layout_id, *previous_layout);
}
TEST_F(ObjectBuiltinsTest,
InstanceDelAttrWithDictOverflowAttributeDeletesAttribute) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def instance(): pass
instance.foo = 42
)")
.isError());
Instance instance(&scope, mainModuleAt(runtime_, "instance"));
Layout layout(&scope, runtime_->layoutOf(*instance));
ASSERT_TRUE(layout.hasDictOverflow());
Object name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
AttributeInfo info;
ASSERT_FALSE(Runtime::layoutFindAttribute(*layout, name, &info));
EXPECT_TRUE(instanceDelAttr(thread_, instance, name).isNoneType());
EXPECT_TRUE(instanceGetAttribute(thread_, instance, name).isErrorNotFound());
EXPECT_TRUE(instanceDelAttr(thread_, instance, name).isErrorNotFound());
}
TEST_F(
ObjectBuiltinsTest,
InstanceDelAttrWithNonexistentAttributeDictOverflowReturnsErrorNotFound) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def instance(): pass
)")
.isError());
Instance instance(&scope, mainModuleAt(runtime_, "instance"));
Layout layout(&scope, runtime_->layoutOf(*instance));
ASSERT_TRUE(layout.hasDictOverflow());
Object name(&scope, Runtime::internStrFromCStr(thread_, "does_not_exist"));
EXPECT_TRUE(instanceDelAttr(thread_, instance, name).isErrorNotFound());
}
TEST_F(ObjectBuiltinsTest,
InstanceGetAttributeWithInObjectAttributeReturnsValue) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __init__(self):
self.foo = 42
instance = C()
)")
.isError());
Instance instance(&scope, mainModuleAt(runtime_, "instance"));
Layout layout(&scope, runtime_->layoutOf(*instance));
ASSERT_TRUE(layout.hasTupleOverflow());
Object name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
AttributeInfo info;
ASSERT_TRUE(Runtime::layoutFindAttribute(*layout, name, &info));
ASSERT_TRUE(info.isInObject());
EXPECT_TRUE(
isIntEqualsWord(instanceGetAttribute(thread_, instance, name), 42));
}
TEST_F(ObjectBuiltinsTest,
InstanceGetAttributeWithTupleOverflowAttributeReturnsValue) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C: pass
instance = C()
instance.foo = 42
)")
.isError());
Instance instance(&scope, mainModuleAt(runtime_, "instance"));
Layout layout(&scope, runtime_->layoutOf(*instance));
ASSERT_TRUE(layout.hasTupleOverflow());
Object name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
AttributeInfo info;
ASSERT_TRUE(Runtime::layoutFindAttribute(*layout, name, &info));
ASSERT_TRUE(info.isOverflow());
EXPECT_TRUE(
isIntEqualsWord(instanceGetAttribute(thread_, instance, name), 42));
}
TEST_F(ObjectBuiltinsTest,
InstanceGetAttributeWithDictOverflowAttributeReturnsValue) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def instance(): pass
instance.foo = 42
)")
.isError());
Instance instance(&scope, mainModuleAt(runtime_, "instance"));
Layout layout(&scope, runtime_->layoutOf(*instance));
ASSERT_TRUE(layout.hasDictOverflow());
Object name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
AttributeInfo info;
ASSERT_FALSE(Runtime::layoutFindAttribute(*layout, name, &info));
EXPECT_TRUE(
isIntEqualsWord(instanceGetAttribute(thread_, instance, name), 42));
}
TEST_F(ObjectBuiltinsTest,
InstanceGetattributeWithHiddenAttributeReturnsErrorNotFound) {
HandleScope scope(thread_);
LayoutId layout_id = LayoutId::kUserWarning;
Object previous_layout(&scope, runtime_->layoutAt(layout_id));
BuiltinAttribute attrs[] = {
{ID(__globals__), 0, AttributeFlags::kHidden},
};
Type type(&scope, addBuiltinType(thread_, ID(UserWarning), layout_id,
LayoutId::kObject, attrs,
/*size=*/kPointerSize,
/*basetype=*/true));
Layout layout(&scope, type.instanceLayout());
runtime_->layoutAtPut(layout_id, *layout);
Instance instance(&scope, runtime_->newInstance(layout));
Str attribute_name(&scope,
Runtime::internStrFromCStr(thread_, "__globals__"));
EXPECT_TRUE(instanceGetAttribute(thread_, instance, attribute_name)
.isErrorNotFound());
EXPECT_EQ(instance.layoutId(), layout.id());
runtime_->layoutAtPut(layout_id, *previous_layout);
}
TEST_F(
ObjectBuiltinsTest,
InstanceGetAttributeWithNonExistentAttributeDictOverflowReturnsErrorNotFound) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def instance(): pass
)")
.isError());
Instance instance(&scope, mainModuleAt(runtime_, "instance"));
Layout layout(&scope, runtime_->layoutOf(*instance));
ASSERT_TRUE(layout.hasDictOverflow());
Object name(&scope, Runtime::internStrFromCStr(thread_, "does_not_exist"));
EXPECT_TRUE(instanceGetAttribute(thread_, instance, name).isErrorNotFound());
}
TEST_F(ObjectBuiltinsTest, InstanceSetAttrSetsInObjectAttribute) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __init__(self, set_value):
if set_value:
self.foo = 42
instance = C(False)
)")
.isError());
Instance instance(&scope, mainModuleAt(runtime_, "instance"));
Object name(&scope, Runtime::internStrFromCStr(thread_, "bar"));
Object value(&scope, runtime_->newInt(-7));
EXPECT_TRUE(instanceSetAttr(thread_, instance, name, value).isNoneType());
Layout layout(&scope, runtime_->layoutOf(*instance));
AttributeInfo info;
ASSERT_TRUE(Runtime::layoutFindAttribute(*layout, name, &info));
ASSERT_TRUE(info.isInObject());
EXPECT_TRUE(
isIntEqualsWord(instanceGetAttribute(thread_, instance, name), -7));
}
TEST_F(ObjectBuiltinsTest, InstanceSetAttrSetsNewTupleOverflowAttribute) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C: pass
instance = C()
)")
.isError());
Instance instance(&scope, mainModuleAt(runtime_, "instance"));
Object name(&scope, Runtime::internStrFromCStr(thread_, "bar"));
Object value(&scope, runtime_->newInt(-14));
EXPECT_TRUE(instanceSetAttr(thread_, instance, name, value).isNoneType());
Layout layout(&scope, runtime_->layoutOf(*instance));
Tuple overflow(&scope, instance.instanceVariableAt(layout.overflowOffset()));
EXPECT_EQ(overflow.length(), 1);
AttributeInfo info;
ASSERT_TRUE(Runtime::layoutFindAttribute(*layout, name, &info));
ASSERT_TRUE(info.isOverflow());
EXPECT_TRUE(
isIntEqualsWord(instanceGetAttribute(thread_, instance, name), -14));
}
TEST_F(ObjectBuiltinsTest, InstanceSetAttrSetsExistingTupleOverflowAttribute) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C: pass
instance = C()
instance.bar = 5000
)")
.isError());
Instance instance(&scope, mainModuleAt(runtime_, "instance"));
Layout layout(&scope, runtime_->layoutOf(*instance));
Tuple overflow(&scope, instance.instanceVariableAt(layout.overflowOffset()));
ASSERT_EQ(overflow.length(), 1);
Object name(&scope, Runtime::internStrFromCStr(thread_, "bar"));
Object value(&scope, runtime_->newInt(-14));
EXPECT_TRUE(instanceSetAttr(thread_, instance, name, value).isNoneType());
ASSERT_EQ(overflow.length(), 1);
AttributeInfo info;
ASSERT_TRUE(Runtime::layoutFindAttribute(*layout, name, &info));
ASSERT_TRUE(info.isOverflow());
EXPECT_TRUE(
isIntEqualsWord(instanceGetAttribute(thread_, instance, name), -14));
}
TEST_F(ObjectBuiltinsTest, InstanceSetAttrSetsDictOverflowAttribute) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def instance(): pass
)")
.isError());
Instance instance(&scope, mainModuleAt(runtime_, "instance"));
Object name(&scope, Runtime::internStrFromCStr(thread_, "bar"));
Object value(&scope, runtime_->newInt(4711));
Layout layout(&scope, runtime_->layoutOf(*instance));
ASSERT_TRUE(layout.hasDictOverflow());
EXPECT_TRUE(instanceSetAttr(thread_, instance, name, value).isNoneType());
EXPECT_EQ(instance.layoutId(), layout.id());
AttributeInfo info;
ASSERT_FALSE(Runtime::layoutFindAttribute(*layout, name, &info));
EXPECT_TRUE(
isIntEqualsWord(instanceGetAttribute(thread_, instance, name), 4711));
}
TEST_F(ObjectBuiltinsTest,
InstanceSetAttrWithHiddenAttributeRaisesAttributeError) {
HandleScope scope(thread_);
LayoutId layout_id = LayoutId::kUserWarning;
Object previous_layout(&scope, runtime_->layoutAt(layout_id));
BuiltinAttribute attrs[] = {
{ID(__globals__), 0, AttributeFlags::kHidden},
};
Type type(&scope, addBuiltinType(thread_, ID(UserWarning), layout_id,
LayoutId::kObject, attrs,
/*size=*/kPointerSize,
/*basetype=*/true));
Layout layout(&scope, type.instanceLayout());
runtime_->layoutAtPut(layout_id, *layout);
Instance instance(&scope, runtime_->newInstance(layout));
Str attribute_name(&scope,
Runtime::internStrFromCStr(thread_, "__globals__"));
Object value(&scope, runtime_->newInt(4711));
EXPECT_TRUE(
raisedWithStr(instanceSetAttr(thread_, instance, attribute_name, value),
LayoutId::kAttributeError,
"'UserWarning.__globals__' attribute cannot be set"));
EXPECT_EQ(instance.layoutId(), layout.id());
runtime_->layoutAtPut(layout_id, *previous_layout);
}
TEST_F(ObjectBuiltinsTest, ObjectGetAttributeReturnsInstanceValue) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C: pass
c = C()
c.__hash__ = 42
)")
.isError());
Object c(&scope, mainModuleAt(runtime_, "c"));
Object name(&scope, Runtime::internStrFromCStr(thread_, "__hash__"));
EXPECT_TRUE(isIntEqualsWord(objectGetAttribute(thread_, c, name), 42));
}
TEST_F(ObjectBuiltinsTest, ObjectGetAttributeReturnsTypeValue) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
x = -11
c = C()
)")
.isError());
Object c(&scope, mainModuleAt(runtime_, "c"));
Object name(&scope, Runtime::internStrFromCStr(thread_, "x"));
EXPECT_TRUE(isIntEqualsWord(objectGetAttribute(thread_, c, name), -11));
}
TEST_F(ObjectBuiltinsTest, ObjectGetAttributeWithNonExistentNameReturnsError) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C: pass
c = C()
)")
.isError());
Object c(&scope, mainModuleAt(runtime_, "c"));
Object name(&scope, Runtime::internStrFromCStr(thread_, "xxx"));
EXPECT_TRUE(objectGetAttribute(thread_, c, name).isError());
EXPECT_FALSE(thread_->hasPendingException());
}
TEST_F(ObjectBuiltinsTest, ObjectGetAttributeCallsDunderGetOnDataDescriptor) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class D:
def __set__(self, instance, value): pass
def __get__(self, instance, owner): return 42
class A:
foo = D()
a = A()
)")
.isError());
Object a(&scope, mainModuleAt(runtime_, "a"));
Object name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
EXPECT_TRUE(isIntEqualsWord(objectGetAttribute(thread_, a, name), 42));
}
TEST_F(ObjectBuiltinsTest,
ObjectGetAttributeCallsDunderGetOnNonDataDescriptor) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class D:
def __get__(self, instance, owner): return 42
class A:
foo = D()
a = A()
)")
.isError());
Object a(&scope, mainModuleAt(runtime_, "a"));
Object name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
EXPECT_TRUE(isIntEqualsWord(objectGetAttribute(thread_, a, name), 42));
}
TEST_F(ObjectBuiltinsTest,
ObjectGetAttributePrefersDataDescriptorOverInstanceAttr) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class D:
def __set__(self, instance, value): pass
def __get__(self, instance, owner): return 42
class A:
pass
a = A()
a.foo = 12
A.foo = D()
)")
.isError());
Object a(&scope, mainModuleAt(runtime_, "a"));
Object name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
EXPECT_TRUE(isIntEqualsWord(objectGetAttribute(thread_, a, name), 42));
}
TEST_F(ObjectBuiltinsTest,
ObjectGetAttributePrefersInstanceAttrOverNonDataDescriptor) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class D:
def __get__(self, instance, owner): return 42
class A:
foo = D()
a = A()
a.foo = 12
)")
.isError());
Object a(&scope, mainModuleAt(runtime_, "a"));
Object name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
EXPECT_TRUE(isIntEqualsWord(objectGetAttribute(thread_, a, name), 12));
}
TEST_F(ObjectBuiltinsTest, ObjectGetAttributePropagatesDunderGetException) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class D:
def __set__(self, instance, value): pass
def __get__(self, instance, owner): raise UserWarning()
class A:
foo = D()
a = A()
)")
.isError());
Object a(&scope, mainModuleAt(runtime_, "a"));
Object name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
EXPECT_TRUE(
raised(objectGetAttribute(thread_, a, name), LayoutId::kUserWarning));
}
TEST_F(ObjectBuiltinsTest,
ObjectGetAttributeOnNoneNonDataDescriptorReturnsBoundMethod) {
HandleScope scope(thread_);
Object none(&scope, NoneType::object());
Object name(&scope, Runtime::internStrFromCStr(thread_, "__repr__"));
EXPECT_TRUE(objectGetAttribute(thread_, none, name).isBoundMethod());
}
TEST_F(ObjectBuiltinsTest,
ObjectGetAttributeSetLocationReturnsBoundMethodAndCachesFunction) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def foo():
pass
foo = C.foo
i = C()
)")
.isError());
Object foo(&scope, mainModuleAt(runtime_, "foo"));
Object i(&scope, mainModuleAt(runtime_, "i"));
Object name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
Object to_cache(&scope, NoneType::object());
LoadAttrKind kind = LoadAttrKind::kUnknown;
Object result_obj(&scope, objectGetAttributeSetLocation(thread_, i, name,
&to_cache, &kind));
ASSERT_TRUE(result_obj.isBoundMethod());
BoundMethod result(&scope, *result_obj);
EXPECT_EQ(result.function(), foo);
EXPECT_EQ(result.self(), i);
EXPECT_EQ(to_cache, foo);
EXPECT_EQ(kind, LoadAttrKind::kInstanceFunction);
Object load_cached_result_obj(
&scope, Interpreter::loadAttrWithLocation(thread_, *i, *to_cache));
ASSERT_TRUE(load_cached_result_obj.isBoundMethod());
BoundMethod load_cached_result(&scope, *load_cached_result_obj);
EXPECT_EQ(load_cached_result.function(), foo);
EXPECT_EQ(load_cached_result.self(), i);
}
TEST_F(ObjectBuiltinsTest,
ObjectGetAttributeSetLocationReturnsInstanceVariableAndCachesOffset) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __init__(self):
self.foo = 42
i = C()
)")
.isError());
Object i(&scope, mainModuleAt(runtime_, "i"));
Layout layout(&scope, runtime_->layoutOf(*i));
Object name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
AttributeInfo info;
ASSERT_TRUE(Runtime::layoutFindAttribute(*layout, name, &info));
ASSERT_TRUE(info.isInObject());
Object to_cache(&scope, NoneType::object());
LoadAttrKind kind = LoadAttrKind::kUnknown;
EXPECT_TRUE(isIntEqualsWord(
objectGetAttributeSetLocation(thread_, i, name, &to_cache, &kind), 42));
EXPECT_TRUE(isIntEqualsWord(*to_cache, info.offset()));
EXPECT_EQ(kind, LoadAttrKind::kInstanceOffset);
EXPECT_TRUE(isIntEqualsWord(
Interpreter::loadAttrWithLocation(thread_, *i, *to_cache), 42));
}
TEST_F(
ObjectBuiltinsTest,
ObjectGetAttributeSetLocationReturnsInstanceVariableAndCachesNegativeOffset) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
pass
i = C()
i.foo = 17
)")
.isError());
Object i(&scope, mainModuleAt(runtime_, "i"));
Layout layout(&scope, runtime_->layoutOf(*i));
Object name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
AttributeInfo info;
ASSERT_TRUE(Runtime::layoutFindAttribute(*layout, name, &info));
ASSERT_TRUE(info.isOverflow());
Object to_cache(&scope, NoneType::object());
LoadAttrKind kind = LoadAttrKind::kUnknown;
EXPECT_TRUE(isIntEqualsWord(
objectGetAttributeSetLocation(thread_, i, name, &to_cache, &kind), 17));
EXPECT_TRUE(isIntEqualsWord(*to_cache, -info.offset() - 1));
EXPECT_TRUE(isIntEqualsWord(
Interpreter::loadAttrWithLocation(thread_, *i, *to_cache), 17));
}
TEST_F(ObjectBuiltinsTest,
ObjectGetAttributeSetLocationRaisesAttributeErrorAndDoesNotSetLocation) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
pass
i = C()
)")
.isError());
Object i(&scope, mainModuleAt(runtime_, "i"));
Object name(&scope, Runtime::internStrFromCStr(thread_, "xxx"));
Object to_cache(&scope, NoneType::object());
LoadAttrKind kind = LoadAttrKind::kUnknown;
EXPECT_TRUE(objectGetAttributeSetLocation(thread_, i, name, &to_cache, &kind)
.isError());
EXPECT_TRUE(to_cache.isNoneType());
EXPECT_EQ(kind, LoadAttrKind::kUnknown);
}
TEST_F(ObjectBuiltinsTest, ObjectGetItemCallsTypeObjectDunderClassItem) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __class_getitem__(cls, item):
return f"C:{cls.__name__}[{item.__name__}]"
)")
.isError());
Type type_c(&scope, mainModuleAt(runtime_, "C"));
Type key(&scope, runtime_->typeAt(LayoutId::kInt));
Object result(&scope, objectGetItem(thread_, type_c, key));
EXPECT_TRUE(isStrEqualsCStr(*result, "C:C[int]"));
}
TEST_F(ObjectBuiltinsTest, ObjectSetAttrSetsInstanceValue) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C: pass
i = C()
)")
.isError());
Object i(&scope, mainModuleAt(runtime_, "i"));
Object name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
Object value(&scope, runtime_->newInt(47));
EXPECT_TRUE(objectSetAttr(thread_, i, name, value).isNoneType());
EXPECT_TRUE(isIntEqualsWord(objectGetAttribute(thread_, i, name), 47));
}
TEST_F(ObjectBuiltinsTest, ObjectSetAttrOnDataDescriptorCallsDunderSet) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class D:
def __set__(self, instance, value):
global set_args
set_args = (self, instance, value)
return "ignored result"
def __get__(self, instance, owner): pass
foo_descr = D()
class C:
foo = foo_descr
i = C()
)")
.isError());
Object i(&scope, mainModuleAt(runtime_, "i"));
Object foo_descr(&scope, mainModuleAt(runtime_, "foo_descr"));
Object name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
Object value(&scope, runtime_->newInt(47));
EXPECT_TRUE(objectSetAttr(thread_, i, name, value).isNoneType());
Object set_args_obj(&scope, mainModuleAt(runtime_, "set_args"));
ASSERT_TRUE(set_args_obj.isTuple());
Tuple dunder_set_args(&scope, *set_args_obj);
ASSERT_EQ(dunder_set_args.length(), 3);
EXPECT_EQ(dunder_set_args.at(0), foo_descr);
EXPECT_EQ(dunder_set_args.at(1), i);
EXPECT_TRUE(isIntEqualsWord(dunder_set_args.at(2), 47));
}
TEST_F(ObjectBuiltinsTest, ObjectSetAttrPropagatesErrorsInDunderSet) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class D:
def __set__(self, instance, value): raise UserWarning()
def __get__(self, instance, owner): pass
class C:
foo = D()
i = C()
)")
.isError());
Object i(&scope, mainModuleAt(runtime_, "i"));
Object name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
Object value(&scope, runtime_->newInt(1));
EXPECT_TRUE(
raised(objectSetAttr(thread_, i, name, value), LayoutId::kUserWarning));
}
TEST_F(ObjectBuiltinsTest, ObjectSetAttrOnNonHeapObjectRaisesAttributeError) {
HandleScope scope(thread_);
Object object(&scope, runtime_->newInt(42));
Object name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
Object value(&scope, runtime_->newInt(1));
EXPECT_TRUE(raisedWithStr(objectSetAttr(thread_, object, name, value),
LayoutId::kAttributeError,
"'int' object has no attribute 'foo'"));
}
TEST_F(ObjectBuiltinsTest, ObjectSetAttrSetLocationSetsValueCachesOffset) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __init__(self):
self.foo = 0
i = C()
)")
.isError());
Object i(&scope, mainModuleAt(runtime_, "i"));
Object name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
AttributeInfo info;
Layout layout(&scope, runtime_->layoutOf(*i));
ASSERT_TRUE(Runtime::layoutFindAttribute(*layout, name, &info));
ASSERT_TRUE(info.isInObject());
Object value(&scope, runtime_->newInt(7));
Object value2(&scope, runtime_->newInt(99));
Object to_cache(&scope, NoneType::object());
EXPECT_TRUE(objectSetAttrSetLocation(thread_, i, name, value, &to_cache)
.isNoneType());
EXPECT_TRUE(isIntEqualsWord(*to_cache, info.offset()));
ASSERT_TRUE(i.isInstance());
Instance instance(&scope, *i);
EXPECT_TRUE(isIntEqualsWord(instance.instanceVariableAt(info.offset()), 7));
Interpreter::storeAttrWithLocation(thread_, *i, *to_cache, *value2);
EXPECT_TRUE(isIntEqualsWord(instance.instanceVariableAt(info.offset()), 99));
}
TEST_F(ObjectBuiltinsTest,
ObjectSetAttrSetLocationSetsOverflowValueCachesOffset) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C: pass
i = C()
i.foo = 0
)")
.isError());
Object i(&scope, mainModuleAt(runtime_, "i"));
Object name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
AttributeInfo info;
Layout layout(&scope, runtime_->layoutOf(*i));
ASSERT_TRUE(Runtime::layoutFindAttribute(*layout, name, &info));
ASSERT_TRUE(info.isOverflow());
Object value(&scope, runtime_->newInt(-8));
Object value2(&scope, runtime_->newInt(11));
Object to_cache(&scope, NoneType::object());
EXPECT_TRUE(objectSetAttrSetLocation(thread_, i, name, value, &to_cache)
.isNoneType());
EXPECT_TRUE(isIntEqualsWord(*to_cache, -info.offset() - 1));
ASSERT_TRUE(i.isHeapObject());
Instance instance(&scope, *i);
EXPECT_TRUE(
isIntEqualsWord(instanceGetAttribute(thread_, instance, name), -8));
Interpreter::storeAttrWithLocation(thread_, *i, *to_cache, *value2);
EXPECT_TRUE(
isIntEqualsWord(instanceGetAttribute(thread_, instance, name), 11));
}
} // namespace testing
} // namespace py