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"
17 #include <type_traits>
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
;
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
))) {
54 JSAPITestString
& operator+=(const JSAPITestString
& s
) {
55 if (!chars
.append(s
.begin(), s
.length())) {
62 inline JSAPITestString
operator+(const JSAPITestString
& a
, const char* b
) {
63 JSAPITestString result
= a
;
68 inline JSAPITestString
operator+(const JSAPITestString
& a
,
69 const JSAPITestString
& b
) {
70 JSAPITestString result
= a
;
75 class JSAPIRuntimeTest
;
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) {
93 SprintfLiteral(location
, "%s:%d:", filename
, lineno
);
95 JSAPITestString
message(location
);
98 maybeAppendException(message
);
100 fprintf(stderr
, "%.*s\n", int(message
.length()), message
.begin());
102 if (msgs
.length() != 0) {
109 JSAPITestString
messages() const { return msgs
; }
112 class JSAPIRuntimeTest
: public JSAPITest
{
114 static JSAPIRuntimeTest
* list
;
115 JSAPIRuntimeTest
* next
;
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.
126 JSAPIRuntimeTest() : JSAPITest(), cx(nullptr), reuseGlobal(false) {
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;
155 if (!exec(s, __FILE__, __LINE__)) return 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) \
165 if (!evaluate(s, __FILE__, __LINE__, vp)) return 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
));
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
) {
189 SprintfLiteral(buf
, "%ld", v
);
190 return JSAPITestString(buf
);
193 JSAPITestString
toSource(unsigned long v
) {
195 SprintfLiteral(buf
, "%lu", v
);
196 return JSAPITestString(buf
);
199 JSAPITestString
toSource(long long v
) {
201 SprintfLiteral(buf
, "%lld", v
);
202 return JSAPITestString(buf
);
205 JSAPITestString
toSource(unsigned long long v
) {
207 SprintfLiteral(buf
, "%llu", v
);
208 return JSAPITestString(buf
);
211 JSAPITestString
toSource(double d
) {
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
) {
229 if (flags
.hasIndices()) {
232 if (flags
.global()) {
235 if (flags
.ignoreCase()) {
238 if (flags
.multiline()) {
241 if (flags
.dotAll()) {
244 if (flags
.unicode()) {
247 if (flags
.unicodeSets()) {
250 if (flags
.sticky()) {
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");
271 std::is_unsigned_v
<T
> == std::is_unsigned_v
<U
>,
272 "using CHECK_EQUAL with different-signed inputs triggers compiler "
274 return (actual
== expected
) ||
275 fail(JSAPITestString("CHECK_EQUAL failed: expected (") +
276 expectedExpr
+ ") = " + toSource(expected
) + ", got (" +
277 actualExpr
+ ") = " + toSource(actual
),
281 #define CHECK_EQUAL(actual, expected) \
283 if (!checkEqual(actual, expected, #actual, #expected, __FILE__, __LINE__)) \
287 template <typename T
>
288 bool checkNull(const T
* actual
, const char* actualExpr
, const char* filename
,
290 return (actual
== nullptr) ||
291 fail(JSAPITestString("CHECK_NULL failed: expected nullptr, got (") +
292 actualExpr
+ ") = " + toSource(actual
),
296 #define CHECK_NULL(actual) \
298 if (!checkNull(actual, #actual, __FILE__, __LINE__)) return 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
) {
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
) + ")",
315 #define CHECK_SAME(actual, expected) \
317 if (!checkSame(actual, expected, #actual, #expected, __FILE__, __LINE__)) \
321 #define CHECK(expr) \
324 return fail(JSAPITestString("CHECK failed: " #expr), __FILE__, \
328 void maybeAppendException(JSAPITestString
& message
) override
{
329 if (JS_IsExceptionPending(cx
)) {
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
));
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
};
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
]);
361 JS::UniqueChars bytes
= JS_EncodeStringToUTF8(cx
, str
);
365 printf("%s%s", i
? " " : "", bytes
.get());
370 args
.rval().setUndefined();
376 virtual JSContext
* createContext() {
377 JSContext
* cx
= JS_NewContext(8L * 1024 * 1024);
381 JS::SetWarningReporter(cx
, &reportWarning
);
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
{
400 static JSAPIFrontendTest
* list
;
401 JSAPIFrontendTest
* next
;
403 JSAPIFrontendTest() : JSAPITest() {
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 { \
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 { \
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 { \
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.
480 TempFile() : name(), stream() {}
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+");
499 fprintf(stderr
, "error opening temporary file '%s': %s\n", fileName
,
507 /* Close the temporary file's stream. */
509 if (fclose(stream
) == EOF
) {
510 fprintf(stderr
, "error closing temporary file '%s': %s\n", name
,
517 /* Delete the temporary file. */
519 if (::remove(name
) != 0) {
520 fprintf(stderr
, "error deleting temporary file '%s': %s\n", name
,
528 // Just a wrapper around JSPrincipals that allows static construction.
529 class TestJSPrincipals
: public JSPrincipals
{
531 explicit TestJSPrincipals(int rc
= 0) : JSPrincipals() { refcount
= rc
; }
533 bool write(JSContext
* cx
, JSStructuredCloneWriter
* writer
) override
{
534 MOZ_ASSERT(false, "not implemented");
538 bool isSystemOrAddonPrincipal() override
{ return true; }
541 // A class that simulates externally memory-managed data, for testing with
546 bool uniquePointerCreated_
= false;
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_
; }
558 MOZ_ASSERT(!wasFreed());
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
);
577 class AutoGCParameter
{
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_
); }
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
{
602 explicit AutoLeaveZeal(JSContext
* cx
) : cx_(cx
), zealBits_(0), frequency_(0) {
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
);
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_
);
618 uint32_t zealBitsAfter
, frequencyAfter
, dummy
;
619 JS_GetGCZealBits(cx_
, &zealBitsAfter
, &frequencyAfter
, &dummy
);
620 MOZ_ASSERT(zealBitsAfter
== zealBits_
);
621 MOZ_ASSERT(frequencyAfter
== frequency_
);
627 class AutoLeaveZeal
{
629 explicit AutoLeaveZeal(JSContext
* cx
) {}
633 #endif /* jsapi_tests_tests_h */