ext/Objects/dictobject-test.cpp (1,299 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
#include "Python.h"
#include "gtest/gtest.h"
#include "capi-fixture.h"
#include "capi-testing.h"
namespace py {
namespace testing {
using DictExtensionApiTest = ExtensionApi;
TEST_F(DictExtensionApiTest, GetItemFromNonDictReturnsNull) {
// Pass a non dictionary
PyObject* result = PyDict_GetItem(Py_None, Py_None);
EXPECT_EQ(result, nullptr);
EXPECT_EQ(PyErr_Occurred(), nullptr);
}
TEST_F(DictExtensionApiTest, GetItemNonExistingKeyReturnsNull) {
PyObjectPtr dict(PyDict_New());
PyObjectPtr nonkey(PyLong_FromLong(10));
// Pass a non existing key
PyObject* result = PyDict_GetItem(dict, nonkey);
EXPECT_EQ(result, nullptr);
EXPECT_EQ(PyErr_Occurred(), nullptr);
}
TEST_F(DictExtensionApiTest, GetItemReturnsBorrowedValue) {
PyObject* dict = PyDict_New();
PyObject* key = PyLong_FromLong(10);
PyObject* value = PyLong_FromLong(0);
// Insert the value into the dictionary
ASSERT_EQ(PyDict_SetItem(dict, key, value), 0);
// Record the reference count of the value
long refcnt = Py_REFCNT(value);
// Get a new reference to the value from the dictionary
PyObject* value2 = PyDict_GetItem(dict, key);
// The new reference should be equal to the original reference
EXPECT_EQ(value2, value);
// The reference count should not be affected
EXPECT_EQ(Py_REFCNT(value), refcnt);
Py_DECREF(value);
Py_DECREF(key);
Py_DECREF(dict);
}
TEST_F(DictExtensionApiTest, GetItemWithDictSubclassReturnsValue) {
PyRun_SimpleString(R"(
class Foo(dict): pass
obj = Foo()
)");
PyObjectPtr obj(mainModuleGet("obj"));
PyObjectPtr key(PyLong_FromLong(1));
PyObjectPtr val(PyLong_FromLong(2));
ASSERT_EQ(PyDict_SetItem(obj, key, val), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
PyObject* result = PyDict_GetItem(obj, key);
EXPECT_EQ(result, val);
EXPECT_EQ(PyErr_Occurred(), nullptr);
}
TEST_F(DictExtensionApiTest, GetItemWithBigHashTruncatesHash) {
PyRun_SimpleString(R"(
class C:
def __init__(self, v):
self.v = v
def __hash__(self):
return 1180591620717411303424
def __eq__(self, other):
return self.v == other.v
c1 = C(4)
c2 = C(5)
)");
PyObjectPtr c1(mainModuleGet("c1"));
PyObjectPtr c2(mainModuleGet("c2"));
PyObjectPtr v1(PyLong_FromLong(1));
PyObjectPtr v2(PyLong_FromLong(2));
PyObjectPtr dict(PyDict_New());
ASSERT_EQ(PyDict_SetItem(dict, c1, v1), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
ASSERT_EQ(PyDict_SetItem(dict, c2, v2), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
PyObject* result = PyDict_GetItem(dict, c1);
EXPECT_EQ(PyErr_Occurred(), nullptr);
EXPECT_EQ(result, v1);
}
TEST_F(DictExtensionApiTest, GetItemWithIntSubclassHashUsesInt) {
PyRun_SimpleString(R"(
class H(int):
pass
class C:
def __init__(self, v):
self.v = v
def __hash__(self):
return H(42)
def __eq__(self, other):
return self.v == other.v
c = C(4)
)");
PyObjectPtr c(mainModuleGet("c"));
PyObjectPtr v(PyLong_FromLong(1));
PyObjectPtr dict(PyDict_New());
ASSERT_EQ(PyDict_SetItem(dict, c, v), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
PyObject* result = PyDict_GetItem(dict, c);
EXPECT_EQ(PyErr_Occurred(), nullptr);
EXPECT_EQ(result, v);
}
TEST_F(DictExtensionApiTest, GetItemWithSameIdentityReturnsObject) {
ASSERT_EQ(PyRun_SimpleString(R"(
called_dunder_eq = False
class C:
def __eq__(self, other):
global called_dunder_eq
called_dunder_eq = True
def __hash__(self):
return 5
c = C()
d = {}
d[c] = 4
)"),
0);
PyObjectPtr c(mainModuleGet("c"));
PyObjectPtr dict(mainModuleGet("d"));
PyObject* result = PyDict_GetItem(dict, c);
ASSERT_EQ(PyErr_Occurred(), nullptr);
PyObjectPtr called_dunder_eq(mainModuleGet("called_dunder_eq"));
ASSERT_EQ(called_dunder_eq, Py_False);
EXPECT_EQ(PyLong_AsLong(result), 4);
}
TEST_F(DictExtensionApiTest, GetItemWithDifferentHashReturnsNull) {
ASSERT_EQ(PyRun_SimpleString(R"(
called_dunder_eq = False
class C:
def __init__(self, h):
self.h = h
def __eq__(self, other):
global called_dunder_eq
called_dunder_eq = True
return True
def __hash__(self):
return self.h
c = C(1)
d = {}
d[C(2)] = 2
)"),
0);
PyObjectPtr c(mainModuleGet("c"));
PyObjectPtr dict(mainModuleGet("d"));
PyObject* result = PyDict_GetItem(dict, c);
ASSERT_EQ(PyErr_Occurred(), nullptr);
PyObjectPtr called_dunder_eq(mainModuleGet("called_dunder_eq"));
ASSERT_EQ(called_dunder_eq, Py_False);
EXPECT_EQ(result, nullptr);
}
TEST_F(DictExtensionApiTest, GetItemWithDunderEqReturnsObject) {
ASSERT_EQ(PyRun_SimpleString(R"(
called_dunder_eq = False
class C:
def __eq__(self, other):
global called_dunder_eq
called_dunder_eq = True
return True
def __hash__(self):
return 5
d = {}
c = C()
d[C()] = 4
)"),
0);
PyObjectPtr c(mainModuleGet("c"));
PyObjectPtr dict(mainModuleGet("d"));
PyObject* result = PyDict_GetItem(dict, c);
ASSERT_EQ(PyErr_Occurred(), nullptr);
PyObjectPtr called_dunder_eq(mainModuleGet("called_dunder_eq"));
ASSERT_EQ(called_dunder_eq, Py_True);
EXPECT_EQ(PyLong_AsLong(result), 4);
}
TEST_F(DictExtensionApiTest, GetItemWithFalseDunderEqReturnsNull) {
ASSERT_EQ(PyRun_SimpleString(R"(
called_dunder_eq = False
class C:
def __eq__(self, other):
global called_dunder_eq
called_dunder_eq = True
return False
def __hash__(self):
return 5
c = C()
d = {}
d[C()] = 4
)"),
0);
PyObjectPtr c(mainModuleGet("c"));
PyObjectPtr dict(mainModuleGet("d"));
PyObject* result = PyDict_GetItem(dict, c);
ASSERT_EQ(PyErr_Occurred(), nullptr);
PyObjectPtr called_dunder_eq(mainModuleGet("called_dunder_eq"));
ASSERT_EQ(called_dunder_eq, Py_True);
EXPECT_EQ(result, nullptr);
}
TEST_F(DictExtensionApiTest,
GetItemWithExceptionDunderEqSwallowsAndReturnsNull) {
ASSERT_EQ(PyRun_SimpleString(R"(
called_dunder_eq = False
class C:
def __eq__(self, other):
global called_dunder_eq
called_dunder_eq = True
raise ValueError('foo')
def __hash__(self):
return 5
c = C()
d = {}
d[C()] = 4
)"),
0);
PyObjectPtr c(mainModuleGet("c"));
PyObjectPtr dict(mainModuleGet("d"));
PyObject* result = PyDict_GetItem(dict, c);
ASSERT_EQ(PyErr_Occurred(), nullptr);
PyObjectPtr called_dunder_eq(mainModuleGet("called_dunder_eq"));
ASSERT_EQ(called_dunder_eq, Py_True);
EXPECT_EQ(result, nullptr);
}
TEST_F(DictExtensionApiTest, GetItemWithNotImplementedDunderEqReturnsNull) {
ASSERT_EQ(PyRun_SimpleString(R"(
called_dunder_eq = False
class C:
def __eq__(self, other):
global called_dunder_eq
called_dunder_eq = True
return NotImplemented
def __hash__(self):
return 5
c = C()
d = {}
d[C()] = 4
)"),
0);
PyObjectPtr c(mainModuleGet("c"));
PyObjectPtr dict(mainModuleGet("d"));
PyObject* result = PyDict_GetItem(dict, c);
ASSERT_EQ(PyErr_Occurred(), nullptr);
PyObjectPtr called_dunder_eq(mainModuleGet("called_dunder_eq"));
ASSERT_EQ(called_dunder_eq, Py_True);
EXPECT_EQ(result, nullptr);
}
TEST_F(DictExtensionApiTest,
GetItemCallsExistingKeyDunderEqAndThenLookedKeyDunderEq) {
ASSERT_EQ(PyRun_SimpleString(R"(
seq_num = 0
def new_seq_num():
global seq_num
seq_num += 1
return seq_num
c_eq = 0
c_hash = 0
class C:
def __eq__(self, other):
global c_eq
c_eq = new_seq_num()
return NotImplemented
def __hash__(self):
global c_hash
c_hash = new_seq_num()
return 5
c = C()
d_eq = 0
d_hash = 0
class D:
def __eq__(self, other):
global d_eq
d_eq = new_seq_num()
return True
def __hash__(self):
global d_hash
d_hash = new_seq_num()
return 5
d = D()
)"),
0);
PyObjectPtr dict(PyDict_New());
PyObjectPtr c(mainModuleGet("c"));
PyObjectPtr value(PyLong_FromLong(500));
ASSERT_EQ(PyDict_SetItem(dict, c, value), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
ASSERT_EQ(PyDict_Size(dict), 1);
PyObjectPtr c_eq(mainModuleGet("c_eq"));
ASSERT_EQ(PyLong_AsLong(c_eq), 0);
PyObjectPtr c_hash(mainModuleGet("c_hash"));
ASSERT_EQ(PyLong_AsLong(c_hash), 1);
PyObjectPtr d(mainModuleGet("d"));
PyObject* result = PyDict_GetItem(dict, d);
ASSERT_EQ(PyErr_Occurred(), nullptr);
ASSERT_EQ(PyLong_AsLong(result), 500);
c_hash = mainModuleGet("c_hash");
EXPECT_EQ(PyLong_AsLong(c_hash), 1);
PyObjectPtr d_hash(mainModuleGet("d_hash"));
EXPECT_EQ(PyLong_AsLong(d_hash), 2);
c_eq = mainModuleGet("c_eq");
EXPECT_EQ(PyLong_AsLong(c_eq), 3);
PyObjectPtr d_eq(mainModuleGet("d_eq"));
EXPECT_EQ(PyLong_AsLong(d_eq), 4);
}
TEST_F(DictExtensionApiTest, GetItemComparesHashValueFirst) {
ASSERT_EQ(PyRun_SimpleString(R"(
class C:
def __init__(self, hash_code):
self.hash_code = hash_code
def __eq__(self, other):
raise UserWarning("unexpected")
def __hash__(self):
return self.hash_code
c = C(4)
d = C(5)
)"),
0);
PyObjectPtr dict(PyDict_New());
PyObjectPtr c(mainModuleGet("c"));
PyObjectPtr value(PyLong_FromLong(500));
ASSERT_EQ(PyDict_SetItem(dict, c, value), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
ASSERT_EQ(PyDict_Size(dict), 1);
PyObjectPtr d(mainModuleGet("d"));
ASSERT_EQ(PyDict_GetItem(dict, d), nullptr);
ASSERT_EQ(PyErr_Occurred(), nullptr);
}
TEST_F(DictExtensionApiTest, GetItemKnownHashFromNonDictRaisesSystemError) {
// Pass a non dictionary
PyObject* result = _PyDict_GetItem_KnownHash(Py_None, Py_None, 0);
EXPECT_EQ(result, nullptr);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_SystemError));
}
TEST_F(DictExtensionApiTest, GetItemKnownHashNonExistingKeyReturnsNull) {
PyObjectPtr dict(PyDict_New());
PyObjectPtr nonkey(PyLong_FromLong(11));
// Pass a non existing key
PyObject* result = _PyDict_GetItem_KnownHash(dict, nonkey, 0);
EXPECT_EQ(result, nullptr);
EXPECT_EQ(PyErr_Occurred(), nullptr);
}
TEST_F(DictExtensionApiTest, GetItemKnownHashReturnsBorrowedValue) {
PyObject* dict = PyDict_New();
PyObject* key = PyLong_FromLong(10);
PyObject* value = PyLong_FromLong(0);
// Insert the value into the dictionary
Py_hash_t hash = Py_hash_t{1} << ((sizeof(Py_hash_t) * CHAR_BIT) - 1);
ASSERT_EQ(_PyDict_SetItem_KnownHash(dict, key, value, hash), 0);
// Record the reference count of the value
long refcnt = Py_REFCNT(value);
// Get a new reference to the value from the dictionary
PyObject* value2 = _PyDict_GetItem_KnownHash(dict, key, hash);
// The new reference should be equal to the original reference
EXPECT_EQ(value2, value);
// The reference count should not be affected
EXPECT_EQ(Py_REFCNT(value), refcnt);
Py_DECREF(value);
Py_DECREF(key);
Py_DECREF(dict);
}
TEST_F(DictExtensionApiTest, GetItemStringReturnsValue) {
PyObjectPtr dict(PyDict_New());
const char* key_cstr = "key";
PyObjectPtr key(PyUnicode_FromString(key_cstr));
PyObjectPtr value(PyLong_FromLong(0));
ASSERT_EQ(PyDict_SetItem(dict, key, value), 0);
PyObject* item = PyDict_GetItemString(dict, key_cstr);
EXPECT_EQ(item, value);
}
TEST_F(DictExtensionApiTest, SetItemWithNonDictRaisesSystemError) {
PyObjectPtr set(PySet_New(nullptr));
PyObjectPtr key(PyLong_FromLong(0));
PyObjectPtr val(PyLong_FromLong(0));
ASSERT_EQ(PyDict_SetItem(set, key, val), -1);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_SystemError));
}
TEST_F(DictExtensionApiTest, SetItemWithNewDictReturnsZero) {
PyObjectPtr dict(PyDict_New());
PyObjectPtr key(PyLong_FromLong(0));
PyObjectPtr val(PyLong_FromLong(0));
EXPECT_EQ(PyDict_SetItem(dict, key, val), 0);
EXPECT_EQ(PyErr_Occurred(), nullptr);
}
TEST_F(DictExtensionApiTest, SetItemWithNewDictSubclassReturnsZero) {
PyRun_SimpleString(R"(
class Foo(dict): pass
obj = Foo()
)");
PyObjectPtr obj(mainModuleGet("obj"));
PyObjectPtr key(PyLong_FromLong(0));
PyObjectPtr val(PyLong_FromLong(0));
EXPECT_EQ(PyDict_SetItem(obj, key, val), 0);
EXPECT_EQ(PyErr_Occurred(), nullptr);
}
TEST_F(DictExtensionApiTest,
SetItemWithDunderHashReturningNonIntRaisesTypeError) {
PyRun_SimpleString(R"(
class C:
def __hash__(self):
return "foo"
def __eq__(self, other):
return self == other
c = C()
)");
PyObjectPtr dict(PyDict_New());
PyObjectPtr key(mainModuleGet("c"));
PyObjectPtr val(PyLong_FromLong(0));
ASSERT_EQ(PyDict_SetItem(dict, key, val), -1);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_TypeError));
}
TEST_F(DictExtensionApiTest, SetItemWithIntSubclassHashReturnsZero) {
PyRun_SimpleString(R"(
class H(int):
pass
class C:
def __init__(self, v):
self.v = v
def __hash__(self):
return H(42)
def __eq__(self, other):
return self.v == other.v
c = C(4)
)");
PyObjectPtr c(mainModuleGet("c"));
PyObjectPtr v(PyLong_FromLong(1));
PyObjectPtr dict(PyDict_New());
EXPECT_EQ(PyDict_SetItem(dict, c, v), 0);
EXPECT_EQ(PyErr_Occurred(), nullptr);
}
TEST_F(DictExtensionApiTest, SetItemWithSameIdentitySupersedesValue) {
ASSERT_EQ(PyRun_SimpleString(R"(
called_dunder_eq = False
class C:
def __eq__(self, other):
global called_dunder_eq
called_dunder_eq = True
def __hash__(self):
return 5
c = C()
d = {}
d[c] = 0
)"),
0);
PyObjectPtr c(mainModuleGet("c"));
PyObjectPtr dict(mainModuleGet("d"));
PyObjectPtr value(PyLong_FromLong(1));
ASSERT_EQ(PyDict_SetItem(dict, c, value), 0);
ASSERT_EQ(PyDict_Size(dict), 1);
PyObject* result = PyDict_GetItem(dict, c);
ASSERT_EQ(PyErr_Occurred(), nullptr);
PyObjectPtr called_dunder_eq(mainModuleGet("called_dunder_eq"));
ASSERT_EQ(called_dunder_eq, Py_False);
EXPECT_EQ(value, result);
}
TEST_F(DictExtensionApiTest, SetItemWithDifferentHashInsertsValue) {
ASSERT_EQ(PyRun_SimpleString(R"(
called_dunder_eq = False
class C:
def __init__(self, h):
self.h = h
def __eq__(self, other):
global called_dunder_eq
called_dunder_eq = True
return True
def __hash__(self):
return self.h
c = C(1)
d = {}
d[C(2)] = 2
)"),
0);
PyObjectPtr c(mainModuleGet("c"));
PyObjectPtr dict(mainModuleGet("d"));
PyObjectPtr value(PyLong_FromLong(1));
ASSERT_EQ(PyDict_SetItem(dict, c, value), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
PyObjectPtr called_dunder_eq(mainModuleGet("called_dunder_eq"));
ASSERT_EQ(called_dunder_eq, Py_False);
EXPECT_EQ(PyDict_Size(dict), 2);
}
TEST_F(DictExtensionApiTest, SetItemWithDunderEqSupersedesValue) {
ASSERT_EQ(PyRun_SimpleString(R"(
called_dunder_eq = False
class C:
def __eq__(self, other):
global called_dunder_eq
called_dunder_eq = True
return True
def __hash__(self):
return 5
c = C()
d = {}
d[C()] = 0
)"),
0);
PyObjectPtr c(mainModuleGet("c"));
PyObjectPtr dict(mainModuleGet("d"));
PyObjectPtr value(PyLong_FromLong(1));
ASSERT_EQ(PyDict_SetItem(dict, c, value), 0);
ASSERT_EQ(PyDict_Size(dict), 1);
PyObject* result = PyDict_GetItem(dict, c);
ASSERT_EQ(PyErr_Occurred(), nullptr);
PyObjectPtr called_dunder_eq(mainModuleGet("called_dunder_eq"));
ASSERT_EQ(called_dunder_eq, Py_True);
EXPECT_EQ(value, result);
}
TEST_F(DictExtensionApiTest, SetItemWithFalseDunderEqInsertsValue) {
ASSERT_EQ(PyRun_SimpleString(R"(
called_dunder_eq = False
class C:
def __eq__(self, other):
global called_dunder_eq
called_dunder_eq = True
return False
def __hash__(self):
return 5
c = C()
d = {}
d[C()] = 0
)"),
0);
PyObjectPtr c(mainModuleGet("c"));
PyObjectPtr dict(mainModuleGet("d"));
PyObjectPtr value(PyLong_FromLong(1));
ASSERT_EQ(PyDict_SetItem(dict, c, value), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
PyObjectPtr called_dunder_eq(mainModuleGet("called_dunder_eq"));
ASSERT_EQ(called_dunder_eq, Py_True);
EXPECT_EQ(PyDict_Size(dict), 2);
}
TEST_F(DictExtensionApiTest, SetItemWithExceptionDunderEqRaisesException) {
ASSERT_EQ(PyRun_SimpleString(R"(
called_dunder_eq = False
class C:
def __eq__(self, other):
global called_dunder_eq
called_dunder_eq = True
raise ValueError('foo')
def __hash__(self):
return 5
c = C()
d = {}
d[C()] = 0
)"),
0);
PyObjectPtr c(mainModuleGet("c"));
PyObjectPtr dict(mainModuleGet("d"));
PyObjectPtr value(PyLong_FromLong(1));
EXPECT_EQ(PyDict_SetItem(dict, c, value), -1);
EXPECT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_ValueError));
PyErr_Clear();
PyObjectPtr called_dunder_eq(mainModuleGet("called_dunder_eq"));
ASSERT_EQ(called_dunder_eq, Py_True);
EXPECT_EQ(PyDict_Size(dict), 1);
}
TEST_F(DictExtensionApiTest, SetItemWithNotImplementedDunderEqInsertsValue) {
ASSERT_EQ(PyRun_SimpleString(R"(
called_dunder_eq = False
class C:
def __eq__(self, other):
global called_dunder_eq
called_dunder_eq = True
return NotImplemented
def __hash__(self):
return 5
c = C()
d = {}
d[C()] = 0
)"),
0);
PyObjectPtr c(mainModuleGet("c"));
PyObjectPtr dict(mainModuleGet("d"));
PyObjectPtr value(PyLong_FromLong(1));
ASSERT_EQ(PyDict_SetItem(dict, c, value), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
PyObjectPtr called_dunder_eq(mainModuleGet("called_dunder_eq"));
ASSERT_EQ(called_dunder_eq, Py_True);
EXPECT_EQ(PyDict_Size(dict), 2);
}
TEST_F(DictExtensionApiTest,
SetItemCallsExistingKeyDunderEqAndThenLookedKeyDunderEq) {
ASSERT_EQ(PyRun_SimpleString(R"(
seq_num = 0
def new_seq_num():
global seq_num
seq_num += 1
return seq_num
c_eq = 0
c_hash = 0
class C:
def __eq__(self, other):
global c_eq
c_eq = new_seq_num()
return NotImplemented
def __hash__(self):
global c_hash
c_hash = new_seq_num()
return 5
c = C()
d_eq = 0
d_hash = 0
class D:
def __eq__(self, other):
global d_eq
d_eq = new_seq_num()
return True
def __hash__(self):
global d_hash
d_hash = new_seq_num()
return 5
d = D()
)"),
0);
PyObjectPtr dict(PyDict_New());
PyObjectPtr c(mainModuleGet("c"));
PyObjectPtr value(PyLong_FromLong(1));
ASSERT_EQ(PyDict_SetItem(dict, c, value), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
ASSERT_EQ(PyDict_Size(dict), 1);
PyObjectPtr c_eq(mainModuleGet("c_eq"));
ASSERT_EQ(PyLong_AsLong(c_eq), 0);
PyObjectPtr c_hash(mainModuleGet("c_hash"));
ASSERT_EQ(PyLong_AsLong(c_hash), 1);
PyObjectPtr d(mainModuleGet("d"));
ASSERT_EQ(PyDict_SetItem(dict, d, value), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
ASSERT_EQ(PyDict_Size(dict), 1);
c_hash = mainModuleGet("c_hash");
EXPECT_EQ(PyLong_AsLong(c_hash), 1);
PyObjectPtr d_hash(mainModuleGet("d_hash"));
EXPECT_EQ(PyLong_AsLong(d_hash), 2);
c_eq = mainModuleGet("c_eq");
EXPECT_EQ(PyLong_AsLong(c_eq), 3);
PyObjectPtr d_eq(mainModuleGet("d_eq"));
EXPECT_EQ(PyLong_AsLong(d_eq), 4);
}
TEST_F(DictExtensionApiTest, SetItemRetainsExistingKeyObject) {
ASSERT_EQ(PyRun_SimpleString(R"(
class C:
def __eq__(self, other):
return True
def __hash__(self):
return 5
c = C()
d = C()
)"),
0);
PyObjectPtr dict(PyDict_New());
PyObjectPtr c(mainModuleGet("c"));
PyObjectPtr d(mainModuleGet("d"));
PyObjectPtr c_value(PyLong_FromLong(1));
PyObjectPtr d_value(PyLong_FromLong(2));
ASSERT_EQ(PyDict_SetItem(dict, c, c_value), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
ASSERT_EQ(PyDict_Size(dict), 1);
ASSERT_EQ(PyDict_SetItem(dict, d, d_value), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
ASSERT_EQ(PyDict_Size(dict), 1);
PyObjectPtr result(PyDict_Items(dict));
ASSERT_NE(result, nullptr);
ASSERT_EQ(PyErr_Occurred(), nullptr);
ASSERT_TRUE(PyList_CheckExact(result));
EXPECT_EQ(PyList_Size(result), 1);
PyObjectPtr kv(borrow(PyList_GetItem(result, 0)));
ASSERT_TRUE(PyTuple_CheckExact(kv));
ASSERT_EQ(PyTuple_Size(kv), 2);
EXPECT_EQ(PyTuple_GetItem(kv, 0), c);
EXPECT_EQ(PyTuple_GetItem(kv, 1), d_value);
}
TEST_F(DictExtensionApiTest, SetItemComparesHashValueFirst) {
ASSERT_EQ(PyRun_SimpleString(R"(
class C:
def __init__(self, hash_code):
self.hash_code = hash_code
def __eq__(self, other):
raise UserWarning("unexpected")
def __hash__(self):
return self.hash_code
c = C(4)
d = C(5)
)"),
0);
PyObjectPtr dict(PyDict_New());
PyObjectPtr c(mainModuleGet("c"));
PyObjectPtr value(PyLong_FromLong(500));
ASSERT_EQ(PyDict_SetItem(dict, c, value), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
ASSERT_EQ(PyDict_Size(dict), 1);
PyObjectPtr d(mainModuleGet("d"));
ASSERT_EQ(PyDict_SetItem(dict, d, value), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
ASSERT_EQ(PyDict_Size(dict), 2);
}
TEST_F(DictExtensionApiTest, SetItemStringInternsKeys) {
PyObjectPtr one(PyLong_FromLong(1));
const char* key = "unique-never-before-seen-test-key";
PyObjectPtr pydict(PyDict_New());
// Calling PyDict_SetItemString should create an interned string for the key
PyDict_SetItemString(pydict, key, one);
PyObjectPtr result(PyDict_Keys(pydict));
ASSERT_NE(result, nullptr);
ASSERT_EQ(PyErr_Occurred(), nullptr);
ASSERT_TRUE(PyList_Check(result));
EXPECT_EQ(PyList_Size(result), 1);
PyObjectPtr interned_str(borrow(PyList_GetItem(result, 0)));
ASSERT_EQ(PyErr_Occurred(), nullptr);
// Use a PyObject* directly because InternInPlace requires a reference to a
// PyObject*
PyObject* str = PyUnicode_FromString(key);
// Prior to interning str it should be a different object than
// the dictionary key
ASSERT_NE(str, interned_str);
// InternInPlace will update the object str points to to be the same as
// the dictionary key.
PyUnicode_InternInPlace(&str);
ASSERT_EQ(PyErr_Occurred(), nullptr);
ASSERT_EQ(str, interned_str);
Py_DECREF(str);
}
TEST_F(DictExtensionApiTest, SizeWithNonDictReturnsNegative) {
PyObject* list = PyList_New(0);
EXPECT_EQ(PyDict_Size(list), -1);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_SystemError));
Py_DECREF(list);
}
TEST_F(DictExtensionApiTest, SizeWithEmptyDictReturnsZero) {
PyObjectPtr dict(PyDict_New());
EXPECT_EQ(PyDict_Size(dict), 0);
}
TEST_F(DictExtensionApiTest, SizeWithNonEmptyDict) {
PyObjectPtr dict(PyDict_New());
PyObjectPtr key1(PyLong_FromLong(1));
PyObjectPtr key2(PyLong_FromLong(2));
PyObjectPtr value1(PyLong_FromLong(0));
PyObjectPtr value2(PyLong_FromLong(0));
PyObjectPtr value3(PyLong_FromLong(0));
// Dict starts out empty
EXPECT_EQ(PyDict_Size(dict), 0);
// Inserting items for two different keys
ASSERT_EQ(PyDict_SetItem(dict, key1, value1), 0);
ASSERT_EQ(PyDict_SetItem(dict, key2, value2), 0);
EXPECT_EQ(PyDict_Size(dict), 2);
// Replace value for existing key
ASSERT_EQ(PyDict_SetItem(dict, key1, value3), 0);
EXPECT_EQ(PyDict_Size(dict), 2);
}
TEST_F(DictExtensionApiTest, ContainsWithKeyInDictReturnsOne) {
PyObjectPtr dict(PyDict_New());
PyObjectPtr key(PyLong_FromLong(10));
PyObjectPtr value(PyLong_FromLong(11));
ASSERT_EQ(PyDict_SetItem(dict, key, value), 0);
EXPECT_EQ(PyDict_Contains(dict, key), 1);
EXPECT_EQ(PyErr_Occurred(), nullptr);
}
TEST_F(DictExtensionApiTest, ContainsWithKeyNotInDictReturnsZero) {
PyObjectPtr dict(PyDict_New());
PyObjectPtr key(PyLong_FromLong(10));
PyObjectPtr value(PyLong_FromLong(11));
ASSERT_EQ(PyDict_SetItem(dict, key, value), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
PyObjectPtr key2(PyLong_FromLong(666));
EXPECT_EQ(PyDict_Contains(dict, key2), 0);
EXPECT_EQ(PyErr_Occurred(), nullptr);
}
TEST_F(DictExtensionApiTest, ItemsWithNonDictRaisesSystemError) {
ASSERT_EQ(PyDict_Items(Py_None), nullptr);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_SystemError));
}
TEST_F(DictExtensionApiTest, ItemsWithDictReturnsList) {
PyObjectPtr dict(PyDict_New());
PyObjectPtr key(PyLong_FromLong(10));
PyObjectPtr value(PyLong_FromLong(11));
ASSERT_EQ(PyDict_SetItem(dict, key, value), 0);
PyObjectPtr result(PyDict_Items(dict));
ASSERT_NE(result, nullptr);
ASSERT_EQ(PyErr_Occurred(), nullptr);
ASSERT_TRUE(PyList_CheckExact(result));
EXPECT_EQ(PyList_Size(result), 1);
PyObject* kv = PyList_GetItem(result, 0);
ASSERT_TRUE(PyTuple_CheckExact(kv));
ASSERT_EQ(PyTuple_Size(kv), 2);
EXPECT_EQ(PyTuple_GetItem(kv, 0), key);
EXPECT_EQ(PyTuple_GetItem(kv, 1), value);
}
TEST_F(DictExtensionApiTest, KeysWithNonDictRaisesSystemError) {
EXPECT_EQ(PyDict_Keys(Py_None), nullptr);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_SystemError));
}
TEST_F(DictExtensionApiTest, KeysWithDictReturnsList) {
PyObjectPtr dict(PyDict_New());
PyObjectPtr key(PyLong_FromLong(10));
PyObjectPtr value(PyLong_FromLong(11));
ASSERT_EQ(PyDict_SetItem(dict, key, value), 0);
PyObjectPtr result(PyDict_Keys(dict));
ASSERT_NE(result, nullptr);
ASSERT_EQ(PyErr_Occurred(), nullptr);
ASSERT_TRUE(PyList_CheckExact(result));
EXPECT_EQ(PyList_Size(result), 1);
EXPECT_EQ(PyList_GetItem(result, 0), key);
}
TEST_F(DictExtensionApiTest, ValuesWithNonDictReturnsNull) {
EXPECT_EQ(PyDict_Values(Py_None), nullptr);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_SystemError));
}
TEST_F(DictExtensionApiTest, ValuesWithEmptyDictReturnsEmptyList) {
PyObjectPtr dict(PyDict_New());
PyObjectPtr result(PyDict_Values(dict));
ASSERT_NE(result, nullptr);
ASSERT_EQ(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyList_CheckExact(result));
EXPECT_EQ(PyList_Size(result), 0);
}
TEST_F(DictExtensionApiTest, ValuesWithNonEmptyDictReturnsNonEmptyList) {
PyObjectPtr dict(PyDict_New());
PyObjectPtr key(PyLong_FromLong(10));
PyObjectPtr value(PyLong_FromLong(11));
ASSERT_EQ(PyDict_SetItem(dict, key, value), 0);
PyObjectPtr result(PyDict_Values(dict));
ASSERT_NE(result, nullptr);
ASSERT_EQ(PyErr_Occurred(), nullptr);
ASSERT_TRUE(PyList_CheckExact(result));
EXPECT_EQ(PyList_Size(result), 1);
EXPECT_EQ(PyList_GetItem(result, 0), value);
}
TEST_F(DictExtensionApiTest, ClearWithNonDictDoesNotRaise) {
PyDict_Clear(Py_None);
ASSERT_EQ(PyErr_Occurred(), nullptr);
}
TEST_F(DictExtensionApiTest, ClearRemovesAllItems) {
PyObjectPtr dict(PyDict_New());
PyObjectPtr one(PyLong_FromLong(1));
PyObjectPtr two(PyLong_FromLong(1));
PyDict_SetItem(dict, one, two);
ASSERT_EQ(PyErr_Occurred(), nullptr);
PyObjectPtr three(PyLong_FromLong(1));
PyObjectPtr four(PyLong_FromLong(1));
PyDict_SetItem(dict, three, four);
ASSERT_EQ(PyErr_Occurred(), nullptr);
PyDict_Clear(dict);
EXPECT_EQ(PyErr_Occurred(), nullptr);
EXPECT_EQ(PyDict_Size(dict), 0);
}
TEST_F(DictExtensionApiTest, GETSIZEWithEmptyDictReturnsZero) {
PyObjectPtr dict(PyDict_New());
EXPECT_EQ(PyDict_GET_SIZE(dict.get()), 0);
}
TEST_F(DictExtensionApiTest, GETSIZEWithNonEmptyDict) {
PyObjectPtr dict(PyDict_New());
PyObjectPtr key1(PyLong_FromLong(1));
PyObjectPtr key2(PyLong_FromLong(2));
PyObjectPtr value1(PyLong_FromLong(0));
PyObjectPtr value2(PyLong_FromLong(0));
PyObjectPtr value3(PyLong_FromLong(0));
// Dict starts out empty
EXPECT_EQ(PyDict_GET_SIZE(dict.get()), 0);
// Inserting items for two different keys
ASSERT_EQ(PyDict_SetItem(dict, key1, value1), 0);
ASSERT_EQ(PyDict_SetItem(dict, key2, value2), 0);
EXPECT_EQ(PyDict_GET_SIZE(dict.get()), 2);
// Replace value for existing key
ASSERT_EQ(PyDict_SetItem(dict, key1, value3), 0);
EXPECT_EQ(PyDict_GET_SIZE(dict.get()), 2);
}
TEST_F(DictExtensionApiTest, GetItemWithErrorNonExistingKeyReturnsNull) {
PyObjectPtr dict(PyDict_New());
PyObjectPtr key(PyLong_FromLong(666));
PyObjectPtr result(PyDict_GetItemWithError(dict, key));
EXPECT_EQ(result, nullptr);
ASSERT_EQ(PyErr_Occurred(), nullptr);
}
TEST_F(DictExtensionApiTest, GetItemWithErrorReturnsBorrowedValue) {
PyObjectPtr dict(PyDict_New());
PyObjectPtr key(PyLong_FromLong(10));
PyObjectPtr value(PyLong_FromLong(666));
// Insert the value into the dictionary
ASSERT_EQ(PyDict_SetItem(dict, key, value), 0);
// Record the reference count of the value
long refcnt = Py_REFCNT(value);
// Get a new reference to the value from the dictionary
PyObject* value2 = PyDict_GetItemWithError(dict, key);
ASSERT_EQ(PyErr_Occurred(), nullptr);
// The new reference should be equal to the original reference
EXPECT_EQ(value2, value);
// The reference count should not be affected
EXPECT_EQ(Py_REFCNT(value), refcnt);
}
TEST_F(DictExtensionApiTest, GetItemWithErrorWithDictSubclassReturnsValue) {
PyRun_SimpleString(R"(
class Foo(dict): pass
obj = Foo()
)");
PyObjectPtr obj(mainModuleGet("obj"));
PyObjectPtr key(PyLong_FromLong(1));
PyObjectPtr val(PyLong_FromLong(2));
ASSERT_EQ(PyDict_SetItem(obj, key, val), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
PyObject* result = PyDict_GetItemWithError(obj, key);
ASSERT_EQ(PyErr_Occurred(), nullptr);
EXPECT_EQ(result, val);
EXPECT_EQ(PyErr_Occurred(), nullptr);
}
TEST_F(DictExtensionApiTest,
GetItemWithErrorWithUnhashableObjectRaisesTypeError) {
PyRun_SimpleString(R"(
class C:
__hash__ = None
obj = C()
)");
PyObjectPtr dict(PyDict_New());
PyObjectPtr key(mainModuleGet("obj"));
EXPECT_EQ(PyDict_GetItemWithError(dict, key), nullptr);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_TypeError));
}
TEST_F(DictExtensionApiTest, DelItemWithNonDictReturnsNegativeOne) {
EXPECT_EQ(PyDict_DelItem(Py_None, Py_None), -1);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_SystemError));
}
TEST_F(DictExtensionApiTest, DelItemWithKeyInDictReturnsZero) {
PyObjectPtr dict(PyDict_New());
PyObjectPtr key(PyLong_FromLong(10));
PyObjectPtr value(PyLong_FromLong(11));
ASSERT_EQ(PyDict_SetItem(dict, key, value), 0);
EXPECT_EQ(PyDict_DelItem(dict, key), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
}
TEST_F(DictExtensionApiTest, DelItemWithKeyNotInDictRaisesKeyError) {
PyObjectPtr dict(PyDict_New());
PyObjectPtr key(PyLong_FromLong(10));
EXPECT_EQ(PyDict_DelItem(dict, key), -1);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_KeyError));
}
TEST_F(DictExtensionApiTest, DelItemWithUnhashableObjectRaisesTypeError) {
PyRun_SimpleString(R"(
class C:
__hash__ = None
c = C()
)");
PyObjectPtr dict(PyDict_New());
PyObject* main = PyImport_AddModule("__main__");
PyObjectPtr key(PyObject_GetAttrString(main, "c"));
EXPECT_EQ(PyDict_DelItem(dict, key), -1);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_TypeError));
}
TEST_F(DictExtensionApiTest, DelItemStringWithNonDictReturnsNegativeOne) {
EXPECT_EQ(PyDict_DelItemString(Py_None, "hello, there"), -1);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_SystemError));
}
TEST_F(DictExtensionApiTest, DelItemStringWithKeyInDictReturnsZero) {
PyObjectPtr dict(PyDict_New());
const char* strkey = "hello, there";
PyObjectPtr key(PyUnicode_FromString(strkey));
PyObjectPtr value(PyLong_FromLong(666));
ASSERT_EQ(PyDict_SetItem(dict, key, value), 0);
EXPECT_EQ(PyDict_DelItemString(dict, strkey), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
}
TEST_F(DictExtensionApiTest, DelItemStringWithKeyNotInDictReturnsNegativeOne) {
PyObjectPtr dict(PyDict_New());
EXPECT_EQ(PyDict_DelItemString(dict, "hello, there"), -1);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_KeyError));
}
TEST_F(DictExtensionApiTest, NextWithEmptyDictReturnsFalse) {
PyObject* key = nullptr;
PyObject* value = nullptr;
Py_ssize_t pos = 0;
PyObjectPtr dict(PyDict_New());
EXPECT_EQ(PyDict_Next(dict, &pos, &key, &value), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
}
TEST_F(DictExtensionApiTest, NextWithNonEmptyDictReturnsKeysAndValues) {
PyObjectPtr dict(PyDict_New());
PyObjectPtr one(PyLong_FromLong(1));
PyObjectPtr two(PyLong_FromLong(2));
PyDict_SetItem(dict, one, two);
PyObjectPtr three(PyLong_FromLong(3));
PyObjectPtr four(PyLong_FromLong(4));
PyDict_SetItem(dict, three, four);
Py_ssize_t pos = 0;
PyObject* key = nullptr;
PyObject* value = nullptr;
ASSERT_EQ(PyDict_Next(dict, &pos, &key, &value), 1);
ASSERT_EQ(PyErr_Occurred(), nullptr);
EXPECT_EQ(key, one);
EXPECT_EQ(value, two);
ASSERT_EQ(PyDict_Next(dict, &pos, &key, &value), 1);
ASSERT_EQ(PyErr_Occurred(), nullptr);
EXPECT_EQ(key, three);
EXPECT_EQ(value, four);
ASSERT_EQ(PyDict_Next(dict, &pos, &key, &value), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
}
TEST_F(DictExtensionApiTest, NextAcceptsNullKeyPointer) {
PyObjectPtr dict(PyDict_New());
PyObjectPtr one(PyLong_FromLong(1));
PyObjectPtr two(PyLong_FromLong(2));
PyDict_SetItem(dict, one, two);
PyObjectPtr three(PyLong_FromLong(3));
PyObjectPtr four(PyLong_FromLong(4));
PyDict_SetItem(dict, three, four);
Py_ssize_t pos = 0;
PyObject* value = nullptr;
ASSERT_EQ(PyDict_Next(dict, &pos, nullptr, &value), 1);
ASSERT_EQ(PyErr_Occurred(), nullptr);
}
TEST_F(DictExtensionApiTest, NextAcceptsNullValuePointer) {
PyObjectPtr dict(PyDict_New());
PyObjectPtr one(PyLong_FromLong(1));
PyObjectPtr two(PyLong_FromLong(2));
PyDict_SetItem(dict, one, two);
PyObjectPtr three(PyLong_FromLong(3));
PyObjectPtr four(PyLong_FromLong(4));
PyDict_SetItem(dict, three, four);
Py_ssize_t pos = 0;
PyObject* key = nullptr;
ASSERT_EQ(PyDict_Next(dict, &pos, &key, nullptr), 1);
ASSERT_EQ(PyErr_Occurred(), nullptr);
}
TEST_F(DictExtensionApiTest, UnderNextWithEmptyDictReturnsFalse) {
PyObject* key = nullptr;
PyObject* value = nullptr;
Py_hash_t hash = 0;
Py_ssize_t pos = 0;
PyObjectPtr dict(PyDict_New());
EXPECT_EQ(_PyDict_Next(dict, &pos, &key, &value, &hash), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
}
TEST_F(DictExtensionApiTest, UnderNextWithNonEmptyDictReturnsKeysAndValues) {
PyObjectPtr dict(PyDict_New());
PyObjectPtr one(PyLong_FromLong(1));
PyObjectPtr two(PyLong_FromLong(2));
PyDict_SetItem(dict, one, two);
PyObjectPtr three(PyLong_FromLong(3));
PyObjectPtr four(PyLong_FromLong(4));
PyDict_SetItem(dict, three, four);
Py_ssize_t pos = 0;
PyObject* key = nullptr;
PyObject* value = nullptr;
Py_hash_t hash = 0;
ASSERT_EQ(_PyDict_Next(dict, &pos, &key, &value, &hash), 1);
ASSERT_EQ(PyErr_Occurred(), nullptr);
EXPECT_EQ(key, one);
EXPECT_EQ(value, two);
EXPECT_EQ(hash, 1);
ASSERT_EQ(_PyDict_Next(dict, &pos, &key, &value, &hash), 1);
ASSERT_EQ(PyErr_Occurred(), nullptr);
EXPECT_EQ(key, three);
EXPECT_EQ(value, four);
EXPECT_EQ(hash, 3);
ASSERT_EQ(_PyDict_Next(dict, &pos, &key, &value, &hash), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
}
TEST_F(DictExtensionApiTest, UnderNextAcceptsNullKeyPointer) {
PyObjectPtr dict(PyDict_New());
PyObjectPtr one(PyLong_FromLong(1));
PyObjectPtr two(PyLong_FromLong(2));
PyDict_SetItem(dict, one, two);
PyObjectPtr three(PyLong_FromLong(3));
PyObjectPtr four(PyLong_FromLong(4));
PyDict_SetItem(dict, three, four);
Py_ssize_t pos = 0;
PyObject* value = nullptr;
Py_hash_t hash = 0;
ASSERT_EQ(_PyDict_Next(dict, &pos, nullptr, &value, &hash), 1);
ASSERT_EQ(PyErr_Occurred(), nullptr);
}
TEST_F(DictExtensionApiTest, UnderNextAcceptsNullValuePointer) {
PyObjectPtr dict(PyDict_New());
PyObjectPtr one(PyLong_FromLong(1));
PyObjectPtr two(PyLong_FromLong(2));
PyDict_SetItem(dict, one, two);
PyObjectPtr three(PyLong_FromLong(3));
PyObjectPtr four(PyLong_FromLong(4));
PyDict_SetItem(dict, three, four);
Py_ssize_t pos = 0;
PyObject* key = nullptr;
Py_hash_t hash = 0;
ASSERT_EQ(_PyDict_Next(dict, &pos, &key, nullptr, &hash), 1);
ASSERT_EQ(PyErr_Occurred(), nullptr);
}
TEST_F(DictExtensionApiTest, UnderNextAcceptsNullHashPointer) {
PyObjectPtr dict(PyDict_New());
PyObjectPtr one(PyLong_FromLong(1));
PyObjectPtr two(PyLong_FromLong(2));
PyDict_SetItem(dict, one, two);
PyObjectPtr three(PyLong_FromLong(3));
PyObjectPtr four(PyLong_FromLong(4));
PyDict_SetItem(dict, three, four);
Py_ssize_t pos = 0;
PyObject* key = nullptr;
PyObject* value = nullptr;
ASSERT_EQ(_PyDict_Next(dict, &pos, &key, &value, nullptr), 1);
ASSERT_EQ(PyErr_Occurred(), nullptr);
}
TEST_F(DictExtensionApiTest, CopyWithNullRaisesSystemError) {
EXPECT_EQ(PyDict_Copy(nullptr), nullptr);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_SystemError));
}
TEST_F(DictExtensionApiTest, CopyWithNonDictInstanceRaisesSystemError) {
EXPECT_EQ(PyDict_Copy(Py_None), nullptr);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_SystemError));
}
TEST_F(DictExtensionApiTest, CopyMakesShallowCopyOfDictElements) {
PyObjectPtr dict(PyDict_New());
PyObjectPtr one(PyLong_FromLong(1));
PyObjectPtr val1(PyTuple_New(0));
PyDict_SetItem(dict, one, val1);
PyObjectPtr three(PyLong_FromLong(3));
PyObjectPtr val2(PyTuple_New(0));
PyDict_SetItem(dict, three, val2);
PyObjectPtr copy(PyDict_Copy(dict));
ASSERT_NE(copy, nullptr);
ASSERT_EQ(PyErr_Occurred(), nullptr);
ASSERT_TRUE(PyDict_CheckExact(copy));
EXPECT_EQ(PyDict_Size(copy), 2);
EXPECT_EQ(PyDict_GetItem(copy, one), val1);
EXPECT_EQ(PyDict_GetItem(copy, three), val2);
}
TEST_F(DictExtensionApiTest, MergeWithNullLhsRaisesSystemError) {
PyObjectPtr rhs(PyDict_New());
ASSERT_EQ(PyDict_Merge(nullptr, rhs, 0), -1);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_SystemError));
}
TEST_F(DictExtensionApiTest, MergeWithNonDictLhsRaisesSystemError) {
PyObjectPtr rhs(PyDict_New());
ASSERT_EQ(PyDict_Merge(Py_None, rhs, 0), -1);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_SystemError));
}
TEST_F(DictExtensionApiTest, MergeWithNullRhsRaisesSystemError) {
PyObjectPtr lhs(PyDict_New());
ASSERT_EQ(PyDict_Merge(lhs, nullptr, 0), -1);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_SystemError));
}
TEST_F(DictExtensionApiTest, MergeAddsKeysToLhs) {
PyObjectPtr rhs(PyDict_New());
PyObjectPtr one(PyLong_FromLong(1));
PyObjectPtr two(PyLong_FromLong(2));
PyDict_SetItem(rhs, one, two);
PyObjectPtr three(PyLong_FromLong(3));
PyObjectPtr four(PyLong_FromLong(4));
PyDict_SetItem(rhs, three, four);
PyObjectPtr lhs(PyDict_New());
ASSERT_EQ(PyDict_Merge(lhs, rhs, 0), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
EXPECT_EQ(PyDict_Size(lhs), 2);
EXPECT_TRUE(PyDict_Contains(lhs, one));
EXPECT_EQ(PyDict_GetItem(lhs, one), two);
EXPECT_TRUE(PyDict_Contains(lhs, three));
EXPECT_EQ(PyDict_GetItem(lhs, three), four);
}
TEST_F(DictExtensionApiTest, MergeWithoutOverrideIgnoresKeys) {
PyObjectPtr lhs(PyDict_New());
PyObjectPtr rhs(PyDict_New());
PyObjectPtr one(PyLong_FromLong(1));
PyObjectPtr two(PyLong_FromLong(2));
PyDict_SetItem(lhs, one, two);
PyDict_SetItem(rhs, one, two);
PyObjectPtr three(PyLong_FromLong(3));
PyObjectPtr four(PyLong_FromLong(4));
PyDict_SetItem(rhs, three, four);
PyObjectPtr not_in_rhs(PyLong_FromLong(666));
PyDict_SetItem(lhs, three, not_in_rhs);
ASSERT_EQ(PyDict_Merge(lhs, rhs, 0), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
EXPECT_EQ(PyDict_Size(lhs), 2);
EXPECT_EQ(PyDict_GetItem(lhs, one), two);
EXPECT_EQ(PyDict_GetItem(lhs, three), not_in_rhs);
}
TEST_F(DictExtensionApiTest, MergeWithOverrideReplacesKeys) {
PyObjectPtr lhs(PyDict_New());
PyObjectPtr rhs(PyDict_New());
PyObjectPtr one(PyLong_FromLong(1));
PyObjectPtr two(PyLong_FromLong(2));
PyDict_SetItem(lhs, one, two);
PyDict_SetItem(rhs, one, two);
PyObjectPtr three(PyLong_FromLong(3));
PyObjectPtr four(PyLong_FromLong(4));
PyDict_SetItem(rhs, three, four);
PyObjectPtr not_in_rhs(PyLong_FromLong(666));
PyDict_SetItem(lhs, three, not_in_rhs);
ASSERT_EQ(PyDict_Merge(lhs, rhs, 1), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
EXPECT_EQ(PyDict_Size(lhs), 2);
EXPECT_TRUE(PyDict_Contains(lhs, one));
EXPECT_EQ(PyDict_GetItem(lhs, one), two);
EXPECT_TRUE(PyDict_Contains(lhs, three));
EXPECT_EQ(PyDict_GetItem(lhs, three), four);
}
TEST_F(DictExtensionApiTest, MergeWithNonMappingRaisesAttributeError) {
PyRun_SimpleString(R"(
class Mapping:
pass
m = Mapping()
)");
PyObjectPtr rhs(mainModuleGet("m"));
PyObjectPtr lhs(PyDict_New());
ASSERT_EQ(PyDict_Merge(lhs, rhs, 0), -1);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_AttributeError));
}
TEST_F(DictExtensionApiTest, MergeWithMappingRhsAddsKeysToLhs) {
PyRun_SimpleString(R"(
class Mapping:
def __init__(self):
self.d = {1:2, 3:4}
def keys(self):
return self.d.keys()
def __getitem__(self, i):
return self.d[i]
m = Mapping()
)");
PyObjectPtr rhs(mainModuleGet("m"));
PyObjectPtr lhs(PyDict_New());
ASSERT_EQ(PyDict_Merge(lhs, rhs, 0), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
EXPECT_EQ(PyDict_Size(lhs), 2);
PyObjectPtr one(PyLong_FromLong(1));
EXPECT_TRUE(PyDict_Contains(lhs, one));
PyObject* two = PyDict_GetItem(lhs, one);
EXPECT_EQ(PyLong_AsLong(two), 2);
PyObjectPtr three(PyLong_FromLong(3));
EXPECT_TRUE(PyDict_Contains(lhs, three));
PyObject* four = PyDict_GetItem(lhs, three);
EXPECT_EQ(PyLong_AsLong(four), 4);
}
TEST_F(DictExtensionApiTest, UpdateWithNullLhsRaisesSystemError) {
PyObjectPtr rhs(PyDict_New());
ASSERT_EQ(PyDict_Update(nullptr, rhs), -1);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_SystemError));
}
TEST_F(DictExtensionApiTest, UpdateWithNonListLhsRaisesSystemError) {
PyObjectPtr rhs(PyDict_New());
ASSERT_EQ(PyDict_Update(Py_None, rhs), -1);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_SystemError));
}
TEST_F(DictExtensionApiTest, UpdateWithNullRhsRaisesSystemError) {
PyObjectPtr lhs(PyDict_New());
ASSERT_EQ(PyDict_Update(lhs, nullptr), -1);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_SystemError));
}
TEST_F(DictExtensionApiTest, UpdateWithLhsEqualRhsDoesNothing) {
PyObjectPtr lhs(PyDict_New());
PyObject* rhs = lhs;
ASSERT_EQ(PyDict_Update(lhs, rhs), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
EXPECT_EQ(lhs, rhs);
}
TEST_F(DictExtensionApiTest, UpdateWithEmptyRhsDoesNothing) {
PyObjectPtr lhs(PyDict_New());
PyObjectPtr one(PyLong_FromLong(1));
PyObjectPtr two(PyLong_FromLong(2));
PyDict_SetItem(lhs, one, two);
PyObjectPtr three(PyLong_FromLong(3));
PyObjectPtr four(PyLong_FromLong(4));
PyDict_SetItem(lhs, three, four);
ASSERT_EQ(PyDict_Size(lhs), 2);
PyObjectPtr rhs(PyDict_New());
ASSERT_EQ(PyDict_Update(lhs, rhs), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
EXPECT_EQ(PyDict_Size(lhs), 2);
EXPECT_EQ(PyDict_GetItem(lhs, one), two);
EXPECT_EQ(PyDict_GetItem(lhs, three), four);
}
TEST_F(DictExtensionApiTest, UpdateWithEmptyLhsAddsKeysToLhs) {
PyObjectPtr rhs(PyDict_New());
PyObjectPtr one(PyLong_FromLong(1));
PyObjectPtr two(PyLong_FromLong(2));
PyDict_SetItem(rhs, one, two);
PyObjectPtr three(PyLong_FromLong(3));
PyObjectPtr four(PyLong_FromLong(4));
PyDict_SetItem(rhs, three, four);
ASSERT_EQ(PyDict_Size(rhs), 2);
PyObjectPtr lhs(PyDict_New());
ASSERT_EQ(PyDict_Update(lhs, rhs), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
EXPECT_EQ(PyDict_Size(lhs), 2);
EXPECT_TRUE(PyDict_Contains(lhs, one));
EXPECT_EQ(PyDict_GetItem(lhs, one), two);
EXPECT_TRUE(PyDict_Contains(lhs, three));
EXPECT_EQ(PyDict_GetItem(lhs, three), four);
}
TEST_F(DictExtensionApiTest, UpdateOverwritesKeys) {
PyObjectPtr rhs(PyDict_New());
PyObjectPtr one(PyLong_FromLong(1));
PyObjectPtr two(PyLong_FromLong(2));
PyDict_SetItem(rhs, one, two);
PyObjectPtr three(PyLong_FromLong(3));
PyObjectPtr four(PyLong_FromLong(4));
PyDict_SetItem(rhs, three, four);
ASSERT_EQ(PyDict_Size(rhs), 2);
PyObjectPtr lhs(PyDict_New());
PyObjectPtr not_in_rhs(PyLong_FromLong(666));
ASSERT_EQ(PyDict_SetItem(lhs, one, not_in_rhs), 0);
ASSERT_EQ(PyDict_GetItem(lhs, one), not_in_rhs);
ASSERT_EQ(PyDict_Update(lhs, rhs), 0);
ASSERT_EQ(PyErr_Occurred(), nullptr);
EXPECT_EQ(PyDict_Size(lhs), 2);
EXPECT_TRUE(PyDict_Contains(lhs, one));
EXPECT_EQ(PyDict_GetItem(lhs, one), two);
EXPECT_TRUE(PyDict_Contains(lhs, three));
EXPECT_EQ(PyDict_GetItem(lhs, three), four);
}
TEST_F(DictExtensionApiTest,
ObjectGenericGetDictWithInstanceRaisesAttributeError) {
PyObjectPtr obj(PyLong_FromLong(0));
ASSERT_EQ(PyObject_GenericGetDict(obj, nullptr), nullptr);
ASSERT_NE(PyErr_Occurred(), nullptr);
EXPECT_TRUE(PyErr_ExceptionMatches(PyExc_AttributeError));
}
TEST_F(DictExtensionApiTest, ObjectGenericGetDictWithTypeReturnsTypeDict) {
PyObject* obj = reinterpret_cast<PyObject*>(&PyLong_Type);
PyObjectPtr dict(PyObject_GenericGetDict(obj, nullptr));
ASSERT_TRUE(PyMapping_Check(dict));
EXPECT_GT(PyMapping_Length(dict), 0);
}
} // namespace testing
} // namespace py