Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#include "jsapi-tests/tests.h"
#include "mozilla/Utf8.h" // mozilla::Utf8Unit
#include <stdio.h>
#include "js/ArrayBuffer.h"
#include "js/CompilationAndEvaluation.h" // JS::Evaluate
#include "js/GlobalObject.h" // JS_NewGlobalObject
#include "js/Initialization.h"
#include "js/Prefs.h"
#include "js/PropertyAndElement.h" // JS_DefineFunction
#include "js/RootingAPI.h"
#include "js/SourceText.h" // JS::Source{Ownership,Text}
JSAPIRuntimeTest* JSAPIRuntimeTest::list;
JSAPIFrontendTest* JSAPIFrontendTest::list;
bool JSAPIRuntimeTest::init(JSContext* maybeReusableContext) {
if (maybeReusableContext && reuseGlobal) {
cx = maybeReusableContext;
global.init(cx, JS::CurrentGlobalOrNull(cx));
return init();
}
MaybeFreeContext(maybeReusableContext);
cx = createContext();
if (!cx) {
return false;
}
js::UseInternalJobQueues(cx);
if (!JS::InitSelfHostedCode(cx)) {
return false;
}
global.init(cx);
createGlobal();
if (!global) {
return false;
}
JS::EnterRealm(cx, global);
return init();
}
JSContext* JSAPIRuntimeTest::maybeForgetContext() {
if (!reuseGlobal) {
return nullptr;
}
JSContext* reusableCx = cx;
global.reset();
cx = nullptr;
return reusableCx;
}
/* static */
void JSAPIRuntimeTest::MaybeFreeContext(JSContext* maybeCx) {
if (maybeCx) {
JS::LeaveRealm(maybeCx, nullptr);
JS_DestroyContext(maybeCx);
}
}
void JSAPIRuntimeTest::uninit() {
global.reset();
MaybeFreeContext(cx);
cx = nullptr;
msgs.clear();
}
bool JSAPIRuntimeTest::exec(const char* utf8, const char* filename,
int lineno) {
JS::CompileOptions opts(cx);
opts.setFileAndLine(filename, lineno);
JS::SourceText<mozilla::Utf8Unit> srcBuf;
JS::RootedValue v(cx);
return (srcBuf.init(cx, utf8, strlen(utf8), JS::SourceOwnership::Borrowed) &&
JS::Evaluate(cx, opts, srcBuf, &v)) ||
fail(JSAPITestString(utf8), filename, lineno);
}
bool JSAPIRuntimeTest::execDontReport(const char* utf8, const char* filename,
int lineno) {
JS::CompileOptions opts(cx);
opts.setFileAndLine(filename, lineno);
JS::SourceText<mozilla::Utf8Unit> srcBuf;
JS::RootedValue v(cx);
return srcBuf.init(cx, utf8, strlen(utf8), JS::SourceOwnership::Borrowed) &&
JS::Evaluate(cx, opts, srcBuf, &v);
}
bool JSAPIRuntimeTest::evaluate(const char* utf8, const char* filename,
int lineno, JS::MutableHandleValue vp) {
JS::CompileOptions opts(cx);
opts.setFileAndLine(filename, lineno);
JS::SourceText<mozilla::Utf8Unit> srcBuf;
return (srcBuf.init(cx, utf8, strlen(utf8), JS::SourceOwnership::Borrowed) &&
JS::Evaluate(cx, opts, srcBuf, vp)) ||
fail(JSAPITestString(utf8), filename, lineno);
}
bool JSAPIRuntimeTest::definePrint() {
return JS_DefineFunction(cx, global, "print", (JSNative)print, 0, 0);
}
JSObject* JSAPIRuntimeTest::createGlobal(JSPrincipals* principals) {
/* Create the global object. */
JS::RootedObject newGlobal(cx);
JS::RealmOptions options;
options.creationOptions().setSharedMemoryAndAtomicsEnabled(true);
newGlobal = JS_NewGlobalObject(cx, getGlobalClass(), principals,
JS::FireOnNewGlobalHook, options);
if (!newGlobal) {
return nullptr;
}
global = newGlobal;
return newGlobal;
}
struct CommandOptions {
bool list = false;
bool frontendOnly = false;
bool help = false;
const char* filter = nullptr;
};
void parseArgs(int argc, char* argv[], CommandOptions& options) {
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
options.help = true;
continue;
}
if (strcmp(argv[i], "--list") == 0) {
options.list = true;
continue;
}
if (strcmp(argv[i], "--frontend-only") == 0) {
options.frontendOnly = true;
continue;
}
if (!options.filter) {
options.filter = argv[i];
continue;
}
printf("error: Unrecognized option: %s\n", argv[i]);
options.help = true;
}
}
template <typename TestT>
void PrintTests(TestT* list) {
for (TestT* test = list; test; test = test->next) {
printf("%s\n", test->name());
}
}
template <typename TestT, typename InitF, typename RunF, typename BeforeUninitF>
void RunTests(int& total, int& failures, CommandOptions& options, TestT* list,
InitF init, RunF run, BeforeUninitF beforeUninit) {
for (TestT* test = list; test; test = test->next) {
const char* name = test->name();
if (options.filter && strstr(name, options.filter) == nullptr) {
continue;
}
total += 1;
printf("%s\n", name);
// Make sure the test name is printed before we enter the test that can
// crash on failure.
fflush(stdout);
if (!init(test)) {
printf("TEST-UNEXPECTED-FAIL | %s | Failed to initialize.\n", name);
failures++;
test->uninit();
continue;
}
if (run(test)) {
printf("TEST-PASS | %s | ok\n", name);
} else {
JSAPITestString messages = test->messages();
printf("%s | %s | %.*s\n",
(test->knownFail ? "TEST-KNOWN-FAIL" : "TEST-UNEXPECTED-FAIL"),
name, (int)messages.length(), messages.begin());
if (!test->knownFail) {
failures++;
}
}
beforeUninit(test);
test->uninit();
}
}
int main(int argc, char* argv[]) {
int total = 0;
int failures = 0;
CommandOptions options;
parseArgs(argc, argv, options);
if (options.help) {
printf("Usage: jsapi-tests [OPTIONS] [FILTER]\n");
printf("\n");
printf("Options:\n");
printf(" -h, --help Display this message\n");
printf(" --list List all tests\n");
printf(
" --frontend-only Run tests for frontend-only APIs, with "
"light-weight entry point\n");
return 0;
}
// Override prefs for jsapi-tests.
JS::Prefs::setAtStartup_weakrefs(true);
JS::Prefs::setAtStartup_experimental_weakrefs_expose_cleanupSome(true);
#ifdef NIGHTLY_BUILD
JS::Prefs::setAtStartup_experimental_symbols_as_weakmap_keys(true);
#endif
if (!options.frontendOnly) {
if (!JS_Init()) {
printf("TEST-UNEXPECTED-FAIL | jsapi-tests | JS_Init() failed.\n");
return 1;
}
} else {
if (!JS_FrontendOnlyInit()) {
printf("TEST-UNEXPECTED-FAIL | jsapi-tests | JS_Init() failed.\n");
return 1;
}
}
if (options.list) {
PrintTests(JSAPIRuntimeTest::list);
PrintTests(JSAPIFrontendTest::list);
return 0;
}
// Reinitializing the global for every test is quite slow, due to having to
// recompile all self-hosted builtins. Allow tests to opt-in to reusing the
// global.
JSContext* maybeReusedContext = nullptr;
if (!options.frontendOnly) {
RunTests(
total, failures, options, JSAPIRuntimeTest::list,
[&maybeReusedContext](JSAPIRuntimeTest* test) {
return test->init(maybeReusedContext);
},
[](JSAPIRuntimeTest* test) { return test->run(test->global); },
[&maybeReusedContext](JSAPIRuntimeTest* test) {
// Return a non-nullptr pointer if the context & global can safely be
// reused for the next test.
maybeReusedContext = test->maybeForgetContext();
});
}
RunTests(
total, failures, options, JSAPIFrontendTest::list,
[](JSAPIFrontendTest* test) { return test->init(); },
[](JSAPIFrontendTest* test) { return test->run(); },
[](JSAPIFrontendTest* test) {});
if (!options.frontendOnly) {
JSAPIRuntimeTest::MaybeFreeContext(maybeReusedContext);
MOZ_RELEASE_ASSERT(!JSRuntime::hasLiveRuntimes());
JS_ShutDown();
} else {
JS_FrontendOnlyShutDown();
}
if (failures) {
printf("\n%d unexpected failure%s.\n", failures,
(failures == 1 ? "" : "s"));
return 1;
}
printf("\nPassed: ran %d tests.\n", total);
return 0;
}