/* * Tencent is pleased to support the open source community by making ScriptX available. * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "test.h" namespace script::test { DEFINE_ENGINE_TEST(ValueTest); constexpr auto kLuaClassScript = u8R"( f = {}; f.__index = f; local mt = {}; mt.__call = function (self, name, age) local ret = {}; ret.name = name; ret.age = age; setmetatable(ret, f); return ret; end setmetatable(f, mt) function f:greet() return "Hello, I'm " .. self.name .. " " .. self.age .. " years old."; end return f; )"; TEST_F(ValueTest, Object_NewObject) { EngineScope engineScope(engine); Local func = engine->eval(TS().js(u8R"( function f(name, age) { this.name = name; this.age = age; } f.prototype.greet = function() { let str = "Hello, I'm " + this.name + " " + this.age + " years old."; // console.log(str); return str; } f; )") .lua(kLuaClassScript) .select()); ASSERT_TRUE(func.isObject()); std::initializer_list> jennyList{ Object::newObject(func, {String::newString("Jenny"), Number::newNumber(5)}), // variadic helper Object::newObject(func, String::newString("Jenny"), Number::newNumber(5)), // C++ types Object::newObject(func, "Jenny", 5), // mixed Object::newObject(func, String::newString("Jenny"), 5)}; for (auto& jenny : jennyList) { auto name = jenny.get(String::newString(u8"name")); auto age = jenny.get(String::newString(u8"age")); auto greet = jenny.get(String::newString(u8"greet")); ASSERT_TRUE(name.isString()) << name.describeUtf8(); ASSERT_TRUE(age.isNumber()) << age.describeUtf8(); ASSERT_STREQ(name.asString().toString().c_str(), "Jenny"); ASSERT_EQ(age.asNumber().toInt32(), 5); ASSERT_TRUE(greet.isFunction()); auto greetRet = greet.asFunction().call(jenny); ASSERT_TRUE(greetRet.isString()) << greetRet.describeUtf8(); ASSERT_STREQ(greetRet.asString().toString().c_str(), "Hello, I'm Jenny 5 years old."); } } TEST_F(ValueTest, Object) { EngineScope engineScope(engine); try { auto hello = String::newString("hello"); auto obj = Object::newObject(); EXPECT_EQ(obj.asValue().getKind(), ValueKind::kObject); obj.set("hello", Number::newNumber(321)); EXPECT_TRUE(obj.has(hello)); ASSERT_TRUE(obj.get(hello).isNumber()); EXPECT_EQ(obj.get(hello).asNumber().toInt32(), 321); obj.remove("hello"); EXPECT_FALSE(obj.has(hello)); EXPECT_TRUE(obj.get(hello).isNull()); obj.set(hello, 1); ASSERT_TRUE(obj.get(hello).isNumber()); EXPECT_EQ(obj.get(hello).asNumber().toInt32(), 1); obj.set("hello", Number::newNumber(1)); ASSERT_TRUE(obj.get(hello).isNumber()); EXPECT_EQ(obj.get(hello).asNumber().toInt32(), 1); obj.set("one", 1); ASSERT_TRUE(obj.get("one").isNumber()); EXPECT_EQ(obj.get("one").asNumber().toInt32(), 1); obj.set("one", "1.1"); ASSERT_TRUE(obj.get("one").isString()); EXPECT_STREQ(obj.get("one").asString().toString().c_str(), "1.1"); // won't compile, non-convertible type // obj.set("one", &obj); engine->set("one", "1.1"); engine->set("one", 1); engine->set("hello", Number::newNumber(1)); engine->set(hello, 1); } catch (const Exception& e) { FAIL() << e.message() << e.stacktrace(); } } TEST_F(ValueTest, ObjectKeys) { EngineScope engineScope(engine); auto obj = Object::newObject(); obj.set("hello", 1); obj.set("world", 2); auto keys = obj.getKeys(); // the order is undefined std::set keyNames; for (auto& k : keys) { keyNames.insert(k.toString()); } ASSERT_EQ(keys.size(), 2); EXPECT_TRUE(keyNames.find("hello") != keyNames.end()); EXPECT_TRUE(keyNames.find("world") != keyNames.end()); auto names = obj.getKeyNames(); ASSERT_EQ(names.size(), 2); EXPECT_TRUE(std::find(names.begin(), names.end(), "hello") != names.end()); EXPECT_TRUE(std::find(names.begin(), names.end(), "world") != names.end()); } TEST_F(ValueTest, String) { EngineScope engineScope(engine); auto string = "hello world"; Local strVal = String::newString(string); EXPECT_FALSE(strVal.isNull()); EXPECT_TRUE(strVal.isString()); EXPECT_EQ(strVal.getKind(), ValueKind::kString); auto str = String::newString(string); EXPECT_STREQ(string, str.toString().c_str()); #ifdef __cpp_char8_t EXPECT_EQ(u8"hello world", str.toU8string()); #endif EXPECT_STREQ(string, str.describeUtf8().c_str()); EXPECT_EQ(strVal, str); str = engine->eval(TS().js("'hello world'").lua("return 'hello world'").select()).asString(); EXPECT_STREQ(string, str.toString().c_str()); } #ifdef __cpp_char8_t TEST_F(ValueTest, U8String) { EngineScope engineScope(engine); std::u8string string = u8"你好, 世界"; auto str = String::newString(string); EXPECT_EQ(string, str.toU8string()); str = engine->eval(TS().js(u8"'你好, 世界'").lua(u8"return '你好, 世界'").select()).asString(); EXPECT_EQ(string, str.toU8string()); } #endif TEST_F(ValueTest, InstanceOf) { EngineScope engineScope(engine); try { #ifdef SCRIPTX_LANG_JAVASCRIPT auto f = engine->eval("function instanceoftest() {}; instanceoftest"); auto ins = engine->eval("new instanceoftest()"); EXPECT_TRUE(ins.asObject().instanceOf(f)); #elif defined(SCRIPTX_LANG_LUA) auto f = engine->eval(kLuaClassScript); auto ins = engine->eval("return f()"); EXPECT_TRUE(ins.asObject().instanceOf(f)); #else FAIL() << "add test impl here"; #endif } catch (const Exception& e) { FAIL() << e; } } namespace { size_t i = 1; std::string greet; TEST_F(ValueTest, Function) { EngineScope engineScope(engine); auto func = Function::newFunction([this](const script::Arguments& args) { EXPECT_EQ(args.engine(), engine); EXPECT_EQ(args.engineAs(), engine); EXPECT_EQ(args.engineAs(), engine); i = args.size(); if (args.size() > 0 && args[0].isString()) { greet = args[0].asString().toString(); } if (args.size() > 0 && args[0].isBoolean()) { // throw in here throw Exception("invalid arguments"); } return Local{}; }); EXPECT_TRUE(func.asValue().isFunction()); func.call({}); EXPECT_EQ(i, 0); // call initializer_list func.call({}, {String::newString("hello")}); EXPECT_EQ(i, 1); EXPECT_STREQ(greet.c_str(), "hello"); // call vector std::vector> args; args.emplace_back(String::newString("hello").asValue()); func.call({}, args); // type-safe variadic template helper method func.call({}, String::newString("hello"), Object::newObject()); func.call({}, String::newString("hello")); func.call({}, "hello2"); EXPECT_EQ(i, 1); EXPECT_STREQ(greet.c_str(), "hello2"); try { func.call({}, script::Boolean::newBoolean(true)); FAIL(); } catch (const Exception& e) { EXPECT_NE(e.message().find("invalid arguments"), std::string::npos); } } TEST_F(ValueTest, FunctionReceiver) { EngineScope engineScope(engine); #ifdef SCRIPTX_LANG_JAVASCRIPT auto func = Function::newFunction([](const Arguments& args) { return args.thiz(); }); auto obj = Object::newObject(); auto g = static_cast(engine)->getGlobal(); auto ret = func.call({}); EXPECT_EQ(ret, g); ret = func.call(obj); EXPECT_TRUE(ret.isObject()); EXPECT_EQ(obj, ret); func = engine ->eval(R"( (function() { return function() { return this; } })(); )") .asFunction(); ret = func.call({}); EXPECT_EQ(ret, g); ret = func.call(obj); EXPECT_TRUE(ret.isObject()); EXPECT_EQ(obj, ret); #endif } TEST_F(ValueTest, FunctionCall) { EngineScope engineScope(engine); engine->eval(TS().js(R"( function unitTestFuncCall(arg1, arg2) { if (arg1 == 0) return "hello world"; if (arg2 == "x") return "hello X"; } )") .lua(R"( function unitTestFuncCall(arg1, arg2) if arg1 == 0 then return "hello world"; end if arg2 == "x" then return "hello X"; end end )") .select()); auto func = engine->get("unitTestFuncCall").asFunction(); auto ret = func.call({}, 0); ASSERT_TRUE(ret.isString()); EXPECT_STREQ(ret.asString().toString().c_str(), "hello world"); ret = func.call({}, 1, "x"); ASSERT_TRUE(ret.isString()); EXPECT_STREQ(ret.asString().toString().c_str(), "hello X"); } TEST_F(ValueTest, FunctionReturn) { EngineScope engineScope(engine); #if defined(SCRIPTX_LANG_LUA) auto func = engine ->eval(R"( return function (count) if count ~= 2 then return "hello world"; else return "hello", "world"; end end )") .asFunction(); auto ret = func.call({}, 0); ASSERT_TRUE(ret.isString()); EXPECT_STREQ(ret.asString().toString().c_str(), "hello world"); // multi return value ret = func.call({}, 2); ASSERT_TRUE(ret.isArray()); EXPECT_STREQ(ret.asArray().get(0).asString().toString().c_str(), "hello"); EXPECT_STREQ(ret.asArray().get(1).asString().toString().c_str(), "world"); #endif } TEST_F(ValueTest, FunctionArgumentsOutOfRange) { EngineScope engineScope(engine); auto func = Function::newFunction([](const Arguments& args) { EXPECT_TRUE(args[-1].isNull()); EXPECT_TRUE(args[args.size() + 1].isNull()); return Local{}; }); func.call({}); } TEST_F(ValueTest, FunctionHasALotOfArguments) { EngineScope engineScope(engine); auto func = Function::newFunction([](const Arguments& args) { int total = 0; for (size_t j = 0; j < args.size(); ++j) { total += args[j].asNumber().toInt32(); } return Number::newNumber(total); }); for (int j = 0; j < 100; ++j) { StackFrameScope stack; std::vector> args; args.reserve(j); for (int k = 0; k < j; ++k) { args.push_back(Number::newNumber(k)); } auto ret = func.call({}, args).asNumber().toInt32(); EXPECT_EQ(ret, (j * (j - 1)) / 2); } } TEST_F(ValueTest, FunctionHasThiz) { EngineScope engineScope(engine); auto func = Function::newFunction( [](const Arguments& args) { return Boolean::newBoolean(args.hasThiz()); }); engine->set("func", func); auto hasThiz = engine->eval(TS().js("var x = {func: func}; x.func()").lua("return func()").select()) .asBoolean() .value(); #ifdef SCRIPTX_LANG_JAVASCRIPT EXPECT_TRUE(hasThiz); #elif SCRIPTX_LANG_Lua EXPECT_FALSE(hasThiz); #endif } TEST_F(ValueTest, EngineEvalReturnValue) { EngineScope engineScope(engine); Local val; #if defined(SCRIPTX_LANG_JAVASCRIPT) val = engine->eval(R"(3.14)"); #elif defined(SCRIPTX_LANG_LUA) val = engine->eval(R"(return 3.14)"); #else FAIL(); #endif ASSERT_TRUE(val.isNumber()); EXPECT_DOUBLE_EQ(3.14, val.asNumber().toDouble()); } } // namespace TEST_F(ValueTest, Binding) { EngineScope engineScope(engine); auto func = Function::newFunction([](int, int) { return 0; }); EXPECT_TRUE(func.asValue().isFunction()); func.call({}, Number::newNumber(1), Number::newNumber(2)); } TEST_F(ValueTest, ExceptionDeath) { Global gf; { EngineScope engineScope(engine); gf = Function::newFunction([](const script::Arguments& args) { if (args.size() == 0) { throw Exception("invalid argument"); } return Number::newNumber(0); }); } try { EngineScope engineScope(engine); gf.get().call(); } catch (Exception& e) { EngineScope engineScope(engine); // handle exception again... e.message(); } EngineScope engineScope(engine); gf.reset(); } TEST_F(ValueTest, Array) { EngineScope engineScope(engine); Local arr = Array::newArray(4); #ifdef SCRIPTX_LANG_JAVASCRIPT EXPECT_EQ(arr.asValue().getKind(), ValueKind::kArray); #elif defined(SCRIPTX_LANG_LUA) EXPECT_EQ(arr.asValue().getKind(), ValueKind::kObject); #endif #ifndef SCRIPTX_BACKEND_LUA EXPECT_EQ(arr.size(), 4); #endif EXPECT_TRUE(arr.get(0).isNull()); EXPECT_TRUE(arr.get(1).isNull()); EXPECT_TRUE(arr.get(2).isNull()); EXPECT_TRUE(arr.get(3).isNull()); arr.set(0, String::newString("hello")); ASSERT_TRUE(arr.get(0).isString()); EXPECT_STREQ(arr.get(0).asString().toString().c_str(), "hello"); arr.clear(); EXPECT_EQ(arr.size(), 0); arr = Array::newArray( {String::newString("hello"), Number::newNumber(1), ::script::Boolean::newBoolean(true)}); EXPECT_EQ(arr.size(), 3); EXPECT_STREQ(arr.get(0).asString().toString().c_str(), "hello"); EXPECT_EQ(arr.get(1).asNumber().toInt32(), 1); EXPECT_TRUE(arr.get(2).asBoolean().value()); arr.set(2, {}); EXPECT_TRUE(arr.get(2).isNull()); arr.set(3, 1); EXPECT_TRUE(arr.get(3).isNumber()); arr = Array::of("hello", 1, ::script::Boolean::newBoolean(true)); EXPECT_EQ(arr.size(), 3); EXPECT_STREQ(arr.get(0).asString().toString().c_str(), "hello"); EXPECT_EQ(arr.get(1).asNumber().toInt32(), 1); EXPECT_TRUE(arr.get(2).asBoolean().value()); arr.set(2, {}); EXPECT_TRUE(arr.get(2).isNull()); arr.set(3, 1); EXPECT_TRUE(arr.get(3).isNumber()); arr = Array::newArray(); arr.add(Number::newNumber(42)); EXPECT_EQ(arr.get(0).asNumber().toInt32(), 42); } TEST_F(ValueTest, Null) { EngineScope engineScope(engine); EXPECT_EQ(Local().getKind(), ValueKind::kNull); EXPECT_STREQ(Local().describeUtf8().c_str(), "null"); #ifdef SCRIPTX_LANG_JAVASCRIPT // JS: null & undefined -> script::Null EXPECT_TRUE(engine->eval(String::newString(u8"null")).isNull()); EXPECT_EQ(engine->eval(String::newString(u8"null")), Local()); EXPECT_TRUE(engine->eval(String::newString(u8"undefined")).isNull()); EXPECT_EQ(engine->eval(String::newString(u8"undefined")), Local()); // script::Null -> JS: undefined auto key = String::newString(u8"ValueTest_Null_null"); engine->set(key, Local()); auto isUndef = engine->eval(String::newString(u8"ValueTest_Null_null === undefined")); ASSERT_TRUE(isUndef.isBoolean()); EXPECT_TRUE(isUndef.asBoolean().value()); #endif } template void testNumber(T value) { auto num = Number::newNumber(value); EXPECT_EQ(num.toInt32(), static_cast(value)); #ifndef SCRIPTX_BACKEND_WEBASSEMBLY EXPECT_EQ(num.toInt64(), static_cast(value)); #endif EXPECT_FLOAT_EQ(num.toFloat(), static_cast(value)); EXPECT_FLOAT_EQ(num.toDouble(), static_cast(value)); } TEST_F(ValueTest, Number) { EngineScope engineScope(engine); EXPECT_EQ(Number::newNumber(0).asValue().getKind(), ValueKind::kNumber); EXPECT_EQ(Number::newNumber(42).describeUtf8(), "42"); testNumber(42); testNumber(42); testNumber(3.14f); testNumber(3.14); } TEST_F(ValueTest, Equals) { EngineScope engineScope(engine); auto n1 = Number::newNumber(1); auto n1_ = Number::newNumber(1); auto n2 = Number::newNumber(2); EXPECT_TRUE(n1 == n1_); EXPECT_FALSE(n1 == n2); EXPECT_TRUE(n1 != n2); EXPECT_TRUE(Local() == Local()); auto s1 = String::newString("hello"); auto s2 = String::newString("hello"); auto s3 = String::newString("world"); EXPECT_TRUE(s1 == s2); EXPECT_FALSE(s1 == s3); EXPECT_TRUE(s1 != s3); } TEST_F(ValueTest, Kinds) { auto test = [](const Local& v) { ValueKind kind = v.getKind(); if (kind == ValueKind::kNull) { EXPECT_TRUE(v.isNull()); } #define CAST_TEST(TYPE) \ if (kind != ValueKind::k##TYPE) { \ EXPECT_THROW({ v.as##TYPE(); }, Exception); \ } CAST_TEST(String) CAST_TEST(Number) CAST_TEST(Boolean) CAST_TEST(Function) CAST_TEST(ByteBuffer) #undef CAST_TEST }; EngineScope engineScope(engine); test({}); test(String::newString("hello")); test(Object::newObject()); test(Number::newNumber(0)); test(Boolean::newBoolean(false)); test(Function::newFunction([]() {})); test(Array::newArray()); test(ByteBuffer::newByteBuffer(0)); EXPECT_THROW({ Number::newNumber(0).asValue().asObject(); }, Exception); EXPECT_THROW({ String::newString("hello").asValue().asArray(); }, Exception); } TEST_F(ValueTest, Unsupported) { EngineScope engineScope(engine); #ifdef SCRIPTX_LANG_JAVASCRIPT auto strange = engine->eval("Symbol('x')"); #elif defined(SCRIPTX_LANG_LUA) auto lua = lua_interop::currentEngineLua(); lua_newuserdata(lua, 4); auto strange = lua_interop::makeLocal(lua_gettop(lua)); #else FAIL() << "add test here"; auto strange = Local(); #endif EXPECT_EQ(strange.getKind(), ValueKind::kUnsupported); strange.asUnsupported(); EXPECT_THROW({ Number::newNumber(0).asValue().asUnsupported(); }, Exception); } TEST_F(ValueTest, KindNames) { EXPECT_STREQ(valueKindName(ValueKind::kNull), "Null"); EXPECT_STREQ(valueKindName(ValueKind::kString), "String"); EXPECT_STREQ(valueKindName(ValueKind::kObject), "Object"); EXPECT_STREQ(valueKindName(ValueKind::kNumber), "Number"); EXPECT_STREQ(valueKindName(ValueKind::kBoolean), "Boolean"); EXPECT_STREQ(valueKindName(ValueKind::kFunction), "Function"); EXPECT_STREQ(valueKindName(ValueKind::kArray), "Array"); EXPECT_STREQ(valueKindName(ValueKind::kByteBuffer), "ByteBuffer"); EXPECT_STREQ(valueKindName(ValueKind::kUnsupported), "Unsupported"); std::ostringstream oss; oss << ValueKind::kNull; EXPECT_EQ(oss.str(), "Null"); } } // namespace script::test