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