diff --git a/js/src/Makefile.in b/js/src/Makefile.in index 34d226c53a5a..eb2ba2a5700a 100644 --- a/js/src/Makefile.in +++ b/js/src/Makefile.in @@ -59,6 +59,10 @@ ifndef JS_DISABLE_SHELL DIRS += shell endif +ifdef ENABLE_TESTS +DIRS += jsapi-tests +endif + MODULE = js LIBRARY_NAME = mozjs STATIC_LIBRARY_NAME = js_static diff --git a/js/src/configure.in b/js/src/configure.in index e8a10505947e..a57e7b1ce7f0 100644 --- a/js/src/configure.in +++ b/js/src/configure.in @@ -5215,6 +5215,7 @@ MAKEFILES=" Makefile shell/Makefile lirasm/Makefile + jsapi-tests/Makefile config/Makefile config/autoconf.mk config/mkdepend/Makefile diff --git a/js/src/jsapi-tests/Makefile.in b/js/src/jsapi-tests/Makefile.in new file mode 100644 index 000000000000..2433dbd5dd1c --- /dev/null +++ b/js/src/jsapi-tests/Makefile.in @@ -0,0 +1,70 @@ +# -*- Mode: makefile -*- +# +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (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.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is Spidermonkey build system. +# +# The Initial Developer of the Original Code is +# The Mozilla Foundation. +# Portions created by the Initial Developer are Copyright (C) 2009 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Jason Orendorff +# +# Alternatively, the contents of this file may be used under the terms of +# either of the GNU General Public License Version 2 or later (the "GPL"), +# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +DEPTH = .. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +PROGRAM = jsapi-tests$(BIN_SUFFIX) +CPPSRCS = \ + tests.cpp \ + selfTest.cpp \ + testPropCache.cpp \ + testXDR.cpp \ + $(NULL) + +DEFINES += -DEXPORT_JS_API + +LIBS = $(NSPR_LIBS) $(DEPTH)/$(LIB_PREFIX)js_static.$(LIB_SUFFIX) + +LOCAL_INCLUDES += -I$(topsrcdir) -I.. + +ifdef _MSC_VER +ifdef WINCE +WIN32_EXE_LDFLAGS += -ENTRY:mainACRTStartup +endif +endif + +include $(topsrcdir)/config/rules.mk + +check:: + $(wildcard $(RUN_TEST_PROGRAM)) $(DIST)/bin/jsapi-tests$(BIN_SUFFIX) diff --git a/js/src/jsapi-tests/selfTest.cpp b/js/src/jsapi-tests/selfTest.cpp new file mode 100644 index 000000000000..0aa504d2a74d --- /dev/null +++ b/js/src/jsapi-tests/selfTest.cpp @@ -0,0 +1,22 @@ +#include "tests.h" + +BEGIN_TEST(selfTest_NaNsAreSame) +{ + jsvalRoot v1(cx), v2(cx); + EVAL("0/0", v1.addr()); // NaN + CHECK_SAME(v1, v1); + + EVAL("Math.sin('no')", v2.addr()); // also NaN + CHECK_SAME(v1, v2); + return true; +} +END_TEST(selfTest_NaNsAreSame) + +BEGIN_TEST(selfTest_negativeZeroIsNotTheSameAsZero) +{ + jsvalRoot negativeZero(cx); + EVAL("-0", negativeZero.addr()); + CHECK(!sameValue(negativeZero, JSVAL_ZERO)); + return true; +} +END_TEST(selfTest_negativeZeroIsNotTheSameAsZero) diff --git a/js/src/jsapi-tests/testPropCache.cpp b/js/src/jsapi-tests/testPropCache.cpp new file mode 100644 index 000000000000..fbb41a7adb0d --- /dev/null +++ b/js/src/jsapi-tests/testPropCache.cpp @@ -0,0 +1,32 @@ +#include "tests.h" + +static int g_counter; + +static JSBool +CounterAdd(JSContext *cx, JSObject *obj, jsval idval, jsval *vp) +{ + g_counter++; + return JS_TRUE; +} + +static JSClass CounterClass = { + "Counter", /* name */ + 0, /* flags */ + CounterAdd, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +BEGIN_TEST(testPropCache_bug505798) +{ + g_counter = 0; + EXEC("var x = {};"); + CHECK(JS_DefineObject(cx, global, "y", &CounterClass, NULL, JSPROP_ENUMERATE)); + EXEC("var arr = [x, y];\n" + "for (var i = 0; i < arr.length; i++)\n" + " arr[i].p = 1;\n"); + knownFail = true; + CHECK(g_counter == 1); + return true; +} +END_TEST(testPropCache_bug505798) diff --git a/js/src/jsapi-tests/testXDR.cpp b/js/src/jsapi-tests/testXDR.cpp new file mode 100644 index 000000000000..1f0cc5a7d0f5 --- /dev/null +++ b/js/src/jsapi-tests/testXDR.cpp @@ -0,0 +1,52 @@ +#include "tests.h" +#include "jsxdrapi.h" + +BEGIN_TEST(testXDR_bug506491) +{ + const char *s = + "function makeClosure(s, name, value) {\n" + " eval(s);\n" + " return let (n = name, v = value) function () { return String(v); };\n" + "}\n" + "var f = makeClosure('0;', 'status', 'ok');\n"; + + // compile + JSScript *script = JS_CompileScript(cx, global, s, strlen(s), __FILE__, __LINE__); + CHECK(script); + JSObject *scrobj = JS_NewScriptObject(cx, script); + CHECK(scrobj); + jsvalRoot v(cx, OBJECT_TO_JSVAL(scrobj)); + + // freeze + JSXDRState *w = JS_XDRNewMem(cx, JSXDR_ENCODE); + CHECK(w); + CHECK(JS_XDRScript(w, &script)); + uint32 nbytes; + void *p = JS_XDRMemGetData(w, &nbytes); + CHECK(p); + void *frozen = malloc(nbytes); + CHECK(frozen); + memcpy(frozen, p, nbytes); + JS_XDRDestroy(w); + + // thaw + script = NULL; + JSXDRState *r = JS_XDRNewMem(cx, JSXDR_DECODE); + JS_XDRMemSetData(r, frozen, nbytes); + CHECK(JS_XDRScript(r, &script)); + JS_XDRDestroy(r); // this frees `frozen` + + // execute + jsvalRoot v2(cx); + CHECK(JS_ExecuteScript(cx, global, script, v2.addr())); + + // try to break the Block object that is the parent of f + JS_GC(cx); + + // confirm + EVAL("f() === 'ok';\n", v2.addr()); + jsvalRoot trueval(cx, JSVAL_TRUE); + CHECK_SAME(v2, trueval); + return true; +} +END_TEST(testXDR_bug506491) diff --git a/js/src/jsapi-tests/tests.cpp b/js/src/jsapi-tests/tests.cpp new file mode 100644 index 000000000000..659f430807d4 --- /dev/null +++ b/js/src/jsapi-tests/tests.cpp @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sw=4 et tw=78: + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is JSAPI tests. + * + * The Initial Developer of the Original Code is + * Mozilla Corporation. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Jason Orendorff + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "tests.h" +#include + +using namespace std; + +JSAPITest *JSAPITest::list; + +int main() +{ + int failures = 0; + + for (JSAPITest *test = JSAPITest::list; test; test = test->next) { + string name = test->name(); + + cout << name << endl; + if (!test->init()) { + cout << "TEST-UNEXPECTED-FAIL | " << name << " | Failed to initialize." << endl; + failures++; + continue; + } + + if (test->run()) { + cout << "TEST-PASS | " << name << " | ok" << endl; + } else { + cout << (test->knownFail ? "TEST-KNOWN-FAIL" : "TEST-UNEXPECTED-FAIL") + << " | " << name << " | " << test->messages() << endl; + if (!test->knownFail) + failures++; + } + test->uninit(); + } + + if (failures) { + cout << "\n" << failures << " unexpected failure" << (failures == 1 ? "." : "s.") << endl; + return 1; + } + cout << "\nPassed." << endl; + return 0; +} diff --git a/js/src/jsapi-tests/tests.h b/js/src/jsapi-tests/tests.h new file mode 100644 index 000000000000..9aeb6580092b --- /dev/null +++ b/js/src/jsapi-tests/tests.h @@ -0,0 +1,264 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sw=4 et tw=78: + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is JSAPI tests. + * + * The Initial Developer of the Original Code is + * Mozilla Corporation. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Jason Orendorff + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "jsapi.h" +#include +#include +#include +#include + +class jsvalRoot +{ +public: + explicit jsvalRoot(JSContext *context, jsval value = JSVAL_NULL) + : cx(context), v(value) + { + if (!JS_AddRoot(cx, &v)) { + fprintf(stderr, "Out of memory in jsvalRoot constructor, aborting\n"); + abort(); + } + } + + ~jsvalRoot() { JS_RemoveRoot(cx, &v); } + + operator jsval() const { return v; } + + jsvalRoot & operator=(jsval value) { + v = value; + return *this; + } + + jsval * addr() { return &v; } + +private: + JSContext *cx; + jsval v; +}; + +class JSAPITest +{ +public: + static JSAPITest *list; + JSAPITest *next; + + JSRuntime *rt; + JSContext *cx; + JSObject *global; + bool knownFail; + std::string msgs; + + JSAPITest() : rt(NULL), cx(NULL), global(NULL), knownFail(false) { + next = list; + list = this; + } + + virtual ~JSAPITest() { uninit(); } + + virtual bool init() { + rt = createRuntime(); + if (!rt) + return false; + cx = createContext(); + if (!cx) + return false; + global = createGlobal(); + return global != NULL; + } + + virtual void uninit() { + if (cx) { + JS_DestroyContext(cx); + cx = NULL; + } + if (rt) { + JS_DestroyRuntime(rt); + rt = NULL; + } + } + + virtual const char * name() = 0; + virtual bool run() = 0; + + bool isNegativeZero(jsval v) { + if (!JSVAL_IS_DOUBLE(v)) + return false; + union { + uint64 u64; + jsdouble d; + } pun; + pun.d = *JSVAL_TO_DOUBLE(v); + return pun.d == jsdouble(-0.0) && pun.u64 != uint64(0); + } + + bool isNaN(jsval v) { + if (!JSVAL_IS_DOUBLE(v)) + return false; + jsdouble d = *JSVAL_TO_DOUBLE(v); + return d != d; + } + + bool sameValue(jsval v1, jsval v2) { + if ((isNegativeZero(v1) && v2 == JSVAL_ZERO) || + (isNegativeZero(v2) && v1 == JSVAL_ZERO)) { + return false; + } + if (isNaN(v1) && isNaN(v2)) + return true; + return JS_StrictlyEqual(cx, v1, v2); + } + +#define EXEC(s) do { if (!exec(s, __FILE__, __LINE__)) return false; } while (false) + + bool exec(const char *bytes, const char *filename, int lineno) { + jsvalRoot v(cx); + return JS_EvaluateScript(cx, global, bytes, strlen(bytes), filename, lineno, v.addr()) || + fail(bytes, filename, lineno); + return true; + } + +#define EVAL(s, vp) do { if (!evaluate(s, __FILE__, __LINE__, vp)) return false; } while (false) + + bool evaluate(const char *bytes, const char *filename, int lineno, jsval *vp) { + return JS_EvaluateScript(cx, global, bytes, strlen(bytes), filename, lineno, vp) || + fail(bytes, filename, lineno); + } + + std::string toSource(jsval v) { + JSString *str = JS_ValueToSource(cx, v); + if (str) + return std::string(JS_GetStringBytes(str)); + JS_ClearPendingException(cx); + return std::string("<>"); + } + +#define CHECK_SAME(actual, expected) \ + do { \ + if (!checkSame(actual, expected, #actual, #expected, __FILE__, __LINE__)) \ + return false; \ + } while (false) + + bool checkSame(jsval actual, jsval expected, + const char *actualExpr, const char *expectedExpr, + const char *filename, int lineno) { + return sameValue(actual, expected) || + fail(std::string("CHECK_SAME failed: expected sameValue(") + + actualExpr + ", " + expectedExpr + "), got !sameValue(" + + toSource(actual) + ", " + toSource(expected) + ")", filename, lineno); + } + +#define CHECK(expr) \ + do { \ + if (!(expr)) \ + return fail("CHECK failed: " #expr, __FILE__, __LINE__); \ + } while (false) + + bool fail(std::string msg = std::string(), const char *filename = "-", int lineno = 0) { + if (JS_IsExceptionPending(cx)) { + jsvalRoot v(cx); + JS_GetPendingException(cx, v.addr()); + JS_ClearPendingException(cx); + JSString *s = JS_ValueToString(cx, v); + if (s) + msg += JS_GetStringBytes(s); + } + fprintf(stderr, "%s:%d:%s\n", filename, lineno, msg.c_str()); + msgs += msg; + return false; + } + + std::string messages() const { return msgs; } + +protected: + virtual JSRuntime * createRuntime() { + return JS_NewRuntime(8L * 1024 * 1024); + } + + static void reportError(JSContext *cx, const char *message, JSErrorReport *report) { + fprintf(stderr, "%s:%u:%s\n", + report->filename ? report->filename : "", + (unsigned int) report->lineno, + message); + } + + virtual JSContext * createContext() { + JSContext *cx = JS_NewContext(rt, 8192); + if (!cx) + return NULL; + JS_SetOptions(cx, JSOPTION_VAROBJFIX); + JS_SetVersion(cx, JSVERSION_LATEST); + JS_SetErrorReporter(cx, &reportError); + return cx; + } + + virtual JSClass * getGlobalClass() { + static JSClass basicGlobalClass = { + "global", JSCLASS_GLOBAL_FLAGS, + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS + }; + return &basicGlobalClass; + } + + virtual JSObject * createGlobal() { + /* Create the global object. */ + JSObject *global = JS_NewObject(cx, getGlobalClass(), NULL, NULL); + if (!global) + return NULL; + + /* Populate the global object with the standard globals, + like Object and Array. */ + if (!JS_InitStandardClasses(cx, global)) + return NULL; + return global; + } +}; + +#define BEGIN_TEST(testname) \ + class cls_##testname : public JSAPITest { \ + public: \ + virtual const char * name() { return #testname; } \ + virtual bool run() + +#define END_TEST(testname) \ + }; \ + static cls_##testname cls_##testname##_instance; + +