diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /js/src/tests/shell | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | js/src/tests/shell.js | 588 | ||||
-rw-r--r-- | js/src/tests/shell/README | 1 | ||||
-rw-r--r-- | js/src/tests/shell/futex-apis.js | 125 | ||||
-rw-r--r-- | js/src/tests/shell/futex.js | 117 | ||||
-rw-r--r-- | js/src/tests/shell/gcstats.js | 60 | ||||
-rw-r--r-- | js/src/tests/shell/mailbox.js | 104 | ||||
-rw-r--r-- | js/src/tests/shell/os.js | 39 | ||||
-rw-r--r-- | js/src/tests/shell/script-file-name-utf8.js | 235 | ||||
-rw-r--r-- | js/src/tests/shell/shell.js | 4 | ||||
-rw-r--r-- | js/src/tests/shell/warning.js | 33 |
10 files changed, 1306 insertions, 0 deletions
diff --git a/js/src/tests/shell.js b/js/src/tests/shell.js new file mode 100644 index 0000000000..4fd0942b8d --- /dev/null +++ b/js/src/tests/shell.js @@ -0,0 +1,588 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + * 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/. */ + +// NOTE: If you're adding new test harness functionality -- first, should you +// at all? Most stuff is better in specific tests, or in nested shell.js +// or browser.js. Second, supposing you should, please add it to this +// IIFE for better modularity/resilience against tests that must do +// particularly bizarre things that might break the harness. + +(function(global) { + "use strict"; + + /********************************************************************** + * CACHED PRIMORDIAL FUNCTIONALITY (before a test might overwrite it) * + **********************************************************************/ + + var undefined; // sigh + + var Error = global.Error; + var Function = global.Function; + var Number = global.Number; + var RegExp = global.RegExp; + var String = global.String; + var Symbol = global.Symbol; + var TypeError = global.TypeError; + + var ArrayIsArray = global.Array.isArray; + var MathAbs = global.Math.abs; + var ObjectCreate = global.Object.create; + var ObjectDefineProperty = global.Object.defineProperty; + var ReflectApply = global.Reflect.apply; + var RegExpPrototypeExec = global.RegExp.prototype.exec; + var StringPrototypeCharCodeAt = global.String.prototype.charCodeAt; + var StringPrototypeIndexOf = global.String.prototype.indexOf; + var StringPrototypeSubstring = global.String.prototype.substring; + + var runningInBrowser = typeof global.window !== "undefined"; + if (runningInBrowser) { + // Certain cached functionality only exists (and is only needed) when + // running in the browser. Segregate that caching here. + + var SpecialPowersSetGCZeal = + global.SpecialPowers ? global.SpecialPowers.setGCZeal : undefined; + } + + var evaluate = global.evaluate; + var options = global.options; + + /**************************** + * GENERAL HELPER FUNCTIONS * + ****************************/ + + // We *cannot* use Array.prototype.push for this, because that function sets + // the new trailing element, which could invoke a setter (left by a test) on + // Array.prototype or Object.prototype. + function ArrayPush(arr, val) { + assertEq(ArrayIsArray(arr), true, + "ArrayPush must only be used on actual arrays"); + + var desc = ObjectCreate(null); + desc.value = val; + desc.enumerable = true; + desc.configurable = true; + desc.writable = true; + ObjectDefineProperty(arr, arr.length, desc); + } + + function StringCharCodeAt(str, index) { + return ReflectApply(StringPrototypeCharCodeAt, str, [index]); + } + + function StringSplit(str, delimiter) { + assertEq(typeof str === "string" && typeof delimiter === "string", true, + "StringSplit must be called with two string arguments"); + assertEq(delimiter.length > 0, true, + "StringSplit doesn't support an empty delimiter string"); + + var parts = []; + var last = 0; + while (true) { + var i = ReflectApply(StringPrototypeIndexOf, str, [delimiter, last]); + if (i < 0) { + if (last < str.length) + ArrayPush(parts, ReflectApply(StringPrototypeSubstring, str, [last])); + return parts; + } + + ArrayPush(parts, ReflectApply(StringPrototypeSubstring, str, [last, i])); + last = i + delimiter.length; + } + } + + function shellOptionsClear() { + assertEq(runningInBrowser, false, "Only called when running in the shell."); + + // Return early if no options are set. + var currentOptions = options ? options() : ""; + if (currentOptions === "") + return; + + // Turn off current settings. + var optionNames = StringSplit(currentOptions, ","); + for (var i = 0; i < optionNames.length; i++) { + options(optionNames[i]); + } + } + + /**************************** + * TESTING FUNCTION EXPORTS * + ****************************/ + + function SameValue(v1, v2) { + // We could |return Object.is(v1, v2);|, but that's less portable. + if (v1 === 0 && v2 === 0) + return 1 / v1 === 1 / v2; + if (v1 !== v1 && v2 !== v2) + return true; + return v1 === v2; + } + + var assertEq = global.assertEq; + if (typeof assertEq !== "function") { + assertEq = function assertEq(actual, expected, message) { + if (!SameValue(actual, expected)) { + throw new TypeError(`Assertion failed: got "${actual}", expected "${expected}"` + + (message ? ": " + message : "")); + } + }; + global.assertEq = assertEq; + } + + function assertEqArray(actual, expected) { + var len = actual.length; + assertEq(len, expected.length, "mismatching array lengths"); + + var i = 0; + try { + for (; i < len; i++) + assertEq(actual[i], expected[i], "mismatch at element " + i); + } catch (e) { + throw new Error(`Exception thrown at index ${i}: ${e}`); + } + } + global.assertEqArray = assertEqArray; + + function assertThrows(f) { + if (arguments.length != 1) { + throw new Error("Too many arguments to assertThrows; maybe you meant assertThrowsInstanceOf?"); + } + var ok = false; + try { + f(); + } catch (exc) { + ok = true; + } + if (!ok) + throw new Error(`Assertion failed: ${f} did not throw as expected`); + } + global.assertThrows = assertThrows; + + function assertThrowsInstanceOf(f, ctor, msg) { + var fullmsg; + try { + f(); + } catch (exc) { + if (exc instanceof ctor) + return; + fullmsg = `Assertion failed: expected exception ${ctor.name}, got ${exc}`; + } + + if (fullmsg === undefined) + fullmsg = `Assertion failed: expected exception ${ctor.name}, no exception thrown`; + if (msg !== undefined) + fullmsg += " - " + msg; + + throw new Error(fullmsg); + } + global.assertThrowsInstanceOf = assertThrowsInstanceOf; + + /**************************** + * UTILITY FUNCTION EXPORTS * + ****************************/ + + var dump = global.dump; + if (typeof global.dump === "function") { + // A presumptively-functional |dump| exists, so no need to do anything. + } else { + // We don't have |dump|. Try to simulate the desired effect another way. + if (runningInBrowser) { + // We can't actually print to the console: |global.print| invokes browser + // printing functionality here (it's overwritten just below), and + // |global.dump| isn't a function that'll dump to the console (presumably + // because the preference to enable |dump| wasn't set). Just make it a + // no-op. + dump = function() {}; + } else { + // |print| prints to stdout: make |dump| do likewise. + dump = global.print; + } + global.dump = dump; + } + + var print; + if (runningInBrowser) { + // We're executing in a browser. Using |global.print| would invoke browser + // printing functionality: not what tests want! Instead, use a print + // function that syncs up with the test harness and console. + print = function print() { + var s = "TEST-INFO | "; + for (var i = 0; i < arguments.length; i++) + s += String(arguments[i]) + " "; + + // Dump the string to the console for developers and the harness. + dump(s + "\n"); + + // AddPrintOutput doesn't require HTML special characters be escaped. + global.AddPrintOutput(s); + }; + + global.print = print; + } else { + // We're executing in a shell, and |global.print| is the desired function. + print = global.print; + } + + var gczeal = global.gczeal; + if (typeof gczeal !== "function") { + if (typeof SpecialPowersSetGCZeal === "function") { + gczeal = function gczeal(z) { + SpecialPowersSetGCZeal(z); + }; + } else { + gczeal = function() {}; // no-op if not available + } + + global.gczeal = gczeal; + } + + // Evaluates the given source code as global script code. browser.js provides + // a different implementation for this function. + var evaluateScript = global.evaluateScript; + if (typeof evaluate === "function" && typeof evaluateScript !== "function") { + evaluateScript = function evaluateScript(code) { + evaluate(String(code)); + }; + + global.evaluateScript = evaluateScript; + } + + function toPrinted(value) { + value = String(value); + + var digits = "0123456789ABCDEF"; + var result = ""; + for (var i = 0; i < value.length; i++) { + var ch = StringCharCodeAt(value, i); + if (ch === 0x5C && i + 1 < value.length) { + var d = value[i + 1]; + if (d === "n") { + result += "NL"; + i++; + } else if (d === "r") { + result += "CR"; + i++; + } else { + result += "\\"; + } + } else if (ch === 0x0A) { + result += "NL"; + } else if (ch < 0x20 || ch > 0x7E) { + var a = digits[ch & 0xf]; + ch >>= 4; + var b = digits[ch & 0xf]; + ch >>= 4; + + if (ch) { + var c = digits[ch & 0xf]; + ch >>= 4; + var d = digits[ch & 0xf]; + + result += "\\u" + d + c + b + a; + } else { + result += "\\x" + b + a; + } + } else { + result += value[i]; + } + } + + return result; + } + + /* + * An xorshift pseudo-random number generator see: + * https://en.wikipedia.org/wiki/Xorshift#xorshift.2A + * This generator will always produce a value, n, where + * 0 <= n <= 255 + */ + function *XorShiftGenerator(seed, size) { + let x = seed; + for (let i = 0; i < size; i++) { + x ^= x >> 12; + x ^= x << 25; + x ^= x >> 27; + yield x % 256; + } + } + global.XorShiftGenerator = XorShiftGenerator; + + /************************************************************************* + * HARNESS-CENTRIC EXPORTS (we should generally work to eliminate these) * + *************************************************************************/ + + var PASSED = " PASSED! "; + var FAILED = " FAILED! "; + + /* + * Same as `new TestCase(description, expect, actual)`, except it doesn't + * return the newly created test case object. + */ + function AddTestCase(description, expect, actual) { + new TestCase(description, expect, actual); + } + global.AddTestCase = AddTestCase; + + var testCasesArray = []; + + function TestCase(d, e, a, r) { + this.description = d; + this.expect = e; + this.actual = a; + this.passed = getTestCaseResult(e, a); + this.reason = typeof r !== 'undefined' ? String(r) : ''; + + ArrayPush(testCasesArray, this); + } + global.TestCase = TestCase; + + TestCase.prototype = ObjectCreate(null); + TestCase.prototype.testPassed = (function TestCase_testPassed() { return this.passed; }); + TestCase.prototype.testFailed = (function TestCase_testFailed() { return !this.passed; }); + TestCase.prototype.testDescription = (function TestCase_testDescription() { return this.description + ' ' + this.reason; }); + + function getTestCaseResult(expected, actual) { + if (typeof expected !== typeof actual) + return false; + if (typeof expected !== 'number') + // Note that many tests depend on the use of '==' here, not '==='. + return actual == expected; + + // Distinguish NaN from other values. Using x !== x comparisons here + // works even if tests redefine isNaN. + if (actual !== actual) + return expected !== expected; + if (expected !== expected) + return false; + + // Tolerate a certain degree of error. + if (actual !== expected) + return MathAbs(actual - expected) <= 1E-10; + + // Here would be a good place to distinguish 0 and -0, if we wanted + // to. However, doing so would introduce a number of failures in + // areas where they don't seem important. For example, the WeekDay + // function in ECMA-262 returns -0 for Sundays before the epoch, but + // the Date functions in SpiderMonkey specified in terms of WeekDay + // often don't. This seems unimportant. + return true; + } + + function reportTestCaseResult(description, expected, actual, output) { + var testcase = new TestCase(description, expected, actual, output); + + // if running under reftest, let it handle result reporting. + if (!runningInBrowser) { + if (testcase.passed) { + print(PASSED + description); + } else { + reportFailure(description + " : " + output); + } + } + } + + function getTestCases() { + return testCasesArray; + } + global.getTestCases = getTestCases; + + /* + * The test driver searches for such a phrase in the test output. + * If such phrase exists, it will set n as the expected exit code. + */ + function expectExitCode(n) { + print('--- NOTE: IN THIS TESTCASE, WE EXPECT EXIT CODE ' + n + ' ---'); + } + global.expectExitCode = expectExitCode; + + /* + * Statuses current section of a test + */ + function inSection(x) { + return "Section " + x + " of test - "; + } + global.inSection = inSection; + + /* + * Report a failure in the 'accepted' manner + */ + function reportFailure(msg) { + msg = String(msg); + var lines = StringSplit(msg, "\n"); + + for (var i = 0; i < lines.length; i++) + print(FAILED + " " + lines[i]); + } + global.reportFailure = reportFailure; + + /* + * Print a non-failure message. + */ + function printStatus(msg) { + msg = String(msg); + var lines = StringSplit(msg, "\n"); + + for (var i = 0; i < lines.length; i++) + print("STATUS: " + lines[i]); + } + global.printStatus = printStatus; + + /* + * Print a bugnumber message. + */ + function printBugNumber(num) { + print('BUGNUMBER: ' + num); + } + global.printBugNumber = printBugNumber; + + /* + * Compare expected result to actual result, if they differ (in value and/or + * type) report a failure. If description is provided, include it in the + * failure report. + */ + function reportCompare(expected, actual, description) { + var expected_t = typeof expected; + var actual_t = typeof actual; + var output = ""; + + if (typeof description === "undefined") + description = ""; + + if (expected_t !== actual_t) + output += `Type mismatch, expected type ${expected_t}, actual type ${actual_t} `; + + if (expected != actual) + output += `Expected value '${toPrinted(expected)}', Actual value '${toPrinted(actual)}' `; + + reportTestCaseResult(description, expected, actual, output); + } + global.reportCompare = reportCompare; + + /* + * Attempt to match a regular expression describing the result to + * the actual result, if they differ (in value and/or + * type) report a failure. If description is provided, include it in the + * failure report. + */ + function reportMatch(expectedRegExp, actual, description) { + var expected_t = "string"; + var actual_t = typeof actual; + var output = ""; + + if (typeof description === "undefined") + description = ""; + + if (expected_t !== actual_t) + output += `Type mismatch, expected type ${expected_t}, actual type ${actual_t} `; + + var matches = ReflectApply(RegExpPrototypeExec, expectedRegExp, [actual]) !== null; + if (!matches) { + output += + `Expected match to '${toPrinted(expectedRegExp)}', Actual value '${toPrinted(actual)}' `; + } + + reportTestCaseResult(description, true, matches, output); + } + global.reportMatch = reportMatch; + + function compareSource(expect, actual, summary) { + // compare source + var expectP = String(expect); + var actualP = String(actual); + + print('expect:\n' + expectP); + print('actual:\n' + actualP); + + reportCompare(expectP, actualP, summary); + + // actual must be compilable if expect is? + try { + var expectCompile = 'No Error'; + var actualCompile; + + Function(expect); + try { + Function(actual); + actualCompile = 'No Error'; + } catch(ex1) { + actualCompile = ex1 + ''; + } + reportCompare(expectCompile, actualCompile, + summary + ': compile actual'); + } catch(ex) { + } + } + global.compareSource = compareSource; + + function test() { + var testCases = getTestCases(); + for (var i = 0; i < testCases.length; i++) { + var testCase = testCases[i]; + testCase.reason += testCase.passed ? "" : "wrong value "; + + // if running under reftest, let it handle result reporting. + if (!runningInBrowser) { + var message = `${testCase.description} = ${testCase.actual} expected: ${testCase.expect}`; + print((testCase.passed ? PASSED : FAILED) + message); + } + } + } + global.test = test; + + // This function uses the shell's print function. When running tests in the + // browser, browser.js overrides this function to write to the page. + function writeHeaderToLog(string) { + print(string); + } + global.writeHeaderToLog = writeHeaderToLog; + + /************************************ + * PROMISE TESTING FUNCTION EXPORTS * + ************************************/ + + function getPromiseResult(promise) { + var result, error, caught = false; + promise.then(r => { result = r; }, + e => { caught = true; error = e; }); + drainJobQueue(); + if (caught) + throw error; + return result; + } + global.getPromiseResult = getPromiseResult; + + function assertEventuallyEq(promise, expected) { + assertEq(getPromiseResult(promise), expected); + } + global.assertEventuallyEq = assertEventuallyEq; + + function assertEventuallyThrows(promise, expectedErrorType) { + assertThrowsInstanceOf(() => getPromiseResult(promise), expectedErrorType); + }; + global.assertEventuallyThrows = assertEventuallyThrows; + + function assertEventuallyDeepEq(promise, expected) { + assertDeepEq(getPromiseResult(promise), expected); + }; + global.assertEventuallyDeepEq = assertEventuallyDeepEq; + + /******************************************* + * RUN ONCE CODE TO SETUP ADDITIONAL STATE * + *******************************************/ + + // Clear all options before running any tests. browser.js performs this + // set-up as part of its jsTestDriverBrowserInit function. + if (!runningInBrowser) { + shellOptionsClear(); + } + + if (!runningInBrowser) { + // Set the minimum heap size for parallel marking to zero for testing + // purposes. We don't have access to gcparam in the browser. + gcparam('parallelMarkingThresholdKB', 0); + } +})(this); + +var DESCRIPTION; diff --git a/js/src/tests/shell/README b/js/src/tests/shell/README new file mode 100644 index 0000000000..0c7a3d1498 --- /dev/null +++ b/js/src/tests/shell/README @@ -0,0 +1 @@ +Tests for JS shell-only functions diff --git a/js/src/tests/shell/futex-apis.js b/js/src/tests/shell/futex-apis.js new file mode 100644 index 0000000000..dd38a1c98b --- /dev/null +++ b/js/src/tests/shell/futex-apis.js @@ -0,0 +1,125 @@ +// |reftest| skip-if(!xulRuntime.shell) +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +if (this.SharedArrayBuffer && this.Atomics) { + +// Checks for parameter validation of wait/wake API. All of these test +// cases should throw exceptions during parameter validation, before +// we check whether any waiting should be done. + +let ab = new ArrayBuffer(16); +let sab = new SharedArrayBuffer(16); + +////////////////////////////////////////////////////////////////////// +// +// The view must be an Int32Array on a SharedArrayBuffer. + +// Check against non-TypedArray cases. + +{ + let values = [null, + undefined, + true, + false, + new Boolean(true), + 10, + 3.14, + new Number(4), + "Hi there", + new Date, + /a*utomaton/g, + { password: "qumquat" }, + new DataView(new ArrayBuffer(10)), + new ArrayBuffer(128), + new SharedArrayBuffer(128), + new Error("Ouch"), + [1,1,2,3,5,8], + ((x) => -x), + new Map(), + new Set(), + new WeakMap(), + new WeakSet(), + new Promise(() => "done"), + Symbol("halleluja"), + // TODO: Proxy? + Object, + Int32Array, + Date, + Math, + Atomics ]; + + for ( let i=0 ; i < values.length ; i++ ) { + let view = values[i]; + assertThrowsInstanceOf(() => Atomics.wait(view, 0, 0), TypeError); + assertThrowsInstanceOf(() => Atomics.wake(view, 0), TypeError); + } +} + +// Check against TypedArray on non-shared memory and wrong view types cases. + +{ + let views = [Int8Array, Uint8Array, Int16Array, Uint16Array, Uint32Array, + Uint8ClampedArray, Float32Array, Float64Array]; + + for ( let View of views ) { + let view = new View(ab); + + assertThrowsInstanceOf(() => Atomics.wait(view, 0, 0), TypeError); + assertThrowsInstanceOf(() => Atomics.wake(view, 0), TypeError); + } +} + +// Check against TypedArray on non-shared memory and correct view types cases. + +{ + let views = [Int32Array]; + + for ( let View of views ) { + let view = new View(ab); + + assertThrowsInstanceOf(() => Atomics.wait(view, 0, 0), TypeError); + assertEq(Atomics.wake(view, 0, 0), 0); + } +} + +// Check against TypedArray on shared memory, but wrong view type + +{ + let views = [Int8Array, Uint8Array, Int16Array, Uint16Array, Uint32Array, + Uint8ClampedArray, Float32Array, Float64Array]; + + for ( let View of views ) { + let view = new View(sab); + + assertThrowsInstanceOf(() => Atomics.wait(view, 0, 0), TypeError); + assertThrowsInstanceOf(() => Atomics.wake(view, 0), TypeError); + } +} + +////////////////////////////////////////////////////////////////////// +// +// The indices must be in the range of the array + +{ + let view = new Int32Array(sab); + + let indices = [ (view) => -1, + (view) => view.length, + (view) => view.length*2, + (view) => '-3.5', + (view) => ({ valueOf: () => -8 }) ]; + + for ( let iidx=0 ; iidx < indices.length ; iidx++ ) { + let Idx = indices[iidx](view); + assertThrowsInstanceOf(() => Atomics.wait(view, Idx, 10), RangeError); + assertThrowsInstanceOf(() => Atomics.wake(view, Idx), RangeError); + } +} + +} // if (this.SharedArrayBuffer && this.Atomics) { ... } + +reportCompare(true,true); diff --git a/js/src/tests/shell/futex.js b/js/src/tests/shell/futex.js new file mode 100644 index 0000000000..f3ad8c0a3e --- /dev/null +++ b/js/src/tests/shell/futex.js @@ -0,0 +1,117 @@ +// |reftest| slow skip-if(!xulRuntime.shell) +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +var DEBUG = false; + +function dprint(s) { + if (DEBUG) print(s); +} + +var hasSharedArrayBuffer = !!(this.SharedArrayBuffer && + this.getSharedObject && + this.setSharedObject); + +// Futex test + +// Only run if helper threads are available. +if (hasSharedArrayBuffer && helperThreadCount() !== 0) { + +var mem = new Int32Array(new SharedArrayBuffer(1024)); + +//////////////////////////////////////////////////////////// + +// wait() returns "not-equal" if the value is not the expected one. + +mem[0] = 42; + +assertEq(Atomics.wait(mem, 0, 33), "not-equal"); + +// wait() returns "timed-out" if it times out + +assertEq(Atomics.wait(mem, 0, 42, 100), "timed-out"); + +//////////////////////////////////////////////////////////// + +// Main is sharing the buffer with the worker; the worker is clearing +// the buffer. + +mem[0] = 42; +mem[1] = 37; +mem[2] = DEBUG; + +setSharedObject(mem.buffer); + +evalInWorker(` +var mem = new Int32Array(getSharedObject()); +function dprint(s) { + if (mem[2]) print(s); +} +assertEq(mem[0], 42); // what was written in the main thread +assertEq(mem[1], 37); // is read in the worker +mem[1] = 1337; +dprint("Sleeping for 2 seconds"); +sleep(2); +dprint("Waking the main thread now"); +setSharedObject(null); +assertEq(Atomics.notify(mem, 0, 1), 1); // Can fail spuriously but very unlikely +`); + +var then = Date.now(); +assertEq(Atomics.wait(mem, 0, 42), "ok"); +dprint("Woke up as I should have in " + (Date.now() - then)/1000 + "s"); +assertEq(mem[1], 1337); // what was written in the worker is read in the main thread +assertEq(getSharedObject(), null); // The worker's clearing of the mbx is visible + +//////////////////////////////////////////////////////////// + +// Test the default argument to Atomics.notify() + +setSharedObject(mem.buffer); + +evalInWorker(` +var mem = new Int32Array(getSharedObject()); +sleep(2); // Probably long enough to avoid a spurious error next +assertEq(Atomics.notify(mem, 0), 1); // Last argument to notify should default to +Infinity +`); + +var then = Date.now(); +dprint("Main thread waiting on wakeup (2s)"); +assertEq(Atomics.wait(mem, 0, 42), "ok"); +dprint("Woke up as I should have in " + (Date.now() - then)/1000 + "s"); + +//////////////////////////////////////////////////////////// + +// A tricky case: while in the wait there will be an interrupt, and in +// the interrupt handler we will execute a wait. This is +// explicitly prohibited (for now), so there should be a catchable exception. + +var exn = false; +timeout(2, function () { + dprint("In the interrupt, starting inner wait with timeout 2s"); + try { + Atomics.wait(mem, 0, 42); // Should throw + } catch (e) { + dprint("Got the interrupt exception!"); + exn = true; + } + return true; +}); +try { + dprint("Starting outer wait"); + assertEq(Atomics.wait(mem, 0, 42, 5000), "timed-out"); +} +finally { + timeout(-1); +} +assertEq(exn, true); + +//////////////////////////////////////////////////////////// + +} // if (hasSharedArrayBuffer && helperThreadCount() !== 0) { ... } + +dprint("Done"); +reportCompare(true,true); diff --git a/js/src/tests/shell/gcstats.js b/js/src/tests/shell/gcstats.js new file mode 100644 index 0000000000..8923a1e489 --- /dev/null +++ b/js/src/tests/shell/gcstats.js @@ -0,0 +1,60 @@ +// |reftest| skip-if(!xulRuntime.shell) +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +function garbage() { + var x; + for (var i = 0; i < 100000; i++) + x = { 'i': i }; +} + +setGCCallback({ + action: "majorGC", + depth: 1, + phases: "both" +}); + +gc(); +garbage(); + +setGCCallback({ + action: "majorGC", + depth: 2, + phases: "both" +}); + +gc(); +garbage(); + +setGCCallback({ + action: "majorGC", + depth: 8, + phases: "begin" +}); + +gc(); +garbage(); + +setGCCallback({ + action: "minorGC", + phases: "both" +}); + +gc(); +garbage(); + +var caught = false; +try { + setGCCallback({ + action: "majorGC", + depth: 10000, + phases: "begin" + }); +} catch (e) { + caught = ((""+e).indexOf("Nesting depth too large") >= 0); +} + +reportCompare(caught, true); diff --git a/js/src/tests/shell/mailbox.js b/js/src/tests/shell/mailbox.js new file mode 100644 index 0000000000..1ab2f662cd --- /dev/null +++ b/js/src/tests/shell/mailbox.js @@ -0,0 +1,104 @@ +// |reftest| skip-if(!xulRuntime.shell) +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +// Tests the shared-object mailbox in the shell. + +var hasSharedMemory = !!(this.SharedArrayBuffer && + this.getSharedObject && + this.setSharedObject); + +if (!hasSharedMemory) { + reportCompare(true, true); + quit(0); +} + +var sab = new SharedArrayBuffer(12); +var mem = new Int32Array(sab); + +// SharedArrayBuffer mailbox tests + +assertEq(getSharedObject(), null); // Mbx starts empty + +assertEq(setSharedObject(mem.buffer), undefined); // Setter returns undefined +assertEq(getSharedObject() == null, false); // And then the mbx is not empty + +var v = getSharedObject(); +assertEq(v.byteLength, mem.buffer.byteLength); // Looks like what we put in? +var w = new Int32Array(v); +mem[0] = 314159; +assertEq(w[0], 314159); // Shares memory (locally) with what we put in? +mem[0] = 0; + +setSharedObject(3.14); // Share numbers +assertEq(getSharedObject(), 3.14); + +setSharedObject(null); // Setting to null clears to null +assertEq(getSharedObject(), null); + +setSharedObject(mem.buffer); +setSharedObject(undefined); // Setting to undefined clears to null +assertEq(getSharedObject(), null); + +setSharedObject(mem.buffer); +setSharedObject(); // Setting without arguments clears to null +assertEq(getSharedObject(), null); + +// Non-shared objects cannot be stored in the mbx + +assertThrowsInstanceOf(() => setSharedObject({x:10, y:20}), Error); +assertThrowsInstanceOf(() => setSharedObject([1,2]), Error); +assertThrowsInstanceOf(() => setSharedObject(new ArrayBuffer(10)), Error); +assertThrowsInstanceOf(() => setSharedObject(new Int32Array(10)), Error); +assertThrowsInstanceOf(() => setSharedObject(false), Error); +assertThrowsInstanceOf(() => setSharedObject(mem), Error); +assertThrowsInstanceOf(() => setSharedObject("abracadabra"), Error); +assertThrowsInstanceOf(() => setSharedObject(() => 37), Error); + +// We can store wasm shared memories, too + +if (!this.WebAssembly || !wasmThreadsEnabled()) { + reportCompare(true, true); + quit(0); +} + +setSharedObject(null); + +var mem = new WebAssembly.Memory({initial: 1, maximum: 1, shared: true}); +setSharedObject(mem); +var ia1 = new Int32Array(mem.buffer); + +var mem2 = getSharedObject(); +assertEq(mem2.buffer instanceof SharedArrayBuffer, true); +assertEq(mem2.buffer.byteLength, 65536); +var ia2 = new Int32Array(mem2.buffer); + +ia2[37] = 0x12345678; +assertEq(ia1[37], 0x12345678); + +// Can't store a non-shared memory + +assertThrowsInstanceOf(() => setSharedObject(new WebAssembly.Memory({initial: 1, maximum: 1})), Error); + +// We can store wasm modules + +var mod = new WebAssembly.Module(wasmTextToBinary(`(module + (import "m" "f" (func (param i32) (result i32))) + (func (export "hi") (result i32) + (i32.const 37)) + )`)); + +setSharedObject(mod); + +var mod2 = getSharedObject(); + +// This should fail because we're not providing the correct import object +assertThrowsInstanceOf(() => new WebAssembly.Instance(mod2, {m:{}}), WebAssembly.LinkError); + +// But this should work +new WebAssembly.Instance(mod2, {m:{f:(x) => x}}); + +reportCompare(true,true); diff --git a/js/src/tests/shell/os.js b/js/src/tests/shell/os.js new file mode 100644 index 0000000000..06e08f683b --- /dev/null +++ b/js/src/tests/shell/os.js @@ -0,0 +1,39 @@ +// |reftest| skip-if(!xulRuntime.shell||xulRuntime.OS=="WINNT") +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +var pid = os.getpid(); +assertEq(pid > 0, true); + +var PATH = os.getenv("PATH"); +assertEq(PATH.indexOf("bin") > 0, true); +assertEq(os.getenv("SQUAMMISH_HILLBILLY_GOAT_SQUEEZERS"), undefined); + +assertEq(os.system("true"), 0, "/bin/true should exit 0"); +assertEq(os.system("false") != 0, true, "/bin/false should exit nonzero"); + +var kidpid = os.spawn("sleep 60"); +assertEq(kidpid > 0, true, "spawning sleep"); +var info = os.waitpid(kidpid, true); +assertEq(info.hasOwnProperty("pid"), false); +assertEq(info.hasOwnProperty("exitStatus"), false); + +os.kill(kidpid); + +info = os.waitpid(kidpid); +assertEq(info.hasOwnProperty("pid"), true, "waiting on dead process should return pid"); +assertEq(info.pid, kidpid); +assertEq(info.hasOwnProperty("exitStatus"), false, "killed process should not have exitStatus"); + +kidpid = os.spawn("false"); +assertEq(kidpid > 0, true, "spawning /bin/false"); +info = os.waitpid(kidpid); +assertEq(info.hasOwnProperty("pid"), true, "waiting on dead process should return pid"); +assertEq(info.pid, kidpid); +assertEq(info.hasOwnProperty("exitStatus"), true, "process should have exitStatus"); +assertEq(info.exitStatus, 1, "/bin/false should exit 1"); + +reportCompare(true, true); diff --git a/js/src/tests/shell/script-file-name-utf8.js b/js/src/tests/shell/script-file-name-utf8.js new file mode 100644 index 0000000000..99fb318ae2 --- /dev/null +++ b/js/src/tests/shell/script-file-name-utf8.js @@ -0,0 +1,235 @@ +// |reftest| skip-if(!xulRuntime.shell) + +// Retrieve the script file name through various functions and ensure it's +// always correctly decoded from UTF-8. + +// Special value when filename cannot be retrieved. +const NOT_SUPPORTED = "*not supported*"; + +// Return the file name from the Error#fileName property. +function errorFileName(fileName) { + return evaluate("new Error().fileName", {fileName}); +} + +// Return the file name from the Parser by a SyntaxError. +function errorFileNameParser(fileName) { + try { + evaluate("###", {fileName}); + } catch (e) { + return e.fileName; + } +} + +// Retrieve the file name through DescriptedCaller (1). +function descriptedCallerViaThisFileName(fileName) { + return evaluate("thisFilename()", {fileName}); +} + +// Retrieve the file name through DescriptedCaller (2). +function descriptedCallerViaEvalInContext(fileName) { + return evaluate("evalcx('new Error().fileName')", {fileName}); +} + +// Retrieve the file name through DescriptedCaller (3). +function descriptedCallerViaEval(fileName) { + var pattern = / line 1 > eval$/; + return evaluate("eval('new Error().fileName')", {fileName}).replace(pattern, ""); +} + +// Retrieve the file name through DescriptedCaller (4). +function descriptedCallerViaFunction(fileName) { + var pattern = / line 1 > Function$/; + return evaluate("Function('return new Error().fileName')()", {fileName}).replace(pattern, ""); +} + +// Retrieve the file name through DescriptedCaller (5). +function descriptedCallerViaEvalReturningScope(fileName) { + return evaluate("evalReturningScope('var a = new Error().fileName')", {fileName}).a; +} + +// Retrieve the file name through DescriptedCaller (7). +var wasmModuleConstructorTemp; +function descriptedCallerViaWasm(fileName) { + if (!wasmIsSupported()) { + return fileName; + } + + wasmModuleConstructorTemp = null; + evaluate(` + function wasmEvalText(str, imports) { + let binary = wasmTextToBinary(str); + assertEq(WebAssembly.validate(binary), true); + let m = new WebAssembly.Module(binary); + return new WebAssembly.Instance(m, imports); + } + wasmEvalText('(module (import "" "a" (func)) (func (call 0)) (export "bar" (func 1)))', + { + "": { + a() { + wasmModuleConstructorTemp = new Error().stack; + return 0; + } + } + }).exports.bar(); + `, {fileName}); + var pattern = /^@(.*) line \d+ >.*$/; + var index = 1; // Direct caller is the wasm function. + return wasmModuleConstructorTemp.split("\n")[index].replace(pattern, "$1"); +} + +// Return the file name from Reflect.parse(). +function reflectParseSource(fileName) { + return Reflect.parse("", {source: fileName}).loc.source; +} + +// Return the file name using the Error#stack property. +function fromErrorStack(fileName) { + var pattern = /^@(.*):\d+:\d+$/; + return evaluate("new Error().stack", {fileName}).split("\n")[0].replace(pattern, "$1"); +} + +// Return the file name using the Error#stack property from an asm.js function. +function fromErrorStackAsmJS(fileName) { + var asm = evaluate(`(function asm(stdlib, foreign) { + "use asm"; + var f = foreign.f; + function g() { + return f() | 0; + } + return {g: g}; + })`, {fileName}); + + var stackFileName; + var foreign = { + f() { + var pattern = /^g@(.*):\d+:\d+$/; + var index = 1; // Direct caller is the asm.js function. + var stack = new Error().stack; + stackFileName = stack.split("\n")[index].replace(pattern, "$1"); + return 0; + } + }; + + asm(this, foreign).g(); + + return stackFileName; +} + +// Return the file name using the Error#stacl property when a streaming compiled WASM function. +function fromErrorStackStreamingWasm(fileName) { + if (!wasmIsSupported() || helperThreadCount() == 0) { + return fileName; + } + + var source = new Uint8Array(wasmTextToBinary(` + (module (import "" "a" (func)) (func (call 0)) (export "bar" (func 1))) + `)); + source.url = fileName; + + var stackFileName; + var imports = { + "": { + a() { + var pattern = /^@(.*):wasm-function.*$/; + var index = 1; // Direct caller is the asm.js function. + var stack = new Error().stack; + stackFileName = stack.split("\n")[index].replace(pattern, "$1"); + return 0; + } + } + }; + + var result; + WebAssembly.instantiateStreaming(source, imports).then(r => result = r); + + drainJobQueue(); + + result.instance.exports.bar(); + + return stackFileName; +} + +// Return the file name using the embedded info in getBacktrace(). +function getBacktraceScriptName(fileName) { + var pattern = /^\d+ <TOP LEVEL> \["(.*)":\d+:\d\]$/; + return evaluate("getBacktrace()", {fileName}).split("\n")[0].replace(pattern, "$1"); +} + +// Return the file name from the coverage report. +function getLcovInfoScriptName(fileName) { + var g = newGlobal(); + var scriptFiles = g.evaluate("getLcovInfo()", {fileName}) + .split("\n") + .filter(x => x.startsWith("SF:")); + assertEq(scriptFiles.length, 1); + return scriptFiles[0].substring(3); +} + +// Return the file name from the error during module import. +function moduleResolutionError(fileName) { + const a = parseModule(`import { x } from "b";`, fileName); + const ma = registerModule("a", a); + const b = parseModule(`export var y = 10;`); + const mb = registerModule("b", b); + + try { + moduleLink(ma); + } catch (e) { + return e.fileName; + } +} + +// Return the file name from the profiler stack. +function geckoInterpProfilingStack(fileName) { + enableGeckoProfilingWithSlowAssertions(); + const stack = evaluate(`readGeckoInterpProfilingStack();`, { fileName }); + if (stack.length === 0) { + return NOT_SUPPORTED; + } + const result = stack[0].dynamicString; + disableGeckoProfiling(); + return result; +} + +const testFunctions = [ + errorFileName, + errorFileNameParser, + descriptedCallerViaThisFileName, + descriptedCallerViaEvalInContext, + descriptedCallerViaEval, + descriptedCallerViaFunction, + descriptedCallerViaEvalReturningScope, + descriptedCallerViaWasm, + reflectParseSource, + fromErrorStack, + fromErrorStackAsmJS, + fromErrorStackStreamingWasm, + getBacktraceScriptName, + moduleResolutionError, +]; + +if (isLcovEnabled()) { + testFunctions.push(getLcovInfoScriptName); +} + +const fileNames = [ + "", + "test", + "Ðëßþ", + "тест", + "テスト", + "\u{1F9EA}", +]; + +for (const fn of testFunctions) { + for (const fileName of fileNames) { + const result = fn(fileName); + if (result === NOT_SUPPORTED) { + continue; + } + assertEq(result, fileName, `Caller '${fn.name}'`); + } +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/shell/shell.js b/js/src/tests/shell/shell.js new file mode 100644 index 0000000000..7584133dad --- /dev/null +++ b/js/src/tests/shell/shell.js @@ -0,0 +1,4 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + * 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/. */ diff --git a/js/src/tests/shell/warning.js b/js/src/tests/shell/warning.js new file mode 100644 index 0000000000..3a390b0864 --- /dev/null +++ b/js/src/tests/shell/warning.js @@ -0,0 +1,33 @@ +// |reftest| skip-if(!xulRuntime.shell) + +var BUGNUMBER = 1170716; +var summary = 'Add js shell functions to get last warning'; + +print(BUGNUMBER + ": " + summary); + +// Warning with JSEXN_SYNTAXERR. + +enableLastWarning(); +eval(`function f() { if (false) { "use asm"; } }`); + +warning = getLastWarning(); +assertEq(warning !== null, true); +assertEq(warning.name, "SyntaxError"); +assertEq(warning.message.includes("Directive Prologue"), true); +assertEq(warning.lineNumber, 1); +assertEq(warning.columnNumber, 28); + +// Disabled. + +disableLastWarning(); + +eval(`function f() { if (false) { "use asm"; } }`); + +enableLastWarning(); +warning = getLastWarning(); +assertEq(warning, null); + +disableLastWarning(); + +if (typeof reportCompare === "function") + reportCompare(true, true); |