/* -*- 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/. */ #include "jsapi-tests/tests.h" #include "mozilla/Utf8.h" // mozilla::Utf8Unit #include #include "js/ArrayBuffer.h" #include "js/CompilationAndEvaluation.h" // JS::Evaluate #include "js/GlobalObject.h" // JS_NewGlobalObject #include "js/Initialization.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 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 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 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() .setWeakRefsEnabled(JS::WeakRefSpecifier::EnabledWithCleanupSome) .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 void PrintTests(TestT* list) { for (TestT* test = list; test; test = test->next) { printf("%s\n", test->name()); } } template 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; } 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; }