michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef jsapi_tests_tests_h michael@0: #define jsapi_tests_tests_h michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "jsalloc.h" michael@0: #include "jscntxt.h" michael@0: #include "jsgc.h" michael@0: michael@0: #include "js/Vector.h" michael@0: michael@0: /* Note: Aborts on OOM. */ michael@0: class JSAPITestString { michael@0: js::Vector chars; michael@0: public: michael@0: JSAPITestString() {} michael@0: JSAPITestString(const char *s) { *this += s; } michael@0: JSAPITestString(const JSAPITestString &s) { *this += s; } michael@0: michael@0: const char *begin() const { return chars.begin(); } michael@0: const char *end() const { return chars.end(); } michael@0: size_t length() const { return chars.length(); } michael@0: michael@0: JSAPITestString & operator +=(const char *s) { michael@0: if (!chars.append(s, strlen(s))) michael@0: abort(); michael@0: return *this; michael@0: } michael@0: michael@0: JSAPITestString & operator +=(const JSAPITestString &s) { michael@0: if (!chars.append(s.begin(), s.length())) michael@0: abort(); michael@0: return *this; michael@0: } michael@0: }; michael@0: michael@0: inline JSAPITestString operator+(JSAPITestString a, const char *b) { return a += b; } michael@0: inline JSAPITestString operator+(JSAPITestString a, const JSAPITestString &b) { return a += b; } michael@0: michael@0: class JSAPITest michael@0: { michael@0: public: michael@0: static JSAPITest *list; michael@0: JSAPITest *next; michael@0: michael@0: JSRuntime *rt; michael@0: JSContext *cx; michael@0: JS::Heap global; michael@0: bool knownFail; michael@0: JSAPITestString msgs; michael@0: JSCompartment *oldCompartment; michael@0: michael@0: JSAPITest() : rt(nullptr), cx(nullptr), global(nullptr), michael@0: knownFail(false), oldCompartment(nullptr) { michael@0: next = list; michael@0: list = this; michael@0: } michael@0: michael@0: virtual ~JSAPITest() { uninit(); } michael@0: michael@0: virtual bool init(); michael@0: michael@0: virtual void uninit() { michael@0: if (oldCompartment) { michael@0: JS_LeaveCompartment(cx, oldCompartment); michael@0: oldCompartment = nullptr; michael@0: } michael@0: global = nullptr; michael@0: if (cx) { michael@0: JS::RemoveObjectRoot(cx, &global); michael@0: JS_LeaveCompartment(cx, nullptr); michael@0: JS_EndRequest(cx); michael@0: JS_DestroyContext(cx); michael@0: cx = nullptr; michael@0: } michael@0: if (rt) { michael@0: destroyRuntime(); michael@0: rt = nullptr; michael@0: } michael@0: } michael@0: michael@0: virtual const char * name() = 0; michael@0: virtual bool run(JS::HandleObject global) = 0; michael@0: michael@0: #define EXEC(s) do { if (!exec(s, __FILE__, __LINE__)) return false; } while (false) michael@0: michael@0: bool exec(const char *bytes, const char *filename, int lineno); michael@0: michael@0: #define EVAL(s, vp) do { if (!evaluate(s, __FILE__, __LINE__, vp)) return false; } while (false) michael@0: michael@0: bool evaluate(const char *bytes, const char *filename, int lineno, JS::MutableHandleValue vp); michael@0: michael@0: JSAPITestString jsvalToSource(JS::HandleValue v) { michael@0: JSString *str = JS_ValueToSource(cx, v); michael@0: if (str) { michael@0: JSAutoByteString bytes(cx, str); michael@0: if (!!bytes) michael@0: return JSAPITestString(bytes.ptr()); michael@0: } michael@0: JS_ClearPendingException(cx); michael@0: return JSAPITestString("<>"); michael@0: } michael@0: michael@0: JSAPITestString toSource(long v) { michael@0: char buf[40]; michael@0: sprintf(buf, "%ld", v); michael@0: return JSAPITestString(buf); michael@0: } michael@0: michael@0: JSAPITestString toSource(unsigned long v) { michael@0: char buf[40]; michael@0: sprintf(buf, "%lu", v); michael@0: return JSAPITestString(buf); michael@0: } michael@0: michael@0: JSAPITestString toSource(long long v) { michael@0: char buf[40]; michael@0: sprintf(buf, "%lld", v); michael@0: return JSAPITestString(buf); michael@0: } michael@0: michael@0: JSAPITestString toSource(unsigned long long v) { michael@0: char buf[40]; michael@0: sprintf(buf, "%llu", v); michael@0: return JSAPITestString(buf); michael@0: } michael@0: michael@0: JSAPITestString toSource(unsigned int v) { michael@0: return toSource((unsigned long)v); michael@0: } michael@0: michael@0: JSAPITestString toSource(int v) { michael@0: return toSource((long)v); michael@0: } michael@0: michael@0: JSAPITestString toSource(bool v) { michael@0: return JSAPITestString(v ? "true" : "false"); michael@0: } michael@0: michael@0: JSAPITestString toSource(JSAtom *v) { michael@0: JS::RootedValue val(cx, JS::StringValue((JSString *)v)); michael@0: return jsvalToSource(val); michael@0: } michael@0: michael@0: JSAPITestString toSource(JSVersion v) { michael@0: return JSAPITestString(JS_VersionToString(v)); michael@0: } michael@0: michael@0: template michael@0: bool checkEqual(const T &actual, const T &expected, michael@0: const char *actualExpr, const char *expectedExpr, michael@0: const char *filename, int lineno) { michael@0: return (actual == expected) || michael@0: fail(JSAPITestString("CHECK_EQUAL failed: expected (") + michael@0: expectedExpr + ") = " + toSource(expected) + michael@0: ", got (" + actualExpr + ") = " + toSource(actual), filename, lineno); michael@0: } michael@0: michael@0: // There are many cases where the static types of 'actual' and 'expected' michael@0: // are not identical, and C++ is understandably cautious about automatic michael@0: // coercions. So catch those cases and forcibly coerce, then use the michael@0: // identical-type specialization. This may do bad things if the types are michael@0: // actually *not* compatible. michael@0: template michael@0: bool checkEqual(const T &actual, const U &expected, michael@0: const char *actualExpr, const char *expectedExpr, michael@0: const char *filename, int lineno) { michael@0: return checkEqual(U(actual), expected, actualExpr, expectedExpr, filename, lineno); michael@0: } michael@0: michael@0: #define CHECK_EQUAL(actual, expected) \ michael@0: do { \ michael@0: if (!checkEqual(actual, expected, #actual, #expected, __FILE__, __LINE__)) \ michael@0: return false; \ michael@0: } while (false) michael@0: michael@0: bool checkSame(jsval actualArg, jsval expectedArg, michael@0: const char *actualExpr, const char *expectedExpr, michael@0: const char *filename, int lineno) { michael@0: bool same; michael@0: JS::RootedValue actual(cx, actualArg), expected(cx, expectedArg); michael@0: return (JS_SameValue(cx, actual, expected, &same) && same) || michael@0: fail(JSAPITestString("CHECK_SAME failed: expected JS_SameValue(cx, ") + michael@0: actualExpr + ", " + expectedExpr + "), got !JS_SameValue(cx, " + michael@0: jsvalToSource(actual) + ", " + jsvalToSource(expected) + ")", filename, lineno); michael@0: } michael@0: michael@0: #define CHECK_SAME(actual, expected) \ michael@0: do { \ michael@0: if (!checkSame(actual, expected, #actual, #expected, __FILE__, __LINE__)) \ michael@0: return false; \ michael@0: } while (false) michael@0: michael@0: #define CHECK(expr) \ michael@0: do { \ michael@0: if (!(expr)) \ michael@0: return fail("CHECK failed: " #expr, __FILE__, __LINE__); \ michael@0: } while (false) michael@0: michael@0: bool fail(JSAPITestString msg = JSAPITestString(), const char *filename = "-", int lineno = 0) { michael@0: if (JS_IsExceptionPending(cx)) { michael@0: js::gc::AutoSuppressGC gcoff(cx); michael@0: JS::RootedValue v(cx); michael@0: JS_GetPendingException(cx, &v); michael@0: JS_ClearPendingException(cx); michael@0: JSString *s = JS::ToString(cx, v); michael@0: if (s) { michael@0: JSAutoByteString bytes(cx, s); michael@0: if (!!bytes) michael@0: msg += bytes.ptr(); michael@0: } michael@0: } michael@0: fprintf(stderr, "%s:%d:%.*s\n", filename, lineno, (int) msg.length(), msg.begin()); michael@0: msgs += msg; michael@0: return false; michael@0: } michael@0: michael@0: JSAPITestString messages() const { return msgs; } michael@0: michael@0: static const JSClass * basicGlobalClass() { michael@0: static const JSClass c = { michael@0: "global", JSCLASS_GLOBAL_FLAGS, michael@0: JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, michael@0: JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, nullptr, michael@0: nullptr, nullptr, nullptr, michael@0: JS_GlobalObjectTraceHook michael@0: }; michael@0: return &c; michael@0: } michael@0: michael@0: protected: michael@0: static bool michael@0: print(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: JS::CallArgs args = JS::CallArgsFromVp(argc, vp); michael@0: michael@0: for (unsigned i = 0; i < args.length(); i++) { michael@0: JSString *str = JS::ToString(cx, args[i]); michael@0: if (!str) michael@0: return false; michael@0: char *bytes = JS_EncodeString(cx, str); michael@0: if (!bytes) michael@0: return false; michael@0: printf("%s%s", i ? " " : "", bytes); michael@0: JS_free(cx, bytes); michael@0: } michael@0: michael@0: putchar('\n'); michael@0: fflush(stdout); michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: bool definePrint(); michael@0: michael@0: static void setNativeStackQuota(JSRuntime *rt) michael@0: { michael@0: const size_t MAX_STACK_SIZE = michael@0: /* Assume we can't use more than 5e5 bytes of C stack by default. */ michael@0: #if (defined(DEBUG) && defined(__SUNPRO_CC)) || defined(JS_CPU_SPARC) michael@0: /* michael@0: * Sun compiler uses a larger stack space for js::Interpret() with michael@0: * debug. Use a bigger gMaxStackSize to make "make check" happy. michael@0: */ michael@0: 5000000 michael@0: #else michael@0: 500000 michael@0: #endif michael@0: ; michael@0: michael@0: JS_SetNativeStackQuota(rt, MAX_STACK_SIZE); michael@0: } michael@0: michael@0: virtual JSRuntime * createRuntime() { michael@0: JSRuntime *rt = JS_NewRuntime(8L * 1024 * 1024, JS_USE_HELPER_THREADS); michael@0: if (!rt) michael@0: return nullptr; michael@0: setNativeStackQuota(rt); michael@0: return rt; michael@0: } michael@0: michael@0: virtual void destroyRuntime() { michael@0: JS_ASSERT(!cx); michael@0: JS_ASSERT(rt); michael@0: JS_DestroyRuntime(rt); michael@0: } michael@0: michael@0: static void reportError(JSContext *cx, const char *message, JSErrorReport *report) { michael@0: fprintf(stderr, "%s:%u:%s\n", michael@0: report->filename ? report->filename : "", michael@0: (unsigned int) report->lineno, michael@0: message); michael@0: } michael@0: michael@0: virtual JSContext * createContext() { michael@0: JSContext *cx = JS_NewContext(rt, 8192); michael@0: if (!cx) michael@0: return nullptr; michael@0: JS::ContextOptionsRef(cx).setVarObjFix(true); michael@0: JS_SetErrorReporter(cx, &reportError); michael@0: return cx; michael@0: } michael@0: michael@0: virtual const JSClass * getGlobalClass() { michael@0: return basicGlobalClass(); michael@0: } michael@0: michael@0: virtual JSObject * createGlobal(JSPrincipals *principals = nullptr); michael@0: }; michael@0: michael@0: #define BEGIN_TEST(testname) \ michael@0: class cls_##testname : public JSAPITest { \ michael@0: public: \ michael@0: virtual const char * name() { return #testname; } \ michael@0: virtual bool run(JS::HandleObject global) michael@0: michael@0: #define END_TEST(testname) \ michael@0: }; \ michael@0: static cls_##testname cls_##testname##_instance; michael@0: michael@0: /* michael@0: * A "fixture" is a subclass of JSAPITest that holds common definitions for a michael@0: * set of tests. Each test that wants to use the fixture should use michael@0: * BEGIN_FIXTURE_TEST and END_FIXTURE_TEST, just as one would use BEGIN_TEST and michael@0: * END_TEST, but include the fixture class as the first argument. The fixture michael@0: * class's declarations are then in scope for the test bodies. michael@0: */ michael@0: michael@0: #define BEGIN_FIXTURE_TEST(fixture, testname) \ michael@0: class cls_##testname : public fixture { \ michael@0: public: \ michael@0: virtual const char * name() { return #testname; } \ michael@0: virtual bool run(JS::HandleObject global) michael@0: michael@0: #define END_FIXTURE_TEST(fixture, testname) \ michael@0: }; \ michael@0: static cls_##testname cls_##testname##_instance; michael@0: michael@0: /* michael@0: * A class for creating and managing one temporary file. michael@0: * michael@0: * We could use the ISO C temporary file functions here, but those try to michael@0: * create files in the root directory on Windows, which fails for users michael@0: * without Administrator privileges. michael@0: */ michael@0: class TempFile { michael@0: const char *name; michael@0: FILE *stream; michael@0: michael@0: public: michael@0: TempFile() : name(), stream() { } michael@0: ~TempFile() { michael@0: if (stream) michael@0: close(); michael@0: if (name) michael@0: remove(); michael@0: } michael@0: michael@0: /* michael@0: * Return a stream for a temporary file named |fileName|. Infallible. michael@0: * Use only once per TempFile instance. If the file is not explicitly michael@0: * closed and deleted via the member functions below, this object's michael@0: * destructor will clean them up. michael@0: */ michael@0: FILE *open(const char *fileName) michael@0: { michael@0: stream = fopen(fileName, "wb+"); michael@0: if (!stream) { michael@0: fprintf(stderr, "error opening temporary file '%s': %s\n", michael@0: fileName, strerror(errno)); michael@0: exit(1); michael@0: } michael@0: name = fileName; michael@0: return stream; michael@0: } michael@0: michael@0: /* Close the temporary file's stream. */ michael@0: void close() { michael@0: if (fclose(stream) == EOF) { michael@0: fprintf(stderr, "error closing temporary file '%s': %s\n", michael@0: name, strerror(errno)); michael@0: exit(1); michael@0: } michael@0: stream = nullptr; michael@0: } michael@0: michael@0: /* Delete the temporary file. */ michael@0: void remove() { michael@0: if (::remove(name) != 0) { michael@0: fprintf(stderr, "error deleting temporary file '%s': %s\n", michael@0: name, strerror(errno)); michael@0: exit(1); michael@0: } michael@0: name = nullptr; michael@0: } michael@0: }; michael@0: michael@0: // Just a wrapper around JSPrincipals that allows static construction. michael@0: class TestJSPrincipals : public JSPrincipals michael@0: { michael@0: public: michael@0: TestJSPrincipals(int rc = 0) michael@0: : JSPrincipals() michael@0: { michael@0: refcount = rc; michael@0: } michael@0: }; michael@0: michael@0: #endif /* jsapi_tests_tests_h */