runtime/test-utils.cpp (870 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
#include "test-utils.h"
#include <cmath>
#include <cstdint>
#include <cstring>
#include <fstream>
#include <functional>
#include <iostream>
#include <memory>
#include <sstream>
#include "attributedict.h"
#include "builtins-module.h"
#include "bytearray-builtins.h"
#include "bytes-builtins.h"
#include "debugging.h"
#include "exception-builtins.h"
#include "frame.h"
#include "handles.h"
#include "ic.h"
#include "int-builtins.h"
#include "module-builtins.h"
#include "modules.h"
#include "os.h"
#include "runtime.h"
#include "set-builtins.h"
#include "str-builtins.h"
#include "sys-module.h"
#include "thread.h"
#include "type-builtins.h"
#include "utils.h"
namespace py {
namespace testing {
static RawObject initializeSysWithDefaults(Thread* thread) {
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
unique_c_ptr<char> path(OS::executablePath());
Str executable(&scope, runtime->newStrFromCStr(path.get()));
List python_path(&scope, runtime->newList());
MutableTuple data(
&scope, runtime->newMutableTuple(static_cast<word>(SysFlag::kNumFlags)));
data.atPut(static_cast<word>(SysFlag::kDebug), SmallInt::fromWord(0));
data.atPut(static_cast<word>(SysFlag::kInspect), SmallInt::fromWord(0));
data.atPut(static_cast<word>(SysFlag::kInteractive), SmallInt::fromWord(0));
data.atPut(static_cast<word>(SysFlag::kOptimize), SmallInt::fromWord(0));
data.atPut(static_cast<word>(SysFlag::kDontWriteBytecode),
SmallInt::fromWord(0));
data.atPut(static_cast<word>(SysFlag::kNoUserSite), SmallInt::fromWord(1));
data.atPut(static_cast<word>(SysFlag::kNoSite), SmallInt::fromWord(1));
data.atPut(static_cast<word>(SysFlag::kIgnoreEnvironment),
SmallInt::fromWord(1));
data.atPut(static_cast<word>(SysFlag::kVerbose), SmallInt::fromWord(0));
data.atPut(static_cast<word>(SysFlag::kBytesWarning), SmallInt::fromWord(0));
data.atPut(static_cast<word>(SysFlag::kQuiet), SmallInt::fromWord(0));
data.atPut(static_cast<word>(SysFlag::kHashRandomization),
SmallInt::fromWord(1));
data.atPut(static_cast<word>(SysFlag::kIsolated), SmallInt::fromWord(0));
data.atPut(static_cast<word>(SysFlag::kDevMode), Bool::falseObj());
data.atPut(static_cast<word>(SysFlag::kUTF8Mode), SmallInt::fromWord(1));
static_assert(static_cast<word>(SysFlag::kNumFlags) == 15,
"unexpected flag count");
Tuple flags_data(&scope, data.becomeImmutable());
List warnoptions(&scope, runtime->newList());
return initializeSys(thread, executable, python_path, flags_data, warnoptions,
/*extend_python_path_with_stdlib=*/true);
}
bool useCppInterpreter() {
const char* pyro_cpp_interpreter = std::getenv("PYRO_CPP_INTERPRETER");
return pyro_cpp_interpreter != nullptr &&
::strcmp(pyro_cpp_interpreter, "1") == 0;
}
Runtime* createTestRuntime() {
bool use_cpp_interpreter = useCppInterpreter();
word heap_size = 128 * kMiB;
Interpreter* interpreter =
use_cpp_interpreter ? createCppInterpreter() : createAsmInterpreter();
RandomState random_state = randomState();
Runtime* runtime = new Runtime(heap_size, interpreter, random_state);
Thread* thread = Thread::current();
CHECK(initializeSysWithDefaults(thread).isNoneType(),
"initializeSys() failed");
CHECK(runtime->initialize(thread).isNoneType(),
"Runtime::initialize() failed");
return runtime;
}
Value::Type Value::type() const { return type_; }
bool Value::boolVal() const {
DCHECK(type() == Type::Bool, "expected bool");
return bool_;
}
word Value::intVal() const {
DCHECK(type() == Type::Int, "expected int");
return int_;
}
double Value::floatVal() const {
DCHECK(type() == Type::Float, "expected float");
return float_;
}
const char* Value::strVal() const {
DCHECK(type() == Type::Str, "expected str");
return str_;
}
template <typename T1, typename T2>
::testing::AssertionResult badListValue(const char* actual_expr, word i,
const T1& actual, const T2& expected) {
return ::testing::AssertionFailure()
<< "Value of: " << actual_expr << '[' << i << "]\n"
<< " Actual: " << actual << '\n'
<< "Expected: " << expected;
}
::testing::AssertionResult AssertPyListEqual(
const char* actual_expr, const char* /* expected_expr */,
const Object& actual, const std::vector<Value>& expected) {
Thread* thread = Thread::current();
Runtime* runtime = thread->runtime();
if (!actual.isList()) {
return ::testing::AssertionFailure()
<< " Type of: " << actual_expr << "\n"
<< " Actual: " << typeName(runtime, *actual) << "\n"
<< "Expected: list";
}
HandleScope scope(thread);
List list(&scope, *actual);
if (static_cast<size_t>(list.numItems()) != expected.size()) {
return ::testing::AssertionFailure()
<< "Length of: " << actual_expr << "\n"
<< " Actual: " << list.numItems() << "\n"
<< " Expected: " << expected.size();
}
for (size_t i = 0; i < expected.size(); i++) {
Object actual_item(&scope, list.at(i));
const Value& expected_item = expected[i];
auto bad_type = [&](const char* expected_type) {
return ::testing::AssertionFailure()
<< " Type of: " << actual_expr << '[' << i << "]\n"
<< " Actual: " << typeName(runtime, *actual_item) << '\n'
<< "Expected: " << expected_type;
};
switch (expected_item.type()) {
case Value::Type::None: {
if (!actual_item.isNoneType()) return bad_type("RawNoneType");
break;
}
case Value::Type::Bool: {
if (!actual_item.isBool()) return bad_type("bool");
auto const actual_val = Bool::cast(*actual_item) == Bool::trueObj();
auto const expected_val = expected_item.boolVal();
if (actual_val != expected_val) {
return badListValue(actual_expr, i, actual_val ? "True" : "False",
expected_val ? "True" : "False");
}
break;
}
case Value::Type::Int: {
if (!actual_item.isInt()) return bad_type("int");
Int actual_val(&scope, *actual_item);
Int expected_val(&scope, runtime->newInt(expected_item.intVal()));
if (actual_val.compare(*expected_val) != 0) {
// TODO(bsimmers): Support multi-digit values when we can print them.
return badListValue(actual_expr, i, actual_val.digitAt(0),
expected_item.intVal());
}
break;
}
case Value::Type::Float: {
if (!actual_item.isFloat()) return bad_type("float");
auto const actual_val = Float::cast(*actual_item).value();
auto const expected_val = expected_item.floatVal();
if (std::abs(actual_val - expected_val) >= DBL_EPSILON) {
return badListValue(actual_expr, i, actual_val, expected_val);
}
break;
}
case Value::Type::Str: {
if (!actual_item.isStr()) return bad_type("str");
Str actual_val(&scope, *actual_item);
const char* expected_val = expected_item.strVal();
if (!actual_val.equalsCStr(expected_val)) {
return badListValue(actual_expr, i, actual_val, expected_val);
}
break;
}
}
}
return ::testing::AssertionSuccess();
}
RawObject callFunction(const Function& func, const Tuple& args) {
Thread* thread = Thread::current();
thread->stackPush(*func);
word args_length = args.length();
for (word i = 0; i < args_length; i++) {
thread->stackPush(args.at(i));
}
return Interpreter::call(thread, args_length);
}
bool tupleContains(const Tuple& object_array, const Object& key) {
for (word i = 0; i < object_array.length(); i++) {
if (object_array.at(i) == *key) {
return true;
}
}
return false;
}
bool listContains(const Object& list_obj, const Object& key) {
Thread* thread = Thread::current();
HandleScope scope(thread);
if (!thread->runtime()->isInstanceOfList(*list_obj)) {
return false;
}
List list(&scope, *list_obj);
for (word i = 0, num_items = list.numItems(); i < num_items; i++) {
if (list.at(i) == *key) {
return true;
}
}
return false;
}
bool setIncludes(Thread* thread, const SetBase& set, const Object& key) {
HandleScope scope(thread);
Object hash_obj(&scope, Interpreter::hash(thread, key));
CHECK(hash_obj.isSmallInt(), "key must be hashable");
word hash = SmallInt::cast(*hash_obj).value();
return setIncludes(thread, set, key, hash);
}
void setHashAndAdd(Thread* thread, const SetBase& set, const Object& value) {
HandleScope scope(thread);
Object hash_obj(&scope, Interpreter::hash(thread, value));
CHECK(hash_obj.isSmallInt(), "value must be hashable");
word hash = SmallInt::cast(*hash_obj).value();
setAdd(thread, set, value, hash);
}
static RawObject findModuleByCStr(Runtime* runtime, const char* name) {
HandleScope scope(Thread::current());
Object key(&scope, runtime->newStrFromCStr(name));
return runtime->findModule(key);
}
RawObject findMainModule(Runtime* runtime) {
return findModuleByCStr(runtime, "__main__");
}
RawObject mainModuleAt(Runtime* runtime, const char* name) {
return moduleAtByCStr(runtime, "__main__", name);
}
RawObject moduleAtByCStr(Runtime* runtime, const char* module_name,
const char* name) {
Thread* thread = Thread::current();
HandleScope scope(thread);
Object mod_obj(&scope, findModuleByCStr(runtime, module_name));
if (mod_obj.isNoneType()) {
return Error::notFound();
}
Module module(&scope, *mod_obj);
Object name_obj(&scope, Runtime::internStrFromCStr(thread, name));
return moduleAt(module, name_obj);
}
std::string typeName(Runtime* runtime, RawObject obj) {
if (obj.layoutId() == LayoutId::kError) return "Error";
RawStr name = Str::cast(Type::cast(runtime->typeOf(obj)).name());
word length = name.length();
std::string result(length, '\0');
name.copyTo(reinterpret_cast<byte*>(&result[0]), length);
return result;
}
RawObject typeValueCellAt(RawType type, RawObject name) {
RawObject result = NoneType::object();
if (!attributeValueCellAt(type, name, &result)) return Error::notFound();
return result;
}
static RawCode newCodeHelper(Thread* thread, View<byte> bytes,
const Tuple& consts, const Tuple& names,
Locals* locals, word flags) {
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
word argcount = 0;
word posonlyargcount = 0;
word kwonlyargcount = 0;
word nlocals = 0;
word stacksize = 20;
Tuple varnames_tuple(&scope, runtime->emptyTuple());
if (locals != nullptr) {
argcount = locals->argcount;
posonlyargcount = locals->posonlyargcount;
kwonlyargcount = locals->kwonlyargcount;
nlocals = argcount + kwonlyargcount + locals->varcount;
if (locals->varargs) {
nlocals++;
flags |= Code::Flags::kVarargs;
}
if (locals->varkeyargs) {
nlocals++;
flags |= Code::Flags::kVarkeyargs;
}
MutableTuple varnames(&scope, runtime->newMutableTuple(nlocals));
word idx = 0;
for (word i = 0; i < locals->argcount; i++) {
varnames.atPut(idx++, runtime->newStrFromFmt("arg%w", i));
}
if (locals->varargs) {
varnames.atPut(idx++, runtime->newStrFromCStr("args"));
}
if (locals->varkeyargs) {
varnames.atPut(idx++, runtime->newStrFromCStr("kwargs"));
}
for (word i = 0; i < locals->varcount; i++) {
varnames.atPut(idx++, runtime->newStrFromFmt("var%w", i));
}
CHECK(idx == nlocals, "local count mismatch");
varnames_tuple = varnames.becomeImmutable();
}
Bytes code(&scope, runtime->newBytesWithAll(bytes));
Tuple empty_tuple(&scope, runtime->emptyTuple());
Object empty_string(&scope, Str::empty());
Object empty_bytes(&scope, Bytes::empty());
return Code::cast(runtime->newCode(/*argcount=*/argcount,
/*posonlyargcount=*/posonlyargcount,
/*kwonlyargcount=*/kwonlyargcount,
/*nlocals=*/nlocals,
/*stacksize=*/stacksize,
/*flags=*/flags, code, consts, names,
varnames_tuple,
/*freevars=*/empty_tuple,
/*cellvars=*/empty_tuple,
/*filename=*/empty_string,
/*name=*/empty_string,
/*firstlineno=*/0,
/*lnotab=*/empty_bytes));
}
RawCode newCodeWithBytesConstsNames(View<byte> bytes, const Tuple& consts,
const Tuple& names) {
Thread* thread = Thread::current();
word flags = Code::Flags::kOptimized | Code::Flags::kNewlocals;
return newCodeHelper(thread, bytes, consts, names, /*locals=*/nullptr, flags);
}
RawCode newCodeWithBytesConstsNamesFlags(View<byte> bytes, const Tuple& consts,
const Tuple& names, word flags) {
Thread* thread = Thread::current();
return newCodeHelper(thread, bytes, consts, names, /*locals=*/nullptr, flags);
}
RawCode newCodeWithBytesConstsNamesLocals(View<byte> bytes, const Tuple& consts,
const Tuple& names, Locals* locals) {
Thread* thread = Thread::current();
word flags = Code::Flags::kOptimized | Code::Flags::kNewlocals;
return newCodeHelper(thread, bytes, consts, names, locals, flags);
}
RawCode newCodeWithBytesConsts(View<byte> bytes, const Tuple& consts) {
Thread* thread = Thread::current();
HandleScope scope(thread);
Tuple names(&scope, thread->runtime()->emptyTuple());
return newCodeWithBytesConstsNames(bytes, consts, names);
}
RawCode newCodeWithBytes(View<byte> bytes) {
Thread* thread = Thread::current();
HandleScope scope(thread);
Tuple consts(&scope, thread->runtime()->emptyTuple());
return newCodeWithBytesConsts(bytes, consts);
}
RawFunction newEmptyFunction() {
Thread* thread = Thread::current();
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
Code code(&scope, newCodeWithBytes(View<byte>(nullptr, 0)));
Object qualname(&scope, Str::empty());
Module main(&scope, findMainModule(runtime));
return Function::cast(
runtime->newFunctionWithCode(thread, qualname, code, main));
}
RawBytes newBytesFromCStr(Thread* thread, const char* str) {
return Bytes::cast(thread->runtime()->newBytesWithAll(
View<byte>(reinterpret_cast<const byte*>(str), std::strlen(str))));
}
RawBytearray newBytearrayFromCStr(Thread* thread, const char* str) {
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
Bytearray result(&scope, runtime->newBytearray());
runtime->bytearrayExtend(
thread, result,
View<byte>(reinterpret_cast<const byte*>(str), std::strlen(str)));
return *result;
}
RawLargeInt newLargeIntWithDigits(View<uword> digits) {
Thread* thread = Thread::current();
HandleScope scope(thread);
LargeInt result(&scope, thread->runtime()->createLargeInt(digits.length()));
for (word i = 0, e = digits.length(); i < e; ++i) {
result.digitAtPut(i, digits.get(i));
}
return *result;
}
RawObject newMemoryView(View<byte> bytes, const char* format,
ReadOnly read_only) {
Thread* thread = Thread::current();
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
Bytes bytes_obj(&scope, runtime->newBytesWithAll(bytes));
if (read_only == ReadOnly::ReadWrite) {
bytes_obj = runtime->mutableBytesFromBytes(thread, bytes_obj);
}
MemoryView result(&scope,
runtime->newMemoryView(thread, bytes_obj, bytes_obj,
bytes_obj.length(), read_only));
result.setFormat(Str::cast(runtime->newStrFromCStr(format)));
return *result;
}
RawTuple newTupleWithNone(word length) {
Thread* thread = Thread::current();
HandleScope scope(thread);
MutableTuple tuple(&scope, thread->runtime()->newMutableTuple(length));
tuple.fill(NoneType::object());
return Tuple::cast(tuple.becomeImmutable());
}
RawObject newWeakRefWithCallback(Runtime* runtime, Thread* thread,
const Object& referent,
const Object& callback) {
HandleScope scope(thread);
WeakRef ref(&scope, runtime->newWeakRef(thread, referent));
ref.setCallback(runtime->newBoundMethod(callback, ref));
return *ref;
}
RawObject setFromRange(word start, word stop) {
Thread* thread = Thread::current();
HandleScope scope(thread);
Set result(&scope, thread->runtime()->newSet());
Object value(&scope, NoneType::object());
Object hash_obj(&scope, NoneType::object());
for (word i = start; i < stop; i++) {
value = SmallInt::fromWord(i);
hash_obj = Interpreter::hash(thread, value);
if (hash_obj.isErrorException()) return *hash_obj;
word hash = SmallInt::cast(*hash_obj).value();
setAdd(thread, result, value, hash);
}
return *result;
}
RawObject runBuiltinImpl(BuiltinFunction function,
View<std::reference_wrapper<const Object>> args) {
Thread* thread = Thread::current();
HandleScope scope(thread);
// Push an empty function so we have one at the expected place in the stack.
word args_length = args.length();
Runtime* runtime = thread->runtime();
Tuple parameter_names(&scope, runtime->emptyTuple());
if (args_length > 0) {
MutableTuple parameter_names_mut(&scope,
runtime->newMutableTuple(args_length));
for (word i = 0; i < args_length; i++) {
parameter_names_mut.atPut(i, runtime->newStrFromFmt("arg%w", i));
}
parameter_names = parameter_names_mut.becomeImmutable();
}
Object name(&scope, Runtime::internStrFromCStr(thread, "<anonymous>"));
Code code(&scope, runtime->newBuiltinCode(args_length, /*posonlyargcount=*/0,
/*kwonlyargcount=*/0,
/*flags=*/0, function,
parameter_names, name));
Module main(&scope, findMainModule(runtime));
Function function_obj(&scope,
runtime->newFunctionWithCode(thread, name, code, main));
thread->stackPush(*function_obj);
for (word i = 0; i < args_length; i++) {
thread->stackPush(*args.get(i).get());
}
return Interpreter::call(thread, args_length);
}
RawObject runBuiltin(BuiltinFunction function) {
return runBuiltinImpl(function,
View<std::reference_wrapper<const Object>>{nullptr, 0});
}
RawObject runCode(const Code& code) {
Thread* thread = Thread::current();
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
Module main(&scope, findMainModule(runtime));
Object qualname(&scope, Runtime::internStrFromCStr(thread, "<anonymous>"));
Function function(&scope,
runtime->newFunctionWithCode(thread, qualname, code, main));
return Interpreter::call0(thread, function);
}
RawObject runCodeNoBytecodeRewriting(const Code& code) {
Thread* thread = Thread::current();
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
Module main(&scope, findMainModule(runtime));
Object qualname(&scope, Runtime::internStrFromCStr(thread, "<anonymous>"));
Bytes bytecode(&scope, code.code());
code.setCode(runtime->newBytes(0, 0));
Function function(&scope,
runtime->newFunctionWithCode(thread, qualname, code, main));
MutableBytes rewritten_bytecode(
&scope, runtime->newMutableBytesUninitialized(bytecode.length()));
rewritten_bytecode.replaceFromWithBytes(0, *bytecode, bytecode.length());
function.setRewrittenBytecode(*rewritten_bytecode);
return Interpreter::call0(thread, function);
}
RawObject runFromCStr(Runtime* runtime, const char* c_str) {
Thread* thread = Thread::current();
HandleScope scope(thread);
Object str(&scope, runtime->newStrFromCStr(c_str));
Object filename(&scope, runtime->newStrFromCStr("<test string>"));
Code code(&scope, compile(thread, str, filename, ID(exec), /*flags=*/0,
/*optimize=*/0));
Module main_module(&scope, findMainModule(runtime));
Object result(&scope, executeModule(thread, code, main_module));
// Barebones emulation of the top-level SystemExit handling, to allow for
// testing of handleSystemExit().
DCHECK(thread->isErrorValueOk(*result), "error/exception mismatch");
if (result.isError()) {
Type type(&scope, thread->pendingExceptionType());
if (type.builtinBase() == LayoutId::kSystemExit) handleSystemExit(thread);
}
return *result;
}
void addBuiltin(const char* name_cstr, BuiltinFunction function,
View<const char*> parameter_names, word flags) {
Thread* thread = Thread::current();
Runtime* runtime = thread->runtime();
HandleScope scope(thread);
Module main(&scope, findMainModule(runtime));
word num_parameters = parameter_names.length();
Object parameter_names_tuple(&scope, NoneType::object());
if (num_parameters > 0) {
MutableTuple parameter_names_mtuple(
&scope, runtime->newMutableTuple(num_parameters));
for (word i = 0; i < num_parameters; i++) {
parameter_names_mtuple.atPut(
i, Runtime::internStrFromCStr(thread, parameter_names.get(i)));
}
parameter_names_tuple = parameter_names_mtuple.becomeImmutable();
} else {
parameter_names_tuple = runtime->emptyTuple();
}
Object name(&scope, Runtime::internStrFromCStr(thread, name_cstr));
word argcount = num_parameters - ((flags & Code::Flags::kVarargs) != 0) -
((flags & Code::Flags::kVarkeyargs) != 0);
Code code(&scope, runtime->newBuiltinCode(
/*argcount=*/argcount, /*posonlyargcount=*/0,
/*kwonlyargcount=*/0, flags, function,
/*parameter_names=*/parameter_names_tuple, name));
Function function_obj(&scope,
runtime->newFunctionWithCode(thread, name, code, main));
moduleAtPut(thread, main, name, function_obj);
}
// Equivalent to evaluating "list(range(start, stop))" in Python
RawObject listFromRange(word start, word stop) {
Thread* thread = Thread::current();
HandleScope scope(thread);
List result(&scope, thread->runtime()->newList());
Object value(&scope, NoneType::object());
for (word i = start; i < stop; i++) {
value = SmallInt::fromWord(i);
thread->runtime()->listAdd(thread, result, value);
}
return *result;
}
RawObject icLookupAttr(RawMutableTuple caches, word index, LayoutId layout_id) {
word i = index * kIcPointersPerEntry;
bool is_found = false;
if (caches.at(i + kIcEntryValueOffset).isTuple()) {
return icLookupPolymorphic(caches, index, layout_id, &is_found);
}
return icLookupMonomorphic(caches, index, layout_id, &is_found);
}
RawObject icLookupBinaryOp(RawMutableTuple caches, word index,
LayoutId left_layout_id, LayoutId right_layout_id,
BinaryOpFlags* flags_out) {
word i = index * kIcPointersPerEntry;
if (caches.at(i + kIcEntryValueOffset).isTuple()) {
return icLookupBinOpPolymorphic(caches, index, left_layout_id,
right_layout_id, flags_out);
}
return icLookupBinOpMonomorphic(caches, index, left_layout_id,
right_layout_id, flags_out);
}
::testing::AssertionResult containsBytecode(const Function& function,
Bytecode bc) {
Thread* thread = Thread::current();
HandleScope scope(thread);
MutableBytes bytecode(&scope, function.rewrittenBytecode());
for (word i = 0, num_opcodes = rewrittenBytecodeLength(bytecode);
i < num_opcodes;) {
BytecodeOp bco = nextBytecodeOp(bytecode, &i);
if (bco.bc == bc) {
return ::testing::AssertionSuccess();
}
}
unique_c_ptr<char> name(Str::cast(function.name()).toCStr());
return ::testing::AssertionFailure()
<< "opcode " << kBytecodeNames[static_cast<word>(bc)]
<< " not found in '" << name.get() << "'";
}
::testing::AssertionResult isBytearrayEqualsBytes(const Object& result,
View<byte> expected) {
Thread* thread = Thread::current();
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
if (result.isError()) {
if (result.isErrorException()) {
Type type(&scope, thread->pendingExceptionType());
unique_c_ptr<char> name(Str::cast(type.name()).toCStr());
return ::testing::AssertionFailure()
<< "pending '" << name.get() << "' exception";
}
return ::testing::AssertionFailure() << "is an " << result;
}
if (!runtime->isInstanceOfBytearray(*result)) {
return ::testing::AssertionFailure()
<< "is a '" << typeName(runtime, *result) << "'";
}
Bytearray result_array(&scope, *result);
Bytes result_bytes(&scope, bytearrayAsBytes(thread, result_array));
Bytes expected_bytes(&scope, runtime->newBytesWithAll(expected));
if (result_bytes.compare(*expected_bytes) != 0) {
return ::testing::AssertionFailure()
<< "bytearray(" << result_bytes << ") is not equal to bytearray("
<< expected_bytes << ")";
}
return ::testing::AssertionSuccess();
}
::testing::AssertionResult isBytearrayEqualsCStr(const Object& result,
const char* expected) {
return isBytearrayEqualsBytes(
result, View<byte>(reinterpret_cast<const byte*>(expected),
static_cast<word>(std::strlen(expected))));
}
::testing::AssertionResult isBytesEqualsBytes(const Object& result,
View<byte> expected) {
Thread* thread = Thread::current();
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
if (result.isError()) {
if (result.isErrorException()) {
Type type(&scope, thread->pendingExceptionType());
unique_c_ptr<char> name(Str::cast(type.name()).toCStr());
return ::testing::AssertionFailure()
<< "pending '" << name.get() << "' exception";
}
return ::testing::AssertionFailure() << "is an " << result;
}
if (!runtime->isInstanceOfBytes(*result)) {
return ::testing::AssertionFailure()
<< "is a '" << typeName(runtime, *result) << "'";
}
Bytes result_bytes(&scope, bytesUnderlying(*result));
Bytes expected_bytes(&scope, runtime->newBytesWithAll(expected));
if (result_bytes.compare(*expected_bytes) != 0) {
return ::testing::AssertionFailure()
<< result_bytes << " is not equal to " << expected_bytes;
}
return ::testing::AssertionSuccess();
}
::testing::AssertionResult isMutableBytesEqualsBytes(const Object& result,
View<byte> expected) {
if (!result.isError() && !result.isMutableBytes()) {
return ::testing::AssertionFailure()
<< "is a '" << typeName(Thread::current()->runtime(), *result)
<< "'";
}
return isBytesEqualsBytes(result, expected);
}
::testing::AssertionResult isBytesEqualsCStr(const Object& result,
const char* expected) {
return isBytesEqualsBytes(
result, View<byte>(reinterpret_cast<const byte*>(expected),
static_cast<word>(std::strlen(expected))));
}
::testing::AssertionResult isStrEquals(const Object& str1, const Object& str2) {
Thread* thread = Thread::current();
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
if (!runtime->isInstanceOfStr(*str1)) {
return ::testing::AssertionFailure()
<< "is a '" << typeName(runtime, *str1) << "'";
}
if (!runtime->isInstanceOfStr(*str2)) {
return ::testing::AssertionFailure()
<< "is a '" << typeName(runtime, *str2) << "'";
}
Str s1(&scope, strUnderlying(*str1));
Str s2(&scope, strUnderlying(*str2));
if (!s1.equals(*s2)) {
unique_c_ptr<char> s2_ptr(s2.toCStr());
return ::testing::AssertionFailure()
<< "is not equal to '" << s2_ptr.get() << "'";
}
return ::testing::AssertionSuccess();
}
::testing::AssertionResult isStrEqualsCStr(RawObject obj, const char* c_str) {
Thread* thread = Thread::current();
HandleScope scope(thread);
Object str_obj(&scope, obj);
Runtime* runtime = thread->runtime();
if (!runtime->isInstanceOfStr(*str_obj)) {
return ::testing::AssertionFailure()
<< "is a '" << typeName(runtime, *str_obj) << "'";
}
Str str(&scope, strUnderlying(*str_obj));
if (!str.equalsCStr(c_str)) {
unique_c_ptr<char> str_ptr(str.toCStr());
return ::testing::AssertionFailure()
<< "'" << str_ptr.get() << "' is not equal to '" << c_str << "'";
}
return ::testing::AssertionSuccess();
}
::testing::AssertionResult isSymbolIdEquals(SymbolId result,
SymbolId expected) {
if (result == expected) return ::testing::AssertionSuccess();
const char* result_name = result == SymbolId::kInvalid
? "<Invalid>"
: Symbols::predefinedSymbolAt(result);
return ::testing::AssertionFailure()
<< "Expected '" << Symbols::predefinedSymbolAt(expected)
<< "', but got '" << result_name << "'";
}
::testing::AssertionResult isFloatEqualsDouble(RawObject obj, double expected) {
Thread* thread = Thread::current();
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
if (obj.isError()) {
if (obj.isErrorException()) {
Type type(&scope, thread->pendingExceptionType());
Str type_name(&scope, type.name());
return ::testing::AssertionFailure()
<< "pending " << type_name << " exception";
}
return ::testing::AssertionFailure() << "is an " << obj;
}
if (!runtime->isInstanceOfFloat(obj)) {
return ::testing::AssertionFailure()
<< "is a '" << typeName(runtime, obj) << "'";
}
double value = floatUnderlying(obj).value();
// Test with memcmp instead of ==, so we can differentiate -0, and 0 or test
// for NaNs.
if (value != expected) {
return ::testing::AssertionFailure() << value << " is not " << expected;
}
return ::testing::AssertionSuccess();
}
::testing::AssertionResult isIntEqualsWord(RawObject obj, word value) {
Thread* thread = Thread::current();
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
if (obj.isError()) {
if (obj.isErrorException()) {
Type type(&scope, thread->pendingExceptionType());
Str type_name(&scope, type.name());
return ::testing::AssertionFailure()
<< "pending " << type_name << " exception";
}
return ::testing::AssertionFailure() << "is an " << obj;
}
if (!runtime->isInstanceOfInt(obj)) {
return ::testing::AssertionFailure()
<< "is a '" << typeName(runtime, obj) << "'";
}
Object object(&scope, obj);
Int value_int(&scope, intUnderlying(*object));
if (value_int.numDigits() > 1 || value_int.asWord() != value) {
return ::testing::AssertionFailure()
<< value_int << " is not equal to " << value;
}
return ::testing::AssertionSuccess();
}
::testing::AssertionResult isIntEqualsDigits(RawObject obj,
View<uword> digits) {
Thread* thread = Thread::current();
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
if (obj.isError()) {
if (obj.isErrorException()) {
Type type(&scope, thread->pendingExceptionType());
Str type_name(&scope, type.name());
return ::testing::AssertionFailure()
<< "pending " << type_name << " exception";
}
return ::testing::AssertionFailure() << "is an " << obj;
}
if (!runtime->isInstanceOfInt(obj)) {
return ::testing::AssertionFailure()
<< "is a '" << typeName(runtime, obj) << "'";
}
Int expected(&scope, newLargeIntWithDigits(digits));
Object value_obj(&scope, obj);
Int value_int(&scope, intUnderlying(*value_obj));
if (expected.compare(*value_int) != 0) {
return ::testing::AssertionFailure()
<< value_int << " is not equal to " << expected;
}
return ::testing::AssertionSuccess();
}
RawObject layoutCreateEmpty(Thread* thread) {
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
LayoutId id = runtime->reserveLayoutId(thread);
Layout result(&scope, runtime->newLayout(id));
runtime->layoutSetTupleOverflow(*result);
runtime->layoutAtPut(id, *result);
return *result;
}
::testing::AssertionResult raised(RawObject return_value, LayoutId layout_id) {
return raisedWithStr(return_value, layout_id, nullptr);
}
::testing::AssertionResult raisedWithStr(RawObject return_value,
LayoutId layout_id,
const char* message) {
Thread* thread = Thread::current();
Runtime* runtime = thread->runtime();
HandleScope scope(thread);
Object return_value_obj(&scope, return_value);
if (!return_value_obj.isError()) {
Type type(&scope, runtime->typeOf(*return_value_obj));
Str name(&scope, type.name());
return ::testing::AssertionFailure()
<< "call returned " << name << ", not Error";
}
if (!thread->hasPendingException()) {
return ::testing::AssertionFailure() << "no exception pending";
}
Type expected_type(&scope, runtime->typeAt(layout_id));
Type exception_type(&scope, thread->pendingExceptionType());
if (!typeIsSubclass(*exception_type, *expected_type)) {
Str expected_name(&scope, expected_type.name());
Str actual_name(&scope, exception_type.name());
return ::testing::AssertionFailure()
<< "\npending exception has type:\n " << actual_name
<< "\nexpected:\n " << expected_name << "\n";
}
if (message == nullptr) return ::testing::AssertionSuccess();
Object exc_value(&scope, thread->pendingExceptionValue());
if (!runtime->isInstanceOfStr(*exc_value)) {
if (runtime->isInstanceOfBaseException(*exc_value)) {
BaseException exc(&scope, *exc_value);
Tuple args(&scope, exc.args());
if (args.length() == 0) {
return ::testing::AssertionFailure()
<< "pending exception args tuple is empty";
}
exc_value = args.at(0);
}
if (!runtime->isInstanceOfStr(*exc_value)) {
return ::testing::AssertionFailure()
<< "pending exception value is not str";
}
}
Str exc_msg(&scope, *exc_value);
if (!exc_msg.equalsCStr(message)) {
return ::testing::AssertionFailure()
<< "\npending exception value:\n '" << exc_msg
<< "'\nexpected:\n '" << message << "'\n";
}
return ::testing::AssertionSuccess();
}
TemporaryDirectory::TemporaryDirectory() {
const char* tmpdir = std::getenv("TMPDIR");
if (tmpdir == nullptr) {
tmpdir = "/tmp";
}
const char format[] = "%s/PyroTest.XXXXXXXX";
word length = std::snprintf(nullptr, 0, format, tmpdir) + 1;
std::unique_ptr<char[]> buffer(new char[length]);
std::snprintf(buffer.get(), length, format, tmpdir);
char* result = ::mkdtemp(buffer.get());
CHECK(result != nullptr, "failed to create temporary directory");
path = buffer.get();
CHECK(!path.empty(), "must not be empty");
if (path.back() != '/') path += "/";
}
TemporaryDirectory::~TemporaryDirectory() {
std::string cleanup = "rm -rf " + path;
CHECK(system(cleanup.c_str()) == 0, "failed to cleanup temporary directory");
}
void writeFile(const std::string& path, const std::string& contents) {
CHECK(!path.empty() && path.front() == '/', "Should be an absolute path");
std::ofstream of(path);
CHECK(of.good(), "file creation failed");
of << contents;
CHECK(of.good(), "file write failed");
of.close();
}
} // namespace testing
} // namespace py