summaryrefslogtreecommitdiffstats
path: root/js/src/jsapi-tests/tests.h
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /js/src/jsapi-tests/tests.h
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/jsapi-tests/tests.h')
-rw-r--r--js/src/jsapi-tests/tests.h563
1 files changed, 563 insertions, 0 deletions
diff --git a/js/src/jsapi-tests/tests.h b/js/src/jsapi-tests/tests.h
new file mode 100644
index 0000000000..53e8d2d4fb
--- /dev/null
+++ b/js/src/jsapi-tests/tests.h
@@ -0,0 +1,563 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/Sprintf.h"
+
+#include <errno.h>
+#include <iterator>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <type_traits>
+
+#include "gc/GC.h"
+#include "js/AllocPolicy.h"
+#include "js/CharacterEncoding.h"
+#include "js/Equality.h" // JS::SameValue
+#include "js/RegExpFlags.h" // JS::RegExpFlags
+#include "js/Vector.h"
+#include "js/Warnings.h" // JS::SetWarningReporter
+#include "vm/JSContext.h"
+
+/* Note: Aborts on OOM. */
+class JSAPITestString {
+ js::Vector<char, 0, js::SystemAllocPolicy> chars;
+
+ public:
+ JSAPITestString() {}
+ explicit 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(); }
+ void clear() { chars.clearAndFree(); }
+
+ 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+(const JSAPITestString& a, const char* b) {
+ JSAPITestString result = a;
+ result += b;
+ return result;
+}
+
+inline JSAPITestString operator+(const JSAPITestString& a,
+ const JSAPITestString& b) {
+ JSAPITestString result = a;
+ result += b;
+ return result;
+}
+
+class JSAPITest {
+ public:
+ static JSAPITest* list;
+ JSAPITest* next;
+
+ JSContext* cx;
+ JS::PersistentRootedObject global;
+ bool knownFail;
+ JSAPITestString msgs;
+
+ // Whether this test is willing to skip its init() and reuse a global (and
+ // JSContext etc.) from a previous test that also has reuseGlobal=true. It
+ // also means this test is willing to skip its uninit() if it is followed by
+ // another reuseGlobal test.
+ bool reuseGlobal;
+
+ JSAPITest() : cx(nullptr), knownFail(false), reuseGlobal(false) {
+ next = list;
+ list = this;
+ }
+
+ virtual ~JSAPITest() {
+ MOZ_RELEASE_ASSERT(!cx);
+ MOZ_RELEASE_ASSERT(!global);
+ }
+
+ // Initialize this test, possibly with the cx from a previously run test.
+ bool init(JSContext* maybeReusedContext);
+
+ // If this test is ok with its cx and global being reused, release this
+ // test's cx to be reused by another test.
+ JSContext* maybeForgetContext();
+
+ static void MaybeFreeContext(JSContext* maybeCx);
+
+ // The real initialization happens in init(JSContext*), above, but this
+ // method may be overridden to perform additional initialization after the
+ // JSContext and global have been created.
+ virtual bool init() { return true; }
+ virtual void uninit();
+
+ 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* utf8, const char* filename, int lineno);
+
+ // Like exec(), but doesn't call fail() if JS::Evaluate returns false.
+ bool execDontReport(const char* utf8, const char* filename, int lineno);
+
+#define EVAL(s, vp) \
+ do { \
+ if (!evaluate(s, __FILE__, __LINE__, vp)) return false; \
+ } while (false)
+
+ bool evaluate(const char* utf8, const char* filename, int lineno,
+ JS::MutableHandleValue vp);
+
+ JSAPITestString jsvalToSource(JS::HandleValue v) {
+ if (JSString* str = JS_ValueToSource(cx, v)) {
+ if (JS::UniqueChars bytes = JS_EncodeStringToLatin1(cx, str)) {
+ return JSAPITestString(bytes.get());
+ }
+ }
+ JS_ClearPendingException(cx);
+ return JSAPITestString("<<error converting value to string>>");
+ }
+
+ JSAPITestString toSource(char c) {
+ char buf[2] = {c, '\0'};
+ return JSAPITestString(buf);
+ }
+
+ 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(double d) {
+ char buf[40];
+ SprintfLiteral(buf, "%17lg", d);
+ 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(JS::RegExpFlags flags) {
+ JSAPITestString str;
+ if (flags.global()) {
+ str += "g";
+ }
+ if (flags.ignoreCase()) {
+ str += "i";
+ }
+ if (flags.multiline()) {
+ str += "m";
+ }
+ if (flags.dotAll()) {
+ str += "s";
+ }
+ if (flags.unicode()) {
+ str += "u";
+ }
+ if (flags.sticky()) {
+ str += "y";
+ }
+ return str;
+ }
+
+ JSAPITestString toSource(JSAtom* v) {
+ JS::RootedValue val(cx, JS::StringValue((JSString*)v));
+ return jsvalToSource(val);
+ }
+
+ // Note that in some still-supported GCC versions (we think anything before
+ // GCC 4.6), this template does not work when the second argument is
+ // nullptr. It infers type U = long int. Use CHECK_NULL instead.
+ template <typename T, typename U>
+ bool checkEqual(const T& actual, const U& expected, const char* actualExpr,
+ const char* expectedExpr, const char* filename, int lineno) {
+ static_assert(std::is_signed_v<T> == std::is_signed_v<U>,
+ "using CHECK_EQUAL with different-signed inputs triggers "
+ "compiler warnings");
+ static_assert(
+ std::is_unsigned_v<T> == std::is_unsigned_v<U>,
+ "using CHECK_EQUAL with different-signed inputs triggers compiler "
+ "warnings");
+ return (actual == expected) ||
+ fail(JSAPITestString("CHECK_EQUAL failed: expected (") +
+ expectedExpr + ") = " + toSource(expected) + ", got (" +
+ actualExpr + ") = " + toSource(actual),
+ filename, lineno);
+ }
+
+#define CHECK_EQUAL(actual, expected) \
+ do { \
+ if (!checkEqual(actual, expected, #actual, #expected, __FILE__, __LINE__)) \
+ return false; \
+ } while (false)
+
+ template <typename T>
+ bool checkNull(const T* actual, const char* actualExpr, const char* filename,
+ int lineno) {
+ return (actual == nullptr) ||
+ fail(JSAPITestString("CHECK_NULL failed: expected nullptr, got (") +
+ actualExpr + ") = " + toSource(actual),
+ filename, lineno);
+ }
+
+#define CHECK_NULL(actual) \
+ do { \
+ if (!checkNull(actual, #actual, __FILE__, __LINE__)) return false; \
+ } while (false)
+
+ bool checkSame(const JS::Value& actualArg, const JS::Value& expectedArg,
+ const char* actualExpr, const char* expectedExpr,
+ const char* filename, int lineno) {
+ bool 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(JSAPITestString("CHECK failed: " #expr), __FILE__, \
+ __LINE__); \
+ } while (false)
+
+ bool fail(const JSAPITestString& msg = JSAPITestString(),
+ const char* filename = "-", int lineno = 0) {
+ char location[256];
+ snprintf(location, std::size(location), "%s:%d:", filename, lineno);
+
+ JSAPITestString message(location);
+ message += msg;
+
+ if (JS_IsExceptionPending(cx)) {
+ message += " -- ";
+
+ js::gc::AutoSuppressGC gcoff(cx);
+ JS::RootedValue v(cx);
+ JS_GetPendingException(cx, &v);
+ JS_ClearPendingException(cx);
+ JSString* s = JS::ToString(cx, v);
+ if (s) {
+ if (JS::UniqueChars bytes = JS_EncodeStringToLatin1(cx, s)) {
+ message += bytes.get();
+ }
+ }
+ }
+
+ fprintf(stderr, "%.*s\n", int(message.length()), message.begin());
+
+ if (msgs.length() != 0) {
+ msgs += " | ";
+ }
+ msgs += message;
+ return false;
+ }
+
+ JSAPITestString messages() const { return msgs; }
+
+ static const JSClass* basicGlobalClass() {
+ static const JSClass c = {"global", JSCLASS_GLOBAL_FLAGS,
+ &JS::DefaultGlobalClassOps};
+ return &c;
+ }
+
+ protected:
+ static bool print(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+ for (unsigned i = 0; i < args.length(); i++) {
+ JSString* str = JS::ToString(cx, args[i]);
+ if (!str) {
+ return false;
+ }
+ JS::UniqueChars bytes = JS_EncodeStringToLatin1(cx, str);
+ if (!bytes) {
+ return false;
+ }
+ printf("%s%s", i ? " " : "", bytes.get());
+ }
+
+ putchar('\n');
+ fflush(stdout);
+ args.rval().setUndefined();
+ return true;
+ }
+
+ bool definePrint();
+
+ static void setNativeStackQuota(JSContext* cx) {
+ 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(__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(cx, MAX_STACK_SIZE);
+ }
+
+ virtual JSContext* createContext() {
+ JSContext* cx = JS_NewContext(8L * 1024 * 1024);
+ if (!cx) {
+ return nullptr;
+ }
+ JS::SetWarningReporter(cx, &reportWarning);
+ setNativeStackQuota(cx);
+ return cx;
+ }
+
+ static void reportWarning(JSContext* cx, JSErrorReport* report) {
+ MOZ_RELEASE_ASSERT(report->isWarning());
+
+ fprintf(stderr, "%s:%u:%s\n",
+ report->filename ? report->filename : "<no filename>",
+ (unsigned int)report->lineno, report->message().c_str());
+ }
+
+ virtual const JSClass* getGlobalClass() { return basicGlobalClass(); }
+
+ virtual JSObject* createGlobal(JSPrincipals* principals = nullptr);
+};
+
+#define BEGIN_TEST_WITH_ATTRIBUTES_AND_EXTRA(testname, attrs, extra) \
+ class cls_##testname : public JSAPITest { \
+ public: \
+ virtual const char* name() override { return #testname; } \
+ extra virtual bool run(JS::HandleObject global) override attrs
+
+#define BEGIN_TEST_WITH_ATTRIBUTES(testname, attrs) \
+ BEGIN_TEST_WITH_ATTRIBUTES_AND_EXTRA(testname, attrs, )
+
+#define BEGIN_TEST(testname) BEGIN_TEST_WITH_ATTRIBUTES(testname, )
+
+#define BEGIN_REUSABLE_TEST(testname) \
+ BEGIN_TEST_WITH_ATTRIBUTES_AND_EXTRA( \
+ testname, , cls_##testname() \
+ : JSAPITest() { reuseGlobal = true; })
+
+#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() override { return #testname; } \
+ virtual bool run(JS::HandleObject global) override
+
+#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 = nullptr;
+ }
+
+ /* 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 = nullptr;
+ }
+};
+
+// Just a wrapper around JSPrincipals that allows static construction.
+class TestJSPrincipals : public JSPrincipals {
+ public:
+ explicit TestJSPrincipals(int rc = 0) : JSPrincipals() { refcount = rc; }
+
+ bool write(JSContext* cx, JSStructuredCloneWriter* writer) override {
+ MOZ_ASSERT(false, "not implemented");
+ return false;
+ }
+
+ bool isSystemOrAddonPrincipal() override { return true; }
+};
+
+// A class that simulates externally memory-managed data, for testing with
+// array buffers.
+class ExternalData {
+ char* contents_;
+ size_t len_;
+
+ public:
+ explicit ExternalData(const char* str)
+ : contents_(strdup(str)), len_(strlen(str) + 1) {}
+
+ size_t len() const { return len_; }
+ void* contents() const { return contents_; }
+ char* asString() const { return contents_; }
+ bool wasFreed() const { return !contents_; }
+
+ void free() {
+ MOZ_ASSERT(!wasFreed());
+ ::free(contents_);
+ contents_ = nullptr;
+ }
+
+ static void freeCallback(void* contents, void* userData) {
+ auto self = static_cast<ExternalData*>(userData);
+ MOZ_ASSERT(self->contents() == contents);
+ self->free();
+ }
+};
+
+#ifdef JS_GC_ZEAL
+/*
+ * Temporarily disable the GC zeal setting. This is only useful in tests that
+ * need very explicit GC behavior and should not be used elsewhere.
+ */
+class AutoLeaveZeal {
+ JSContext* cx_;
+ uint32_t zealBits_;
+ uint32_t frequency_;
+
+ public:
+ explicit AutoLeaveZeal(JSContext* cx) : cx_(cx), zealBits_(0), frequency_(0) {
+ uint32_t dummy;
+ JS_GetGCZealBits(cx_, &zealBits_, &frequency_, &dummy);
+ JS_SetGCZeal(cx_, 0, 0);
+ JS::PrepareForFullGC(cx_);
+ JS::NonIncrementalGC(cx_, GC_SHRINK, JS::GCReason::DEBUG_GC);
+ }
+ ~AutoLeaveZeal() {
+ JS_SetGCZeal(cx_, 0, 0);
+ for (size_t i = 0; i < sizeof(zealBits_) * 8; i++) {
+ if (zealBits_ & (1 << i)) {
+ JS_SetGCZeal(cx_, i, frequency_);
+ }
+ }
+
+# ifdef DEBUG
+ uint32_t zealBitsAfter, frequencyAfter, dummy;
+ JS_GetGCZealBits(cx_, &zealBitsAfter, &frequencyAfter, &dummy);
+ MOZ_ASSERT(zealBitsAfter == zealBits_);
+ MOZ_ASSERT(frequencyAfter == frequency_);
+# endif
+ }
+};
+#endif /* JS_GC_ZEAL */
+
+#endif /* jsapi_tests_tests_h */