ext/Internal/api-handle-test.cpp (343 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
#include "api-handle.h"
#include "cpython-func.h"
#include "gtest/gtest.h"
#include "capi-state.h"
#include "capi.h"
#include "dict-builtins.h"
#include "int-builtins.h"
#include "object-builtins.h"
#include "runtime.h"
#include "test-utils.h"
#include "type-builtins.h"
namespace py {
namespace testing {
using CApiHandlesDeathTest = RuntimeFixture;
using ApiHandleTest = RuntimeFixture;
static RawObject initializeExtensionType(PyObject* extension_type) {
Thread* thread = Thread::current();
Runtime* runtime = thread->runtime();
HandleScope scope(thread);
Str name(&scope, runtime->newStrFromCStr("ExtType"));
Object object_type(&scope, runtime->typeAt(LayoutId::kObject));
Tuple bases(&scope, runtime->newTupleWith1(object_type));
Dict dict(&scope, runtime->newDict());
Type metaclass(&scope, runtime->typeAt(LayoutId::kType));
Type type(&scope, typeNew(thread, metaclass, name, bases, dict,
Type::Flag::kHasNativeData,
/*inherit_slots=*/false,
/*add_instance_dict=*/false));
extension_type->reference_ = type.raw();
return *type;
}
static ApiHandle* findHandleMatching(Runtime* runtime,
const char* str_contents) {
class Visitor : public HandleVisitor {
public:
void visitHandle(void* handle, RawObject object) override {
if (object.isStr() && Str::cast(object).equalsCStr(str_contents)) {
found = handle;
}
}
const char* str_contents;
void* found = nullptr;
};
Visitor visitor;
visitor.str_contents = str_contents;
visitApiHandles(runtime, &visitor);
return reinterpret_cast<ApiHandle*>(visitor.found);
}
TEST_F(ApiHandleTest, ManagedObjectHandleWithRefcountZeroIsDisposed) {
HandleScope scope(thread_);
Object object(&scope, runtime_->newStrFromCStr("hello world"));
ApiHandle* handle = ApiHandle::newReference(runtime_, *object);
ASSERT_FALSE(handle->isImmediate());
ASSERT_FALSE(handle->isBorrowedNoImmediate());
EXPECT_EQ(handle->refcnt(), 1);
EXPECT_EQ(findHandleMatching(runtime_, "hello world"), handle);
handle->decref();
EXPECT_EQ(findHandleMatching(runtime_, "hello world"), nullptr);
}
TEST_F(ApiHandleTest, ManagedObjectHandleWithRefcountZeroAfterGCIsDisposed) {
HandleScope scope(thread_);
Object object(&scope, runtime_->newStrFromCStr("hello world"));
ApiHandle* handle = ApiHandle::newReference(runtime_, *object);
ASSERT_FALSE(handle->isImmediate());
ASSERT_FALSE(handle->isBorrowedNoImmediate());
EXPECT_EQ(handle->refcnt(), 1);
EXPECT_EQ(findHandleMatching(runtime_, "hello world"), handle);
runtime_->collectGarbage();
EXPECT_EQ(findHandleMatching(runtime_, "hello world"), handle);
handle->decref();
EXPECT_EQ(findHandleMatching(runtime_, "hello world"), nullptr);
}
TEST_F(ApiHandleTest, ManagedObjectHandleBorrowedIsNotDisposed) {
HandleScope scope(thread_);
Object object(&scope, runtime_->newStrFromCStr("hello world"));
ApiHandle* handle = ApiHandle::borrowedReference(runtime_, *object);
ASSERT_FALSE(handle->isImmediate());
ASSERT_TRUE(handle->isBorrowedNoImmediate());
EXPECT_EQ(handle->refcnt(), 0);
EXPECT_EQ(findHandleMatching(runtime_, "hello world"), handle);
runtime_->collectGarbage();
EXPECT_EQ(findHandleMatching(runtime_, "hello world"), handle);
}
TEST_F(ApiHandleTest, ManagedObjectHandleBorrowedIsDisposedByGC) {
HandleScope scope(thread_);
Object object(&scope, runtime_->newStrFromCStr("hello world"));
ApiHandle* handle = ApiHandle::borrowedReference(runtime_, *object);
ASSERT_FALSE(handle->isImmediate());
ASSERT_TRUE(handle->isBorrowedNoImmediate());
EXPECT_EQ(handle->refcnt(), 0);
EXPECT_EQ(findHandleMatching(runtime_, "hello world"), handle);
object = NoneType::object();
runtime_->collectGarbage();
EXPECT_EQ(findHandleMatching(runtime_, "hello world"), nullptr);
}
TEST_F(ApiHandleTest, ManagedObjectHandleCachedIsDisposedByGC) {
HandleScope scope(thread_);
Object object(&scope, runtime_->newStrFromCStr("hello world"));
ApiHandle* handle = ApiHandle::newReference(runtime_, *object);
ASSERT_FALSE(handle->isImmediate());
ASSERT_FALSE(handle->isBorrowedNoImmediate());
ASSERT_EQ(handle->refcnt(), 1);
EXPECT_EQ(capiCaches(runtime_)->at(*object), nullptr);
const char* as_utf8 = PyUnicode_AsUTF8(handle);
EXPECT_EQ(capiCaches(runtime_)->at(*object), as_utf8);
EXPECT_TRUE(handle->isBorrowedNoImmediate());
handle->decref();
ASSERT_EQ(handle->refcnt(), 0);
EXPECT_EQ(findHandleMatching(runtime_, "hello world"), handle);
// Check that handle is not disposed while still references by `object`.
runtime_->collectGarbage();
EXPECT_EQ(capiCaches(runtime_)->at(*object), as_utf8);
EXPECT_TRUE(handle->isBorrowedNoImmediate());
EXPECT_EQ(std::strcmp(as_utf8, "hello world"), 0);
EXPECT_EQ(findHandleMatching(runtime_, "hello world"), handle);
// Check that handle is disposed when last reference in `object` disappears.
object = NoneType::object();
runtime_->collectGarbage();
EXPECT_EQ(findHandleMatching(runtime_, "hello world"), nullptr);
}
TEST_F(ApiHandleTest, BorrowedApiHandles) {
HandleScope scope(thread_);
// Create a new object and a new reference to that object.
Object obj(&scope, runtime_->newList());
ApiHandle* new_ref = ApiHandle::newReference(runtime_, *obj);
word refcnt = new_ref->refcnt();
// Create a borrowed reference to the same object. This should not affect the
// reference count of the handle.
ApiHandle* borrowed_ref = ApiHandle::borrowedReference(runtime_, *obj);
EXPECT_EQ(borrowed_ref, new_ref);
EXPECT_EQ(borrowed_ref->refcnt(), refcnt);
// Create another new reference. This should increment the reference count
// of the handle.
ApiHandle* another_ref = ApiHandle::newReference(runtime_, *obj);
EXPECT_EQ(another_ref, new_ref);
EXPECT_EQ(another_ref->refcnt(), refcnt + 1);
}
TEST_F(ApiHandleTest, BuiltinHeapAllocatedIntObjectReturnsApiHandle) {
HandleScope scope(thread_);
Object obj(&scope, runtime_->newInt(SmallInt::kMaxValue + 1));
ApiHandle* handle = ApiHandle::newReference(runtime_, *obj);
EXPECT_NE(handle, nullptr);
EXPECT_FALSE(handle->isImmediate());
ApiHandleDict* dict = capiHandles(runtime_);
EXPECT_EQ(dict->at(*obj), handle);
handle->decref();
}
TEST_F(ApiHandleTest, BuiltinImmediateIntObjectReturnsImmediateApiHandle) {
HandleScope scope(thread_);
Object obj(&scope, runtime_->newInt(1));
ApiHandle* handle = ApiHandle::newReference(runtime_, *obj);
EXPECT_NE(handle, nullptr);
EXPECT_TRUE(handle->isImmediate());
ApiHandleDict* dict = capiHandles(runtime_);
EXPECT_EQ(dict->at(*obj), nullptr);
handle->decref();
}
TEST_F(ApiHandleTest, BuiltinImmediateTrueObjectReturnsImmediateApiHandle) {
HandleScope scope(thread_);
Object obj(&scope, Bool::trueObj());
ApiHandle* handle = ApiHandle::newReference(runtime_, *obj);
EXPECT_NE(handle, nullptr);
EXPECT_TRUE(handle->isImmediate());
ApiHandleDict* dict = capiHandles(runtime_);
EXPECT_EQ(dict->at(*obj), nullptr);
handle->decref();
}
TEST_F(ApiHandleTest, BuiltinImmediateFalseObjectReturnsImmediateApiHandle) {
HandleScope scope(thread_);
Object obj(&scope, Bool::falseObj());
ApiHandle* handle = ApiHandle::newReference(runtime_, *obj);
EXPECT_NE(handle, nullptr);
EXPECT_TRUE(handle->isImmediate());
ApiHandleDict* dict = capiHandles(runtime_);
EXPECT_EQ(dict->at(*obj), nullptr);
handle->decref();
}
TEST_F(ApiHandleTest,
BuiltinImmediateNotImplementedObjectReturnsImmediateApiHandle) {
HandleScope scope(thread_);
Object obj(&scope, NotImplementedType::object());
ApiHandle* handle = ApiHandle::newReference(runtime_, *obj);
EXPECT_NE(handle, nullptr);
EXPECT_TRUE(handle->isImmediate());
ApiHandleDict* dict = capiHandles(runtime_);
EXPECT_EQ(dict->at(*obj), nullptr);
handle->decref();
}
TEST_F(ApiHandleTest, BuiltinImmediateUnboundObjectReturnsImmediateApiHandle) {
HandleScope scope(thread_);
Object obj(&scope, Unbound::object());
ApiHandle* handle = ApiHandle::newReference(runtime_, *obj);
EXPECT_NE(handle, nullptr);
EXPECT_TRUE(handle->isImmediate());
ApiHandleDict* dict = capiHandles(runtime_);
EXPECT_EQ(dict->at(*obj), nullptr);
handle->decref();
}
TEST_F(ApiHandleTest, ApiHandleReturnsBuiltinIntObject) {
HandleScope scope(thread_);
Object obj(&scope, runtime_->newInt(1));
ApiHandle* handle = ApiHandle::newReference(runtime_, *obj);
Object handle_obj(&scope, handle->asObject());
EXPECT_TRUE(isIntEqualsWord(*handle_obj, 1));
}
TEST_F(ApiHandleTest, BuiltinObjectReturnsApiHandle) {
HandleScope scope(thread_);
ApiHandleDict* dict = capiHandles(runtime_);
Object obj(&scope, runtime_->newList());
ASSERT_FALSE(dict->at(*obj) != nullptr);
ApiHandle* handle = ApiHandle::newReference(runtime_, *obj);
EXPECT_NE(handle, nullptr);
EXPECT_TRUE(dict->at(*obj) != nullptr);
}
TEST_F(ApiHandleTest, BuiltinObjectReturnsSameApiHandle) {
HandleScope scope(thread_);
Object obj(&scope, runtime_->newList());
ApiHandle* handle = ApiHandle::newReference(runtime_, *obj);
ApiHandle* handle2 = ApiHandle::newReference(runtime_, *obj);
EXPECT_EQ(handle, handle2);
}
TEST_F(ApiHandleTest, ApiHandleReturnsBuiltinObject) {
HandleScope scope(thread_);
Object obj(&scope, runtime_->newList());
ApiHandle* handle = ApiHandle::newReference(runtime_, *obj);
Object handle_obj(&scope, handle->asObject());
EXPECT_TRUE(handle_obj.isList());
}
TEST_F(ApiHandleTest, ExtensionInstanceObjectReturnsPyObject) {
HandleScope scope(thread_);
// Create type
PyObject extension_type;
Type type(&scope, initializeExtensionType(&extension_type));
Layout layout(&scope, type.instanceLayout());
// Create instance
NativeProxy proxy(&scope, runtime_->newInstance(layout));
PyObject pyobj = {0, 1};
proxy.setNative(runtime_->newIntFromCPtr(&pyobj));
PyObject* result = ApiHandle::newReference(runtime_, *proxy);
EXPECT_TRUE(result);
EXPECT_EQ(result, &pyobj);
}
TEST_F(ApiHandleTest, RuntimeInstanceObjectReturnsPyObject) {
HandleScope scope(thread_);
// Create instance
Layout layout(&scope, runtime_->layoutAt(LayoutId::kObject));
Object instance(&scope, runtime_->newInstance(layout));
PyObject* result = ApiHandle::newReference(runtime_, *instance);
ASSERT_NE(result, nullptr);
Object obj(&scope, ApiHandle::fromPyObject(result)->asObject());
EXPECT_EQ(*obj, *instance);
}
TEST_F(ApiHandleTest,
CheckFunctionResultNonNullptrWithoutPendingExceptionReturnsResult) {
RawObject value = SmallInt::fromWord(1234);
ApiHandle* handle = ApiHandle::newReference(runtime_, value);
RawObject result = ApiHandle::checkFunctionResult(thread_, handle);
EXPECT_EQ(result, value);
}
TEST_F(ApiHandleTest,
CheckFunctionResultNullptrWithPendingExceptionReturnsError) {
thread_->raiseBadArgument(); // TypeError
RawObject result = ApiHandle::checkFunctionResult(thread_, nullptr);
EXPECT_TRUE(result.isErrorException());
EXPECT_TRUE(thread_->hasPendingException());
EXPECT_TRUE(thread_->pendingExceptionMatches(LayoutId::kTypeError));
}
TEST_F(ApiHandleTest,
CheckFunctionResultNullptrWithoutPendingExceptionRaisesSystemError) {
EXPECT_FALSE(thread_->hasPendingException());
RawObject result = ApiHandle::checkFunctionResult(thread_, nullptr);
EXPECT_TRUE(result.isErrorException());
EXPECT_TRUE(thread_->hasPendingException());
EXPECT_TRUE(thread_->pendingExceptionMatches(LayoutId::kSystemError));
}
TEST_F(ApiHandleTest,
CheckFunctionResultNonNullptrWithPendingExceptionRaisesSystemError) {
thread_->raiseBadArgument(); // TypeError
ApiHandle* handle =
ApiHandle::newReference(runtime_, SmallInt::fromWord(1234));
RawObject result = ApiHandle::checkFunctionResult(thread_, handle);
EXPECT_TRUE(result.isErrorException());
EXPECT_TRUE(thread_->hasPendingException());
EXPECT_TRUE(thread_->pendingExceptionMatches(LayoutId::kSystemError));
}
TEST_F(ApiHandleTest, Cache) {
HandleScope scope(thread_);
auto handle1 = ApiHandle::newReference(runtime_, runtime_->newList());
EXPECT_EQ(handle1->cache(runtime_), nullptr);
Str str(&scope,
runtime_->newStrFromCStr("this is too long for a RawSmallStr"));
auto handle2 = ApiHandle::newReference(runtime_, *str);
EXPECT_EQ(handle2->cache(runtime_), nullptr);
void* buffer1 = std::malloc(16);
handle1->setCache(runtime_, buffer1);
EXPECT_EQ(handle1->cache(runtime_), buffer1);
EXPECT_EQ(handle2->cache(runtime_), nullptr);
void* buffer2 = std::malloc(16);
handle2->setCache(runtime_, buffer2);
EXPECT_EQ(handle2->cache(runtime_), buffer2);
EXPECT_EQ(handle1->cache(runtime_), buffer1);
handle1->setCache(runtime_, buffer2);
handle2->setCache(runtime_, buffer1);
EXPECT_EQ(handle1->cache(runtime_), buffer2);
EXPECT_EQ(handle2->cache(runtime_), buffer1);
Object key(&scope, handle1->asObject());
handle1->disposeWithRuntime(runtime_);
ApiHandleDict* caches = capiCaches(runtime_);
EXPECT_FALSE(caches->at(*key) != nullptr);
EXPECT_EQ(handle2->cache(runtime_), buffer1);
}
TEST_F(ApiHandleTest, VisitApiHandlesVisitsAllHandles) {
HandleScope scope(thread_);
Object obj0(&scope, runtime_->newDict());
Object obj1(&scope, runtime_->newStrFromCStr("hello world"));
ApiHandle* handle0 = ApiHandle::newReference(runtime_, *obj0);
ApiHandle* handle1 = ApiHandle::borrowedReference(runtime_, *obj1);
struct Visitor : public HandleVisitor {
void visitHandle(void* handle, RawObject object) override {
if (object == obj0) {
EXPECT_EQ(obj0_handle, nullptr);
obj0_handle = handle;
}
if (object == obj1) {
EXPECT_EQ(obj1_handle, nullptr);
obj1_handle = handle;
}
}
RawObject obj0 = NoneType::object();
RawObject obj1 = NoneType::object();
void* obj0_handle = nullptr;
void* obj1_handle = nullptr;
};
Visitor visitor;
visitor.obj0 = *obj0;
visitor.obj1 = *obj1;
visitApiHandles(runtime_, &visitor);
EXPECT_EQ(visitor.obj0_handle, handle0);
EXPECT_EQ(visitor.obj1_handle, handle1);
handle0->decref();
}
TEST_F(CApiHandlesDeathTest, CleanupApiHandlesOnExit) {
HandleScope scope(thread_);
Object obj(&scope, runtime_->newStrFromCStr("hello"));
ApiHandle::newReference(runtime_, *obj);
ASSERT_EXIT(static_cast<void>(runFromCStr(runtime_, R"(
import sys
sys.exit()
)")),
::testing::ExitedWithCode(0), "");
}
} // namespace testing
} // namespace py