view C++/tests/Js/main.cpp @ 415:ac4573b9bb4b

Js: add js::Pointer for non-managed pointers
author David Demelier <markand@malikania.fr>
date Thu, 08 Oct 2015 08:29:48 +0200
parents 2a0b8613498f
children 865224c2191a
line wrap: on
line source

/*
 * TestJsUnicode.cpp -- test irccd JS functions
 *
 * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <gtest/gtest.h>

#include <Js.h>

/*
 * TODO:
 *
 * - document stack modification in all functions,
 * - check that the stack is correct,
 * - add more C Duktape wrappers.
 */

using namespace js;

/* --------------------------------------------------------
 * Push & get
 * -------------------------------------------------------- */

TEST(PushAndGet, boolean)
{
	Context context;

	ASSERT_EQ(0, context.top());
	context.push(true);
	ASSERT_TRUE(context.get<bool>(-1));
	ASSERT_EQ(1, context.top());
	context.push(false);
	ASSERT_FALSE(context.get<bool>(-1));
	ASSERT_EQ(2, context.top());
}

TEST(PushAndGet, integer)
{
	Context context;

	ASSERT_EQ(0, context.top());
	context.push(123);
	ASSERT_EQ(123, context.get<int>(-1));
	ASSERT_EQ(1, context.top());
	context.push(456);
	ASSERT_EQ(456, context.get<int>(-1));
	ASSERT_EQ(2, context.top());
}

TEST(PushAndGet, number)
{
	Context context;

	ASSERT_EQ(0, context.top());
	context.push(10.5);
	ASSERT_EQ(10.5, context.get<double>(-1));
	ASSERT_EQ(1, context.top());
	context.push(50.1);
	ASSERT_EQ(50.1, context.get<double>(-1));
	ASSERT_EQ(2, context.top());
}

TEST(PushAndGet, string)
{
	Context context;

	ASSERT_EQ(0, context.top());
	context.push("hello world!");
	ASSERT_EQ("hello world!", context.get<std::string>(-1));
	ASSERT_EQ(1, context.top());
}

TEST(PushAndGet, undefined)
{
	Context context;

	ASSERT_EQ(0, context.top());
	context.push(Undefined{});
	ASSERT_EQ(DUK_TYPE_UNDEFINED, context.type(-1));
	ASSERT_EQ(1, context.top());
}

TEST(PushAndGet, null)
{
	Context context;

	ASSERT_EQ(0, context.top());
	context.push(Null{});
	ASSERT_EQ(DUK_TYPE_NULL, context.type(-1));
	ASSERT_EQ(1, context.top());
}

TEST(PushAndGet, map)
{
	Context context;
	Map<int> map{
		{ "one",	1 },
		{ "two",	2 }
	};

	ASSERT_EQ(0, context.top());
	context.push(js::Object{});
	context.push(map);

	ASSERT_EQ(DUK_TYPE_OBJECT, context.type(-1));
	ASSERT_EQ(1, context.getProperty<int>(-1, "one"));
	ASSERT_EQ(2, context.getProperty<int>(-1, "two"));
	ASSERT_EQ(1, context.top());
}

TEST(PushAndGet, vector)
{
	Context context;

	ASSERT_EQ(0, context.top());
	context.push(std::vector<int>{1, 2, 3});
	ASSERT_EQ(DUK_TYPE_OBJECT, context.type(-1));
	ASSERT_EQ(1, context.top());

	auto array = context.get<std::vector<int>>(-1);
	ASSERT_EQ(3U, array.size());
	ASSERT_EQ(1, array[0]);
	ASSERT_EQ(2, array[1]);
	ASSERT_EQ(3, array[2]);
}

TEST(PushAndGet, pointer)
{
	Context context;

	ASSERT_EQ(0, context.top());
	context.push(Pointer<int>{new int(1)});
	ASSERT_EQ(1, *context.get<Pointer<int>>(-1));
	ASSERT_EQ(1, context.top());
}

/* --------------------------------------------------------
 * Require
 * -------------------------------------------------------- */

TEST(Require, boolean)
{
	Context context;

	ASSERT_EQ(0, context.top());

	try {
		context.push(Function{[] (Context &ctx) -> int {
			ctx.require<bool>(0);

			return 0;
		}});
		context.peval();

		FAIL() << "exception expected";
	} catch (...) {
	}

	ASSERT_EQ(0, context.top());
}

TEST(Require, integer)
{
	Context context;

	ASSERT_EQ(0, context.top());

	try {
		context.push(Function{[] (Context &ctx) -> int {
			ctx.require<int>(0);

			return 0;
		}});
		context.peval();

		FAIL() << "exception expected";
	} catch (...) {
	}

	ASSERT_EQ(0, context.top());
}

TEST(Require, number)
{
	Context context;

	ASSERT_EQ(0, context.top());

	try {
		context.push(Function{[] (Context &ctx) -> int {
			ctx.require<double>(0);

			return 0;
		}});
		context.peval();

		FAIL() << "exception expected";
	} catch (...) {
	}

	ASSERT_EQ(0, context.top());
}

TEST(Require, string)
{
	Context context;

	ASSERT_EQ(0, context.top());

	try {
		context.push(Function{[] (Context &ctx) -> int {
			ctx.require<std::string>(0);

			return 0;
		}});
		context.peval();

		FAIL() << "exception expected";
	} catch (...) {
	}

	ASSERT_EQ(0, context.top());
}

TEST(Require, cstring)
{
	Context context;

	ASSERT_EQ(0, context.top());

	try {
		context.push(Function{[] (Context &ctx) -> int {
			ctx.require<const char *>(0);

			return 0;
		}});
		context.peval();

		FAIL() << "exception expected";
	} catch (...) {
	}

	ASSERT_EQ(0, context.top());
}

TEST(Require, pointer)
{
	Context context;

	ASSERT_EQ(0, context.top());

	try {
		context.push(Function{[] (Context &ctx) -> int {
			ctx.require<Pointer<int>>(0);

			return 0;
		}});
		context.peval();

		FAIL() << "exception expected";
	} catch (...) {
	}

	ASSERT_EQ(0, context.top());
}

/* --------------------------------------------------------
 * Is
 * -------------------------------------------------------- */

TEST(Is, boolean)
{
	Context context;

	ASSERT_EQ(0, context.top());
	context.push(true);
	ASSERT_TRUE(context.is<bool>(-1));
	ASSERT_EQ(1, context.top());
}

TEST(Is, integer)
{
	Context context;

	ASSERT_EQ(0, context.top());
	context.push(123);
	ASSERT_TRUE(context.is<int>(-1));
	ASSERT_EQ(1, context.top());
}

TEST(Is, number)
{
	Context context;

	ASSERT_EQ(0, context.top());
	context.push(50.5);
	ASSERT_TRUE(context.is<double>(-1));
	ASSERT_EQ(1, context.top());
}

TEST(Is, string)
{
	Context context;

	ASSERT_EQ(0, context.top());
	context.push(std::string{"hello"});
	ASSERT_TRUE(context.is<std::string>(-1));
	ASSERT_EQ(1, context.top());
}

TEST(Is, cstring)
{
	Context context;

	ASSERT_EQ(0, context.top());
	context.push("hello");
	ASSERT_TRUE(context.is<const char *>(-1));
	ASSERT_EQ(1, context.top());
}

TEST(Is, undefined)
{
	Context context;

	ASSERT_EQ(0, context.top());
	context.push(Undefined{});
	ASSERT_TRUE(context.is<Undefined>(-1));
	ASSERT_EQ(1, context.top());
}

TEST(Is, null)
{
	Context context;

	ASSERT_EQ(0, context.top());
	context.push(Null{});
	ASSERT_TRUE(context.is<Null>(-1));
	ASSERT_EQ(1, context.top());
}

TEST(Is, object)
{
	Context context;

	ASSERT_EQ(0, context.top());
	context.push(Object{});
	ASSERT_TRUE(context.is<Object>(-1));
	ASSERT_EQ(1, context.top());
}

TEST(Is, array)
{
	Context context;

	ASSERT_EQ(0, context.top());
	context.push(Array{});
	ASSERT_TRUE(context.is<Array>(-1));
	ASSERT_EQ(1, context.top());
}

TEST(Is, pointer)
{
	Context context;

	ASSERT_EQ(0, context.top());
	context.push(Pointer<int>{new int(1)});
	ASSERT_TRUE(context.is<Pointer<int>>(-1));
	ASSERT_EQ(1, context.top());
}

/* --------------------------------------------------------
 * Optional
 * -------------------------------------------------------- */

TEST(Optional, boolean)
{
	Context context;

	ASSERT_EQ(0, context.top());
	ASSERT_TRUE(context.optional<bool>(0, true));
	ASSERT_FALSE(context.optional<bool>(0, false));
	ASSERT_EQ(0, context.top());
}

TEST(Optional, integer)
{
	Context context;

	ASSERT_EQ(0, context.top());
	ASSERT_EQ(123, context.optional<int>(0, 123));
	ASSERT_EQ(456, context.optional<int>(0, 456));
	ASSERT_EQ(0, context.top());
}

TEST(Optional, number)
{
	Context context;

	ASSERT_EQ(0, context.top());
	ASSERT_EQ(10.0, context.optional<double>(0, 10.0));
	ASSERT_EQ(20.0, context.optional<double>(0, 20.0));
	ASSERT_EQ(0, context.top());
}

TEST(Optional, string)
{
	Context context;

	ASSERT_EQ(0, context.top());
	ASSERT_EQ("no", context.optional<std::string>(0, "no"));
	ASSERT_EQ("yes", context.optional<std::string>(0, "yes"));
	ASSERT_EQ(0, context.top());
}

TEST(Optional, cstring)
{
	Context context;

	ASSERT_EQ(0, context.top());
	ASSERT_STREQ("no", context.optional<const char *>(0, "no"));
	ASSERT_STREQ("yes", context.optional<const char *>(0, "yes"));
	ASSERT_EQ(0, context.top());
}

TEST(Optional, pointer)
{
	Context context;

	ASSERT_EQ(0, context.top());
	ASSERT_EQ(nullptr, context.optional<js::Pointer<int>>(0, js::Pointer<int>{nullptr}));
	ASSERT_EQ(0, context.top());
}

/* --------------------------------------------------------
 * Basics
 * -------------------------------------------------------- */

TEST(Basics, global)
{
	Context context;

	// boolean
	ASSERT_EQ(0, context.top());
	context.putGlobal("valueBoolean", true);
	ASSERT_TRUE(context.getGlobal<bool>("valueBoolean"));
	ASSERT_EQ(0, context.top());

	// integer
	ASSERT_EQ(0, context.top());
	context.putGlobal("valueInteger", 123);
	ASSERT_EQ(123, context.getGlobal<int>("valueInteger"));
	ASSERT_EQ(0, context.top());
}

TEST(Basics, top)
{
	Context context;

	ASSERT_EQ(0, context.top());
	context.push(true);
	ASSERT_EQ(1, context.top());
}

TEST(Basics, pop1)
{
	Context context;

	ASSERT_EQ(0, context.top());
	context.push(true);
	context.pop();
	ASSERT_EQ(0, context.top());
}

TEST(Basics, pop2)
{
	Context context;

	ASSERT_EQ(0, context.top());
	context.push(true);
	context.push(true);
	context.pop(2);
	ASSERT_EQ(0, context.top());
}

TEST(Basics, putProperty)
{
	Context context;

	ASSERT_EQ(0, context.top());
	context.push(Object{});
	context.putProperty(-1, "x", 123);
	ASSERT_EQ(123, context.getProperty<int>(-1, "x"));
	ASSERT_EQ(1, context.top());
}

TEST(Basics, enumerate)
{
	Context context;

	ASSERT_EQ(0, context.top());
	context.push(Object{});
	context.putProperty(-1, "x", 123);
	context.putProperty(-1, "y", 456);
	ASSERT_EQ(1, context.top());
	context.enumerate(-1, 0, true, [] (Context &ctx) {
		ASSERT_EQ(DUK_TYPE_STRING, ctx.type(-2));
		ASSERT_EQ(DUK_TYPE_NUMBER, ctx.type(-1));

		if (ctx.get<std::string>(-2) == "x")
			ASSERT_EQ(123, ctx.get<int>(-1));
		if (ctx.get<std::string>(-2) == "y")
			ASSERT_EQ(456, ctx.get<int>(-1));
	});
	ASSERT_EQ(1, context.top());
}

TEST(Basics, call)
{
	Context context;

	ASSERT_EQ(0, context.top());
	context.push(Function{[] (Context &ctx) -> int {
		ctx.putGlobal("x", 123);

		return 0;
	}});
	context.call();
	ASSERT_EQ(1, context.top());

	ASSERT_EQ(123, context.getGlobal<int>("x"));
}

/* ------------------------------------------------------------------
 * Eval
 * ------------------------------------------------------------------ */

TEST(Eval, simple)
{
	Context context;

	ASSERT_EQ(0, context.top());
	context.eval(Script{"x = 123;"});
	ASSERT_EQ(123, context.getGlobal<int>("x"));
	ASSERT_EQ(1, context.top());
}

TEST(Eval, function)
{
	Context context;

	ASSERT_EQ(0, context.top());
	context.eval(Script{"function f() { x = 123; }; f();"});
	ASSERT_EQ(123, context.getGlobal<int>("x"));
	ASSERT_EQ(1, context.top());
}

TEST(Eval, cfunction)
{
	Context context;

	ASSERT_EQ(0, context.top());
	context.putGlobal("f", Function{[] (Context &ctx) -> int {
		ctx.putGlobal("x", 123);

		return 0;
	}});
	context.eval(Script{"f()"});
	ASSERT_EQ(123, context.getGlobal<int>("x"));
	ASSERT_EQ(1, context.top());
}

/* ------------------------------------------------------------------
 * Protected eval
 * ------------------------------------------------------------------ */

TEST(Peval, success)
{
	Context context;

	ASSERT_EQ(0, context.top());
	try {
		context.peval(Script{"x = 1"});
	} catch (const ErrorInfo &info) {
		FAIL() << "error unexpected: " << info.what();
	}
	ASSERT_EQ(1, context.top());
}

TEST(Peval, failure)
{
	Context context;

	ASSERT_EQ(0, context.top());
	try {
		context.peval(Script{"doesnotexists()"});

		FAIL() << "expected exception";
	} catch (const std::exception &) {
	}
	ASSERT_EQ(0, context.top());
}

/* ------------------------------------------------------------------
 * Exception handling
 * ------------------------------------------------------------------ */

TEST(Exception, error)
{
	Context context;

	context.putGlobal("f", Function{[] (Context &ctx) -> duk_idx_t {
		ctx.raise(Error{"error thrown"});

		return 0;
	}});
	context.eval(Script{
		"try {"
		"  f();"
		"} catch (ex) {"
		"  name = ex.name;"
		"  message = ex.message;"
		"  received = true;"
		"}"
	});

	ASSERT_TRUE(context.getGlobal<bool>("received"));
	ASSERT_EQ("Error", context.getGlobal<std::string>("name"));
	ASSERT_EQ("error thrown", context.getGlobal<std::string>("message"));
}

/* --------------------------------------------------------
 * TypeInfo with shared and pointer
 * -------------------------------------------------------- */

class Player {
public:
	bool m_updated{false};
};

namespace js {

template <>
class TypeInfo<std::shared_ptr<Player>> : public TypeInfoShared<Player> {
public:
	static void prototype(Context &ctx)
	{
		/* Create a temporary for the test */
		ctx.push(Object{});

		/* "update" method */
		ctx.putProperty(-1, "update", Function{[] (Context &ctx) -> int {
			ctx.self<std::shared_ptr<Player>>()->m_updated = true;

			return 0;
		}});
	}
};

template <>
class TypeInfo<Player *> : public TypeInfoPointer<Player> {
public:
	static void prototype(Context &ctx)
	{
		/* Create a temporary for the test */
		ctx.push(Object{});

		/* "update" method */
		ctx.putProperty(-1, "update", Function{[&] (Context &ctx) -> int {
			ctx.self<Player *>()->m_updated = true;

			return 0;
		}});
	}
};

} // !js

TEST(Shared, simple)
{
	Context ctx;
	std::shared_ptr<Player> p{new Player};

	ctx.push(p);
	std::shared_ptr<Player> p2 = ctx.get<std::shared_ptr<Player>>(-1);

	p2->m_updated = true;

	ASSERT_TRUE(p->m_updated);
}

TEST(Shared, self)
{
	Context ctx;
	std::shared_ptr<Player> p{new Player};

	try {
		ctx.putGlobal("player", p);
		ctx.peval(Script{"player.update();"});

		ASSERT_TRUE(p->m_updated);
	} catch (const std::exception &ex) {
		FAIL() << ex.what();
	}
}

TEST(Pointer, simple)
{
	Context context;
	Player *p{new Player};

	ASSERT_EQ(0, context.top());
	context.push(p);
	Player *p2 = context.get<Player *>(-1);
	ASSERT_EQ(1, context.top());

	p2->m_updated = true;

	ASSERT_TRUE(p->m_updated);
}

TEST(Pointer, self)
{
	Context ctx;
	Player *p{new Player};

	try {
		ctx.putGlobal("player", p);
		ctx.peval(Script{"player.update();"});

		ASSERT_TRUE(p->m_updated);
	} catch (const std::exception &ex) {
		FAIL() << ex.what();
	}
}

/* --------------------------------------------------------
 * FunctionMap
 * -------------------------------------------------------- */

TEST(PushFunctionMap, basic)
{
	bool f1called{false}, f2called{false};
	auto f1 = [&] (Context &) -> int { f1called = true; return 0; };
	auto f2 = [&] (Context &) -> int { f2called = true; return 0; };

	FunctionMap map{
		{ "f1", { f1, 0 } },
		{ "f2", { f2, 0 } }
	};
	Context context;

	ASSERT_EQ(0, context.top());
	context.push(Global{});
	ASSERT_EQ(1, context.top());
	context.push(map);
	ASSERT_EQ(1, context.top());
	context.pop();
	context.peval(Script{"f1();"});
	context.peval(Script{"f2();"});

	ASSERT_TRUE(f1called);
	ASSERT_TRUE(f2called);
}

int main(int argc, char **argv)
{
	testing::InitGoogleTest(&argc, argv);

	return RUN_ALL_TESTS();
}