diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5494da801..2707136dc 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -107,6 +107,14 @@ if (GTEST_FOUND AND GMOCK_FOUND) if(WIN32) target_link_libraries(tests winmm) endif() + + create_test(aul_test + SOURCES + aul/aul_test.cpp + ../src/script/C4ScriptStandaloneStubs.cpp + LIBRARIES + libmisc + libc4script) else() set(_gtest_missing "") if (NOT GTEST_INCLUDE_DIR) diff --git a/tests/aul/aul_test.cpp b/tests/aul/aul_test.cpp new file mode 100644 index 000000000..f4a25d50b --- /dev/null +++ b/tests/aul/aul_test.cpp @@ -0,0 +1,202 @@ +/* + * OpenClonk, http://www.openclonk.org + * + * Copyright (c) 2015, The OpenClonk Team and contributors + * + * Distributed under the terms of the ISC license; see accompanying file + * "COPYING" for details. + * + * "Clonk" is a registered trademark of Matthes Bender, used with permission. + * See accompanying file "TRADEMARK" for details. + * + * To redistribute this file separately, substitute the full license texts + * for the above references. + */ + +// Testing C4Aul behaviour. + +#include +#include +#include + +#include "gamescript/C4Script.h" +#include "script/C4ScriptHost.h" +#include "lib/C4Random.h" +#include "object/C4DefList.h" +#include "script/C4Value.h" + +std::ostream &operator<<(std::ostream &os, const C4Value &val) +{ + return os << val.GetDataString().getData(); +} + +class C4AulTest : public ::testing::Test +{ +protected: + C4Value RunCode(const char *code, bool wrap = true) + { + class OnScopeExit + { + public: + ~OnScopeExit() + { + GameScript.Clear(); + ScriptEngine.Clear(); + } + } _cleanup; + + InitCoreFunctionMap(&ScriptEngine); + FixedRandom(0x40490fdb); + + std::string wrapped; + if (wrap) + { + wrapped = "func Main() {\n"; + wrapped += code; + wrapped += "\n}\n"; + } + else + { + wrapped = code; + } + std::string src("<"); + auto test_info = ::testing::UnitTest::GetInstance()->current_test_info(); + src += test_info->test_case_name(); + src += "::"; + src += test_info->name(); + src += ">"; + + GameScript.LoadData(src.c_str(), wrapped.c_str(), NULL); + ScriptEngine.Link(&::Definitions); + ScriptEngine.GlobalNamed.SetNameList(&ScriptEngine.GlobalNamedNames); + + return GameScript.Call("Main", nullptr, true); + } + C4Value RunExpr(const char *expr) + { + std::string code = "return "; + code += expr; + code += ';'; + return RunCode(code.c_str()); + } +}; + +namespace { + void _setItems(C4ValueArray *a, int i) {} + template + void _setItems(C4ValueArray *a, int i, T0 &&v, T &&...t) + { + a->SetItem(i, v); + _setItems(a, ++i, std::forward(t)...); + } + + // Helper functions to create array C4Values inline + template + C4Value C4VArray(T &&...v) + { + C4ValueArray *a = new C4ValueArray(sizeof...(v)); + _setItems(a, 0, std::forward(v)...); + return C4VArray(a); + } +} + +namespace { + void _setItems(C4PropList *p) {} + template + void _setItems(C4PropList *p, T0 &&k, T1 &&v, T &&...t) + { + p->SetPropertyByS(::Strings.RegString(k), v); + _setItems(p, std::forward(t)...); + } + + // Helper function to create proplist C4Values inline + template + C4Value C4VPropList(T &&...v) + { + static_assert(sizeof...(v) % 2 == 0, "Proplist constructor needs even number of arguments"); + C4PropList *p = C4PropList::New(); + _setItems(p, std::forward(v)...); + return C4VPropList(p); + } +} + +TEST_F(C4AulTest, ValueReturn) +{ + // Make sure primitive value returns work. + EXPECT_EQ(C4VNull, RunCode("return;")); + EXPECT_EQ(C4VNull, RunExpr("nil")); + EXPECT_EQ(C4VTrue, RunExpr("true")); + EXPECT_EQ(C4VFalse, RunExpr("false")); + EXPECT_EQ(C4VInt(42), RunExpr("42")); + EXPECT_EQ(C4VString("Hello World!"), RunExpr("\"Hello World!\"")); + + // Make sure array returns work. + EXPECT_EQ(C4VArray(), RunExpr("[]")); + EXPECT_EQ( + C4VArray(C4VInt(0), C4VNull, C4VArray(C4VInt(1)), C4VString("Hi")), + RunExpr("[0, nil, [1], \"Hi\"]")); + + // Make sure proplist returns work. + EXPECT_EQ(C4VPropList(), RunExpr("{}")); + EXPECT_EQ( + C4VPropList("a", C4VInt(1), "b", C4VArray()), + RunExpr("{\"a\": 1, \"b\"=[]}")); +} + +TEST_F(C4AulTest, Translate) +{ + EXPECT_EQ(C4VString("a"), RunExpr("Translate(\"a\")")); +} + +class AulMathTest : public C4AulTest +{ +protected: + static const C4Value C4VINT_MIN; + static const C4Value C4VINT_MAX; +}; +const C4Value AulMathTest::C4VINT_MIN = C4VInt(-2147483647 - 1); +const C4Value AulMathTest::C4VINT_MAX = C4VInt(2147483647); + +TEST_F(AulMathTest, Addition) +{ + // Adding 0 to a value should return the same value. + EXPECT_EQ(C4VInt(0), RunExpr("0 + 0")); + EXPECT_EQ(C4VINT_MAX, RunExpr("2147483647 + 0")); + EXPECT_EQ(C4VINT_MAX, RunExpr("0 + 2147483647")); + EXPECT_EQ(C4VINT_MIN, RunExpr("-2147483648 + 0")); + EXPECT_EQ(C4VINT_MIN, RunExpr("0 + -2147483648")); + + EXPECT_EQ(C4VInt(1078530011), RunExpr("1078530011 + 0")); + EXPECT_EQ(C4VInt(1078530011), RunExpr("0 + 1078530011")); + EXPECT_EQ(C4VInt(1068827891), RunExpr("1068827891 + 0")); + EXPECT_EQ(C4VInt(1068827891), RunExpr("0 + 1068827891")); +} + +TEST_F(AulMathTest, Division) +{ + // Dividing a value by 0 should fail. + EXPECT_THROW(RunExpr("1 / 0"), C4AulExecError); + // Dividing C4VINT_MIN by -1 should fail. + EXPECT_THROW(RunExpr("-2147483648 / -1"), C4AulExecError); + // Dividing C4VINT_MIN by 1 should return C4VINT_MIN. + EXPECT_EQ(C4VINT_MIN, RunExpr("-2147483648 / 1")); +} + +TEST_F(AulMathTest, Bug1389) +{ + // 0001389: Aul integer overflow results in strange numbers on 64 bit + // machines + + // "Strange numbers" shall mean numbers that are impossible to express in + // C4Script as an integer literal. In particular, in these cases, the + // high dword of the numbers, which is not exposed to C4Script, is not + // guaranteed to be zero. Therefore, straight comparison of these numbers + // will result in unexpected results. + + EXPECT_EQ(C4VINT_MIN, RunExpr("2147483647 + 1")); + EXPECT_EQ(C4VINT_MAX, RunExpr("-2147483648 - 1")); + // x ± 1 ± 1 is handled differently from x ± 2, yet the result should be + // the same. + EXPECT_EQ(C4VTrue, RunExpr("2147483647 + 1 + 1 == 2147483647 + 2")); + EXPECT_EQ(C4VTrue, RunExpr("-2147483648 - 1 - 1 == -2147483648 - 2")); +}