Bug 1852740: add tests for the `fetchpriority` attribute in Link headers. r=necko...
[gecko.git] / js / src / jsapi-tests / tests.h
blob75522478d79a2bb3c48390ee1d0be28c6845f6d8
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifndef jsapi_tests_tests_h
8 #define jsapi_tests_tests_h
10 #include "mozilla/Sprintf.h"
12 #include <errno.h>
13 #include <iterator>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <type_traits>
19 #include "jsapi.h"
21 #include "gc/GC.h"
22 #include "js/AllocPolicy.h"
23 #include "js/ArrayBuffer.h"
24 #include "js/CharacterEncoding.h"
25 #include "js/Conversions.h"
26 #include "js/Equality.h" // JS::SameValue
27 #include "js/GlobalObject.h" // JS::DefaultGlobalClassOps
28 #include "js/RegExpFlags.h" // JS::RegExpFlags
29 #include "js/Vector.h"
30 #include "js/Warnings.h" // JS::SetWarningReporter
31 #include "vm/JSContext.h"
33 /* Note: Aborts on OOM. */
34 class JSAPITestString {
35 js::Vector<char, 0, js::SystemAllocPolicy> chars;
37 public:
38 JSAPITestString() {}
39 explicit JSAPITestString(const char* s) { *this += s; }
40 JSAPITestString(const JSAPITestString& s) { *this += s; }
42 const char* begin() const { return chars.begin(); }
43 const char* end() const { return chars.end(); }
44 size_t length() const { return chars.length(); }
45 void clear() { chars.clearAndFree(); }
47 JSAPITestString& operator+=(const char* s) {
48 if (!chars.append(s, strlen(s))) {
49 abort();
51 return *this;
54 JSAPITestString& operator+=(const JSAPITestString& s) {
55 if (!chars.append(s.begin(), s.length())) {
56 abort();
58 return *this;
62 inline JSAPITestString operator+(const JSAPITestString& a, const char* b) {
63 JSAPITestString result = a;
64 result += b;
65 return result;
68 inline JSAPITestString operator+(const JSAPITestString& a,
69 const JSAPITestString& b) {
70 JSAPITestString result = a;
71 result += b;
72 return result;
75 class JSAPIRuntimeTest;
77 class JSAPITest {
78 public:
79 bool knownFail;
80 JSAPITestString msgs;
82 JSAPITest() : knownFail(false) {}
84 virtual ~JSAPITest() {}
86 virtual const char* name() = 0;
88 virtual void maybeAppendException(JSAPITestString& message) {}
90 bool fail(const JSAPITestString& msg = JSAPITestString(),
91 const char* filename = "-", int lineno = 0) {
92 char location[256];
93 SprintfLiteral(location, "%s:%d:", filename, lineno);
95 JSAPITestString message(location);
96 message += msg;
98 maybeAppendException(message);
100 fprintf(stderr, "%.*s\n", int(message.length()), message.begin());
102 if (msgs.length() != 0) {
103 msgs += " | ";
105 msgs += message;
106 return false;
109 JSAPITestString messages() const { return msgs; }
112 class JSAPIRuntimeTest : public JSAPITest {
113 public:
114 static JSAPIRuntimeTest* list;
115 JSAPIRuntimeTest* next;
117 JSContext* cx;
118 JS::PersistentRootedObject global;
120 // Whether this test is willing to skip its init() and reuse a global (and
121 // JSContext etc.) from a previous test that also has reuseGlobal=true. It
122 // also means this test is willing to skip its uninit() if it is followed by
123 // another reuseGlobal test.
124 bool reuseGlobal;
126 JSAPIRuntimeTest() : JSAPITest(), cx(nullptr), reuseGlobal(false) {
127 next = list;
128 list = this;
131 virtual ~JSAPIRuntimeTest() {
132 MOZ_RELEASE_ASSERT(!cx);
133 MOZ_RELEASE_ASSERT(!global);
136 // Initialize this test, possibly with the cx from a previously run test.
137 bool init(JSContext* maybeReusedContext);
139 // If this test is ok with its cx and global being reused, release this
140 // test's cx to be reused by another test.
141 JSContext* maybeForgetContext();
143 static void MaybeFreeContext(JSContext* maybeCx);
145 // The real initialization happens in init(JSContext*), above, but this
146 // method may be overridden to perform additional initialization after the
147 // JSContext and global have been created.
148 virtual bool init() { return true; }
149 virtual void uninit();
151 virtual bool run(JS::HandleObject global) = 0;
153 #define EXEC(s) \
154 do { \
155 if (!exec(s, __FILE__, __LINE__)) return false; \
156 } while (false)
158 bool exec(const char* utf8, const char* filename, int lineno);
160 // Like exec(), but doesn't call fail() if JS::Evaluate returns false.
161 bool execDontReport(const char* utf8, const char* filename, int lineno);
163 #define EVAL(s, vp) \
164 do { \
165 if (!evaluate(s, __FILE__, __LINE__, vp)) return false; \
166 } while (false)
168 bool evaluate(const char* utf8, const char* filename, int lineno,
169 JS::MutableHandleValue vp);
171 JSAPITestString jsvalToSource(JS::HandleValue v) {
172 JS::Rooted<JSString*> str(cx, JS_ValueToSource(cx, v));
173 if (str) {
174 if (JS::UniqueChars bytes = JS_EncodeStringToUTF8(cx, str)) {
175 return JSAPITestString(bytes.get());
178 JS_ClearPendingException(cx);
179 return JSAPITestString("<<error converting value to string>>");
182 JSAPITestString toSource(char c) {
183 char buf[2] = {c, '\0'};
184 return JSAPITestString(buf);
187 JSAPITestString toSource(long v) {
188 char buf[40];
189 SprintfLiteral(buf, "%ld", v);
190 return JSAPITestString(buf);
193 JSAPITestString toSource(unsigned long v) {
194 char buf[40];
195 SprintfLiteral(buf, "%lu", v);
196 return JSAPITestString(buf);
199 JSAPITestString toSource(long long v) {
200 char buf[40];
201 SprintfLiteral(buf, "%lld", v);
202 return JSAPITestString(buf);
205 JSAPITestString toSource(unsigned long long v) {
206 char buf[40];
207 SprintfLiteral(buf, "%llu", v);
208 return JSAPITestString(buf);
211 JSAPITestString toSource(double d) {
212 char buf[40];
213 SprintfLiteral(buf, "%17lg", d);
214 return JSAPITestString(buf);
217 JSAPITestString toSource(unsigned int v) {
218 return toSource((unsigned long)v);
221 JSAPITestString toSource(int v) { return toSource((long)v); }
223 JSAPITestString toSource(bool v) {
224 return JSAPITestString(v ? "true" : "false");
227 JSAPITestString toSource(JS::RegExpFlags flags) {
228 JSAPITestString str;
229 if (flags.hasIndices()) {
230 str += "d";
232 if (flags.global()) {
233 str += "g";
235 if (flags.ignoreCase()) {
236 str += "i";
238 if (flags.multiline()) {
239 str += "m";
241 if (flags.dotAll()) {
242 str += "s";
244 if (flags.unicode()) {
245 str += "u";
247 if (flags.unicodeSets()) {
248 str += "v";
250 if (flags.sticky()) {
251 str += "y";
253 return str;
256 JSAPITestString toSource(JSAtom* v) {
257 JS::RootedValue val(cx, JS::StringValue((JSString*)v));
258 return jsvalToSource(val);
261 // Note that in some still-supported GCC versions (we think anything before
262 // GCC 4.6), this template does not work when the second argument is
263 // nullptr. It infers type U = long int. Use CHECK_NULL instead.
264 template <typename T, typename U>
265 bool checkEqual(const T& actual, const U& expected, const char* actualExpr,
266 const char* expectedExpr, const char* filename, int lineno) {
267 static_assert(std::is_signed_v<T> == std::is_signed_v<U>,
268 "using CHECK_EQUAL with different-signed inputs triggers "
269 "compiler warnings");
270 static_assert(
271 std::is_unsigned_v<T> == std::is_unsigned_v<U>,
272 "using CHECK_EQUAL with different-signed inputs triggers compiler "
273 "warnings");
274 return (actual == expected) ||
275 fail(JSAPITestString("CHECK_EQUAL failed: expected (") +
276 expectedExpr + ") = " + toSource(expected) + ", got (" +
277 actualExpr + ") = " + toSource(actual),
278 filename, lineno);
281 #define CHECK_EQUAL(actual, expected) \
282 do { \
283 if (!checkEqual(actual, expected, #actual, #expected, __FILE__, __LINE__)) \
284 return false; \
285 } while (false)
287 template <typename T>
288 bool checkNull(const T* actual, const char* actualExpr, const char* filename,
289 int lineno) {
290 return (actual == nullptr) ||
291 fail(JSAPITestString("CHECK_NULL failed: expected nullptr, got (") +
292 actualExpr + ") = " + toSource(actual),
293 filename, lineno);
296 #define CHECK_NULL(actual) \
297 do { \
298 if (!checkNull(actual, #actual, __FILE__, __LINE__)) return false; \
299 } while (false)
301 bool checkSame(const JS::Value& actualArg, const JS::Value& expectedArg,
302 const char* actualExpr, const char* expectedExpr,
303 const char* filename, int lineno) {
304 bool same;
305 JS::RootedValue actual(cx, actualArg), expected(cx, expectedArg);
306 return (JS::SameValue(cx, actual, expected, &same) && same) ||
307 fail(JSAPITestString(
308 "CHECK_SAME failed: expected JS::SameValue(cx, ") +
309 actualExpr + ", " + expectedExpr +
310 "), got !JS::SameValue(cx, " + jsvalToSource(actual) +
311 ", " + jsvalToSource(expected) + ")",
312 filename, lineno);
315 #define CHECK_SAME(actual, expected) \
316 do { \
317 if (!checkSame(actual, expected, #actual, #expected, __FILE__, __LINE__)) \
318 return false; \
319 } while (false)
321 #define CHECK(expr) \
322 do { \
323 if (!(expr)) \
324 return fail(JSAPITestString("CHECK failed: " #expr), __FILE__, \
325 __LINE__); \
326 } while (false)
328 void maybeAppendException(JSAPITestString& message) override {
329 if (JS_IsExceptionPending(cx)) {
330 message += " -- ";
332 js::gc::AutoSuppressGC gcoff(cx);
333 JS::RootedValue v(cx);
334 JS_GetPendingException(cx, &v);
335 JS_ClearPendingException(cx);
336 JS::Rooted<JSString*> s(cx, JS::ToString(cx, v));
337 if (s) {
338 if (JS::UniqueChars bytes = JS_EncodeStringToLatin1(cx, s)) {
339 message += bytes.get();
345 static const JSClass* basicGlobalClass() {
346 static const JSClass c = {"global", JSCLASS_GLOBAL_FLAGS,
347 &JS::DefaultGlobalClassOps};
348 return &c;
351 protected:
352 static bool print(JSContext* cx, unsigned argc, JS::Value* vp) {
353 JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
355 JS::Rooted<JSString*> str(cx);
356 for (unsigned i = 0; i < args.length(); i++) {
357 str = JS::ToString(cx, args[i]);
358 if (!str) {
359 return false;
361 JS::UniqueChars bytes = JS_EncodeStringToUTF8(cx, str);
362 if (!bytes) {
363 return false;
365 printf("%s%s", i ? " " : "", bytes.get());
368 putchar('\n');
369 fflush(stdout);
370 args.rval().setUndefined();
371 return true;
374 bool definePrint();
376 virtual JSContext* createContext() {
377 JSContext* cx = JS_NewContext(8L * 1024 * 1024);
378 if (!cx) {
379 return nullptr;
381 JS::SetWarningReporter(cx, &reportWarning);
382 return cx;
385 static void reportWarning(JSContext* cx, JSErrorReport* report) {
386 MOZ_RELEASE_ASSERT(report->isWarning());
388 fprintf(stderr, "%s:%u:%s\n",
389 report->filename ? report->filename.c_str() : "<no filename>",
390 (unsigned int)report->lineno, report->message().c_str());
393 virtual const JSClass* getGlobalClass() { return basicGlobalClass(); }
395 virtual JSObject* createGlobal(JSPrincipals* principals = nullptr);
398 class JSAPIFrontendTest : public JSAPITest {
399 public:
400 static JSAPIFrontendTest* list;
401 JSAPIFrontendTest* next;
403 JSAPIFrontendTest() : JSAPITest() {
404 next = list;
405 list = this;
408 virtual ~JSAPIFrontendTest() {}
410 virtual bool init() { return true; }
411 virtual void uninit() {}
413 virtual bool run() = 0;
416 #define BEGIN_TEST_WITH_ATTRIBUTES_AND_EXTRA(testname, attrs, extra) \
417 class cls_##testname : public JSAPIRuntimeTest { \
418 public: \
419 virtual const char* name() override { return #testname; } \
420 extra virtual bool run(JS::HandleObject global) override attrs
422 #define BEGIN_TEST_WITH_ATTRIBUTES(testname, attrs) \
423 BEGIN_TEST_WITH_ATTRIBUTES_AND_EXTRA(testname, attrs, )
425 #define BEGIN_TEST(testname) BEGIN_TEST_WITH_ATTRIBUTES(testname, )
427 #define BEGIN_FRONTEND_TEST_WITH_ATTRIBUTES_AND_EXTRA(testname, attrs, extra) \
428 class cls_##testname : public JSAPIFrontendTest { \
429 public: \
430 virtual const char* name() override { return #testname; } \
431 extra virtual bool run() override attrs
433 #define BEGIN_FRONTEND_TEST_WITH_ATTRIBUTES(testname, attrs) \
434 BEGIN_FRONTEND_TEST_WITH_ATTRIBUTES_AND_EXTRA(testname, attrs, )
436 #define BEGIN_FRONTEND_TEST(testname) \
437 BEGIN_FRONTEND_TEST_WITH_ATTRIBUTES(testname, )
439 #define BEGIN_REUSABLE_TEST(testname) \
440 BEGIN_TEST_WITH_ATTRIBUTES_AND_EXTRA( \
441 testname, , cls_##testname() \
442 : JSAPIRuntimeTest() { reuseGlobal = true; })
444 #define END_TEST(testname) \
447 static cls_##testname cls_##testname##_instance;
450 * A "fixture" is a subclass of JSAPIRuntimeTest that holds common definitions
451 * for a set of tests. Each test that wants to use the fixture should use
452 * BEGIN_FIXTURE_TEST and END_FIXTURE_TEST, just as one would use BEGIN_TEST and
453 * END_TEST, but include the fixture class as the first argument. The fixture
454 * class's declarations are then in scope for the test bodies.
457 #define BEGIN_FIXTURE_TEST(fixture, testname) \
458 class cls_##testname : public fixture { \
459 public: \
460 virtual const char* name() override { return #testname; } \
461 virtual bool run(JS::HandleObject global) override
463 #define END_FIXTURE_TEST(fixture, testname) \
466 static cls_##testname cls_##testname##_instance;
469 * A class for creating and managing one temporary file.
471 * We could use the ISO C temporary file functions here, but those try to
472 * create files in the root directory on Windows, which fails for users
473 * without Administrator privileges.
475 class TempFile {
476 const char* name;
477 FILE* stream;
479 public:
480 TempFile() : name(), stream() {}
481 ~TempFile() {
482 if (stream) {
483 close();
485 if (name) {
486 remove();
491 * Return a stream for a temporary file named |fileName|. Infallible.
492 * Use only once per TempFile instance. If the file is not explicitly
493 * closed and deleted via the member functions below, this object's
494 * destructor will clean them up.
496 FILE* open(const char* fileName) {
497 stream = fopen(fileName, "wb+");
498 if (!stream) {
499 fprintf(stderr, "error opening temporary file '%s': %s\n", fileName,
500 strerror(errno));
501 exit(1);
503 name = fileName;
504 return stream;
507 /* Close the temporary file's stream. */
508 void close() {
509 if (fclose(stream) == EOF) {
510 fprintf(stderr, "error closing temporary file '%s': %s\n", name,
511 strerror(errno));
512 exit(1);
514 stream = nullptr;
517 /* Delete the temporary file. */
518 void remove() {
519 if (::remove(name) != 0) {
520 fprintf(stderr, "error deleting temporary file '%s': %s\n", name,
521 strerror(errno));
522 exit(1);
524 name = nullptr;
528 // Just a wrapper around JSPrincipals that allows static construction.
529 class TestJSPrincipals : public JSPrincipals {
530 public:
531 explicit TestJSPrincipals(int rc = 0) : JSPrincipals() { refcount = rc; }
533 bool write(JSContext* cx, JSStructuredCloneWriter* writer) override {
534 MOZ_ASSERT(false, "not implemented");
535 return false;
538 bool isSystemOrAddonPrincipal() override { return true; }
541 // A class that simulates externally memory-managed data, for testing with
542 // array buffers.
543 class ExternalData {
544 char* contents_;
545 size_t len_;
546 bool uniquePointerCreated_ = false;
548 public:
549 explicit ExternalData(const char* str)
550 : contents_(strdup(str)), len_(strlen(str) + 1) {}
552 size_t len() const { return len_; }
553 void* contents() const { return contents_; }
554 char* asString() const { return contents_; }
555 bool wasFreed() const { return !contents_; }
557 void free() {
558 MOZ_ASSERT(!wasFreed());
559 ::free(contents_);
560 contents_ = nullptr;
563 mozilla::UniquePtr<void, JS::BufferContentsDeleter> pointer() {
564 MOZ_ASSERT(!uniquePointerCreated_,
565 "Not allowed to create multiple unique pointers to contents");
566 uniquePointerCreated_ = true;
567 return {contents_, {ExternalData::freeCallback, this}};
570 static void freeCallback(void* contents, void* userData) {
571 auto self = static_cast<ExternalData*>(userData);
572 MOZ_ASSERT(self->contents() == contents);
573 self->free();
577 class AutoGCParameter {
578 JSContext* cx_;
579 JSGCParamKey key_;
580 uint32_t value_;
582 public:
583 explicit AutoGCParameter(JSContext* cx, JSGCParamKey key, uint32_t value)
584 : cx_(cx), key_(key), value_() {
585 value_ = JS_GetGCParameter(cx, key);
586 JS_SetGCParameter(cx, key, value);
588 ~AutoGCParameter() { JS_SetGCParameter(cx_, key_, value_); }
591 #ifdef JS_GC_ZEAL
593 * Temporarily disable the GC zeal setting. This is only useful in tests that
594 * need very explicit GC behavior and should not be used elsewhere.
596 class AutoLeaveZeal {
597 JSContext* cx_;
598 uint32_t zealBits_;
599 uint32_t frequency_;
601 public:
602 explicit AutoLeaveZeal(JSContext* cx) : cx_(cx), zealBits_(0), frequency_(0) {
603 uint32_t dummy;
604 JS_GetGCZealBits(cx_, &zealBits_, &frequency_, &dummy);
605 JS_SetGCZeal(cx_, 0, 0);
606 JS::PrepareForFullGC(cx_);
607 JS::NonIncrementalGC(cx_, JS::GCOptions::Normal, JS::GCReason::DEBUG_GC);
609 ~AutoLeaveZeal() {
610 JS_SetGCZeal(cx_, 0, 0);
611 for (size_t i = 0; i < sizeof(zealBits_) * 8; i++) {
612 if (zealBits_ & (1 << i)) {
613 JS_SetGCZeal(cx_, i, frequency_);
617 # ifdef DEBUG
618 uint32_t zealBitsAfter, frequencyAfter, dummy;
619 JS_GetGCZealBits(cx_, &zealBitsAfter, &frequencyAfter, &dummy);
620 MOZ_ASSERT(zealBitsAfter == zealBits_);
621 MOZ_ASSERT(frequencyAfter == frequency_);
622 # endif
626 #else
627 class AutoLeaveZeal {
628 public:
629 explicit AutoLeaveZeal(JSContext* cx) {}
631 #endif
633 #endif /* jsapi_tests_tests_h */