/* * 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 #include "test.h" namespace script::test { DEFINE_ENGINE_TEST(ReferenceTest); TEST_F(ReferenceTest, Global) { EngineScope engineScope(engine); Global global; { EngineScope engineScope(engine); EXPECT_TRUE(global.isEmpty()); auto hello = String::newString(u8"Hello"); EXPECT_STREQ("Hello", hello.toString().c_str()); Global g(hello); EXPECT_FALSE(g.isEmpty()); EXPECT_STREQ("Hello", g.get().toString().c_str()); // assign global = g; EXPECT_FALSE(global.isEmpty()); EXPECT_STREQ("Hello", global.get().toString().c_str()); } { EngineScope engineScope(engine); // copy Global copy(global); EXPECT_FALSE(copy.isEmpty()); EXPECT_STREQ("Hello", copy.get().toString().c_str()); // move Global move(std::move(copy)); EXPECT_TRUE(copy.isEmpty()); EXPECT_TRUE(copy.getValue().isNull()); // EXPECT_THROW({ copy.get(); }, Exception); EXPECT_FALSE(move.isEmpty()); EXPECT_STREQ("Hello", move.get().toString().c_str()); // move-assign global = std::move(move); global.reset(); EXPECT_TRUE(global.isEmpty()); } { EngineScope engineScope(engine); global.reset(); } } TEST_F(ReferenceTest, GlobalVector) { std::vector> v; EngineScope engineScope(engine); v.push_back({}); } TEST_F(ReferenceTest, Weak) { Weak weak; Global global; { EngineScope engineScope(engine); EXPECT_TRUE(weak.isEmpty()); auto hello = String::newString(u8"Hello"); EXPECT_STREQ("Hello", hello.toString().c_str()); weak = Weak(hello); global = Global(hello); EXPECT_FALSE(weak.isEmpty()); EXPECT_STREQ("Hello", weak.get().toString().c_str()); auto obj = Object::newObject(); Weak weakObj{obj}; EXPECT_EQ(obj, weakObj.get()); auto PI = 3.1416926; auto number = Number::newNumber(PI); Weak weakNumber{number}; EXPECT_DOUBLE_EQ(PI, weakNumber.get().toDouble()); } { EngineScope engineScope(engine); Weak copy(weak); EXPECT_FALSE(copy.isEmpty()); EXPECT_STREQ("Hello", copy.get().toString().c_str()); Weak move(std::move(copy)); EXPECT_TRUE(copy.isEmpty()); EXPECT_TRUE(copy.getValue().isNull()); EXPECT_THROW({ copy.get(); }, Exception); EXPECT_FALSE(move.isEmpty()); EXPECT_STREQ("Hello", move.get().toString().c_str()); Weak assign(String::newString(u8"Hello")); // assign move = assign; // move-assign move = std::move(assign); weak.reset(); EXPECT_TRUE(weak.isEmpty()); global.reset(); } { EngineScope engineScope(engine); weak.reset(); global.reset(); } } TEST_F(ReferenceTest, LocalReset) { EngineScope engineScope(engine); Local ref = Object::newObject(); EXPECT_FALSE(ref.isNull()); ref.reset(); EXPECT_TRUE(ref.isNull()); ref = Object::newObject(); EXPECT_FALSE(ref.isNull()); Local newRef(std::move(ref)); EXPECT_TRUE(ref.isNull()); } // test local reference can be GCed TEST_F(ReferenceTest, LocalGc) { { std::string chunk; chunk.resize(1024 * 1024, '.'); EngineScope engineScope(engine); // test gc { for (int i = 0; i < 50; ++i) { StackFrameScope inner; Local local = String::newString(chunk); } } // can we trust gc? engine->gc(); } } TEST_F(ReferenceTest, WeakGc) { std::vector> weaks; { std::string chunk; chunk.resize(1024 * 1024, '.'); EngineScope engineScope(engine); { StackFrameScope inner; for (int i = 0; i < 50; ++i) { auto obj = Object::newObject(); obj.set("junk", String::newString(chunk)); weaks.emplace_back(obj); engine->gc(); } } // can we trust gc? engine->gc(); } { EngineScope engineScope(engine); EXPECT_TRUE(std::find_if(weaks.begin(), weaks.end(), [](auto& w) { return w.getValue().isNull(); }) != weaks.end()); weaks.clear(); } } TEST_F(ReferenceTest, WeakGlobal) { Weak weak; Global global; { EngineScope engineScope(engine); auto hello = String::newString(u8"Hello"); global = Global(hello); weak = Weak(global); EXPECT_FALSE(weak.isEmpty()); global.reset(); weak.reset(); weak = Weak(hello); global = Global(weak); EXPECT_FALSE(global.isEmpty()); weak.reset(); global.reset(); } } TEST_F(ReferenceTest, WeakNotClrear) { Weak weak; { EngineScope engineScope(engine); weak = String::newString("hello"); } EXPECT_FALSE(weak.isEmpty()); destroyEngine(); EXPECT_TRUE(weak.isEmpty()); } TEST_F(ReferenceTest, GlobalNotClear) { Global global; { EngineScope engineScope(engine); global = String::newString("hello"); } EXPECT_FALSE(global.isEmpty()); destroyEngine(); EXPECT_TRUE(global.isEmpty()); } template inline void testGlobalOrWeakOnEngineDestroy(ScriptEngine* engine, DFunc destroyEngine) { Ref globalEmpty; std::optional ctor; Ref assignCreate; Ref copyAssign; std::optional copyCtor; Ref moveAssign; std::optional moveCtor; std::vector blackBoxTest; { EngineScope engineScope(engine); assignCreate = Object::newObject(); ctor.emplace(Object::newObject()); copyAssign = assignCreate; copyCtor.emplace(assignCreate); moveAssign = std::move(assignCreate); moveCtor.emplace(std::move(copyAssign)); copyAssign.swap(moveAssign); auto iteration = 64; for (int i = 0; i < iteration; ++i) { blackBoxTest.emplace_back(Object::newObject()); // request vector reconstruct blackBoxTest.shrink_to_fit(); } for (int i = 0; i < iteration; ++i) { blackBoxTest.emplace_back(assignCreate); // request vector reconstruct blackBoxTest.shrink_to_fit(); } } destroyEngine(); EXPECT_TRUE(globalEmpty.isEmpty()); EXPECT_TRUE(ctor->isEmpty()); EXPECT_TRUE(assignCreate.isEmpty()); EXPECT_TRUE(copyAssign.isEmpty()); EXPECT_TRUE(copyCtor->isEmpty()); EXPECT_TRUE(moveAssign.isEmpty()); EXPECT_TRUE(moveCtor->isEmpty()); bool allRefsAreReset = true; for (auto&& i : blackBoxTest) { allRefsAreReset &= i.isEmpty(); } EXPECT_TRUE(allRefsAreReset); } TEST_F(ReferenceTest, GlobalOnEngineDestroy) { // this test should be compiled with ASAN ON testGlobalOrWeakOnEngineDestroy>(engine, [this]() { destroyEngine(); }); } TEST_F(ReferenceTest, WeakOnEngineDestroy) { // this test should be compiled with ASAN ON testGlobalOrWeakOnEngineDestroy>(engine, [this]() { destroyEngine(); }); } #define ALL_TYPES_EACH(FN) \ FN(Value) \ FN(Object) \ FN(String) \ FN(Number) \ FN(Boolean) \ FN(Function) \ FN(Array) \ FN(ByteBuffer) \ FN(Unsupported) TEST_F(ReferenceTest, GlobalTypes) { EngineScope engineScope(engine); #define FN(V) \ Global test_##V; \ Global test_copy_##V(test_##V); \ Global test_move_##V(std::move(test_##V)); \ test_##V = test_copy_##V; \ test_##V = std::move(test_copy_##V); \ test_##V.reset(); \ test_##V.weakify(); \ test_##V.get(); \ test_##V.isNull(); // TODO(taylorcyang): add test // ALL_TYPES_EACH(FN) #undef FN } TEST_F(ReferenceTest, WeakTypes) { EngineScope engineScope(engine); #define FN(V) \ Weak test_##V; \ Weak test_copy_##V(test_##V); \ Weak test_move_##V(std::move(test_##V)); \ test_##V = test_copy_##V; \ test_##V = std::move(test_copy_##V); \ test_##V.reset(); \ test_##V.strongify(); \ test_##V.get(); \ test_##V.isNull(); // TODO(taylorcyang): add test // ALL_TYPES_EACH(FN) #undef FN } TEST_F(ReferenceTest, LocalTypes) { EngineScope engineScope(engine); #define FN(V) \ Local test_##V; \ Local test_copy_##V(test_##V); \ Local test_move_##V(std::move(test_##V)); \ test_##V = test_copy_##V; \ test_##V = std::move(test_copy_##V); \ test_##V.reset(); \ test_##V.isNull(); \ test_##V.describe(); \ test_##V.describeUtf8(); // TODO(taylorcyang): add test // ALL_TYPES_EACH(FN) #undef FN } #ifdef QUICK_JS_HAS_SCRIPTX_PATCH TEST_F(ReferenceTest, QuickJsPatchStrictEqual) { EngineScope engineScope(engine); auto context = qjs_interop::currentContext(); auto testEqual = [&](auto&& ref, auto&& ref2, bool eq = true) { EXPECT_TRUE( JS_StrictEqual(context, qjs_interop::peekLocal(ref), qjs_interop::peekLocal(ref2)) == eq); }; auto str1 = String::newString("hello"); auto str2 = String::newString("hello"); auto str3 = String::newString("hello world"); testEqual(str1, str1); testEqual(str1, str2); testEqual(str1, str3, false); auto obj1 = Object::newObject(); auto obj2 = Object::newObject(); testEqual(obj1, obj1); testEqual(obj1, obj2, false); } TEST_F(ReferenceTest, QuickJsPatchWeakRef) { EngineScope engineScope(engine); auto context = qjs_interop::currentContext(); { auto obj = Object::newObject(); auto weak = JS_NewWeakRef(context, qjs_interop::peekLocal(obj)); auto g = JS_GetWeakRef(context, weak); EXPECT_TRUE(JS_StrictEqual(context, qjs_interop::peekLocal(obj), g)); JS_FreeValue(context, g); JS_FreeValue(context, weak); } { // only object is weak JSValue weak; { auto obj = Object::newObject(); weak = JS_NewWeakRef(context, qjs_interop::peekLocal(obj)); } auto g = JS_GetWeakRef(context, weak); EXPECT_TRUE(JS_IsUndefined(g)); JS_FreeValue(context, g); JS_FreeValue(context, weak); } { // non-object is actually not weak { JSValue weak; { auto str = String::newString(""); weak = JS_NewWeakRef(context, qjs_interop::peekLocal(str)); } auto g = JS_GetWeakRef(context, weak); EXPECT_TRUE(!JS_IsUndefined(g)); JS_FreeValue(context, g); JS_FreeValue(context, weak); } } } #endif } // namespace script::test