runtime/debugging-test.cpp (784 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
#include "debugging.h"
#include <iostream>
#include "gmock/gmock-matchers.h"
#include "gtest/gtest.h"
#include "bytecode.h"
#include "dict-builtins.h"
#include "test-utils.h"
namespace py {
namespace testing {
using DebuggingTests = RuntimeFixture;
static RawObject makeTestCode(Thread* thread) {
Runtime* runtime = thread->runtime();
HandleScope scope(thread);
const byte bytes_array[] = {LOAD_CONST, 0, LOAD_ATTR, 0, RETURN_VALUE, 0};
Bytes bytes(&scope, runtime->newBytesWithAll(bytes_array));
Object const0(&scope, runtime->newStrFromCStr("const0"));
Tuple consts(&scope, runtime->newTupleWith1(const0));
Object name0(&scope, runtime->newStrFromCStr("name0"));
Tuple names(&scope, runtime->newTupleWith1(name0));
Object argument0(&scope, runtime->newStrFromCStr("argument0"));
Object varargs(&scope, runtime->newStrFromCStr("varargs"));
Object varkeyargs(&scope, runtime->newStrFromCStr("varkeyargs"));
Object variable0(&scope, runtime->newStrFromCStr("variable0"));
Tuple varnames(&scope, runtime->newTupleWith4(argument0, varargs, varkeyargs,
variable0));
Object freevar0(&scope, runtime->newStrFromCStr("freevar0"));
Tuple freevars(&scope, runtime->newTupleWith1(freevar0));
Object cellvar0(&scope, runtime->newStrFromCStr("cellvar0"));
Object cellvar1(&scope, runtime->newStrFromCStr("cellvar1"));
Object cellvar2(&scope, runtime->newStrFromCStr("cellvar2"));
Tuple cellvars(&scope, runtime->newTupleWith3(cellvar0, cellvar1, cellvar2));
Str filename(&scope, runtime->newStrFromCStr("filename0"));
Str name(&scope, runtime->newStrFromCStr("name0"));
DCHECK(freevars.length() != cellvars.length(),
"it's helpful for debugging if they are different lengths");
Object lnotab(&scope, Bytes::empty());
word argcount = 1;
word posonlyargcount = 0;
word kwonlyargcount = 0;
word nlocals = 4;
word stacksize = 1;
word flags = Code::kNested | Code::kOptimized | Code::kNewlocals |
Code::kVarargs | Code::kVarkeyargs;
return runtime->newCode(argcount, posonlyargcount, kwonlyargcount, nlocals,
stacksize, flags, bytes, consts, names, varnames,
freevars, cellvars, filename, name, 0, lnotab);
}
static RawObject makeTestFunction(Thread* thread) {
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
Object qualname(&scope, runtime->newStrFromCStr("footype.baz"));
Code code(&scope, makeTestCode(thread));
Module module(&scope, findMainModule(runtime));
Function func(&scope,
runtime->newFunctionWithCode(thread, qualname, code, module));
func.setEntry(reinterpret_cast<Function::Entry>(100));
func.setEntryEx(reinterpret_cast<Function::Entry>(200));
func.setEntryKw(reinterpret_cast<Function::Entry>(300));
Dict annotations(&scope, runtime->newDict());
Str return_name(&scope, runtime->newStrFromCStr("return"));
Object int_type(&scope, runtime->typeAt(LayoutId::kInt));
dictAtPutByStr(thread, annotations, return_name, int_type);
func.setAnnotations(*annotations);
func.setClosure(runtime->emptyTuple());
Dict kw_defaults(&scope, runtime->newDict());
Str name0(&scope, runtime->newStrFromCStr("name0"));
Object none(&scope, NoneType::object());
dictAtPutByStr(thread, kw_defaults, name0, none);
func.setKwDefaults(*kw_defaults);
Object num(&scope, runtime->newInt(-9));
Tuple defaults(&scope, runtime->newTupleWith1(num));
func.setDefaults(*defaults);
func.setIntrinsic(reinterpret_cast<void*>(0x12340));
func.setModuleName(runtime->newStrFromCStr("barmodule"));
func.setName(runtime->newStrFromCStr("baz"));
Dict attrs(&scope, runtime->newDict());
Str attr_name(&scope, runtime->newStrFromCStr("funcattr0"));
Object attr_value(&scope, runtime->newInt(4));
dictAtPutByStr(thread, attrs, attr_name, attr_value);
func.setDict(*attrs);
return *func;
}
TEST_F(DebuggingTests, DumpExtendedCode) {
HandleScope scope(thread_);
Object code(&scope, makeTestCode(thread_));
std::stringstream ss;
dumpExtended(ss, *code);
EXPECT_EQ(ss.str(),
R"(code "name0":
flags: optimized newlocals varargs varkeyargs nested
argcount: 1
posonlyargcount: 0
kwonlyargcount: 0
nlocals: 4
stacksize: 1
filename: "filename0"
consts: ("const0",)
names: ("name0",)
cellvars: ("cellvar0", "cellvar1", "cellvar2")
freevars: ("freevar0",)
varnames: ("argument0", "varargs", "varkeyargs", "variable0")
0 LOAD_CONST 0
2 LOAD_ATTR 0
4 RETURN_VALUE 0
)");
}
TEST_F(DebuggingTests, DumpExtendedFunction) {
Thread* thread = Thread::current();
HandleScope scope(thread);
Object func(&scope, makeTestFunction(thread));
std::stringstream ss;
dumpExtended(ss, *func);
EXPECT_EQ(ss.str(), R"(function "baz":
qualname: "footype.baz"
module: "barmodule"
annotations: {"return": <type "int">}
closure: ()
defaults: (-9,)
kwdefaults: {"name0": None}
intrinsic: 0x12340
dict: {"funcattr0": 4}
flags: optimized newlocals varargs varkeyargs nested interpreted
code: code "name0":
flags: optimized newlocals varargs varkeyargs nested
argcount: 1
posonlyargcount: 0
kwonlyargcount: 0
nlocals: 4
stacksize: 1
filename: "filename0"
consts: ("const0",)
names: ("name0",)
cellvars: ("cellvar0", "cellvar1", "cellvar2")
freevars: ("freevar0",)
varnames: ("argument0", "varargs", "varkeyargs", "variable0")
0 LOAD_CONST 0
2 LOAD_ATTR 0
4 RETURN_VALUE 0
Rewritten bytecode:
0 [ 0] LOAD_CONST 0
4 [ 1] LOAD_ATTR_ANAMORPHIC 0
8 [ 0] RETURN_VALUE 0
)");
}
TEST_F(DebuggingTests, DumpExtendedInstance) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __init__(self):
self.foo = 5
self.bar = "hello"
i = C()
i.baz = ()
)")
.isError());
Object i(&scope, mainModuleAt(runtime_, "i"));
ASSERT_TRUE(i.isInstance());
std::stringstream ss;
dumpExtended(ss, *i);
std::stringstream expected;
expected << "heap object with layout " << static_cast<word>(i.layoutId())
<< R"( (<type "C">):
(in-object) "foo" = 5
(in-object) "bar" = "hello"
(overflow) "baz" = ()
)";
EXPECT_EQ(ss.str(), expected.str());
}
TEST_F(DebuggingTests, DumpExtendedInstanceWithOverflowDict) {
HandleScope scope(thread_);
Function func(&scope, makeTestFunction(thread_));
std::stringstream ss;
dumpExtendedInstance(ss, RawInstance::cast(*func));
std::stringstream expected;
word raw_flags =
SmallInt::cast(func.instanceVariableAt(RawFunction::kFlagsOffset))
.value();
word entry_asm =
SmallInt::cast(func.instanceVariableAt(RawFunction::kEntryAsmOffset))
.value();
expected << "heap object with layout " << static_cast<word>(func.layoutId())
<< R"( (<type "function">):
(in-object) "__code__" = <code "name0">
(in-object) "_function__flags" = )"
<< raw_flags << R"(
(in-object) "_function__argcount" = 1
(in-object) "_function__total_args" = 3
(in-object) "_function__total_vars" = 5
(in-object) "_function__stack_size" = 2
(in-object) "__doc__" = "const0"
(in-object) "__name__" = "baz"
(in-object) "__qualname__" = "footype.baz"
(in-object) "__module__" = "barmodule"
(in-object) "__module_object__" = <module "__main__">
(in-object) "_function__defaults" = (-9,)
(in-object) "_function__annotations" = {"return": <type "int">}
(in-object) "_function__kw_defaults" = {"name0": None}
(in-object) "_function__closure" = ()
(in-object) "_function__entry" = 50
(in-object) "_function__entry_kw" = 150
(in-object) "_function__entry_ex" = 100
(in-object) "_function__entry_asm" = )"
<< entry_asm << R"(
(in-object) "_function__rewritten_bytecode" = b'd\x00\x00\x00\xff\x00\x01\x00S\x00\x00\x00'
(in-object) "_function__caches" = mutabletuple(None, None, None, None)
(in-object) "_function__dict" = {"funcattr0": 4}
(in-object) "_function__intrinsic" = 37280
overflow dict: {"funcattr0": 4}
)";
EXPECT_EQ(ss.str(), expected.str());
}
TEST_F(DebuggingTests, DumpExtendedInstanceWithInvalidLayout) {
HandleScope scope(thread_);
Instance instance(&scope, runtime_->newList());
LayoutId old_id = instance.layoutId();
// Temporarily set an invalid layout id...
instance.setHeader(
instance.header().withLayoutId(static_cast<LayoutId>(9999)));
std::stringstream ss;
dumpExtendedInstance(ss, *instance);
instance.setHeader(instance.header().withLayoutId(old_id));
EXPECT_EQ(ss.str(), "heap object with layout 9999\n");
}
TEST_F(DebuggingTests, DumpExtendedInstanceWithLayoutWithoutType) {
HandleScope scope(thread_);
Instance instance(&scope, runtime_->newList());
Layout layout(&scope, runtime_->layoutOf(*instance));
Object old_type(&scope, layout.describedType());
// Temporarily set an invalid type...
layout.setDescribedType(NoneType::object());
std::stringstream ss;
dumpExtendedInstance(ss, *instance);
layout.setDescribedType(*old_type);
std::stringstream expected;
expected << "heap object with layout " << static_cast<word>(LayoutId::kList)
<< '\n';
EXPECT_EQ(ss.str(), expected.str());
}
TEST_F(DebuggingTests, DumpExtendedLayout) {
HandleScope scope(thread_);
// Create a new layout with several overflow attributes
Object attr(&scope, runtime_->newStrFromCStr("myattr"));
Object attr2(&scope, runtime_->newStrFromCStr("myattr2"));
Object attr3(&scope, runtime_->newStrFromCStr("myattr3"));
MutableTuple overflow(&scope, runtime_->newMutableTuple(3));
Object* overflow_names[] = {&attr, &attr2, &attr3};
for (word i = 0; i < overflow.length(); i++) {
Object info(&scope, AttributeInfo(i, 0).asSmallInt());
overflow.atPut(i, runtime_->newTupleWith2(*overflow_names[i], info));
}
Layout layout(&scope, layoutCreateEmpty(thread_));
layout.setOverflowAttributes(overflow.becomeImmutable());
// Set some in-object attributes
Object inobj1(&scope, runtime_->newStrFromCStr("foo"));
Object inobj2(&scope, runtime_->newStrFromCStr("bar"));
MutableTuple inobj(&scope, runtime_->newMutableTuple(2));
Object* inobj_names[] = {&inobj1, &inobj2};
for (word i = 0; i < inobj.length(); i++) {
Object info(&scope, AttributeInfo(i, 0).asSmallInt());
inobj.atPut(i, runtime_->newTupleWith2(*inobj_names[i], info));
}
layout.setInObjectAttributes(inobj.becomeImmutable());
layout.setNumInObjectAttributes(9);
layout.setId(static_cast<LayoutId>(103));
Type type(&scope, runtime_->typeAt(LayoutId::kObject));
layout.setDescribedType(*type);
std::stringstream ss;
dumpExtended(ss, *layout);
EXPECT_EQ(ss.str(), R"(layout 103:
described type: <type "object">
num in-object attributes: 9
"foo" @ 0
"bar" @ 1
overflow tuple:
"myattr" @ 0
"myattr2" @ 1
"myattr3" @ 2
)");
}
TEST_F(DebuggingTests, DumpExtendedLayoutWithSealedLayout) {
HandleScope scope(thread_);
Layout layout(&scope, layoutCreateEmpty(thread_));
layout.setOverflowAttributes(NoneType::object());
// Set some in-object attributes
Object inobj1(&scope, runtime_->newStrFromCStr("foo"));
Object inobj2(&scope, runtime_->newStrFromCStr("bar"));
MutableTuple inobj(&scope, runtime_->newMutableTuple(2));
Object* inobj_names[] = {&inobj1, &inobj2};
for (word i = 0; i < inobj.length(); i++) {
Object info(&scope, AttributeInfo(i, 0).asSmallInt());
inobj.atPut(i, runtime_->newTupleWith2(*inobj_names[i], info));
}
layout.setInObjectAttributes(*inobj);
layout.setId(static_cast<LayoutId>(13));
layout.setNumInObjectAttributes(2);
std::stringstream ss;
dumpExtended(ss, *layout);
EXPECT_EQ(ss.str(), R"(layout 13:
described type: None
num in-object attributes: 2
"foo" @ 0
"bar" @ 1
sealed
)");
}
TEST_F(DebuggingTests, DumpExtendedLayoutWithDictOverflow) {
HandleScope scope(thread_);
Layout layout(&scope, layoutCreateEmpty(thread_));
layout.setOverflowAttributes(SmallInt::fromWord(654321));
layout.setInObjectAttributes(runtime_->emptyTuple());
layout.setNumInObjectAttributes(0);
layout.setId(static_cast<LayoutId>(1234));
std::stringstream ss;
dumpExtended(ss, *layout);
EXPECT_EQ(ss.str(), R"(layout 1234:
described type: None
num in-object attributes: 0
overflow dict @ 654321
)");
}
TEST_F(DebuggingTests, DumpExtendedType) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class A:
pass
class B(bytes):
pass
class C(A, B):
def __init__(self):
self.x = 0
self.y = 1
)")
.isError());
Object c(&scope, mainModuleAt(runtime_, "C"));
ASSERT_TRUE(c.isType());
std::stringstream ss;
dumpExtended(ss, *c);
std::stringstream expected;
expected << R"(type "C":
bases: (<type "A">, <type "B">)
mro: (<type "C">, <type "A">, <type "B">, <type "bytes">, <type "object">)
flags:
builtin base: <layout )"
<< static_cast<word>(LayoutId::kBytes) << R"( ("bytes")>
layout )"
<< static_cast<word>(
Layout::cast(Type::cast(*c).instanceLayout()).id())
<< R"(:
described type: <type "C">
num in-object attributes: 3
"_UserBytes__value" @ 0
overflow tuple:
)";
EXPECT_EQ(ss.str(), expected.str());
}
TEST_F(DebuggingTests, DumpExtendedTypePrintsFlags) {
HandleScope scope(thread_);
Type type(&scope, runtime_->newType());
word flags = Type::Flag::kIsAbstract | Type::Flag::kHasCustomDict |
Type::Flag::kHasNativeData | Type::Flag::kHasCycleGC |
Type::Flag::kHasDefaultDealloc | Type::Flag::kHasSlots |
Type::Flag::kIsFixedAttributeBase;
type.setFlagsAndBuiltinBase(static_cast<Type::Flag>(flags),
LayoutId::kUserWarning);
std::stringstream ss;
dumpExtended(ss, *type);
word builtin_base = static_cast<word>(LayoutId::kUserWarning);
std::stringstream expected;
expected << R"(type None:
bases: None
mro: None
flags: abstract has_custom_dict has_native_data has_cycle_gc has_default_dealloc has_slots is_fixed_attribute_base
builtin base: <layout )"
<< builtin_base << R"( ("UserWarning")>
layout: None
)";
EXPECT_EQ(ss.str(), expected.str());
}
TEST_F(DebuggingTests,
DumpExtendedPrefersSimpleDumperOverDumpExtendedInstance) {
HandleScope scope(thread_);
List list(&scope, runtime_->newList());
std::stringstream ss;
dumpExtended(ss, *list);
EXPECT_EQ(ss.str(), "[]\n");
}
TEST_F(DebuggingTests, FormatBool) {
std::stringstream ss;
ss << Bool::trueObj() << ';' << Bool::falseObj();
EXPECT_EQ(ss.str(), "True;False");
}
TEST_F(DebuggingTests, FormatBoundMethod) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def foo():
pass
bound_method = C().foo
)")
.isError());
Object bound_method(&scope, mainModuleAt(runtime_, "bound_method"));
ASSERT_TRUE(bound_method.isBoundMethod());
std::stringstream ss;
ss << bound_method;
EXPECT_EQ(ss.str(), "<bound_method <function \"C.foo\">, <\"C\" object>>");
}
TEST_F(DebuggingTests, FormatBoundMethodWithCallable) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class C:
def __call__(self):
pass
from types import MethodType
bound_method = MethodType(C(), 42)
)")
.isError());
Object bound_method(&scope, mainModuleAt(runtime_, "bound_method"));
ASSERT_TRUE(bound_method.isBoundMethod());
std::stringstream ss;
ss << bound_method;
EXPECT_EQ(ss.str(), "<bound_method <\"C\" object>, 42>");
}
TEST_F(DebuggingTests, FormatBytes) {
std::stringstream ss;
const byte bytes[] = {'h', 'e', 'l', 'l', 'o', 0, 'w', '2',
0xa4, '"', '\'', '\t', '\r', '\n', '\\'};
ss << runtime_->newBytesWithAll(bytes);
EXPECT_EQ(ss.str(), R"(b'hello\x00w2\xa4"\'\t\r\n\\')");
}
TEST_F(DebuggingTests, FormatBytearray) {
ASSERT_FALSE(runFromCStr(runtime_, "ba = bytearray(b\"foo'\")").isError());
HandleScope scope(thread_);
Object bytearray(&scope, mainModuleAt(runtime_, "ba"));
ASSERT_TRUE(bytearray.isBytearray());
std::stringstream ss;
ss << bytearray;
EXPECT_EQ(ss.str(), R"(bytearray(b'foo\''))");
}
TEST_F(DebuggingTests, FormatCode) {
HandleScope scope(thread_);
Code code(&scope, newCodeWithBytes(View<byte>(nullptr, 0)));
code.setName(runtime_->newStrFromCStr("foobar"));
std::stringstream ss;
ss << code;
EXPECT_EQ(ss.str(), "<code \"foobar\">");
}
TEST_F(DebuggingTests, FormatDict) {
HandleScope scope(thread_);
Dict dict(&scope, runtime_->newDict());
Str key0(&scope, runtime_->newStrFromCStr("hello"));
Object key1(&scope, NoneType::object());
Object hash_obj(&scope, Interpreter::hash(thread_, key1));
ASSERT_FALSE(hash_obj.isErrorException());
word hash = SmallInt::cast(*hash_obj).value();
Object value0(&scope, runtime_->newInt(88));
Object value1(&scope, runtime_->emptyTuple());
dictAtPutByStr(thread_, dict, key0, value0);
ASSERT_TRUE(dictAtPut(thread_, dict, key1, hash, value1).isNoneType());
std::stringstream ss;
ss << dict;
EXPECT_TRUE(ss.str() == R"({"hello": 88, None: ()})" ||
ss.str() == R"({None: (), "hello": 88}")");
}
TEST_F(DebuggingTests, FormatError) {
std::stringstream ss;
ss << Error::error();
EXPECT_EQ(ss.str(), "Error");
ss.str("");
ss << Error::exception();
EXPECT_EQ(ss.str(), "Error<Exception>");
ss.str("");
ss << Error::notFound();
EXPECT_EQ(ss.str(), "Error<NotFound>");
ss.str("");
ss << Error::noMoreItems();
EXPECT_EQ(ss.str(), "Error<NoMoreItems>");
ss.str("");
ss << Error::outOfMemory();
EXPECT_EQ(ss.str(), "Error<OutOfMemory>");
ss.str("");
ss << Error::outOfBounds();
EXPECT_EQ(ss.str(), "Error<OutOfBounds>");
}
TEST_F(DebuggingTests, FormatFloat) {
std::stringstream ss;
ss << runtime_->newFloat(42.42);
EXPECT_EQ(ss.str(), "0x1.535c28f5c28f6p+5");
}
TEST_F(DebuggingTests, FormatFunction) {
HandleScope scope(thread_);
std::stringstream ss;
Object function(&scope, moduleAtByCStr(runtime_, "builtins", "callable"));
ASSERT_TRUE(function.isFunction());
ss << function;
EXPECT_EQ(ss.str(), R"(<function "callable">)");
}
TEST_F(DebuggingTests, FormatLargeInt) {
std::stringstream ss;
const uword digits[] = {0x12345, kMaxUword};
ss << runtime_->newLargeIntWithDigits(digits);
EXPECT_EQ(ss.str(), "largeint([0x0000000000012345, 0xffffffffffffffff])");
}
TEST_F(DebuggingTests, FormatLargeStr) {
HandleScope scope(thread_);
std::stringstream ss;
Object str(&scope, runtime_->newStrFromCStr("hello world"));
EXPECT_TRUE(str.isLargeStr());
ss << str;
EXPECT_EQ(ss.str(), "\"hello world\"");
}
TEST_F(DebuggingTests, FormatLayout) {
HandleScope scope(thread_);
Layout layout(&scope, layoutCreateEmpty(thread_));
layout.setId(static_cast<LayoutId>(101));
Type type(&scope, runtime_->typeAt(LayoutId::kFloat));
layout.setDescribedType(*type);
std::stringstream ss;
ss << layout;
EXPECT_EQ(ss.str(), "<layout 101 (\"float\")>");
}
TEST_F(DebuggingTests, FormatList) {
HandleScope scope(thread_);
List list(&scope, runtime_->newList());
Object o0(&scope, NoneType::object());
Object o1(&scope, runtime_->newInt(17));
runtime_->listAdd(thread_, list, o0);
runtime_->listAdd(thread_, list, o1);
std::stringstream ss;
ss << list;
EXPECT_EQ(ss.str(), "[None, 17]");
}
TEST_F(DebuggingTests, FormatModule) {
HandleScope scope(thread_);
Object name(&scope, runtime_->newStrFromCStr("foomodule"));
Object module(&scope, runtime_->newModule(name));
std::stringstream ss;
ss << module;
EXPECT_EQ(ss.str(), R"(<module "foomodule">)");
}
TEST_F(DebuggingTests, FormatNone) {
std::stringstream ss;
ss << NoneType::object();
EXPECT_EQ(ss.str(), "None");
}
TEST_F(DebuggingTests, FormatObjectWithBuiltinClass) {
std::stringstream ss;
ss << NotImplementedType::object();
EXPECT_EQ(ss.str(), R"(<"NotImplementedType" object>)");
}
TEST_F(DebuggingTests, FormatObjectWithUserDefinedClass) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class Foo:
pass
foo = Foo()
)")
.isError());
Object foo(&scope, mainModuleAt(runtime_, "foo"));
std::stringstream ss;
ss << foo;
EXPECT_EQ(ss.str(), R"(<"Foo" object>)");
}
TEST_F(DebuggingTests, FormatObjectWithTypeWithoutName) {
HandleScope scope(thread_);
Object obj(&scope, NotImplementedType::object());
// Phabricate a nameless type...
Type::cast(runtime_->typeOf(*obj)).setName(NoneType::object());
std::stringstream ss;
std::stringstream expected;
ss << obj;
expected << "<object with LayoutId " << static_cast<word>(obj.layoutId())
<< ">";
EXPECT_EQ(ss.str(), expected.str());
}
TEST_F(DebuggingTests, FormatObjectWithInvalidLayoutId) {
HandleScope scope(thread_);
Object object(&scope, runtime_->newList());
LayoutId old_id = object.layoutId();
// Temporary set an invalid layout id.
HeapObject::cast(*object).setHeader(
HeapObject::cast(*object).header().withLayoutId(
static_cast<LayoutId>(9999)));
std::stringstream ss;
ss << object;
HeapObject::cast(*object).setHeader(
HeapObject::cast(*object).header().withLayoutId(old_id));
EXPECT_EQ(ss.str(), "<object with LayoutId 9999>");
}
TEST_F(DebuggingTests, FormatObjectWithLayoutWithInvalidType) {
HandleScope scope(thread_);
Layout layout(&scope, runtime_->layoutAt(LayoutId::kObject));
Object object(&scope, runtime_->newInstance(layout));
Object old_type(&scope, layout.describedType());
// Temporary set an invalid layout id.
layout.setDescribedType(NoneType::object());
std::stringstream ss;
ss << object;
layout.setDescribedType(*old_type);
std::stringstream expected;
expected << "<object with LayoutId " << static_cast<word>(LayoutId::kObject)
<< '>';
EXPECT_EQ(ss.str(), expected.str());
}
TEST_F(DebuggingTests, FormatSmallInt) {
std::stringstream ss;
ss << SmallInt::fromWord(-42) << ';'
<< SmallInt::fromWord(SmallInt::kMinValue) << ';'
<< SmallInt::fromWord(SmallInt::kMaxValue);
std::stringstream expected;
expected << "-42;" << SmallInt::kMinValue << ";" << SmallInt::kMaxValue;
EXPECT_EQ(ss.str(), expected.str());
}
TEST_F(DebuggingTests, FormatSmallStr) {
HandleScope scope(thread_);
std::stringstream ss;
Object str(&scope, runtime_->newStrFromCStr("aa"));
EXPECT_TRUE(str.isSmallStr());
ss << str;
EXPECT_EQ(ss.str(), "\"aa\"");
}
TEST_F(DebuggingTests, FormatMutableTuple) {
HandleScope scope(thread_);
MutableTuple tuple(&scope, runtime_->newMutableTuple(2));
tuple.atPut(0, Bool::trueObj());
tuple.atPut(1, runtime_->newStrFromCStr("hey"));
std::stringstream ss;
ss << tuple;
EXPECT_EQ(ss.str(), R"(mutabletuple(True, "hey"))");
}
TEST_F(DebuggingTests, FormatTuple) {
HandleScope scope(thread_);
Object true_obj(&scope, Bool::trueObj());
Object hey(&scope, runtime_->newStrFromCStr("hey"));
Tuple tuple(&scope, runtime_->newTupleWith2(true_obj, hey));
std::stringstream ss;
ss << tuple;
EXPECT_EQ(ss.str(), R"((True, "hey"))");
}
TEST_F(DebuggingTests, FormatTupleWithoutElements) {
std::stringstream ss;
ss << runtime_->emptyTuple();
EXPECT_EQ(ss.str(), "()");
}
TEST_F(DebuggingTests, FormatTupleWithOneElement) {
HandleScope scope(thread_);
Object obj(&scope, runtime_->newInt(77));
Tuple tuple(&scope, runtime_->newTupleWith1(obj));
std::stringstream ss;
ss << tuple;
EXPECT_EQ(ss.str(), "(77,)");
}
TEST_F(DebuggingTests, FormatType) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
class MyClass:
pass
)")
.isError());
Object my_class(&scope, mainModuleAt(runtime_, "MyClass"));
std::stringstream ss;
ss << my_class;
EXPECT_EQ(ss.str(), "<type \"MyClass\">");
}
TEST_F(DebuggingTests, FormatForwardedObjects) {
HandleScope scope(thread_);
List list1(&scope, runtime_->newList());
Int i(&scope, runtime_->newInt(1234));
runtime_->listAdd(thread_, list1, i);
Tuple tuple(&scope, runtime_->newTupleWith1(list1));
i = runtime_->newInt(5678);
List list2(&scope, runtime_->newList());
runtime_->listAdd(thread_, list2, i);
list1.forwardTo(*list2);
std::ostringstream ss;
ss << tuple;
EXPECT_EQ(ss.str(), "(<Forward to> [5678],)");
ss.str("");
dumpExtended(ss, *tuple);
EXPECT_EQ(ss.str(), "(<Forward to> [5678],)\n");
}
TEST_F(DebuggingTests, FormatFrame) {
HandleScope scope(thread_);
ASSERT_FALSE(runFromCStr(runtime_, R"(
def func(arg0, arg1):
hello = "world"
return arg0 + arg1
)")
.isError());
Function func(&scope, mainModuleAt(runtime_, "func"));
Object empty_tuple(&scope, runtime_->emptyTuple());
Str name(&scope, runtime_->newStrFromCStr("_bytearray_check"));
Code code(&scope,
runtime_->newBuiltinCode(/*argcount=*/0, /*posonlyargcount=*/0,
/*kwonlyargcount=*/0,
/*flags=*/0, /*function=*/nullptr,
/*parameter_names=*/empty_tuple, name));
Str qualname(&scope, runtime_->newStrFromCStr("test._bytearray_check"));
Module module(&scope, findMainModule(runtime_));
Function builtin(
&scope, runtime_->newFunctionWithCode(thread_, qualname, code, module));
Frame* root = thread_->currentFrame();
ASSERT_TRUE(root->isSentinel());
root->setVirtualPC(8 * kCodeUnitScale);
thread_->stackPush(NoneType::object());
thread_->stackPush(*builtin);
thread_->pushNativeFrame(0);
Function function(&scope, makeTestFunction(thread_));
thread_->stackPush(*function);
thread_->stackPush(runtime_->newStrFromCStr("foo bar"));
thread_->stackPush(runtime_->emptyTuple());
thread_->stackPush(runtime_->newDict());
Frame* frame1 = thread_->pushCallFrame(*function);
frame1->setVirtualPC(42 * kCodeUnitScale);
frame1->setLocal(3, runtime_->newStrFromCStr("bar foo"));
frame1->setLocal(4, runtime_->newInt(88));
frame1->setLocal(5, runtime_->newInt(-99)); // cellvar0
frame1->setLocal(6, runtime_->newInt(12)); // cellvar1
frame1->setLocal(7, runtime_->newInt(34)); // cellvar2
thread_->stackPush(runtime_->newInt(-8));
thread_->stackPush(runtime_->newStrFromCStr("baz bam"));
thread_->stackPush(*func);
thread_->stackPush(runtime_->newInt(-9));
thread_->stackPush(runtime_->newInt(17));
Frame* frame2 = thread_->pushCallFrame(*func);
frame2->setVirtualPC(4 * kCodeUnitScale);
frame2->setLocal(2, runtime_->newStrFromCStr("world"));
std::stringstream ss;
ss << thread_->currentFrame();
EXPECT_EQ(ss.str(), R"(- initial frame
pc: 16
stack:
0: None
- function: <function "test._bytearray_check">
code: "_bytearray_check"
pc: n/a (native)
- function: <function "footype.baz">
code: "name0"
pc: 84 ("filename0":0)
locals:
0 "argument0": "foo bar"
1 "varargs": ()
2 "varkeyargs": {}
3 "variable0": "bar foo"
4 "freevar0": 88
5 "cellvar0": -99
6 "cellvar1": 12
7 "cellvar2": 34
stack:
1: -8
0: "baz bam"
- function: <function "func">
code: "func"
pc: 8 ("<test string>":4)
locals:
0 "arg0": -9
1 "arg1": 17
2 "hello": "world"
)");
}
TEST_F(DebuggingTests, FormatFrameNullptr) {
std::stringstream ss;
ss << static_cast<Frame*>(nullptr);
EXPECT_EQ(ss.str(), "<nullptr>");
}
TEST_F(DebuggingTests, FormatValueCellWithValue) {
HandleScope scope(thread_);
Object value(&scope, runtime_->newInt(42));
Object value_cell(&scope, runtime_->newValueCell());
ValueCell::cast(*value_cell).setValue(*value);
std::stringstream ss;
ss << value_cell;
EXPECT_EQ(ss.str(), "<value_cell (42)>");
}
TEST_F(DebuggingTests, FormatValueCellPlaceHolder) {
HandleScope scope(thread_);
Object value_cell(&scope, runtime_->newValueCell());
ValueCell::cast(*value_cell).makePlaceholder();
std::stringstream ss;
ss << value_cell;
EXPECT_EQ(ss.str(), "<value_cell placeholder>");
}
TEST_F(DebuggingTests, FormatThreadDumpsPendingException) {
thread_->raiseWithFmt(LayoutId::kValueError, "foo");
std::stringstream ss;
ss << thread_;
EXPECT_EQ(ss.str(), R"(pending exception type: <type "ValueError">
pending exception value: "foo"
pending exception traceback: None
)");
}
} // namespace testing
} // namespace py