From dc51e0f35bf5967cdd2071ea541c96ca10f7057e Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Fri, 6 Nov 2026 13:51:04 -0300 Subject: [PATCH 1/3] build(test-runner): stage jasmine-core from npm into the TestRunner app jasmine-core becomes a pinned root devDependency instead of a vendored copy. A new "Stage Test Runner Dependencies" phase on the TestRunner target runs tools/stage-test-runner-deps.mjs before Copy Bundle Resources, copying the package's single-file build and license into the gitignored TestRunner/app/Infrastructure/Jasmine/vendor/ so the app-folder reference bundles it. The phase finds node the same way the js2c phase does (.xcode.env / .xcode.env.local), and CI already runs `npm install` at the repo root through scripts/install-ci-deps.sh. The script rewrites a file only when its content changed, so incremental builds do not recopy the app folder. --- .gitignore | 4 ++ .xcode.env | 3 +- README.md | 5 +- package.json | 1 + tools/stage-test-runner-deps.mjs | 89 ++++++++++++++++++++++++++++++++ v8ios.xcodeproj/project.pbxproj | 21 ++++++++ 6 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 tools/stage-test-runner-deps.mjs diff --git a/.gitignore b/.gitignore index e1159703..61e225b2 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,10 @@ Thumbs.db # Generated by tools/js2c.mjs (Xcode "Generate RuntimeBuiltins" build phase) NativeScript/runtime/generated/ +# Copied from node_modules by tools/stage-test-runner-deps.mjs (Xcode "Stage Test +# Runner Dependencies" build phase) +TestRunner/app/Infrastructure/Jasmine/vendor/ + # Per-machine environment for Xcode build phases (see .xcode.env) .xcode.env.local diff --git a/.xcode.env b/.xcode.env index 92530791..c4e56f8d 100644 --- a/.xcode.env +++ b/.xcode.env @@ -1,5 +1,6 @@ # Environment defaults for Xcode build phases (sourced by the "Generate -# RuntimeBuiltins" phase before running tools/js2c.mjs). +# RuntimeBuiltins" phase before running tools/js2c.mjs, and by the "Stage Test +# Runner Dependencies" phase before running tools/stage-test-runner-deps.mjs). # # Xcode runs build phases with a minimal PATH and never reads your shell # profile, so a node installed through a version manager (nvm, fnm, volta, diff --git a/README.md b/README.md index 830510da..e34660f8 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,9 @@ git submodule update --init sudo gem install xcodeproj sudo gem install cocoapods +# Install the npm dev dependencies (the TestRunner app bundles jasmine-core from node_modules) +npm install + # Record this shell's node in .xcode.env.local and open the runtime in Xcode # (Xcode build phases don't see your shell's PATH — see "Node.js in Xcode builds") npm run open-xcode @@ -50,7 +53,7 @@ If all tests pass, everything is good! At this point you can make changes to the ## Node.js in Xcode builds -Building the `NativeScript` target generates the runtime JS builtins with `tools/js2c.mjs`, which needs Node.js. Xcode runs build phases with a minimal `PATH` and never reads your shell profile, so a node managed by nvm/fnm/volta/asdf is invisible to it. The build phase instead sources two files from the repo root: +Building the `NativeScript` target generates the runtime JS builtins with `tools/js2c.mjs`, and building the `TestRunner` target copies jasmine-core from `node_modules` into the test app with `tools/stage-test-runner-deps.mjs`; both need Node.js. Xcode runs build phases with a minimal `PATH` and never reads your shell profile, so a node managed by nvm/fnm/volta/asdf is invisible to it. The build phase instead sources two files from the repo root: - `.xcode.env` — committed defaults (`NODE_BINARY=$(command -v node)`, enough for Homebrew installs and `xcodebuild` runs from a terminal) - `.xcode.env.local` — gitignored per-machine overrides, sourced last so it wins diff --git a/package.json b/package.json index 12a68216..7351e221 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "eslint": "10.8.0", "globals": "17.8.0", "husky": "^9.0.11", + "jasmine-core": "7.0.2", "lint-staged": "^15.2.7", "semver": "^7.5.0" }, diff --git a/tools/stage-test-runner-deps.mjs b/tools/stage-test-runner-deps.mjs new file mode 100644 index 00000000..33399958 --- /dev/null +++ b/tools/stage-test-runner-deps.mjs @@ -0,0 +1,89 @@ +#!/usr/bin/env node +// Copies the npm packages the TestRunner app loads at runtime from the repo's +// node_modules into a folder inside TestRunner/app, which Xcode bundles as-is. +// Invoked by the TestRunner target's "Stage Test Runner Dependencies" phase. + +import { createRequire } from "node:module"; +import { copyFileSync, existsSync, mkdirSync, readFileSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { parseArgs } from "node:util"; + +const usage = `Usage: node tools/stage-test-runner-deps.mjs --out-dir + +Copies the TestRunner app's npm dependencies into . + + --out-dir destination folder (created if missing) + -h, --help show this help`; + +// Each package's files are copied flat into //. +const PACKAGES = [ + { name: "jasmine-core", files: ["lib/jasmine-core/jasmine.js", "LICENSE"] }, +]; + +let values; +try { + ({ values } = parseArgs({ + options: { + "out-dir": { type: "string" }, + help: { type: "boolean", short: "h" }, + }, + strict: true, + })); +} catch (e) { + console.error(`error: ${e.message}\n\n${usage}`); + process.exit(1); +} +if (values.help) { + console.log(usage); + process.exit(0); +} +if (!values["out-dir"]) { + console.error(`error: --out-dir is required\n\n${usage}`); + process.exit(1); +} + +const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const requireFromRepo = createRequire(join(repoRoot, "package.json")); +const outDir = resolve(values["out-dir"]); + +// Packages may restrict `exports` to their entry point, so the package root is +// found by walking up from the resolved entry to its package.json. +function packageRoot(name) { + let entry; + try { + entry = requireFromRepo.resolve(name); + } catch { + console.error(`error: ${name} is not installed. Run 'npm install' in ${repoRoot}.`); + process.exit(1); + } + for (let dir = dirname(entry); dir !== dirname(dir); dir = dirname(dir)) { + const manifest = join(dir, "package.json"); + if (existsSync(manifest) && JSON.parse(readFileSync(manifest, "utf8")).name === name) { + return dir; + } + } + console.error(`error: could not find the package root of ${name} from ${entry}`); + process.exit(1); +} + +// Unchanged files keep their mtime so Xcode does not recopy the app folder. +function copyIfChanged(from, to) { + if (existsSync(to) && readFileSync(to).equals(readFileSync(from))) { + return false; + } + mkdirSync(dirname(to), { recursive: true }); + copyFileSync(from, to); + return true; +} + +for (const pkg of PACKAGES) { + const root = packageRoot(pkg.name); + for (const file of pkg.files) { + const from = join(root, file); + const to = join(outDir, pkg.name, file.split("/").pop()); + if (copyIfChanged(from, to)) { + console.log(`staged ${pkg.name}/${file} -> ${to}`); + } + } +} diff --git a/v8ios.xcodeproj/project.pbxproj b/v8ios.xcodeproj/project.pbxproj index 45ff68d4..74d96f19 100644 --- a/v8ios.xcodeproj/project.pbxproj +++ b/v8ios.xcodeproj/project.pbxproj @@ -1846,6 +1846,7 @@ C2F4CBBD22C61D480036B56F /* ShellScript */, C2DDEB12229EA89000345BFE /* Sources */, C2DDEB13229EA89000345BFE /* Frameworks */, + 7A51E7C02F00000000000001 /* Stage Test Runner Dependencies */, C2DDEB14229EA89000345BFE /* Resources */, C2C8EE7722CE54EE001F8CEC /* Embed Frameworks */, ); @@ -2081,6 +2082,26 @@ shellPath = /bin/sh; shellScript = "cd \"${SRCROOT}/NativeScript\"\n\necho 'Copying headers into Framework..'\nfor H in `find . -name \"*.h\" -o -name \"*.hpp\"`; do\n echo \"copying ${H} to ${BUILT_PRODUCTS_DIR}/${PUBLIC_HEADERS_FOLDER_PATH}/${H}\"\n ditto \"${H}\" \"${BUILT_PRODUCTS_DIR}/${PUBLIC_HEADERS_FOLDER_PATH}/${H}\"\ndone\n"; }; + 7A51E7C02F00000000000001 /* Stage Test Runner Dependencies */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Stage Test Runner Dependencies"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(SRCROOT)/TestRunner/app/Infrastructure/Jasmine/vendor/jasmine-core/jasmine.js", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "set -e\ncd \"${SRCROOT}\"\n[ -f .xcode.env ] && . ./.xcode.env\n[ -f .xcode.env.local ] && . ./.xcode.env.local\nif [ -z \"${NODE_BINARY:-}\" ] || [ ! -x \"${NODE_BINARY:-}\" ]; then\n echo \"error: no usable node for tools/stage-test-runner-deps.mjs (NODE_BINARY='${NODE_BINARY:-}'). Run 'npm run setup-xcode-env' in the runtime repo to record your node in .xcode.env.local\" >&2\n exit 1\nfi\n\"$NODE_BINARY\" tools/stage-test-runner-deps.mjs --out-dir TestRunner/app/Infrastructure/Jasmine/vendor\n"; + }; C27E5DB322F31FF800498ED0 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; From 25f413d54c278f8af5161e4fea2b74d7efda4ddc Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Fri, 6 Nov 2026 13:51:04 -0300 Subject: [PATCH 2/3] test: run the TestRunner suite on jasmine-core 7 Replaces the vendored Jasmine 2.0.1 (and its 2.0 typings) with jasmine-core 7.0.2 from the staging step. Harness: - New Infrastructure/Jasmine/boot.js: installGlobals, declaration-order runs (random: false), the quarantine specFilter and the progress beacon carry over; the async-function and fail() shims are gone because Jasmine handles both natively. - A custom object formatter keeps Jasmine's pretty-printer from throwing on null-prototype tagged objects such as module namespaces. toBe() formats its message even when it passes, so expect(ns).toBe(ns) threw. - Reporters count excluded/notApplicable specs as skipped and write suite-level (afterAll, between-spec) and run-level errors as failing JUnit testcases, so CI's verify-junit-xml sees them. The JUnit escaping no longer double-escapes entities, and stacks containing "]]>" stay inside their CDATA. Specs: - Jasmine 7 routes global `error`/`unhandledrejection` events to the running spec. Specs that raise them on purpose run under raisesGlobalErrors (Infrastructure/Jasmine/globalErrorSpy.js), a wrapper over jasmine.spyOnGlobalErrorsAsync that leaves the events uncancelled. Worker specs whose error used to be re-reported on the global scope after they finished now cancel it on the parent, and the malformed-URL HTTP loader spec waits for the import it starts. - Duplicate spec names are now a declaration error: MethodCallsTests' instance/static property pairs get the file's Instance/Static naming, a spec pasted into another spec's body is split back out, and a copied Timers spec is removed. - Bumps the shared tests to common-runtime-tests-app test/jasmine-7-compat, which makes the shared specs work on both Jasmine 2.0.1 and 7. --- TestRunner/app/Infrastructure/Jasmine/boot.js | 85 + .../Infrastructure/Jasmine/globalErrorSpy.js | 29 + .../Jasmine/jasmine-2.0.1/boot.js | 237 -- .../Jasmine/jasmine-2.0.1/jasmine.js | 2518 ----------------- .../jasmine-reporters/junit_reporter.js | 76 +- .../jasmine-reporters/terminal_reporter.js | 39 +- .../app/Infrastructure/Jasmine/jasmine.d.ts | 434 --- TestRunner/app/shared | 2 +- TestRunner/app/tests/ErrorEventsTests.js | 30 +- TestRunner/app/tests/EscapeExceptionTests.js | 10 +- TestRunner/app/tests/EventLoopTests.js | 9 +- TestRunner/app/tests/HttpEsmLoaderTests.js | 11 +- TestRunner/app/tests/MessagingTests.js | 6 +- TestRunner/app/tests/MethodCallsTests.js | 121 +- TestRunner/app/tests/Promises.js | 18 +- TestRunner/app/tests/Timers.js | 8 - .../app/tests/WorkerResourceLimitsTests.js | 5 +- TestRunner/app/tests/index.js | 4 +- TestRunnerTests/QUARANTINED_TESTS.md | 5 +- 19 files changed, 332 insertions(+), 3315 deletions(-) create mode 100644 TestRunner/app/Infrastructure/Jasmine/boot.js create mode 100644 TestRunner/app/Infrastructure/Jasmine/globalErrorSpy.js delete mode 100644 TestRunner/app/Infrastructure/Jasmine/jasmine-2.0.1/boot.js delete mode 100644 TestRunner/app/Infrastructure/Jasmine/jasmine-2.0.1/jasmine.js delete mode 100644 TestRunner/app/Infrastructure/Jasmine/jasmine.d.ts diff --git a/TestRunner/app/Infrastructure/Jasmine/boot.js b/TestRunner/app/Infrastructure/Jasmine/boot.js new file mode 100644 index 00000000..bf4ef510 --- /dev/null +++ b/TestRunner/app/Infrastructure/Jasmine/boot.js @@ -0,0 +1,85 @@ +// Boots jasmine-core for the TestRunner app. The "Stage Test Runner Dependencies" +// build phase copies jasmine-core from the repo's node_modules into ./vendor. +var jasmineCore = require("./vendor/jasmine-core/jasmine"); +var JUnitXmlReporter = require("./jasmine-reporters/junit_reporter").JUnitXmlReporter; +var TerminalReporter = require("./jasmine-reporters/terminal_reporter").TerminalReporter; + +jasmineCore.installGlobals(global); + +var env = jasmine.getEnv(); + +// Quarantined specs — skipped at the harness level (no submodule edit), matched +// by substring against the spec's full name. See TestRunnerTests/QUARANTINED_TESTS.md +// for the rationale and how to re-enable each entry. +var QUARANTINED_SPEC_SUBSTRINGS = []; + +env.configure({ + // Suites carry state from one spec to the next (the shared Workers suite + // counts live runtimes across its specs), so they run in declaration order. + random: false, + specFilter: function (spec) { + var fullName = spec.getFullName(); + for (var i = 0; i < QUARANTINED_SPEC_SUBSTRINGS.length; i++) { + if (fullName.indexOf(QUARANTINED_SPEC_SUBSTRINGS[i]) !== -1) { + return false; + } + } + return true; + } +}); + +// jasmine-core's pretty-printer falls back to value.toString() for tagged objects +// it does not recognize, which throws for null-prototype ones such as module +// namespaces. toBe() builds its message even when it passes, so without this an +// expect(namespace).toBe(sameNamespace) throws. +beforeEach(function () { + jasmine.addCustomObjectFormatter(function (value) { + if (value !== null && typeof value === "object" && typeof value.toString !== "function") { + var tag = Object.prototype.toString.call(value); + if (tag !== "[object Object]") { + return tag; + } + } + return undefined; + }); +}); + +env.addReporter(new TerminalReporter({ + verbosity: 2 +})); +env.addReporter(new JUnitXmlReporter()); + +// Progress beacon: fire-and-forget GET of each SUITE name to the XCTest host's +// /progress endpoint. When the run hangs (no JUnit report is ever POSTed), this +// lets the Swift harness name the suite that was running when the JS thread +// stalled. Async via NSURLSession so it never blocks the JS thread; best-effort. +// +// SUITE-level only (not specStarted): a per-spec beacon opens hundreds of +// connections per run, more than CI's tighter fd limits allow the test server, +// while suites number in the dozens and still pinpoint a hang to its suite. +(function installProgressBeacon() { + try { + var reportUrl = NSProcessInfo.processInfo.environment.objectForKey("REPORT_BASEURL"); + if (!reportUrl) { return; } + var origin = new URL(String(reportUrl)).origin; + var beacon = function (name) { + try { + var url = origin + "/progress?spec=" + encodeURIComponent(name || ""); + var req = NSMutableURLRequest.requestWithURL(NSURL.URLWithString(url)); + req.HTTPMethod = "GET"; + req.timeoutInterval = 2.0; + NSURLSession.sharedSession.dataTaskWithRequestCompletionHandler(req, function () {}).resume(); + } catch (e) { /* best-effort */ } + }; + env.addReporter({ + suiteStarted: function (r) { beacon("[suite] " + (r && r.fullName ? r.fullName : "")); } + }); + } catch (e) { /* best-effort */ } +}()); + +exports.execute = function () { + env.execute().catch(function (e) { + // No JUnit report follows; the XCTest host reports the run as hung. + console.log("Jasmine could not run the suite: " + ((e && e.stack) || e)); + }); +}; diff --git a/TestRunner/app/Infrastructure/Jasmine/globalErrorSpy.js b/TestRunner/app/Infrastructure/Jasmine/globalErrorSpy.js new file mode 100644 index 00000000..a802c5d4 --- /dev/null +++ b/TestRunner/app/Infrastructure/Jasmine/globalErrorSpy.js @@ -0,0 +1,29 @@ +// Wraps a done-style spec body that raises global `error` events or unhandled +// rejections on purpose. Jasmine fails the running spec on those unless its +// global error spy is installed, which it is for as long as the body runs; the +// spy is passed as the body's second argument. Jasmine's spy never cancels the +// event, so listeners, preventDefault() and the __onUncaughtError fallback behave +// as they do in production. +// +// The body must call done() only after every error it expects was dispatched: +// one that arrives later is charged to whatever spec is running then. +exports.raisesGlobalErrors = function (body) { + return function () { + var context = this; + return jasmine.spyOnGlobalErrorsAsync(function (globalErrorSpy) { + return new Promise(function (resolve, reject) { + function done(reason) { + if (reason === undefined) { + resolve(); + } else { + reject(reason instanceof Error ? reason : new Error(String(reason))); + } + } + done.fail = function (reason) { + done(reason === undefined ? new Error("Failed") : reason); + }; + body.call(context, done, globalErrorSpy); + }); + }); + }; +}; diff --git a/TestRunner/app/Infrastructure/Jasmine/jasmine-2.0.1/boot.js b/TestRunner/app/Infrastructure/Jasmine/jasmine-2.0.1/boot.js deleted file mode 100644 index 8fcb0d47..00000000 --- a/TestRunner/app/Infrastructure/Jasmine/jasmine-2.0.1/boot.js +++ /dev/null @@ -1,237 +0,0 @@ -/** - Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project. - - If a project is using Jasmine via the standalone distribution, this file can be customized directly. If a project is using Jasmine via the [Ruby gem][jasmine-gem], this file can be copied into the support directory via `jasmine copy_boot_js`. Other environments (e.g., Python) will have different mechanisms. - - The location of `boot.js` can be specified and/or overridden in `jasmine.yml`. - - [jasmine-gem]: http://github.com/pivotal/jasmine-gem - */ - -var jasmineRequire = require('./jasmine'); -var JUnitXmlReporter = require('../jasmine-reporters/junit_reporter').JUnitXmlReporter; -var TerminalReporter = require('../jasmine-reporters/terminal_reporter').TerminalReporter; - -(function() { - /** - * ## Require & Instantiate - * - * Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference. - */ - var jasmine = jasmineRequire.core(jasmineRequire); - - /** - * Create the Jasmine environment. This is used to run all specs in a project. - */ - var env = jasmine.getEnv(); - - /** - * ## The Global Interface - * - * Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged. - */ - // Jasmine 2.0.1 decides whether a body is asynchronous from its declared - // arity alone (QueueRunner: `fn.length > 0`). A zero-arity `async function` - // is therefore treated as synchronous: its promise is discarded and the spec - // is recorded as passed before the first `await` resumes. Assertions then run - // after the fact — attributed to whichever spec is current by then — and - // rejections escape as unhandled. - // - // Give those bodies the `done` callback Jasmine needs so it waits for the - // promise to settle. Only `async function` bodies are rewritten: handing a - // `done` to every zero-arity body would move the entire suite's synchronous - // specs onto the async path, which changes when the runloop turns between - // them. A synchronous throw is left to propagate — QueueRunner's attemptAsync - // catches it and attributes it to the right spec. - var AsyncFunction = (async function() {}).constructor; - - function awaitPromiseResult(func) { - if (typeof func !== 'function' || func.length > 0 || - !(func instanceof AsyncFunction)) { - return func; - } - - return function(done) { - var result = func.call(this); - - if (!result || typeof result.then !== 'function') { - done(); - return; - } - - result.then(function() { - done(); - }, function(error) { - var detail = (error && (error.stack || error.message)) || String(error); - env.expect('promise rejected: ' + detail).toBeUndefined(); - done(); - }); - }; - } - - var jasmineInterface = { - describe: function(description, specDefinitions) { - return env.describe(description, specDefinitions); - }, - - xdescribe: function(description, specDefinitions) { - return env.xdescribe(description, specDefinitions); - }, - - it: function(desc, func) { - return env.it(desc, awaitPromiseResult(func)); - }, - - xit: function(desc, func) { - return env.xit(desc, awaitPromiseResult(func)); - }, - - beforeEach: function(beforeEachFunction) { - return env.beforeEach(awaitPromiseResult(beforeEachFunction)); - }, - - afterEach: function(afterEachFunction) { - return env.afterEach(awaitPromiseResult(afterEachFunction)); - }, - - expect: function(actual) { - return env.expect(actual); - }, - - pending: function() { - return env.pending(); - }, - - fail: function(error) { - // Jasmine 2.0 fail() – mark current spec as failed with given message - var message = error; - if (error && typeof error === 'object') { - message = error.message || String(error); - } - throw new Error(message); - }, - - spyOn: function(obj, methodName) { - return env.spyOn(obj, methodName); - }, - - jsApiReporter: new jasmine.JsApiReporter({ - timer: new jasmine.Timer() - }), - - execute: function() { - return env.execute(); - } - }; - - /** - * Add all of the Jasmine global/public interface to the proper global, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`. - */ - if (typeof window == "undefined" && typeof global == "object") { - extend(global, jasmineInterface); - global.jasmine = jasmine; - } else { - extend(window, jasmineInterface); - window.jasmine = jasmine; - } - - /** - * Expose the interface for adding custom equality testers. - */ - jasmine.addCustomEqualityTester = function(tester) { - env.addCustomEqualityTester(tester); - }; - - /** - * Expose the interface for adding custom expectation matchers - */ - jasmine.addMatchers = function(matchers) { - return env.addMatchers(matchers); - }; - - /** - * Expose the mock interface for the JavaScript timeout functions - */ - jasmine.clock = function() { - return env.clock; - }; - - /** - * ## Runner Parameters - */ - - env.catchExceptions(true); - - /** - * The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript. - */ - env.addReporter(jasmineInterface.jsApiReporter); - - jasmine.getEnv().addReporter(new TerminalReporter({ - verbosity: 2 - })); - jasmine.getEnv().addReporter(new JUnitXmlReporter()); - - // Progress beacon: fire-and-forget GET of each SUITE name to the XCTest host's - // /progress endpoint. When the run hangs (no JUnit report is ever POSTed), this - // lets the Swift harness name the suite that was running when the JS thread - // stalled. Async via NSURLSession so it never blocks the JS thread; best-effort. - // - // SUITE-level only (not specStarted): the minimal Embassy test server crashed - // in handleNewConnection() under the hundreds-of-connections-per-run flood that - // a per-spec beacon produced on CI's tighter fd limits. Suites number in the - // dozens and fire at suite boundaries, which stays well within those limits - // while still pinpointing a hang to its suite. - (function installProgressBeacon() { - try { - var reportUrl = NSProcessInfo.processInfo.environment.objectForKey("REPORT_BASEURL"); - if (!reportUrl) { return; } - var origin = new URL(String(reportUrl)).origin; - var beacon = function (name) { - try { - var url = origin + "/progress?spec=" + encodeURIComponent(name || ""); - var req = NSMutableURLRequest.requestWithURL(NSURL.URLWithString(url)); - req.HTTPMethod = "GET"; - req.timeoutInterval = 2.0; - NSURLSession.sharedSession.dataTaskWithRequestCompletionHandler(req, function () {}).resume(); - } catch (e) { /* best-effort */ } - }; - jasmine.getEnv().addReporter({ - suiteStarted: function (r) { beacon("[suite] " + (r && r.fullName ? r.fullName : "")); } - }); - } catch (e) { /* best-effort */ } - }()); - - // Quarantined specs — skipped at the harness level (no submodule edit). - // Matched by substring against the spec's full name. - // - // "no crash during or after runtime teardown": the TNS Workers teardown stress - // spec triggers an AB-BA deadlock between the main and a worker V8 isolate lock - // — the main thread holds the main isolate lock and waits on a worker isolate - // (a nil-queue NSNotification observer block the worker registered), while the - // worker holds its isolate lock and waits on the main isolate (a main-extended - // class's +initialize; ClassBuilder.mm). It only manifests when those windows - // overlap, which happens reliably on constrained CI runners but never on fast - // multi-core dev machines. Tracking + native stacks: - // https://github.com/NativeScript/ios/issues/397 - // See TestRunnerTests/QUARANTINED_TESTS.md for the full rationale + how to - // re-enable each of these. - var QUARANTINED_SPEC_SUBSTRINGS = []; - env.specFilter = function(spec) { - var fullName = spec.getFullName(); - for (var i = 0; i < QUARANTINED_SPEC_SUBSTRINGS.length; i++) { - if (fullName.indexOf(QUARANTINED_SPEC_SUBSTRINGS[i]) !== -1) { - return false; - } - } - return true; - }; - - /** - * Helper function for readability above. - */ - function extend(destination, source) { - for (var property in source) destination[property] = source[property]; - return destination; - } -}()); diff --git a/TestRunner/app/Infrastructure/Jasmine/jasmine-2.0.1/jasmine.js b/TestRunner/app/Infrastructure/Jasmine/jasmine-2.0.1/jasmine.js deleted file mode 100644 index 92dddeff..00000000 --- a/TestRunner/app/Infrastructure/Jasmine/jasmine-2.0.1/jasmine.js +++ /dev/null @@ -1,2518 +0,0 @@ -/* -Copyright (c) 2008-2014 Pivotal Labs - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ -function getJasmineRequireObj() { - if (typeof module !== 'undefined' && module.exports) { - return exports; - } else { - window.jasmineRequire = window.jasmineRequire || {}; - return window.jasmineRequire; - } -} - -getJasmineRequireObj().core = function(jRequire) { - var j$ = {}; - - jRequire.base(j$); - j$.util = jRequire.util(); - j$.Any = jRequire.Any(); - j$.CallTracker = jRequire.CallTracker(); - j$.MockDate = jRequire.MockDate(); - j$.Clock = jRequire.Clock(); - j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(); - j$.Env = jRequire.Env(j$); - j$.ExceptionFormatter = jRequire.ExceptionFormatter(); - j$.Expectation = jRequire.Expectation(); - j$.buildExpectationResult = jRequire.buildExpectationResult(); - j$.JsApiReporter = jRequire.JsApiReporter(); - j$.matchersUtil = jRequire.matchersUtil(j$); - j$.ObjectContaining = jRequire.ObjectContaining(j$); - j$.pp = jRequire.pp(j$); - j$.QueueRunner = jRequire.QueueRunner(j$); - j$.ReportDispatcher = jRequire.ReportDispatcher(); - j$.Spec = jRequire.Spec(j$); - j$.SpyStrategy = jRequire.SpyStrategy(); - j$.Suite = jRequire.Suite(); - j$.Timer = jRequire.Timer(); - j$.version = jRequire.version(); - - j$.matchers = jRequire.requireMatchers(jRequire, j$); - - return j$; -}; - -getJasmineRequireObj().requireMatchers = function(jRequire, j$) { - var availableMatchers = [ - 'toBe', - 'toBeCloseTo', - 'toBeDefined', - 'toBeFalsy', - 'toBeGreaterThan', - 'toBeLessThan', - 'toBeNaN', - 'toBeNull', - 'toBeTruthy', - 'toBeUndefined', - 'toContain', - 'toEqual', - 'toHaveBeenCalled', - 'toHaveBeenCalledWith', - 'toMatch', - 'toThrow', - 'toThrowError' - ], - matchers = {}; - - for (var i = 0; i < availableMatchers.length; i++) { - var name = availableMatchers[i]; - matchers[name] = jRequire[name](j$); - } - - return matchers; -}; - -getJasmineRequireObj().base = (function (jasmineGlobal) { - if (typeof module !== 'undefined' && module.exports) { - jasmineGlobal = global; - } - - return function(j$) { - j$.unimplementedMethod_ = function() { - throw new Error('unimplemented method'); - }; - - j$.MAX_PRETTY_PRINT_DEPTH = 40; - j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 100; - j$.DEFAULT_TIMEOUT_INTERVAL = 5000; - - j$.getGlobal = function() { - return jasmineGlobal; - }; - - j$.getEnv = function(options) { - var env = j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options); - //jasmine. singletons in here (setTimeout blah blah). - return env; - }; - - j$.isArray_ = function(value) { - return j$.isA_('Array', value); - }; - - j$.isString_ = function(value) { - return j$.isA_('String', value); - }; - - j$.isNumber_ = function(value) { - return j$.isA_('Number', value); - }; - - j$.isA_ = function(typeName, value) { - return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; - }; - - j$.isDomNode = function(obj) { - return obj.nodeType > 0; - }; - - j$.any = function(clazz) { - return new j$.Any(clazz); - }; - - j$.objectContaining = function(sample) { - return new j$.ObjectContaining(sample); - }; - - j$.createSpy = function(name, originalFn) { - - var spyStrategy = new j$.SpyStrategy({ - name: name, - fn: originalFn, - getSpy: function() { return spy; } - }), - callTracker = new j$.CallTracker(), - spy = function() { - callTracker.track({ - object: this, - args: Array.prototype.slice.apply(arguments) - }); - return spyStrategy.exec.apply(this, arguments); - }; - - for (var prop in originalFn) { - if (prop === 'and' || prop === 'calls') { - throw new Error('Jasmine spies would overwrite the \'and\' and \'calls\' properties on the object being spied upon'); - } - - spy[prop] = originalFn[prop]; - } - - spy.and = spyStrategy; - spy.calls = callTracker; - - return spy; - }; - - j$.isSpy = function(putativeSpy) { - if (!putativeSpy) { - return false; - } - return putativeSpy.and instanceof j$.SpyStrategy && - putativeSpy.calls instanceof j$.CallTracker; - }; - - j$.createSpyObj = function(baseName, methodNames) { - if (!j$.isArray_(methodNames) || methodNames.length === 0) { - throw 'createSpyObj requires a non-empty array of method names to create spies for'; - } - var obj = {}; - for (var i = 0; i < methodNames.length; i++) { - obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]); - } - return obj; - }; - }; -})(this); - -getJasmineRequireObj().util = function() { - - var util = {}; - - util.inherit = function(childClass, parentClass) { - var Subclass = function() { - }; - Subclass.prototype = parentClass.prototype; - childClass.prototype = new Subclass(); - }; - - util.htmlEscape = function(str) { - if (!str) { - return str; - } - return str.replace(/&/g, '&') - .replace(//g, '>'); - }; - - util.argsToArray = function(args) { - var arrayOfArgs = []; - for (var i = 0; i < args.length; i++) { - arrayOfArgs.push(args[i]); - } - return arrayOfArgs; - }; - - util.isUndefined = function(obj) { - return obj === void 0; - }; - - util.arrayContains = function(array, search) { - var i = array.length; - while (i--) { - if (array[i] == search) { - return true; - } - } - return false; - }; - - return util; -}; - -getJasmineRequireObj().Spec = function(j$) { - function Spec(attrs) { - this.expectationFactory = attrs.expectationFactory; - this.resultCallback = attrs.resultCallback || function() {}; - this.id = attrs.id; - this.description = attrs.description || ''; - this.fn = attrs.fn; - this.beforeFns = attrs.beforeFns || function() { return []; }; - this.afterFns = attrs.afterFns || function() { return []; }; - this.onStart = attrs.onStart || function() {}; - this.exceptionFormatter = attrs.exceptionFormatter || function() {}; - this.getSpecName = attrs.getSpecName || function() { return ''; }; - this.expectationResultFactory = attrs.expectationResultFactory || function() { }; - this.queueRunnerFactory = attrs.queueRunnerFactory || function() {}; - this.catchingExceptions = attrs.catchingExceptions || function() { return true; }; - - if (!this.fn) { - this.pend(); - } - - this.result = { - id: this.id, - description: this.description, - fullName: this.getFullName(), - failedExpectations: [], - passedExpectations: [] - }; - } - - Spec.prototype.addExpectationResult = function(passed, data) { - var expectationResult = this.expectationResultFactory(data); - if (passed) { - this.result.passedExpectations.push(expectationResult); - } else { - this.result.failedExpectations.push(expectationResult); - } - }; - - Spec.prototype.expect = function(actual) { - return this.expectationFactory(actual, this); - }; - - Spec.prototype.execute = function(onComplete) { - var self = this; - - this.onStart(this); - - if (this.markedPending || this.disabled) { - complete(); - return; - } - - var allFns = this.beforeFns().concat(this.fn).concat(this.afterFns()); - - this.queueRunnerFactory({ - fns: allFns, - onException: onException, - onComplete: complete, - enforceTimeout: function() { return true; } - }); - - function onException(e) { - if (Spec.isPendingSpecException(e)) { - self.pend(); - return; - } - - self.addExpectationResult(false, { - matcherName: '', - passed: false, - expected: '', - actual: '', - error: e - }); - } - - function complete() { - self.result.status = self.status(); - self.resultCallback(self.result); - - if (onComplete) { - onComplete(); - } - } - }; - - Spec.prototype.disable = function() { - this.disabled = true; - }; - - Spec.prototype.pend = function() { - this.markedPending = true; - }; - - Spec.prototype.status = function() { - if (this.disabled) { - return 'disabled'; - } - - if (this.markedPending) { - return 'pending'; - } - - if (this.result.failedExpectations.length > 0) { - return 'failed'; - } else { - return 'passed'; - } - }; - - Spec.prototype.getFullName = function() { - return this.getSpecName(this); - }; - - Spec.pendingSpecExceptionMessage = '=> marked Pending'; - - Spec.isPendingSpecException = function(e) { - return !!(e && e.toString && e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1); - }; - - return Spec; -}; - -if (typeof window == void 0 && typeof exports == 'object') { - exports.Spec = jasmineRequire.Spec; -} - -getJasmineRequireObj().Env = function(j$) { - function Env(options) { - options = options || {}; - - var self = this; - var global = options.global || j$.getGlobal(); - - var totalSpecsDefined = 0; - - var catchExceptions = true; - - var realSetTimeout = j$.getGlobal().setTimeout; - var realClearTimeout = j$.getGlobal().clearTimeout; - this.clock = new j$.Clock(global, new j$.DelayedFunctionScheduler(), new j$.MockDate(global)); - - var runnableLookupTable = {}; - - var spies = []; - - var currentSpec = null; - var currentSuite = null; - - var reporter = new j$.ReportDispatcher([ - 'jasmineStarted', - 'jasmineDone', - 'suiteStarted', - 'suiteDone', - 'specStarted', - 'specDone' - ]); - - this.specFilter = function() { - return true; - }; - - var equalityTesters = []; - - var customEqualityTesters = []; - this.addCustomEqualityTester = function(tester) { - customEqualityTesters.push(tester); - }; - - j$.Expectation.addCoreMatchers(j$.matchers); - - var nextSpecId = 0; - var getNextSpecId = function() { - return 'spec' + nextSpecId++; - }; - - var nextSuiteId = 0; - var getNextSuiteId = function() { - return 'suite' + nextSuiteId++; - }; - - var expectationFactory = function(actual, spec) { - return j$.Expectation.Factory({ - util: j$.matchersUtil, - customEqualityTesters: customEqualityTesters, - actual: actual, - addExpectationResult: addExpectationResult - }); - - function addExpectationResult(passed, result) { - return spec.addExpectationResult(passed, result); - } - }; - - var specStarted = function(spec) { - currentSpec = spec; - reporter.specStarted(spec.result); - }; - - var beforeFns = function(suite) { - return function() { - var befores = []; - while(suite) { - befores = befores.concat(suite.beforeFns); - suite = suite.parentSuite; - } - return befores.reverse(); - }; - }; - - var afterFns = function(suite) { - return function() { - var afters = []; - while(suite) { - afters = afters.concat(suite.afterFns); - suite = suite.parentSuite; - } - return afters; - }; - }; - - var getSpecName = function(spec, suite) { - return suite.getFullName() + ' ' + spec.description; - }; - - // TODO: we may just be able to pass in the fn instead of wrapping here - var buildExpectationResult = j$.buildExpectationResult, - exceptionFormatter = new j$.ExceptionFormatter(), - expectationResultFactory = function(attrs) { - attrs.messageFormatter = exceptionFormatter.message; - attrs.stackFormatter = exceptionFormatter.stack; - - return buildExpectationResult(attrs); - }; - - // TODO: fix this naming, and here's where the value comes in - this.catchExceptions = function(value) { - catchExceptions = !!value; - return catchExceptions; - }; - - this.catchingExceptions = function() { - return catchExceptions; - }; - - var maximumSpecCallbackDepth = 20; - var currentSpecCallbackDepth = 0; - - function clearStack(fn) { - currentSpecCallbackDepth++; - if (currentSpecCallbackDepth >= maximumSpecCallbackDepth) { - currentSpecCallbackDepth = 0; - realSetTimeout(fn, 0); - } else { - fn(); - } - } - - var catchException = function(e) { - return j$.Spec.isPendingSpecException(e) || catchExceptions; - }; - - var queueRunnerFactory = function(options) { - options.catchException = catchException; - options.clearStack = options.clearStack || clearStack; - options.timer = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout}; - - new j$.QueueRunner(options).execute(); - }; - - var topSuite = new j$.Suite({ - env: this, - id: getNextSuiteId(), - description: 'Jasmine__TopLevel__Suite', - queueRunner: queueRunnerFactory, - resultCallback: function() {} // TODO - hook this up - }); - runnableLookupTable[topSuite.id] = topSuite; - currentSuite = topSuite; - - this.topSuite = function() { - return topSuite; - }; - - this.execute = function(runnablesToRun) { - runnablesToRun = runnablesToRun || [topSuite.id]; - - var allFns = []; - for(var i = 0; i < runnablesToRun.length; i++) { - var runnable = runnableLookupTable[runnablesToRun[i]]; - allFns.push((function(runnable) { return function(done) { runnable.execute(done); }; })(runnable)); - } - - reporter.jasmineStarted({ - totalSpecsDefined: totalSpecsDefined - }); - - queueRunnerFactory({fns: allFns, onComplete: reporter.jasmineDone}); - }; - - this.addReporter = function(reporterToAdd) { - reporter.addReporter(reporterToAdd); - }; - - this.addMatchers = function(matchersToAdd) { - j$.Expectation.addMatchers(matchersToAdd); - }; - - this.spyOn = function(obj, methodName) { - if (j$.util.isUndefined(obj)) { - throw new Error('spyOn could not find an object to spy upon for ' + methodName + '()'); - } - - if (j$.util.isUndefined(obj[methodName])) { - throw new Error(methodName + '() method does not exist'); - } - - if (obj[methodName] && j$.isSpy(obj[methodName])) { - //TODO?: should this return the current spy? Downside: may cause user confusion about spy state - throw new Error(methodName + ' has already been spied upon'); - } - - var spy = j$.createSpy(methodName, obj[methodName]); - - spies.push({ - spy: spy, - baseObj: obj, - methodName: methodName, - originalValue: obj[methodName] - }); - - obj[methodName] = spy; - - return spy; - }; - - var suiteFactory = function(description) { - var suite = new j$.Suite({ - env: self, - id: getNextSuiteId(), - description: description, - parentSuite: currentSuite, - queueRunner: queueRunnerFactory, - onStart: suiteStarted, - resultCallback: function(attrs) { - reporter.suiteDone(attrs); - } - }); - - runnableLookupTable[suite.id] = suite; - return suite; - }; - - this.describe = function(description, specDefinitions) { - var suite = suiteFactory(description); - - var parentSuite = currentSuite; - parentSuite.addChild(suite); - currentSuite = suite; - - var declarationError = null; - try { - specDefinitions.call(suite); - } catch (e) { - declarationError = e; - } - - if (declarationError) { - this.it('encountered a declaration exception', function() { - throw declarationError; - }); - } - - currentSuite = parentSuite; - - return suite; - }; - - this.xdescribe = function(description, specDefinitions) { - var suite = this.describe(description, specDefinitions); - suite.disable(); - return suite; - }; - - var specFactory = function(description, fn, suite) { - totalSpecsDefined++; - - var spec = new j$.Spec({ - id: getNextSpecId(), - beforeFns: beforeFns(suite), - afterFns: afterFns(suite), - expectationFactory: expectationFactory, - exceptionFormatter: exceptionFormatter, - resultCallback: specResultCallback, - getSpecName: function(spec) { - return getSpecName(spec, suite); - }, - onStart: specStarted, - description: description, - expectationResultFactory: expectationResultFactory, - queueRunnerFactory: queueRunnerFactory, - fn: fn - }); - - runnableLookupTable[spec.id] = spec; - - if (!self.specFilter(spec)) { - spec.disable(); - } - - return spec; - - function removeAllSpies() { - for (var i = 0; i < spies.length; i++) { - var spyEntry = spies[i]; - spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue; - } - spies = []; - } - - function specResultCallback(result) { - removeAllSpies(); - j$.Expectation.resetMatchers(); - customEqualityTesters = []; - currentSpec = null; - reporter.specDone(result); - } - }; - - var suiteStarted = function(suite) { - reporter.suiteStarted(suite.result); - }; - - this.it = function(description, fn) { - var spec = specFactory(description, fn, currentSuite); - currentSuite.addChild(spec); - return spec; - }; - - this.xit = function(description, fn) { - var spec = this.it(description, fn); - spec.pend(); - return spec; - }; - - this.expect = function(actual) { - if (!currentSpec) { - throw new Error('\'expect\' was used when there was no current spec, this could be because an asynchronous test timed out'); - } - - return currentSpec.expect(actual); - }; - - this.beforeEach = function(beforeEachFunction) { - currentSuite.beforeEach(beforeEachFunction); - }; - - this.afterEach = function(afterEachFunction) { - currentSuite.afterEach(afterEachFunction); - }; - - this.pending = function() { - throw j$.Spec.pendingSpecExceptionMessage; - }; - } - - return Env; -}; - -getJasmineRequireObj().JsApiReporter = function() { - - var noopTimer = { - start: function(){}, - elapsed: function(){ return 0; } - }; - - function JsApiReporter(options) { - var timer = options.timer || noopTimer, - status = 'loaded'; - - this.started = false; - this.finished = false; - - this.jasmineStarted = function() { - this.started = true; - status = 'started'; - timer.start(); - }; - - var executionTime; - - this.jasmineDone = function() { - this.finished = true; - executionTime = timer.elapsed(); - status = 'done'; - }; - - this.status = function() { - return status; - }; - - var suites = {}; - - this.suiteStarted = function(result) { - storeSuite(result); - }; - - this.suiteDone = function(result) { - storeSuite(result); - }; - - function storeSuite(result) { - suites[result.id] = result; - } - - this.suites = function() { - return suites; - }; - - var specs = []; - this.specStarted = function(result) { }; - - this.specDone = function(result) { - specs.push(result); - }; - - this.specResults = function(index, length) { - return specs.slice(index, index + length); - }; - - this.specs = function() { - return specs; - }; - - this.executionTime = function() { - return executionTime; - }; - - } - - return JsApiReporter; -}; - -getJasmineRequireObj().Any = function() { - - function Any(expectedObject) { - this.expectedObject = expectedObject; - } - - Any.prototype.jasmineMatches = function(other) { - if (this.expectedObject == String) { - return typeof other == 'string' || other instanceof String; - } - - if (this.expectedObject == Number) { - return typeof other == 'number' || other instanceof Number; - } - - if (this.expectedObject == Function) { - return typeof other == 'function' || other instanceof Function; - } - - if (this.expectedObject == Object) { - return typeof other == 'object'; - } - - if (this.expectedObject == Boolean) { - return typeof other == 'boolean'; - } - - return other instanceof this.expectedObject; - }; - - Any.prototype.jasmineToString = function() { - return ''; - }; - - return Any; -}; - -getJasmineRequireObj().CallTracker = function() { - - function CallTracker() { - var calls = []; - - this.track = function(context) { - calls.push(context); - }; - - this.any = function() { - return !!calls.length; - }; - - this.count = function() { - return calls.length; - }; - - this.argsFor = function(index) { - var call = calls[index]; - return call ? call.args : []; - }; - - this.all = function() { - return calls; - }; - - this.allArgs = function() { - var callArgs = []; - for(var i = 0; i < calls.length; i++){ - callArgs.push(calls[i].args); - } - - return callArgs; - }; - - this.first = function() { - return calls[0]; - }; - - this.mostRecent = function() { - return calls[calls.length - 1]; - }; - - this.reset = function() { - calls = []; - }; - } - - return CallTracker; -}; - -getJasmineRequireObj().Clock = function() { - function Clock(global, delayedFunctionScheduler, mockDate) { - var self = this, - realTimingFunctions = { - setTimeout: global.setTimeout, - clearTimeout: global.clearTimeout, - setInterval: global.setInterval, - clearInterval: global.clearInterval - }, - fakeTimingFunctions = { - setTimeout: setTimeout, - clearTimeout: clearTimeout, - setInterval: setInterval, - clearInterval: clearInterval - }, - installed = false, - timer; - - - self.install = function() { - replace(global, fakeTimingFunctions); - timer = fakeTimingFunctions; - installed = true; - - return self; - }; - - self.uninstall = function() { - delayedFunctionScheduler.reset(); - mockDate.uninstall(); - replace(global, realTimingFunctions); - - timer = realTimingFunctions; - installed = false; - }; - - self.mockDate = function(initialDate) { - mockDate.install(initialDate); - }; - - self.setTimeout = function(fn, delay, params) { - if (legacyIE()) { - if (arguments.length > 2) { - throw new Error('IE < 9 cannot support extra params to setTimeout without a polyfill'); - } - return timer.setTimeout(fn, delay); - } - return Function.prototype.apply.apply(timer.setTimeout, [global, arguments]); - }; - - self.setInterval = function(fn, delay, params) { - if (legacyIE()) { - if (arguments.length > 2) { - throw new Error('IE < 9 cannot support extra params to setInterval without a polyfill'); - } - return timer.setInterval(fn, delay); - } - return Function.prototype.apply.apply(timer.setInterval, [global, arguments]); - }; - - self.clearTimeout = function(id) { - return Function.prototype.call.apply(timer.clearTimeout, [global, id]); - }; - - self.clearInterval = function(id) { - return Function.prototype.call.apply(timer.clearInterval, [global, id]); - }; - - self.tick = function(millis) { - if (installed) { - mockDate.tick(millis); - delayedFunctionScheduler.tick(millis); - } else { - throw new Error('Mock clock is not installed, use jasmine.clock().install()'); - } - }; - - return self; - - function legacyIE() { - //if these methods are polyfilled, apply will be present - return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply; - } - - function replace(dest, source) { - for (var prop in source) { - dest[prop] = source[prop]; - } - } - - function setTimeout(fn, delay) { - return delayedFunctionScheduler.scheduleFunction(fn, delay, argSlice(arguments, 2)); - } - - function clearTimeout(id) { - return delayedFunctionScheduler.removeFunctionWithId(id); - } - - function setInterval(fn, interval) { - return delayedFunctionScheduler.scheduleFunction(fn, interval, argSlice(arguments, 2), true); - } - - function clearInterval(id) { - return delayedFunctionScheduler.removeFunctionWithId(id); - } - - function argSlice(argsObj, n) { - return Array.prototype.slice.call(argsObj, n); - } - } - - return Clock; -}; - -getJasmineRequireObj().DelayedFunctionScheduler = function() { - function DelayedFunctionScheduler() { - var self = this; - var scheduledLookup = []; - var scheduledFunctions = {}; - var currentTime = 0; - var delayedFnCount = 0; - - self.tick = function(millis) { - millis = millis || 0; - var endTime = currentTime + millis; - - runScheduledFunctions(endTime); - currentTime = endTime; - }; - - self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) { - var f; - if (typeof(funcToCall) === 'string') { - /* jshint evil: true */ - f = function() { return eval(funcToCall); }; - /* jshint evil: false */ - } else { - f = funcToCall; - } - - millis = millis || 0; - timeoutKey = timeoutKey || ++delayedFnCount; - runAtMillis = runAtMillis || (currentTime + millis); - - var funcToSchedule = { - runAtMillis: runAtMillis, - funcToCall: f, - recurring: recurring, - params: params, - timeoutKey: timeoutKey, - millis: millis - }; - - if (runAtMillis in scheduledFunctions) { - scheduledFunctions[runAtMillis].push(funcToSchedule); - } else { - scheduledFunctions[runAtMillis] = [funcToSchedule]; - scheduledLookup.push(runAtMillis); - scheduledLookup.sort(function (a, b) { - return a - b; - }); - } - - return timeoutKey; - }; - - self.removeFunctionWithId = function(timeoutKey) { - for (var runAtMillis in scheduledFunctions) { - var funcs = scheduledFunctions[runAtMillis]; - var i = indexOfFirstToPass(funcs, function (func) { - return func.timeoutKey === timeoutKey; - }); - - if (i > -1) { - if (funcs.length === 1) { - delete scheduledFunctions[runAtMillis]; - deleteFromLookup(runAtMillis); - } else { - funcs.splice(i, 1); - } - - // intervals get rescheduled when executed, so there's never more - // than a single scheduled function with a given timeoutKey - break; - } - } - }; - - self.reset = function() { - currentTime = 0; - scheduledLookup = []; - scheduledFunctions = {}; - delayedFnCount = 0; - }; - - return self; - - function indexOfFirstToPass(array, testFn) { - var index = -1; - - for (var i = 0; i < array.length; ++i) { - if (testFn(array[i])) { - index = i; - break; - } - } - - return index; - } - - function deleteFromLookup(key) { - var value = Number(key); - var i = indexOfFirstToPass(scheduledLookup, function (millis) { - return millis === value; - }); - - if (i > -1) { - scheduledLookup.splice(i, 1); - } - } - - function reschedule(scheduledFn) { - self.scheduleFunction(scheduledFn.funcToCall, - scheduledFn.millis, - scheduledFn.params, - true, - scheduledFn.timeoutKey, - scheduledFn.runAtMillis + scheduledFn.millis); - } - - function runScheduledFunctions(endTime) { - if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) { - return; - } - - do { - currentTime = scheduledLookup.shift(); - - var funcsToRun = scheduledFunctions[currentTime]; - delete scheduledFunctions[currentTime]; - - for (var i = 0; i < funcsToRun.length; ++i) { - var funcToRun = funcsToRun[i]; - funcToRun.funcToCall.apply(null, funcToRun.params || []); - - if (funcToRun.recurring) { - reschedule(funcToRun); - } - } - } while (scheduledLookup.length > 0 && - // checking first if we're out of time prevents setTimeout(0) - // scheduled in a funcToRun from forcing an extra iteration - currentTime !== endTime && - scheduledLookup[0] <= endTime); - } - } - - return DelayedFunctionScheduler; -}; - -getJasmineRequireObj().ExceptionFormatter = function() { - function ExceptionFormatter() { - this.message = function(error) { - var message = ''; - - if (error.name && error.message) { - message += error.name + ': ' + error.message; - } else { - message += error.toString() + ' thrown'; - } - - if (error.fileName || error.sourceURL) { - message += ' in ' + (error.fileName || error.sourceURL); - } - - if (error.line || error.lineNumber) { - message += ' (line ' + (error.line || error.lineNumber) + ')'; - } - - return message; - }; - - this.stack = function(error) { - return error ? error.stack : null; - }; - } - - return ExceptionFormatter; -}; - -getJasmineRequireObj().Expectation = function() { - - var matchers = {}; - - function Expectation(options) { - this.util = options.util || { buildFailureMessage: function() {} }; - this.customEqualityTesters = options.customEqualityTesters || []; - this.actual = options.actual; - this.addExpectationResult = options.addExpectationResult || function(){}; - this.isNot = options.isNot; - - for (var matcherName in matchers) { - this[matcherName] = matchers[matcherName]; - } - } - - Expectation.prototype.wrapCompare = function(name, matcherFactory) { - return function() { - var args = Array.prototype.slice.call(arguments, 0), - expected = args.slice(0), - message = ''; - - args.unshift(this.actual); - - var matcher = matcherFactory(this.util, this.customEqualityTesters), - matcherCompare = matcher.compare; - - function defaultNegativeCompare() { - var result = matcher.compare.apply(null, args); - result.pass = !result.pass; - return result; - } - - if (this.isNot) { - matcherCompare = matcher.negativeCompare || defaultNegativeCompare; - } - - var result = matcherCompare.apply(null, args); - - if (!result.pass) { - if (!result.message) { - args.unshift(this.isNot); - args.unshift(name); - message = this.util.buildFailureMessage.apply(null, args); - } else { - if (Object.prototype.toString.apply(result.message) === '[object Function]') { - message = result.message(); - } else { - message = result.message; - } - } - } - - if (expected.length == 1) { - expected = expected[0]; - } - - // TODO: how many of these params are needed? - this.addExpectationResult( - result.pass, - { - matcherName: name, - passed: result.pass, - message: message, - actual: this.actual, - expected: expected // TODO: this may need to be arrayified/sliced - } - ); - }; - }; - - Expectation.addCoreMatchers = function(matchers) { - var prototype = Expectation.prototype; - for (var matcherName in matchers) { - var matcher = matchers[matcherName]; - prototype[matcherName] = prototype.wrapCompare(matcherName, matcher); - } - }; - - Expectation.addMatchers = function(matchersToAdd) { - for (var name in matchersToAdd) { - var matcher = matchersToAdd[name]; - matchers[name] = Expectation.prototype.wrapCompare(name, matcher); - } - }; - - Expectation.resetMatchers = function() { - for (var name in matchers) { - delete matchers[name]; - } - }; - - Expectation.Factory = function(options) { - options = options || {}; - - var expect = new Expectation(options); - - // TODO: this would be nice as its own Object - NegativeExpectation - // TODO: copy instead of mutate options - options.isNot = true; - expect.not = new Expectation(options); - - return expect; - }; - - return Expectation; -}; - -//TODO: expectation result may make more sense as a presentation of an expectation. -getJasmineRequireObj().buildExpectationResult = function() { - function buildExpectationResult(options) { - var messageFormatter = options.messageFormatter || function() {}, - stackFormatter = options.stackFormatter || function() {}; - - return { - matcherName: options.matcherName, - expected: options.expected, - actual: options.actual, - message: message(), - stack: stack(), - passed: options.passed - }; - - function message() { - if (options.passed) { - return 'Passed.'; - } else if (options.message) { - return options.message; - } else if (options.error) { - return messageFormatter(options.error); - } - return ''; - } - - function stack() { - if (options.passed) { - return ''; - } - - var error = options.error; - if (!error) { - try { - throw new Error(message()); - } catch (e) { - error = e; - } - } - return stackFormatter(error); - } - } - - return buildExpectationResult; -}; - -getJasmineRequireObj().MockDate = function() { - function MockDate(global) { - var self = this; - var currentTime = 0; - - if (!global || !global.Date) { - self.install = function() {}; - self.tick = function() {}; - self.uninstall = function() {}; - return self; - } - - var GlobalDate = global.Date; - - self.install = function(mockDate) { - if (mockDate instanceof GlobalDate) { - currentTime = mockDate.getTime(); - } else { - currentTime = new GlobalDate().getTime(); - } - - global.Date = FakeDate; - }; - - self.tick = function(millis) { - millis = millis || 0; - currentTime = currentTime + millis; - }; - - self.uninstall = function() { - currentTime = 0; - global.Date = GlobalDate; - }; - - createDateProperties(); - - return self; - - function FakeDate() { - if (arguments.length === 0) { - return new GlobalDate(currentTime); - } else { - return new GlobalDate(arguments[0], arguments[1], arguments[2], - arguments[3], arguments[4], arguments[5], arguments[6]); - } - } - - function createDateProperties() { - - FakeDate.now = function() { - if (GlobalDate.now) { - return currentTime; - } else { - throw new Error('Browser does not support Date.now()'); - } - }; - - FakeDate.toSource = GlobalDate.toSource; - FakeDate.toString = GlobalDate.toString; - FakeDate.parse = GlobalDate.parse; - FakeDate.UTC = GlobalDate.UTC; - } - } - - return MockDate; -}; - -getJasmineRequireObj().ObjectContaining = function(j$) { - - function ObjectContaining(sample) { - this.sample = sample; - } - - ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) { - if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining, not \''+this.sample+'\'.'); } - - mismatchKeys = mismatchKeys || []; - mismatchValues = mismatchValues || []; - - var hasKey = function(obj, keyName) { - return obj !== null && !j$.util.isUndefined(obj[keyName]); - }; - - for (var property in this.sample) { - if (!hasKey(other, property) && hasKey(this.sample, property)) { - mismatchKeys.push('expected has key \'' + property + '\', but missing from actual.'); - } - else if (!j$.matchersUtil.equals(other[property], this.sample[property])) { - mismatchValues.push('\'' + property + '\' was \'' + (other[property] ? j$.util.htmlEscape(other[property].toString()) : other[property]) + '\' in actual, but was \'' + (this.sample[property] ? j$.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + '\' in expected.'); - } - } - - return (mismatchKeys.length === 0 && mismatchValues.length === 0); - }; - - ObjectContaining.prototype.jasmineToString = function() { - return ''; - }; - - return ObjectContaining; -}; - -getJasmineRequireObj().pp = function(j$) { - - function PrettyPrinter() { - this.ppNestLevel_ = 0; - this.seen = []; - } - - PrettyPrinter.prototype.format = function(value) { - this.ppNestLevel_++; - try { - if (j$.util.isUndefined(value)) { - this.emitScalar('undefined'); - } else if (value === null) { - this.emitScalar('null'); - } else if (value === 0 && 1/value === -Infinity) { - this.emitScalar('-0'); - } else if (value === j$.getGlobal()) { - this.emitScalar(''); - } else if (value.jasmineToString) { - this.emitScalar(value.jasmineToString()); - } else if (typeof value === 'string') { - this.emitString(value); - } else if (j$.isSpy(value)) { - this.emitScalar('spy on ' + value.and.identity()); - } else if (value instanceof RegExp) { - this.emitScalar(value.toString()); - } else if (typeof value === 'function') { - this.emitScalar('Function'); - } else if (typeof value.nodeType === 'number') { - this.emitScalar('HTMLNode'); - } else if (value instanceof Date) { - this.emitScalar('Date(' + value + ')'); - } else if (j$.util.arrayContains(this.seen, value)) { - this.emitScalar(''); - } else if (j$.isArray_(value) || j$.isA_('Object', value)) { - this.seen.push(value); - if (j$.isArray_(value)) { - this.emitArray(value); - } else { - this.emitObject(value); - } - this.seen.pop(); - } else { - this.emitScalar(value.toString()); - } - } finally { - this.ppNestLevel_--; - } - }; - - PrettyPrinter.prototype.iterateObject = function(obj, fn) { - for (var property in obj) { - if (!Object.prototype.hasOwnProperty.call(obj, property)) { continue; } - fn(property, obj.__lookupGetter__ ? (!j$.util.isUndefined(obj.__lookupGetter__(property)) && - obj.__lookupGetter__(property) !== null) : false); - } - }; - - PrettyPrinter.prototype.emitArray = j$.unimplementedMethod_; - PrettyPrinter.prototype.emitObject = j$.unimplementedMethod_; - PrettyPrinter.prototype.emitScalar = j$.unimplementedMethod_; - PrettyPrinter.prototype.emitString = j$.unimplementedMethod_; - - function StringPrettyPrinter() { - PrettyPrinter.call(this); - - this.string = ''; - } - - j$.util.inherit(StringPrettyPrinter, PrettyPrinter); - - StringPrettyPrinter.prototype.emitScalar = function(value) { - this.append(value); - }; - - StringPrettyPrinter.prototype.emitString = function(value) { - this.append('\'' + value + '\''); - }; - - StringPrettyPrinter.prototype.emitArray = function(array) { - if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { - this.append('Array'); - return; - } - var length = Math.min(array.length, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH); - this.append('[ '); - for (var i = 0; i < length; i++) { - if (i > 0) { - this.append(', '); - } - this.format(array[i]); - } - if(array.length > length){ - this.append(', ...'); - } - this.append(' ]'); - }; - - StringPrettyPrinter.prototype.emitObject = function(obj) { - if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { - this.append('Object'); - return; - } - - var self = this; - this.append('{ '); - var first = true; - - this.iterateObject(obj, function(property, isGetter) { - if (first) { - first = false; - } else { - self.append(', '); - } - - self.append(property); - self.append(': '); - if (isGetter) { - self.append(''); - } else { - self.format(obj[property]); - } - }); - - this.append(' }'); - }; - - StringPrettyPrinter.prototype.append = function(value) { - this.string += value; - }; - - return function(value) { - var stringPrettyPrinter = new StringPrettyPrinter(); - stringPrettyPrinter.format(value); - return stringPrettyPrinter.string; - }; -}; - -getJasmineRequireObj().QueueRunner = function(j$) { - - function once(fn) { - var called = false; - return function() { - if (!called) { - called = true; - fn(); - } - }; - } - - function QueueRunner(attrs) { - this.fns = attrs.fns || []; - this.onComplete = attrs.onComplete || function() {}; - this.clearStack = attrs.clearStack || function(fn) {fn();}; - this.onException = attrs.onException || function() {}; - this.catchException = attrs.catchException || function() { return true; }; - this.enforceTimeout = attrs.enforceTimeout || function() { return false; }; - this.userContext = {}; - this.timer = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout}; - } - - QueueRunner.prototype.execute = function() { - this.run(this.fns, 0); - }; - - QueueRunner.prototype.run = function(fns, recursiveIndex) { - var length = fns.length, - self = this, - iterativeIndex; - - for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) { - var fn = fns[iterativeIndex]; - // Uncomment the following line for easy identification of a failing/crashing test - // console.log("*** JASMINE *** RUNNING test function:\n", fn); - if (fn.length > 0) { - return attemptAsync(fn); - } else { - attemptSync(fn); - } - } - - var runnerDone = iterativeIndex >= length; - - if (runnerDone) { - this.clearStack(this.onComplete); - } - - function attemptSync(fn) { - try { - fn.call(self.userContext); - } catch (e) { - handleException(e); - } - } - - function attemptAsync(fn) { - var clearTimeout = function () { - Function.prototype.apply.apply(self.timer.clearTimeout, [j$.getGlobal(), [timeoutId]]); - }, - next = once(function () { - clearTimeout(timeoutId); - self.run(fns, iterativeIndex + 1); - }), - timeoutId; - - if (self.enforceTimeout()) { - timeoutId = Function.prototype.apply.apply(self.timer.setTimeout, [j$.getGlobal(), [function() { - self.onException(new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.')); - next(); - }, j$.DEFAULT_TIMEOUT_INTERVAL]]); - } - - try { - fn.call(self.userContext, next); - } catch (e) { - handleException(e); - next(); - } - } - - function handleException(e) { - self.onException(e); - if (!self.catchException(e)) { - //TODO: set a var when we catch an exception and - //use a finally block to close the loop in a nice way.. - throw e; - } - } - }; - - return QueueRunner; -}; - -getJasmineRequireObj().ReportDispatcher = function() { - function ReportDispatcher(methods) { - - var dispatchedMethods = methods || []; - - for (var i = 0; i < dispatchedMethods.length; i++) { - var method = dispatchedMethods[i]; - this[method] = (function(m) { - return function() { - dispatch(m, arguments); - }; - }(method)); - } - - var reporters = []; - - this.addReporter = function(reporter) { - reporters.push(reporter); - }; - - return this; - - function dispatch(method, args) { - for (var i = 0; i < reporters.length; i++) { - var reporter = reporters[i]; - if (reporter[method]) { - reporter[method].apply(reporter, args); - } - } - } - } - - return ReportDispatcher; -}; - - -getJasmineRequireObj().SpyStrategy = function() { - - function SpyStrategy(options) { - options = options || {}; - - var identity = options.name || 'unknown', - originalFn = options.fn || function() {}, - getSpy = options.getSpy || function() {}, - plan = function() {}; - - this.identity = function() { - return identity; - }; - - this.exec = function() { - return plan.apply(this, arguments); - }; - - this.callThrough = function() { - plan = originalFn; - return getSpy(); - }; - - this.returnValue = function(value) { - plan = function() { - return value; - }; - return getSpy(); - }; - - this.throwError = function(something) { - var error = (something instanceof Error) ? something : new Error(something); - plan = function() { - throw error; - }; - return getSpy(); - }; - - this.callFake = function(fn) { - plan = fn; - return getSpy(); - }; - - this.stub = function(fn) { - plan = function() {}; - return getSpy(); - }; - } - - return SpyStrategy; -}; - -getJasmineRequireObj().Suite = function() { - function Suite(attrs) { - this.env = attrs.env; - this.id = attrs.id; - this.parentSuite = attrs.parentSuite; - this.description = attrs.description; - this.onStart = attrs.onStart || function() {}; - this.resultCallback = attrs.resultCallback || function() {}; - this.clearStack = attrs.clearStack || function(fn) {fn();}; - - this.beforeFns = []; - this.afterFns = []; - this.queueRunner = attrs.queueRunner || function() {}; - this.disabled = false; - - this.children = []; - - this.result = { - id: this.id, - status: this.disabled ? 'disabled' : '', - description: this.description, - fullName: this.getFullName() - }; - } - - Suite.prototype.getFullName = function() { - var fullName = this.description; - for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { - if (parentSuite.parentSuite) { - fullName = parentSuite.description + ' ' + fullName; - } - } - return fullName; - }; - - Suite.prototype.disable = function() { - this.disabled = true; - }; - - Suite.prototype.beforeEach = function(fn) { - this.beforeFns.unshift(fn); - }; - - Suite.prototype.afterEach = function(fn) { - this.afterFns.unshift(fn); - }; - - Suite.prototype.addChild = function(child) { - this.children.push(child); - }; - - Suite.prototype.execute = function(onComplete) { - var self = this; - if (this.disabled) { - complete(); - return; - } - - var allFns = []; - - for (var i = 0; i < this.children.length; i++) { - allFns.push(wrapChildAsAsync(this.children[i])); - } - - this.onStart(this); - - this.queueRunner({ - fns: allFns, - onComplete: complete - }); - - function complete() { - self.resultCallback(self.result); - - if (onComplete) { - onComplete(); - } - } - - function wrapChildAsAsync(child) { - return function(done) { child.execute(done); }; - } - }; - - return Suite; -}; - -if (typeof window == void 0 && typeof exports == 'object') { - exports.Suite = jasmineRequire.Suite; -} - -getJasmineRequireObj().Timer = function() { - var defaultNow = (function(Date) { - return function() { return new Date().getTime(); }; - })(Date); - - function Timer(options) { - options = options || {}; - - var now = options.now || defaultNow, - startTime; - - this.start = function() { - startTime = now(); - }; - - this.elapsed = function() { - return now() - startTime; - }; - } - - return Timer; -}; - -getJasmineRequireObj().matchersUtil = function(j$) { - // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter? - - return { - equals: function(a, b, customTesters) { - customTesters = customTesters || []; - - return eq(a, b, [], [], customTesters); - }, - - contains: function(haystack, needle, customTesters) { - customTesters = customTesters || []; - - if (Object.prototype.toString.apply(haystack) === '[object Array]') { - for (var i = 0; i < haystack.length; i++) { - if (eq(haystack[i], needle, [], [], customTesters)) { - return true; - } - } - return false; - } - return !!haystack && haystack.indexOf(needle) >= 0; - }, - - buildFailureMessage: function() { - var args = Array.prototype.slice.call(arguments, 0), - matcherName = args[0], - isNot = args[1], - actual = args[2], - expected = args.slice(3), - englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); - - var message = 'Expected ' + - j$.pp(actual) + - (isNot ? ' not ' : ' ') + - englishyPredicate; - - if (expected.length > 0) { - for (var i = 0; i < expected.length; i++) { - if (i > 0) { - message += ','; - } - message += ' ' + j$.pp(expected[i]); - } - } - - return message + '.'; - } - }; - - // Equality function lovingly adapted from isEqual in - // [Underscore](http://underscorejs.org) - function eq(a, b, aStack, bStack, customTesters) { - var result = true; - - for (var i = 0; i < customTesters.length; i++) { - var customTesterResult = customTesters[i](a, b); - if (!j$.util.isUndefined(customTesterResult)) { - return customTesterResult; - } - } - - if (a instanceof j$.Any) { - result = a.jasmineMatches(b); - if (result) { - return true; - } - } - - if (b instanceof j$.Any) { - result = b.jasmineMatches(a); - if (result) { - return true; - } - } - - if (b instanceof j$.ObjectContaining) { - result = b.jasmineMatches(a); - if (result) { - return true; - } - } - - if (a instanceof Error && b instanceof Error) { - return a.message == b.message; - } - - // Identical objects are equal. `0 === -0`, but they aren't identical. - // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). - if (a === b) { return a !== 0 || 1 / a == 1 / b; } - // A strict comparison is necessary because `null == undefined`. - if (a === null || b === null) { return a === b; } - var className = Object.prototype.toString.call(a); - if (className != Object.prototype.toString.call(b)) { return false; } - switch (className) { - // Strings, numbers, dates, and booleans are compared by value. - case '[object String]': - // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is - // equivalent to `new String("5")`. - return a == String(b); - case '[object Number]': - // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for - // other numeric values. - return a != +a ? b != +b : (a === 0 ? 1 / a == 1 / b : a == +b); - case '[object Date]': - case '[object Boolean]': - // Coerce dates and booleans to numeric primitive values. Dates are compared by their - // millisecond representations. Note that invalid dates with millisecond representations - // of `NaN` are not equivalent. - return +a == +b; - // RegExps are compared by their source patterns and flags. - case '[object RegExp]': - return a.source == b.source && - a.global == b.global && - a.multiline == b.multiline && - a.ignoreCase == b.ignoreCase; - } - if (typeof a != 'object' || typeof b != 'object') { return false; } - // Assume equality for cyclic structures. The algorithm for detecting cyclic - // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. - var length = aStack.length; - while (length--) { - // Linear search. Performance is inversely proportional to the number of - // unique nested structures. - if (aStack[length] == a) { return bStack[length] == b; } - } - // Add the first object to the stack of traversed objects. - aStack.push(a); - bStack.push(b); - var size = 0; - // Recursively compare objects and arrays. - if (className == '[object Array]') { - // Compare array lengths to determine if a deep comparison is necessary. - size = a.length; - result = size == b.length; - if (result) { - // Deep compare the contents, ignoring non-numeric properties. - while (size--) { - if (!(result = eq(a[size], b[size], aStack, bStack, customTesters))) { break; } - } - } - } else { - // Objects with different constructors are not equivalent, but `Object`s - // from different frames are. - var aCtor = a.constructor, bCtor = b.constructor; - if (aCtor !== bCtor && !(isFunction(aCtor) && (aCtor instanceof aCtor) && - isFunction(bCtor) && (bCtor instanceof bCtor))) { - return false; - } - // Deep compare objects. - for (var key in a) { - if (has(a, key)) { - // Count the expected number of properties. - size++; - // Deep compare each member. - if (!(result = has(b, key) && eq(a[key], b[key], aStack, bStack, customTesters))) { break; } - } - } - // Ensure that both objects contain the same number of properties. - if (result) { - for (key in b) { - if (has(b, key) && !(size--)) { break; } - } - result = !size; - } - } - // Remove the first object from the stack of traversed objects. - aStack.pop(); - bStack.pop(); - - return result; - - function has(obj, key) { - return obj.hasOwnProperty(key); - } - - function isFunction(obj) { - return typeof obj === 'function'; - } - } -}; - -getJasmineRequireObj().toBe = function() { - function toBe() { - return { - compare: function(actual, expected) { - return { - pass: actual === expected - }; - } - }; - } - - return toBe; -}; - -getJasmineRequireObj().toBeCloseTo = function() { - - function toBeCloseTo() { - return { - compare: function(actual, expected, precision) { - if (precision !== 0) { - precision = precision || 2; - } - - return { - pass: Math.abs(expected - actual) < (Math.pow(10, -precision) / 2) - }; - } - }; - } - - return toBeCloseTo; -}; - -getJasmineRequireObj().toBeDefined = function() { - function toBeDefined() { - return { - compare: function(actual) { - return { - pass: (void 0 !== actual) - }; - } - }; - } - - return toBeDefined; -}; - -getJasmineRequireObj().toBeFalsy = function() { - function toBeFalsy() { - return { - compare: function(actual) { - return { - pass: !!!actual - }; - } - }; - } - - return toBeFalsy; -}; - -getJasmineRequireObj().toBeGreaterThan = function() { - - function toBeGreaterThan() { - return { - compare: function(actual, expected) { - return { - pass: actual > expected - }; - } - }; - } - - return toBeGreaterThan; -}; - - -getJasmineRequireObj().toBeLessThan = function() { - function toBeLessThan() { - return { - - compare: function(actual, expected) { - return { - pass: actual < expected - }; - } - }; - } - - return toBeLessThan; -}; -getJasmineRequireObj().toBeNaN = function(j$) { - - function toBeNaN() { - return { - compare: function(actual) { - var result = { - pass: (actual !== actual) - }; - - if (result.pass) { - result.message = 'Expected actual not to be NaN.'; - } else { - result.message = function() { return 'Expected ' + j$.pp(actual) + ' to be NaN.'; }; - } - - return result; - } - }; - } - - return toBeNaN; -}; - -getJasmineRequireObj().toBeNull = function() { - - function toBeNull() { - return { - compare: function(actual) { - return { - pass: actual === null - }; - } - }; - } - - return toBeNull; -}; - -getJasmineRequireObj().toBeTruthy = function() { - - function toBeTruthy() { - return { - compare: function(actual) { - return { - pass: !!actual - }; - } - }; - } - - return toBeTruthy; -}; - -getJasmineRequireObj().toBeUndefined = function() { - - function toBeUndefined() { - return { - compare: function(actual) { - return { - pass: void 0 === actual - }; - } - }; - } - - return toBeUndefined; -}; - -getJasmineRequireObj().toContain = function() { - function toContain(util, customEqualityTesters) { - customEqualityTesters = customEqualityTesters || []; - - return { - compare: function(actual, expected) { - - return { - pass: util.contains(actual, expected, customEqualityTesters) - }; - } - }; - } - - return toContain; -}; - -getJasmineRequireObj().toEqual = function() { - - function toEqual(util, customEqualityTesters) { - customEqualityTesters = customEqualityTesters || []; - - return { - compare: function(actual, expected) { - var result = { - pass: false - }; - - result.pass = util.equals(actual, expected, customEqualityTesters); - - return result; - } - }; - } - - return toEqual; -}; - -getJasmineRequireObj().toHaveBeenCalled = function(j$) { - - function toHaveBeenCalled() { - return { - compare: function(actual) { - var result = {}; - - if (!j$.isSpy(actual)) { - throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.'); - } - - if (arguments.length > 1) { - throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); - } - - result.pass = actual.calls.any(); - - result.message = result.pass ? - 'Expected spy ' + actual.and.identity() + ' not to have been called.' : - 'Expected spy ' + actual.and.identity() + ' to have been called.'; - - return result; - } - }; - } - - return toHaveBeenCalled; -}; - -getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { - - function toHaveBeenCalledWith(util, customEqualityTesters) { - return { - compare: function() { - var args = Array.prototype.slice.call(arguments, 0), - actual = args[0], - expectedArgs = args.slice(1), - result = { pass: false }; - - if (!j$.isSpy(actual)) { - throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.'); - } - - if (!actual.calls.any()) { - result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but it was never called.'; }; - return result; - } - - if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) { - result.pass = true; - result.message = function() { return 'Expected spy ' + actual.and.identity() + ' not to have been called with ' + j$.pp(expectedArgs) + ' but it was.'; }; - } else { - result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but actual calls were ' + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + '.'; }; - } - - return result; - } - }; - } - - return toHaveBeenCalledWith; -}; - -getJasmineRequireObj().toMatch = function() { - - function toMatch() { - return { - compare: function(actual, expected) { - var regexp = new RegExp(expected); - - return { - pass: regexp.test(actual) - }; - } - }; - } - - return toMatch; -}; - -getJasmineRequireObj().toThrow = function(j$) { - - function toThrow(util) { - return { - compare: function(actual, expected) { - var result = { pass: false }, - threw = false, - thrown; - - if (typeof actual != 'function') { - throw new Error('Actual is not a Function'); - } - - try { - actual(); - } catch (e) { - threw = true; - thrown = e; - } - - if (!threw) { - result.message = 'Expected function to throw an exception.'; - return result; - } - - if (arguments.length == 1) { - result.pass = true; - result.message = function() { return 'Expected function not to throw, but it threw ' + j$.pp(thrown) + '.'; }; - - return result; - } - - if (util.equals(thrown, expected)) { - result.pass = true; - result.message = function() { return 'Expected function not to throw ' + j$.pp(expected) + '.'; }; - } else { - result.message = function() { return 'Expected function to throw ' + j$.pp(expected) + ', but it threw ' + j$.pp(thrown) + '.'; }; - } - - return result; - } - }; - } - - return toThrow; -}; - -getJasmineRequireObj().toThrowError = function(j$) { - function toThrowError (util) { - return { - compare: function(actual) { - var threw = false, - pass = {pass: true}, - fail = {pass: false}, - thrown, - errorType, - message, - regexp, - name, - constructorName; - - if (typeof actual != 'function') { - throw new Error('Actual is not a Function'); - } - - extractExpectedParams.apply(null, arguments); - - try { - actual(); - } catch (e) { - threw = true; - thrown = e; - } - - if (!threw) { - fail.message = 'Expected function to throw an Error.'; - return fail; - } - - if (!(thrown instanceof Error)) { - fail.message = function() { return 'Expected function to throw an Error, but it threw ' + j$.pp(thrown) + '.'; }; - return fail; - } - - if (arguments.length == 1) { - pass.message = 'Expected function not to throw an Error, but it threw ' + fnNameFor(thrown) + '.'; - return pass; - } - - if (errorType) { - name = fnNameFor(errorType); - constructorName = fnNameFor(thrown.constructor); - } - - if (errorType && message) { - if (thrown.constructor == errorType && util.equals(thrown.message, message)) { - pass.message = function() { return 'Expected function not to throw ' + name + ' with message ' + j$.pp(message) + '.'; }; - return pass; - } else { - fail.message = function() { return 'Expected function to throw ' + name + ' with message ' + j$.pp(message) + - ', but it threw ' + constructorName + ' with message ' + j$.pp(thrown.message) + '.'; }; - return fail; - } - } - - if (errorType && regexp) { - if (thrown.constructor == errorType && regexp.test(thrown.message)) { - pass.message = function() { return 'Expected function not to throw ' + name + ' with message matching ' + j$.pp(regexp) + '.'; }; - return pass; - } else { - fail.message = function() { return 'Expected function to throw ' + name + ' with message matching ' + j$.pp(regexp) + - ', but it threw ' + constructorName + ' with message ' + j$.pp(thrown.message) + '.'; }; - return fail; - } - } - - if (errorType) { - if (thrown.constructor == errorType) { - pass.message = 'Expected function not to throw ' + name + '.'; - return pass; - } else { - fail.message = 'Expected function to throw ' + name + ', but it threw ' + constructorName + '.'; - return fail; - } - } - - if (message) { - if (thrown.message == message) { - pass.message = function() { return 'Expected function not to throw an exception with message ' + j$.pp(message) + '.'; }; - return pass; - } else { - fail.message = function() { return 'Expected function to throw an exception with message ' + j$.pp(message) + - ', but it threw an exception with message ' + j$.pp(thrown.message) + '.'; }; - return fail; - } - } - - if (regexp) { - if (regexp.test(thrown.message)) { - pass.message = function() { return 'Expected function not to throw an exception with a message matching ' + j$.pp(regexp) + '.'; }; - return pass; - } else { - fail.message = function() { return 'Expected function to throw an exception with a message matching ' + j$.pp(regexp) + - ', but it threw an exception with message ' + j$.pp(thrown.message) + '.'; }; - return fail; - } - } - - function fnNameFor(func) { - return func.name || func.toString().match(/^\s*function\s*(\w*)\s*\(/)[1]; - } - - function extractExpectedParams() { - if (arguments.length == 1) { - return; - } - - if (arguments.length == 2) { - var expected = arguments[1]; - - if (expected instanceof RegExp) { - regexp = expected; - } else if (typeof expected == 'string') { - message = expected; - } else if (checkForAnErrorType(expected)) { - errorType = expected; - } - - if (!(errorType || message || regexp)) { - throw new Error('Expected is not an Error, string, or RegExp.'); - } - } else { - if (checkForAnErrorType(arguments[1])) { - errorType = arguments[1]; - } else { - throw new Error('Expected error type is not an Error.'); - } - - if (arguments[2] instanceof RegExp) { - regexp = arguments[2]; - } else if (typeof arguments[2] == 'string') { - message = arguments[2]; - } else { - throw new Error('Expected error message is not a string or RegExp.'); - } - } - } - - function checkForAnErrorType(type) { - if (typeof type !== 'function') { - return false; - } - - var Surrogate = function() {}; - Surrogate.prototype = type.prototype; - return (new Surrogate()) instanceof Error; - } - } - }; - } - - return toThrowError; -}; - -getJasmineRequireObj().version = function() { - return '2.0.1'; -}; diff --git a/TestRunner/app/Infrastructure/Jasmine/jasmine-reporters/junit_reporter.js b/TestRunner/app/Infrastructure/Jasmine/jasmine-reporters/junit_reporter.js index b8fefc5c..5770f4f8 100644 --- a/TestRunner/app/Infrastructure/Jasmine/jasmine-reporters/junit_reporter.js +++ b/TestRunner/app/Infrastructure/Jasmine/jasmine-reporters/junit_reporter.js @@ -12,7 +12,9 @@ function trim(str) { return str.replace(/^\s+/, "" ).replace(/\s+$/, "" ); } function elapsed(start, end) { return (end - start)/1000; } function isFailed(obj) { return obj.status === "failed"; } - function isSkipped(obj) { return obj.status === "pending"; } + function isSkipped(obj) { + return obj.status === "pending" || obj.status === "excluded" || obj.status === "notApplicable"; + } function pad(n) { return n < 10 ? '0'+n : n; } function extend(dupe, obj) { // performs a shallow copy of all props of `obj` onto `dupe` for (var prop in obj) { @@ -31,11 +33,14 @@ pad(d.getSeconds()); } function escapeInvalidXmlChars(str) { - return str.replace(//g, ">") .replace(/\"/g, """) - .replace(/\'/g, "'") - .replace(/\&/g, "&"); + .replace(/\'/g, "'"); + } + function cdata(str) { + return '/g, ']]]]>') + ']]>'; } function getQualifiedFilename(path, filename, separator) { if (path && path.substr(-1) !== separator && filename.substr(0) !== separator) { @@ -141,6 +146,9 @@ }; self.suiteDone = function(suite) { suite = getSuite(suite); + // Errors that belong to the suite itself (an afterAll, or an error + // reported while no spec was running), not to any one spec. + suite._ownFailures = suite.failedExpectations || []; // disabled suite (xdescribe) -- suiteStarted was never called if (suite._parent === UNDEFINED) { self.suiteStarted(suite); @@ -149,11 +157,12 @@ suite._endTime = new Date(); currentSuite = suite._parent; }; - self.jasmineDone = function() { + self.jasmineDone = function(result) { var output = ''; for (var i = 0; i < suites.length; i++) { output += self.getOrWriteNestedOutput(suites[i]); } + output += runFailuresAsXml(result || {}); // if we have anything to write here, write out the consolidated file if (output) { wrapOutputAndWriteFile(self.filePrefix, output); @@ -258,23 +267,67 @@ } function suiteAsXml(suite) { + var ownFailures = suite._ownFailures || []; + var extraCase = ownFailures.length ? 1 : 0; var xml = '\n '; - xml += '\n '; - } + xml += failuresAsXml(spec.failedExpectations); } xml += '\n '; return xml; diff --git a/TestRunner/app/Infrastructure/Jasmine/jasmine-reporters/terminal_reporter.js b/TestRunner/app/Infrastructure/Jasmine/jasmine-reporters/terminal_reporter.js index ab31ba2c..da9a93f9 100644 --- a/TestRunner/app/Infrastructure/Jasmine/jasmine-reporters/terminal_reporter.js +++ b/TestRunner/app/Infrastructure/Jasmine/jasmine-reporters/terminal_reporter.js @@ -10,7 +10,9 @@ function elapsed(start, end) { return (end - start)/1000; } function isFailed(obj) { return obj.status === "failed"; } - function isSkipped(obj) { return obj.status === "pending"; } + function isSkipped(obj) { + return obj.status === "pending" || obj.status === "excluded" || obj.status === "notApplicable"; + } function extend(dupe, obj) { // performs a shallow copy of all props of `obj` onto `dupe` for (var prop in obj) { if (obj.hasOwnProperty(prop)) { @@ -65,8 +67,19 @@ totalSpecsExecuted = 0, totalSpecsSkipped = 0, totalSpecsFailed = 0, + totalOtherFailures = 0, totalSpecsDefined; + function logFailures(depth, header, failures) { + log(indentWithLevel(depth, inColor(header, 'red'))); + for (var i = 0; i < failures.length; i++) { + log(inColor(indentWithLevel(depth, indent_string + failures[i].message), 'red')); + if (failures[i].stack) { + log(indentWithLevel(depth, indent_string + failures[i].stack)); + } + } + } + var __suites = {}, __specs = {}; function getSuite(suite) { __suites[suite.id] = extend(__suites[suite.id] || {}, suite); @@ -155,6 +168,10 @@ }; self.suiteDone = function(suite) { suite = getSuite(suite); + if (suite.failedExpectations && suite.failedExpectations.length) { + totalOtherFailures++; + logFailures(1, 'Suite-level errors in "' + suite.fullName + '":', suite.failedExpectations); + } // disabled suite (xdescribe) -- suiteStarted was never called if (suite._parent === UNDEFINED) { self.suiteStarted(suite); @@ -178,7 +195,21 @@ str = passed + ' of ' + total + ' passed (' + skipped + ' skipped)'; log(indentWithLevel(suite._depth, inColor(str+'.', color))); }; - self.jasmineDone = function() { + self.jasmineDone = function(result) { + result = result || {}; + if (result.failedExpectations && result.failedExpectations.length) { + totalOtherFailures++; + logFailures(1, 'Run-level errors:', result.failedExpectations); + } + if (result.overallStatus === "incomplete") { + totalOtherFailures++; + log(inColor('Run incomplete: ' + result.incompleteReason, 'red')); + } + var deprecations = result.deprecationWarnings || []; + for (var d = 0; d < deprecations.length; d++) { + log('DEPRECATION: ' + deprecations[d].message); + } + var failedOverall = totalSpecsFailed > 0 || totalOtherFailures > 0; var now = new Date(), dur = elapsed(startTime, now), total = totalSpecsDefined || totalSpecsExecuted, @@ -189,8 +220,8 @@ skip_str = skipped + " skipped, ", disabled_str = disabled + " disabled in ", summary_str = spec_str + fail_str + skip_str + disabled_str + dur + "s.", - result_str = (totalSpecsFailed && "FAILURE: " || "SUCCESS: ") + summary_str, - result_color = totalSpecsFailed && "red+bold" || "green+bold"; + result_str = (failedOverall ? "FAILURE: " : "SUCCESS: ") + summary_str, + result_color = failedOverall ? "red+bold" : "green+bold"; if (self.verbosity === 2) { log(''); diff --git a/TestRunner/app/Infrastructure/Jasmine/jasmine.d.ts b/TestRunner/app/Infrastructure/Jasmine/jasmine.d.ts deleted file mode 100644 index 7ccfa959..00000000 --- a/TestRunner/app/Infrastructure/Jasmine/jasmine.d.ts +++ /dev/null @@ -1,434 +0,0 @@ -// Type definitions for Jasmine 2.0 -// Project: http://pivotal.github.com/jasmine/ -// Definitions by: Boris Yankov , Theodore Brown -// Definitions: https://github.com/borisyankov/DefinitelyTyped - - -// For ddescribe / iit use : https://github.com/borisyankov/DefinitelyTyped/blob/master/karma-jasmine/karma-jasmine.d.ts - -declare function describe(description: string, specDefinitions: () => void): void; -// declare function ddescribe(description: string, specDefinitions: () => void): void; Not a part of jasmine. Angular team adds these -declare function xdescribe(description: string, specDefinitions: () => void): void; - -declare function it(expectation: string, assertion?: () => void): void; -declare function it(expectation: string, assertion?: (done: () => void) => void): void; -// declare function iit(expectation: string, assertion?: () => void): void; Not a part of jasmine. Angular team adds these -// declare function iit(expectation: string, assertion?: (done: () => void) => void): void; Not a part of jasmine. Angular team adds these -declare function xit(expectation: string, assertion?: () => void): void; -declare function xit(expectation: string, assertion?: (done: () => void) => void): void; - -/** If you call the function pending anywhere in the spec body, no matter the expectations, the spec will be marked pending. */ -declare function pending(): void; - -declare function beforeEach(action: () => void): void; -declare function beforeEach(action: (done: () => void) => void): void; -declare function afterEach(action: () => void): void; -declare function afterEach(action: (done: () => void) => void): void; - -declare function expect(spy: Function): jasmine.Matchers; -declare function expect(actual: any): jasmine.Matchers; - -declare function spyOn(object: any, method: string): jasmine.Spy; - -declare function runs(asyncMethod: Function): void; -declare function waitsFor(latchMethod: () => boolean, failureMessage?: string, timeout?: number): void; -declare function waits(timeout?: number): void; - -declare module jasmine { - - var clock: () => Clock; - - function any(aclass: any): Any; - function objectContaining(sample: any): ObjectContaining; - function createSpy(name: string, originalFn?: Function): Spy; - function createSpyObj(baseName: string, methodNames: any[]): any; - function createSpyObj(baseName: string, methodNames: any[]): T; - function pp(value: any): string; - function getEnv(): Env; - function addMatchers(matchers: any): Any; - - interface Any { - - new (expectedClass: any): any; - - jasmineMatches(other: any): boolean; - jasmineToString(): string; - } - - interface ObjectContaining { - new (sample: any): any; - - jasmineMatches(other: any, mismatchKeys: any[], mismatchValues: any[]): boolean; - jasmineToString(): string; - } - - interface Block { - - new (env: Env, func: SpecFunction, spec: Spec): any; - - execute(onComplete: () => void): void; - } - - interface WaitsBlock extends Block { - new (env: Env, timeout: number, spec: Spec): any; - } - - interface WaitsForBlock extends Block { - new (env: Env, timeout: number, latchFunction: SpecFunction, message: string, spec: Spec): any; - } - - interface Clock { - install(): void; - uninstall(): void; - /** Calls to any registered callback are triggered when the clock is ticked forward via the jasmine.clock().tick function, which takes a number of milliseconds. */ - tick(ms: number): void; - } - - interface Env { - setTimeout: any; - clearTimeout: void; - setInterval: any; - clearInterval: void; - updateInterval: number; - - currentSpec: Spec; - - matchersClass: Matchers; - - version(): any; - versionString(): string; - nextSpecId(): number; - addReporter(reporter: Reporter): void; - execute(): void; - describe(description: string, specDefinitions: () => void): Suite; - // ddescribe(description: string, specDefinitions: () => void): Suite; Not a part of jasmine. Angular team adds these - beforeEach(beforeEachFunction: () => void): void; - currentRunner(): Runner; - afterEach(afterEachFunction: () => void): void; - xdescribe(desc: string, specDefinitions: () => void): XSuite; - it(description: string, func: () => void): Spec; - // iit(description: string, func: () => void): Spec; Not a part of jasmine. Angular team adds these - xit(desc: string, func: () => void): XSpec; - compareRegExps_(a: RegExp, b: RegExp, mismatchKeys: string[], mismatchValues: string[]): boolean; - compareObjects_(a: any, b: any, mismatchKeys: string[], mismatchValues: string[]): boolean; - equals_(a: any, b: any, mismatchKeys: string[], mismatchValues: string[]): boolean; - contains_(haystack: any, needle: any): boolean; - addEqualityTester(equalityTester: (a: any, b: any, env: Env, mismatchKeys: string[], mismatchValues: string[]) => boolean): void; - specFilter(spec: Spec): boolean; - } - - interface FakeTimer { - - new (): any; - - reset(): void; - tick(millis: number): void; - runFunctionsWithinRange(oldMillis: number, nowMillis: number): void; - scheduleFunction(timeoutKey: any, funcToCall: () => void, millis: number, recurring: boolean): void; - } - - interface HtmlReporter { - new (): any; - } - - interface HtmlSpecFilter { - new (): any; - } - - interface Result { - type: string; - } - - interface NestedResults extends Result { - description: string; - - totalCount: number; - passedCount: number; - failedCount: number; - - skipped: boolean; - - rollupCounts(result: NestedResults): void; - log(values: any): void; - getItems(): Result[]; - addResult(result: Result): void; - passed(): boolean; - } - - interface MessageResult extends Result { - values: any; - trace: Trace; - } - - interface ExpectationResult extends Result { - matcherName: string; - passed(): boolean; - expected: any; - actual: any; - message: string; - trace: Trace; - } - - interface Trace { - name: string; - message: string; - stack: any; - } - - interface PrettyPrinter { - - new (): any; - - format(value: any): void; - iterateObject(obj: any, fn: (property: string, isGetter: boolean) => void): void; - emitScalar(value: any): void; - emitString(value: string): void; - emitArray(array: any[]): void; - emitObject(obj: any): void; - append(value: any): void; - } - - interface StringPrettyPrinter extends PrettyPrinter { - } - - interface Queue { - - new (env: any): any; - - env: Env; - ensured: boolean[]; - blocks: Block[]; - running: boolean; - index: number; - offset: number; - abort: boolean; - - addBefore(block: Block, ensure?: boolean): void; - add(block: any, ensure?: boolean): void; - insertNext(block: any, ensure?: boolean): void; - start(onComplete?: () => void): void; - isRunning(): boolean; - next_(): void; - results(): NestedResults; - } - - interface Matchers { - - new (env: Env, actual: any, spec: Env, isNot?: boolean): any; - - env: Env; - actual: any; - spec: Env; - isNot?: boolean; - message(): any; - - toBe(expected: any): boolean; - toNotBe(expected: any): boolean; - toEqual(expected: any): boolean; - toNotEqual(expected: any): boolean; - toMatch(expected: any): boolean; - toNotMatch(expected: any): boolean; - toBeDefined(): boolean; - toBeUndefined(): boolean; - toBeNull(): boolean; - toBeNaN(): boolean; - toBeTruthy(): boolean; - toBeFalsy(): boolean; - toHaveBeenCalled(): boolean; - wasNotCalled(): boolean; - toHaveBeenCalledWith(...params: any[]): boolean; - toContain(expected: any): boolean; - toNotContain(expected: any): boolean; - toBeLessThan(expected: any): boolean; - toBeGreaterThan(expected: any): boolean; - toBeCloseTo(expected: any, precision: any): boolean; - toContainHtml(expected: string): boolean; - toContainText(expected: string): boolean; - toThrow(expected?: any): boolean; - toThrowError(expected?: any): boolean; - not: Matchers; - - Any: Any; - } - - interface Reporter { - reportRunnerStarting(runner: Runner): void; - reportRunnerResults(runner: Runner): void; - reportSuiteResults(suite: Suite): void; - reportSpecStarting(spec: Spec): void; - reportSpecResults(spec: Spec): void; - log(str: string): void; - } - - interface MultiReporter extends Reporter { - addReporter(reporter: Reporter): void; - } - - interface Runner { - - new (env: Env): any; - - execute(): void; - beforeEach(beforeEachFunction: SpecFunction): void; - afterEach(afterEachFunction: SpecFunction): void; - finishCallback(): void; - addSuite(suite: Suite): void; - add(block: Block): void; - specs(): Spec[]; - suites(): Suite[]; - topLevelSuites(): Suite[]; - results(): NestedResults; - } - - interface SpecFunction { - (spec?: Spec): void; - } - - interface SuiteOrSpec { - id: number; - env: Env; - description: string; - queue: Queue; - } - - interface Spec extends SuiteOrSpec { - - new (env: Env, suite: Suite, description: string): any; - - suite: Suite; - - afterCallbacks: SpecFunction[]; - spies_: Spy[]; - - results_: NestedResults; - matchersClass: Matchers; - - getFullName(): string; - results(): NestedResults; - log(arguments: any): any; - runs(func: SpecFunction): Spec; - addToQueue(block: Block): void; - addMatcherResult(result: Result): void; - expect(actual: any): any; - waits(timeout: number): Spec; - waitsFor(latchFunction: SpecFunction, timeoutMessage?: string, timeout?: number): Spec; - fail(e?: any): void; - getMatchersClass_(): Matchers; - addMatchers(matchersPrototype: any): void; - finishCallback(): void; - finish(onComplete?: () => void): void; - after(doAfter: SpecFunction): void; - execute(onComplete?: () => void): any; - addBeforesAndAftersToQueue(): void; - explodes(): void; - spyOn(obj: any, methodName: string, ignoreMethodDoesntExist: boolean): Spy; - removeAllSpies(): void; - } - - interface XSpec { - id: number; - runs(): void; - } - - interface Suite extends SuiteOrSpec { - - new (env: Env, description: string, specDefinitions: () => void, parentSuite: Suite): any; - - parentSuite: Suite; - - getFullName(): string; - finish(onComplete?: () => void): void; - beforeEach(beforeEachFunction: SpecFunction): void; - afterEach(afterEachFunction: SpecFunction): void; - results(): NestedResults; - add(suiteOrSpec: SuiteOrSpec): void; - specs(): Spec[]; - suites(): Suite[]; - children(): any[]; - execute(onComplete?: () => void): void; - } - - interface XSuite { - execute(): void; - } - - interface Spy { - (...params: any[]): any; - - identity: string; - and: SpyAnd; - calls: Calls; - mostRecentCall: { args: any[]; }; - argsForCall: any[]; - wasCalled: boolean; - callCount: number; - } - - interface SpyAnd { - /** By chaining the spy with and.callThrough, the spy will still track all calls to it but in addition it will delegate to the actual implementation. */ - callThrough(): void; - /** By chaining the spy with and.returnValue, all calls to the function will return a specific value. */ - returnValue(val: any): void; - /** By chaining the spy with and.callFake, all calls to the spy will delegate to the supplied function. */ - callFake(fn: Function): void; - /** By chaining the spy with and.throwError, all calls to the spy will throw the specified value. */ - throwError(msg: string): void; - /** When a calling strategy is used for a spy, the original stubbing behavior can be returned at any time with and.stub. */ - stub(): void; - } - - interface Calls { - /** By chaining the spy with calls.any(), will return false if the spy has not been called at all, and then true once at least one call happens. **/ - any(): boolean; - /** By chaining the spy with calls.count(), will return the number of times the spy was called **/ - count(): number; - /** By chaining the spy with calls.argsFor(), will return the arguments passed to call number index **/ - argsFor(index: number): any[]; - /** By chaining the spy with calls.allArgs(), will return the arguments to all calls **/ - allArgs(): any[]; - /** By chaining the spy with calls.all(), will return the context (the this) and arguments passed all calls **/ - all(): any; - /** By chaining the spy with calls.mostRecent(), will return the context (the this) and arguments for the most recent call **/ - mostRecent(): any; - /** By chaining the spy with calls.first(), will return the context (the this) and arguments for the first call **/ - first(): any; - /** By chaining the spy with calls.reset(), will clears all tracking for a spy **/ - reset(): void; - } - - interface Util { - inherit(childClass: Function, parentClass: Function): any; - formatException(e: any): any; - htmlEscape(str: string): string; - argsToArray(args: any): any; - extend(destination: any, source: any): any; - } - - interface JsApiReporter extends Reporter { - - started: boolean; - finished: boolean; - result: any; - messages: any; - - new (): any; - - suites(): Suite[]; - summarize_(suiteOrSpec: SuiteOrSpec): any; - results(): any; - resultsForSpec(specId: any): any; - log(str: any): any; - resultsForSpecs(specIds: any): any; - summarizeResult_(result: any): any; - } - - interface Jasmine { - Spec: Spec; - clock: Clock; - util: Util; - } - - export var HtmlReporter: HtmlReporter; - export var HtmlSpecFilter: HtmlSpecFilter; - export var DEFAULT_TIMEOUT_INTERVAL: number; -} diff --git a/TestRunner/app/shared b/TestRunner/app/shared index a2ccd8c7..2cb9c23c 160000 --- a/TestRunner/app/shared +++ b/TestRunner/app/shared @@ -1 +1 @@ -Subproject commit a2ccd8c7b211f1e2a614bab84918e27f0181b5de +Subproject commit 2cb9c23ceae51f05e37b170cbbe0bafa4788c9ce diff --git a/TestRunner/app/tests/ErrorEventsTests.js b/TestRunner/app/tests/ErrorEventsTests.js index e615661c..1a3f8a76 100644 --- a/TestRunner/app/tests/ErrorEventsTests.js +++ b/TestRunner/app/tests/ErrorEventsTests.js @@ -1,3 +1,5 @@ +const { raisesGlobalErrors } = require("../Infrastructure/Jasmine/globalErrorSpy"); + describe("WHATWG error events", function () { // Many tests exercise the global error/rejection path, which ends in the // __onUncaughtError shim when a listener does not preventDefault(). Install a @@ -51,7 +53,7 @@ describe("WHATWG error events", function () { }, 25); } - it("reportError fires an 'error' listener with an ErrorEvent carrying error and message", function (done) { + it("reportError fires an 'error' listener with an ErrorEvent carrying error and message", raisesGlobalErrors(function (done) { const err = new Error("x"); let received = null; onGlobal("error", function (e) { @@ -74,9 +76,9 @@ describe("WHATWG error events", function () { expect(uncaught.indexOf(err)).toBe(-1); done(); }); - }); + })); - it("reportError without preventDefault still invokes __onUncaughtError (back-compat)", function (done) { + it("reportError without preventDefault still invokes __onUncaughtError (back-compat)", raisesGlobalErrors(function (done) { const err = new Error("back-compat"); let received = null; onGlobal("error", function (e) { @@ -94,7 +96,7 @@ describe("WHATWG error events", function () { expect(typeof err.stackTrace).toBe("string"); expect(err.stackTrace.length).toBeGreaterThan(0); done(); - }); + })); it("reportError throws TypeError when called with no arguments", function () { expect(function () { @@ -102,7 +104,7 @@ describe("WHATWG error events", function () { }).toThrowError(TypeError); }); - it("an error reported through a native boundary has a combined stackTrace set BEFORE the error event fires", function (done) { + it("an error reported through a native boundary has a combined stackTrace set BEFORE the error event fires", raisesGlobalErrors(function (done) { // Reading an overridden getter from native routes the JS throw through // the C++ boundary reporter (ReportToJsHandlersAndLog), which now sets // `error.stackTrace` before dispatching the cancelable `error` event — @@ -133,9 +135,9 @@ describe("WHATWG error events", function () { expect(stackTraceInListener.length).toBeGreaterThan(0); done(); }); - }); + })); - it("unhandledrejection listener receives reason and promise; preventDefault suppresses the shim", function (done) { + it("unhandledrejection listener receives reason and promise; preventDefault suppresses the shim", raisesGlobalErrors(function (done) { const reason = new Error("rejected"); let received = null; onGlobal("unhandledrejection", function (e) { @@ -161,9 +163,9 @@ describe("WHATWG error events", function () { done(); }); }); - }); + })); - it("an unhandled rejection with no listener still reaches __onUncaughtError", function (done) { + it("an unhandled rejection with no listener still reaches __onUncaughtError", raisesGlobalErrors(function (done) { const reason = new Error("no-listener-rejection"); Promise.reject(reason); @@ -172,9 +174,9 @@ describe("WHATWG error events", function () { expect(uncaught[0]).toBe(reason); done(); }); - }); + })); - it("fires rejectionhandled when a handler is attached after the rejection was reported", function (done) { + it("fires rejectionhandled when a handler is attached after the rejection was reported", raisesGlobalErrors(function (done) { const reason = new Error("late-handler"); let rejectionHandled = null; onGlobal("rejectionhandled", function (e) { @@ -208,7 +210,7 @@ describe("WHATWG error events", function () { }); }, 20); }); - }); + })); describe("constructors and EventTarget semantics", function () { it("Event is spec-sane and cancelable via preventDefault", function () { @@ -307,7 +309,7 @@ describe("WHATWG error events", function () { }); }); - it("reportError still fires listeners after globalThis.dispatchEvent is overwritten", function (done) { + it("reportError still fires listeners after globalThis.dispatchEvent is overwritten", raisesGlobalErrors(function (done) { const err = new Error("resilient"); let received = null; onGlobal("error", function (e) { @@ -329,5 +331,5 @@ describe("WHATWG error events", function () { expect(uncaught.indexOf(err)).toBe(-1); done(); }); - }); + })); }); diff --git a/TestRunner/app/tests/EscapeExceptionTests.js b/TestRunner/app/tests/EscapeExceptionTests.js index 5b184a8c..d4c691c9 100644 --- a/TestRunner/app/tests/EscapeExceptionTests.js +++ b/TestRunner/app/tests/EscapeExceptionTests.js @@ -1,3 +1,5 @@ +const { raisesGlobalErrors } = require("../Infrastructure/Jasmine/globalErrorSpy"); + describe("interop.escapeException and boundary hardening", function () { // Several tests drive an unhandled/reported error through the uncaught path, // which ends in the __onUncaughtError shim (unless a listener prevents it). @@ -205,7 +207,7 @@ describe("interop.escapeException and boundary hardening", function () { }); describe("adapter boundary hardening (no process abort on JS throw)", function () { - it("a throwing getter read through the DictionaryAdapter yields a default, reports, and does not crash", function (done) { + it("a throwing getter read through the DictionaryAdapter yields a default, reports, and does not crash", raisesGlobalErrors(function (done) { const jsObject = { get boom() { throw new Error("dict-adapter-boom"); @@ -220,9 +222,9 @@ describe("interop.escapeException and boundary hardening", function () { expect(uncaughtSeen("dict-adapter-boom")).toBe(true); done(); }); - }); + })); - it("a throwing overridden property getter read from native does not crash and is reported", function (done) { + it("a throwing overridden property getter read from native does not crash and is reported", raisesGlobalErrors(function (done) { // baseProperty uses the default getter selector (KVC-accessible), // unlike TNSApi.property which has a custom getter. const Derived = TNSDerivedInterface.extend({ @@ -247,7 +249,7 @@ describe("interop.escapeException and boundary hardening", function () { expect(uncaughtSeen("getter-adapter-boom")).toBe(true); done(); }); - }); + })); }); // manual: uncaughtErrorPolicy "throw" is not exercised automatically because diff --git a/TestRunner/app/tests/EventLoopTests.js b/TestRunner/app/tests/EventLoopTests.js index b9912b12..cb5ec242 100644 --- a/TestRunner/app/tests/EventLoopTests.js +++ b/TestRunner/app/tests/EventLoopTests.js @@ -1,3 +1,5 @@ +const { raisesGlobalErrors } = require("../Infrastructure/Jasmine/globalErrorSpy"); + // A timer source foreign to the runtime's event loop: a CFRunLoopTimer on the // same runloop, outside the ordered timer lane. The global setTimeout is the // runtime's own timers, so tests probing interleaving with non-runtime work @@ -172,7 +174,7 @@ describe("event loop ordered macrotasks", function () { // a rejection created mid-chain must be reported while the chain is still // running, not after it ends. describe("event loop immediate timer yielding", function () { - it("yields to native work between immediate timer chain steps", function (done) { + it("yields to native work between immediate timer chain steps", raisesGlobalErrors(function (done) { let step = 0; let nstimerStep = -1; let mainQueueStep = -1; @@ -208,7 +210,7 @@ describe("event loop immediate timer yielding", function () { done(); }, 100); })(); - }); + })); }); @@ -400,6 +402,9 @@ describe("event loop workers", function () { .toBe("names the reason"); worker.terminate(); done(); + // Left unhandled here, the error would be re-reported on the main + // global scope, where Jasmine charges it to the next spec. + return true; }; }); diff --git a/TestRunner/app/tests/HttpEsmLoaderTests.js b/TestRunner/app/tests/HttpEsmLoaderTests.js index 783efaef..08fab4cb 100644 --- a/TestRunner/app/tests/HttpEsmLoaderTests.js +++ b/TestRunner/app/tests/HttpEsmLoaderTests.js @@ -889,11 +889,18 @@ describe("HTTP ESM Loader", function() { }); }); - it("should handle malformed URLs gracefully", function() { + it("should handle malformed URLs gracefully", function(done) { // The loader should ignore malformed http specifiers + var loading; expect(function() { - import("http://"); + loading = import("http://"); }).not.toThrow(); + loading.then(function() { + done(); + }, function(error) { + expect(error.message).toBeDefined(); + done(); + }); }); }); diff --git a/TestRunner/app/tests/MessagingTests.js b/TestRunner/app/tests/MessagingTests.js index 718a502d..d04298f2 100644 --- a/TestRunner/app/tests/MessagingTests.js +++ b/TestRunner/app/tests/MessagingTests.js @@ -1,3 +1,5 @@ +const { raisesGlobalErrors } = require("../Infrastructure/Jasmine/globalErrorSpy"); + // iOS-side regression specs for the messaging tier. The shared suites under // app/shared cover the specified behavior; these pin runtime edges that need // native wrappers, a worker that never settles, or a collection. @@ -543,7 +545,7 @@ describe("Messaging runtime edges", function () { jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout; }); - it("reports an error the Worker object left unhandled to the parent scope", function (done) { + it("reports an error the Worker object left unhandled to the parent scope", raisesGlobalErrors(function (done) { var seen = []; var worker = null; var listener = function (event) { @@ -564,7 +566,7 @@ describe("Messaging runtime edges", function () { }; addEventListener("error", listener); worker = new Worker("./messaging/throwingWorker.js"); - }); + })); it("lets a node:worker_threads 'error' listener consume the error", function (done) { var wt = require("node:worker_threads"); diff --git a/TestRunner/app/tests/MethodCallsTests.js b/TestRunner/app/tests/MethodCallsTests.js index 8a645495..1a592c70 100644 --- a/TestRunner/app/tests/MethodCallsTests.js +++ b/TestRunner/app/tests/MethodCallsTests.js @@ -633,7 +633,7 @@ describe(module.id, function () { }); // Properties - it('Base_BaseProtocolProperty1', function () { + it('Base_InstanceBaseProtocolProperty1', function () { var instance = TNSBaseInterface.alloc().init(); instance.baseProtocolProperty1 = 1; UNUSED(instance.baseProtocolProperty1); @@ -641,14 +641,14 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance setBaseProtocolProperty1: calledinstance baseProtocolProperty1 called'); }); - it('Base_BaseProtocolProperty1', function () { + it('Base_StaticBaseProtocolProperty1', function () { TNSBaseInterface.baseProtocolProperty1 = 1; UNUSED(TNSBaseInterface.baseProtocolProperty1); var actual = TNSGetOutput(); expect(actual).toBe('static setBaseProtocolProperty1: calledstatic baseProtocolProperty1 called'); }); - it('Base_BaseProtocolProperty1Optional', function () { + it('Base_InstanceBaseProtocolProperty1Optional', function () { var instance = TNSBaseInterface.alloc().init(); instance.baseProtocolProperty1Optional = 1; UNUSED(instance.baseProtocolProperty1Optional); @@ -656,14 +656,14 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance setBaseProtocolProperty1Optional: calledinstance baseProtocolProperty1Optional called'); }); - it('Base_BaseProtocolProperty1Optional', function () { + it('Base_StaticBaseProtocolProperty1Optional', function () { TNSBaseInterface.baseProtocolProperty1Optional = 1; UNUSED(TNSBaseInterface.baseProtocolProperty1Optional); var actual = TNSGetOutput(); expect(actual).toBe('static setBaseProtocolProperty1Optional: calledstatic baseProtocolProperty1Optional called'); }); - it('Base_BaseProtocolProperty2', function () { + it('Base_InstanceBaseProtocolProperty2', function () { var instance = TNSBaseInterface.alloc().init(); instance.baseProtocolProperty2 = 1; UNUSED(instance.baseProtocolProperty2); @@ -671,7 +671,7 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance setBaseProtocolProperty2: calledinstance baseProtocolProperty2 called'); }); - it('Base_BaseProtocolProperty2', function () { + it('Base_StaticBaseProtocolProperty2', function () { TNSBaseInterface.baseProtocolProperty2 = 1; UNUSED(TNSBaseInterface.baseProtocolProperty2); @@ -715,7 +715,7 @@ describe(module.id, function () { expect(actual.indexOf('static setBaseProtocolProperty2Optional: called')).toBe(0); expect(actual).toContain('static baseProtocolProperty2Optional called'); }); - it('Base_BaseProperty', function () { + it('Base_InstanceBaseProperty', function () { var instance = TNSBaseInterface.alloc().init(); instance.baseProperty = 1; UNUSED(instance.baseProperty); @@ -723,14 +723,14 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance setBaseProperty: calledinstance baseProperty called'); }); - it('Base_BaseProperty', function () { + it('Base_StaticBaseProperty', function () { TNSBaseInterface.baseProperty = 1; UNUSED(TNSBaseInterface.baseProperty); var actual = TNSGetOutput(); expect(actual).toBe('static setBaseProperty: calledstatic baseProperty called'); }); - it('Base_BaseCategoryProtocolProperty1', function () { + it('Base_InstanceBaseCategoryProtocolProperty1', function () { var instance = TNSBaseInterface.alloc().init(); instance.baseCategoryProtocolProperty1 = 1; UNUSED(instance.baseCategoryProtocolProperty1); @@ -738,14 +738,14 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance setBaseCategoryProtocolProperty1: calledinstance baseCategoryProtocolProperty1 called'); }); - it('Base_BaseCategoryProtocolProperty1', function () { + it('Base_StaticBaseCategoryProtocolProperty1', function () { TNSBaseInterface.baseCategoryProtocolProperty1 = 1; UNUSED(TNSBaseInterface.baseCategoryProtocolProperty1); var actual = TNSGetOutput(); expect(actual).toBe('static setBaseCategoryProtocolProperty1: calledstatic baseCategoryProtocolProperty1 called'); }); - it('Base_BaseCategoryProtocolProperty1Optional', function () { + it('Base_InstanceBaseCategoryProtocolProperty1Optional', function () { var instance = TNSBaseInterface.alloc().init(); instance.baseCategoryProtocolProperty1Optional = 1; UNUSED(instance.baseCategoryProtocolProperty1Optional); @@ -753,14 +753,14 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance setBaseCategoryProtocolProperty1Optional: calledinstance baseCategoryProtocolProperty1Optional called'); }); - it('Base_BaseCategoryProtocolProperty1Optional', function () { + it('Base_StaticBaseCategoryProtocolProperty1Optional', function () { TNSBaseInterface.baseCategoryProtocolProperty1Optional = 1; UNUSED(TNSBaseInterface.baseCategoryProtocolProperty1Optional); var actual = TNSGetOutput(); expect(actual).toBe('static setBaseCategoryProtocolProperty1Optional: calledstatic baseCategoryProtocolProperty1Optional called'); }); - it('Base_BaseCategoryProtocolProperty2', function () { + it('Base_InstanceBaseCategoryProtocolProperty2', function () { var instance = TNSBaseInterface.alloc().init(); instance.baseCategoryProtocolProperty2 = 1; UNUSED(instance.baseCategoryProtocolProperty2); @@ -768,14 +768,14 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance setBaseCategoryProtocolProperty2: calledinstance baseCategoryProtocolProperty2 called'); }); - it('Base_BaseCategoryProtocolProperty2', function () { + it('Base_StaticBaseCategoryProtocolProperty2', function () { TNSBaseInterface.baseCategoryProtocolProperty2 = 1; UNUSED(TNSBaseInterface.baseCategoryProtocolProperty2); var actual = TNSGetOutput(); expect(actual).toBe('static setBaseCategoryProtocolProperty2: calledstatic baseCategoryProtocolProperty2 called'); }); - it('Base_BaseCategoryProtocolProperty2Optional', function () { + it('Base_InstanceBaseCategoryProtocolProperty2Optional', function () { var instance = TNSBaseInterface.alloc().init(); instance.baseCategoryProtocolProperty2Optional = 1; UNUSED(instance.baseCategoryProtocolProperty2Optional); @@ -783,14 +783,14 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance setBaseCategoryProtocolProperty2Optional: calledinstance baseCategoryProtocolProperty2Optional called'); }); - it('Base_BaseCategoryProtocolProperty2Optional', function () { + it('Base_StaticBaseCategoryProtocolProperty2Optional', function () { TNSBaseInterface.baseCategoryProtocolProperty2Optional = 1; UNUSED(TNSBaseInterface.baseCategoryProtocolProperty2Optional); var actual = TNSGetOutput(); expect(actual).toBe('static setBaseCategoryProtocolProperty2Optional: calledstatic baseCategoryProtocolProperty2Optional called'); }); - it('Base_BaseCategoryProperty', function () { + it('Base_InstanceBaseCategoryProperty', function () { var instance = TNSBaseInterface.alloc().init(); instance.baseCategoryProperty = 1; UNUSED(instance.baseCategoryProperty); @@ -798,7 +798,7 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance setBaseCategoryProperty: calledinstance baseCategoryProperty called'); }); - it('Base_BaseCategoryProperty', function () { + it('Base_StaticBaseCategoryProperty', function () { TNSBaseInterface.baseCategoryProperty = 1; UNUSED(TNSBaseInterface.baseCategoryProperty); @@ -807,7 +807,7 @@ describe(module.id, function () { }); - it('Derived_BaseProtocolProperty1', function () { + it('Derived_InstanceBaseProtocolProperty1', function () { var instance = TNSDerivedInterface.alloc().init(); instance.baseProtocolProperty1 = 1; UNUSED(instance.baseProtocolProperty1); @@ -815,14 +815,14 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance setBaseProtocolProperty1: calledinstance baseProtocolProperty1 called'); }); - it('Derived_BaseProtocolProperty1', function () { + it('Derived_StaticBaseProtocolProperty1', function () { TNSDerivedInterface.baseProtocolProperty1 = 1; UNUSED(TNSDerivedInterface.baseProtocolProperty1); var actual = TNSGetOutput(); expect(actual).toBe('static setBaseProtocolProperty1: calledstatic baseProtocolProperty1 called'); }); - it('Derived_BaseProtocolProperty1Optional', function () { + it('Derived_InstanceBaseProtocolProperty1Optional', function () { var instance = TNSDerivedInterface.alloc().init(); instance.baseProtocolProperty1Optional = 1; UNUSED(instance.baseProtocolProperty1Optional); @@ -830,14 +830,14 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance setBaseProtocolProperty1Optional: calledinstance baseProtocolProperty1Optional called'); }); - it('Derived_BaseProtocolProperty1Optional', function () { + it('Derived_StaticBaseProtocolProperty1Optional', function () { TNSDerivedInterface.baseProtocolProperty1Optional = 1; UNUSED(TNSDerivedInterface.baseProtocolProperty1Optional); var actual = TNSGetOutput(); expect(actual).toBe('static setBaseProtocolProperty1Optional: calledstatic baseProtocolProperty1Optional called'); }); - it('Derived_BaseProtocolProperty2', function () { + it('Derived_InstanceBaseProtocolProperty2', function () { var instance = TNSDerivedInterface.alloc().init(); instance.baseProtocolProperty2 = 1; UNUSED(instance.baseProtocolProperty2); @@ -845,14 +845,14 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance setBaseProtocolProperty2: calledinstance baseProtocolProperty2 called'); }); - it('Derived_BaseProtocolProperty2', function () { + it('Derived_StaticBaseProtocolProperty2', function () { TNSDerivedInterface.baseProtocolProperty2 = 1; UNUSED(TNSDerivedInterface.baseProtocolProperty2); var actual = TNSGetOutput(); expect(actual).toBe('static setBaseProtocolProperty2: calledstatic baseProtocolProperty2 called'); }); - it('Derived_BaseProtocolProperty2Optional', function () { + it('Derived_InstanceBaseProtocolProperty2Optional', function () { var instance = TNSDerivedInterface.alloc().init(); instance.baseProtocolProperty2Optional = 1; UNUSED(instance.baseProtocolProperty2Optional); @@ -860,14 +860,14 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance setBaseProtocolProperty2Optional: calledinstance baseProtocolProperty2Optional called'); }); - it('Derived_BaseProtocolProperty2Optional', function () { + it('Derived_StaticBaseProtocolProperty2Optional', function () { TNSDerivedInterface.baseProtocolProperty2Optional = 1; UNUSED(TNSDerivedInterface.baseProtocolProperty2Optional); var actual = TNSGetOutput(); expect(actual).toBe('static setBaseProtocolProperty2Optional: calledstatic baseProtocolProperty2Optional called'); }); - it('Derived_BaseProperty', function () { + it('Derived_InstanceBaseProperty', function () { var instance = TNSDerivedInterface.alloc().init(); instance.baseProperty = 1; UNUSED(instance.baseProperty); @@ -875,14 +875,14 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance setBaseProperty: calledinstance baseProperty called'); }); - it('Derived_BaseProperty', function () { + it('Derived_StaticBaseProperty', function () { TNSDerivedInterface.baseProperty = 1; UNUSED(TNSDerivedInterface.baseProperty); var actual = TNSGetOutput(); expect(actual).toBe('static setBaseProperty: calledstatic baseProperty called'); }); - it('Derived_BaseCategoryProtocolProperty1', function () { + it('Derived_InstanceBaseCategoryProtocolProperty1', function () { var instance = TNSDerivedInterface.alloc().init(); instance.baseCategoryProtocolProperty1 = 1; UNUSED(instance.baseCategoryProtocolProperty1); @@ -890,14 +890,14 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance setBaseCategoryProtocolProperty1: calledinstance baseCategoryProtocolProperty1 called'); }); - it('Derived_BaseCategoryProtocolProperty1', function () { + it('Derived_StaticBaseCategoryProtocolProperty1', function () { TNSDerivedInterface.baseCategoryProtocolProperty1 = 1; UNUSED(TNSDerivedInterface.baseCategoryProtocolProperty1); var actual = TNSGetOutput(); expect(actual).toBe('static setBaseCategoryProtocolProperty1: calledstatic baseCategoryProtocolProperty1 called'); }); - it('Derived_BaseCategoryProtocolProperty1Optional', function () { + it('Derived_InstanceBaseCategoryProtocolProperty1Optional', function () { var instance = TNSDerivedInterface.alloc().init(); instance.baseCategoryProtocolProperty1Optional = 1; UNUSED(instance.baseCategoryProtocolProperty1Optional); @@ -905,14 +905,14 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance setBaseCategoryProtocolProperty1Optional: calledinstance baseCategoryProtocolProperty1Optional called'); }); - it('Derived_BaseCategoryProtocolProperty1Optional', function () { + it('Derived_StaticBaseCategoryProtocolProperty1Optional', function () { TNSDerivedInterface.baseCategoryProtocolProperty1Optional = 1; UNUSED(TNSDerivedInterface.baseCategoryProtocolProperty1Optional); var actual = TNSGetOutput(); expect(actual).toBe('static setBaseCategoryProtocolProperty1Optional: calledstatic baseCategoryProtocolProperty1Optional called'); }); - it('Derived_BaseCategoryProtocolProperty2', function () { + it('Derived_InstanceBaseCategoryProtocolProperty2', function () { var instance = TNSDerivedInterface.alloc().init(); instance.baseCategoryProtocolProperty2 = 1; UNUSED(instance.baseCategoryProtocolProperty2); @@ -920,14 +920,14 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance setBaseCategoryProtocolProperty2: calledinstance baseCategoryProtocolProperty2 called'); }); - it('Derived_BaseCategoryProtocolProperty2', function () { + it('Derived_StaticBaseCategoryProtocolProperty2', function () { TNSDerivedInterface.baseCategoryProtocolProperty2 = 1; UNUSED(TNSDerivedInterface.baseCategoryProtocolProperty2); var actual = TNSGetOutput(); expect(actual).toBe('static setBaseCategoryProtocolProperty2: calledstatic baseCategoryProtocolProperty2 called'); }); - it('Derived_BaseCategoryProtocolProperty2Optional', function () { + it('Derived_InstanceBaseCategoryProtocolProperty2Optional', function () { var instance = TNSDerivedInterface.alloc().init(); instance.baseCategoryProtocolProperty2Optional = 1; UNUSED(instance.baseCategoryProtocolProperty2Optional); @@ -935,14 +935,14 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance setBaseCategoryProtocolProperty2Optional: calledinstance baseCategoryProtocolProperty2Optional called'); }); - it('Derived_BaseCategoryProtocolProperty2Optional', function () { + it('Derived_StaticBaseCategoryProtocolProperty2Optional', function () { TNSDerivedInterface.baseCategoryProtocolProperty2Optional = 1; UNUSED(TNSDerivedInterface.baseCategoryProtocolProperty2Optional); var actual = TNSGetOutput(); expect(actual).toBe('static setBaseCategoryProtocolProperty2Optional: calledstatic baseCategoryProtocolProperty2Optional called'); }); - it('Derived_BaseCategoryProperty', function () { + it('Derived_InstanceBaseCategoryProperty', function () { var instance = TNSDerivedInterface.alloc().init(); instance.baseCategoryProperty = 1; UNUSED(instance.baseCategoryProperty); @@ -950,14 +950,14 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance setBaseCategoryProperty: calledinstance baseCategoryProperty called'); }); - it('Derived_BaseCategoryProperty', function () { + it('Derived_StaticBaseCategoryProperty', function () { TNSDerivedInterface.baseCategoryProperty = 1; UNUSED(TNSDerivedInterface.baseCategoryProperty); var actual = TNSGetOutput(); expect(actual).toBe('static setBaseCategoryProperty: calledstatic baseCategoryProperty called'); }); - it('Derived_DerivedProtocolProperty1', function () { + it('Derived_InstanceDerivedProtocolProperty1', function () { var instance = TNSDerivedInterface.alloc().init(); instance.derivedProtocolProperty1 = 1; UNUSED(instance.derivedProtocolProperty1); @@ -965,14 +965,14 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance setDerivedProtocolProperty1: calledinstance derivedProtocolProperty1 called'); }); - it('Derived_DerivedProtocolProperty1', function () { + it('Derived_StaticDerivedProtocolProperty1', function () { TNSDerivedInterface.derivedProtocolProperty1 = 1; UNUSED(TNSDerivedInterface.derivedProtocolProperty1); var actual = TNSGetOutput(); expect(actual).toBe('static setDerivedProtocolProperty1: calledstatic derivedProtocolProperty1 called'); }); - it('Derived_DerivedProtocolProperty1Optional', function () { + it('Derived_InstanceDerivedProtocolProperty1Optional', function () { var instance = TNSDerivedInterface.alloc().init(); instance.derivedProtocolProperty1Optional = 1; UNUSED(instance.derivedProtocolProperty1Optional); @@ -980,14 +980,14 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance setDerivedProtocolProperty1Optional: calledinstance derivedProtocolProperty1Optional called'); }); - it('Derived_DerivedProtocolProperty1Optional', function () { + it('Derived_StaticDerivedProtocolProperty1Optional', function () { TNSDerivedInterface.derivedProtocolProperty1Optional = 1; UNUSED(TNSDerivedInterface.derivedProtocolProperty1Optional); var actual = TNSGetOutput(); expect(actual).toBe('static setDerivedProtocolProperty1Optional: calledstatic derivedProtocolProperty1Optional called'); }); - it('Derived_DerivedProtocolProperty2', function () { + it('Derived_InstanceDerivedProtocolProperty2', function () { var instance = TNSDerivedInterface.alloc().init(); instance.derivedProtocolProperty2 = 1; UNUSED(instance.derivedProtocolProperty2); @@ -995,14 +995,14 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance setDerivedProtocolProperty2: calledinstance derivedProtocolProperty2 called'); }); - it('Derived_DerivedProtocolProperty2', function () { + it('Derived_StaticDerivedProtocolProperty2', function () { TNSDerivedInterface.derivedProtocolProperty2 = 1; UNUSED(TNSDerivedInterface.derivedProtocolProperty2); var actual = TNSGetOutput(); expect(actual).toBe('static setDerivedProtocolProperty2: calledstatic derivedProtocolProperty2 called'); }); - it('Derived_DerivedProtocolProperty2Optional', function () { + it('Derived_InstanceDerivedProtocolProperty2Optional', function () { var instance = TNSDerivedInterface.alloc().init(); instance.derivedProtocolProperty2Optional = 1; UNUSED(instance.derivedProtocolProperty2Optional); @@ -1011,7 +1011,7 @@ describe(module.id, function () { expect(actual).toBe('instance setDerivedProtocolProperty2Optional: calledinstance derivedProtocolProperty2Optional called'); }); - it('Derived_DerivedProperty', function () { + it('Derived_InstanceDerivedProperty', function () { var instance = TNSDerivedInterface.alloc().init(); instance.derivedProperty = 1; UNUSED(instance.derivedProperty); @@ -1028,20 +1028,21 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance derivedPropertyReadOnly called'); }); - it('Derived_DerivedProperty', function () { + it('Derived_StaticDerivedProperty', function () { TNSDerivedInterface.derivedProperty = 1; UNUSED(TNSDerivedInterface.derivedProperty); - it('Derived_DerivedProtocolProperty2Optional', function () { + var actual = TNSGetOutput(); + expect(actual).toBe('static setDerivedProperty: calledstatic derivedProperty called'); + }); + it('Derived_StaticDerivedProtocolProperty2Optional', function () { TNSDerivedInterface.derivedProtocolProperty2Optional = 1; UNUSED(TNSDerivedInterface.derivedProtocolProperty2Optional); var actual = TNSGetOutput(); expect(actual).toBe('static setDerivedProtocolProperty2Optional: calledstatic derivedProtocolProperty2Optional called'); - }); var actual = TNSGetOutput(); - expect(actual).toBe('static setDerivedProperty: calledstatic derivedProperty called'); }); - it('Derived_DerivedCategoryProtocolProperty1', function () { + it('Derived_InstanceDerivedCategoryProtocolProperty1', function () { var instance = TNSDerivedInterface.alloc().init(); instance.derivedCategoryProtocolProperty1 = 1; UNUSED(instance.derivedCategoryProtocolProperty1); @@ -1049,14 +1050,14 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance setDerivedCategoryProtocolProperty1: calledinstance derivedCategoryProtocolProperty1 called'); }); - it('Derived_DerivedCategoryProtocolProperty1', function () { + it('Derived_StaticDerivedCategoryProtocolProperty1', function () { TNSDerivedInterface.derivedCategoryProtocolProperty1 = 1; UNUSED(TNSDerivedInterface.derivedCategoryProtocolProperty1); var actual = TNSGetOutput(); expect(actual).toBe('static setDerivedCategoryProtocolProperty1: calledstatic derivedCategoryProtocolProperty1 called'); }); - it('Derived_DerivedCategoryProtocolProperty1Optional', function () { + it('Derived_InstanceDerivedCategoryProtocolProperty1Optional', function () { var instance = TNSDerivedInterface.alloc().init(); instance.derivedCategoryProtocolProperty1Optional = 1; UNUSED(instance.derivedCategoryProtocolProperty1Optional); @@ -1064,14 +1065,14 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance setDerivedCategoryProtocolProperty1Optional: calledinstance derivedCategoryProtocolProperty1Optional called'); }); - it('Derived_DerivedCategoryProtocolProperty1Optional', function () { + it('Derived_StaticDerivedCategoryProtocolProperty1Optional', function () { TNSDerivedInterface.derivedCategoryProtocolProperty1Optional = 1; UNUSED(TNSDerivedInterface.derivedCategoryProtocolProperty1Optional); var actual = TNSGetOutput(); expect(actual).toBe('static setDerivedCategoryProtocolProperty1Optional: calledstatic derivedCategoryProtocolProperty1Optional called'); }); - it('Derived_DerivedCategoryProtocolProperty2', function () { + it('Derived_InstanceDerivedCategoryProtocolProperty2', function () { var instance = TNSDerivedInterface.alloc().init(); instance.derivedCategoryProtocolProperty2 = 1; UNUSED(instance.derivedCategoryProtocolProperty2); @@ -1079,14 +1080,14 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance setDerivedCategoryProtocolProperty2: calledinstance derivedCategoryProtocolProperty2 called'); }); - it('Derived_DerivedCategoryProtocolProperty2', function () { + it('Derived_StaticDerivedCategoryProtocolProperty2', function () { TNSDerivedInterface.derivedCategoryProtocolProperty2 = 1; UNUSED(TNSDerivedInterface.derivedCategoryProtocolProperty2); var actual = TNSGetOutput(); expect(actual).toBe('static setDerivedCategoryProtocolProperty2: calledstatic derivedCategoryProtocolProperty2 called'); }); - it('Derived_DerivedCategoryProtocolProperty2Optional', function () { + it('Derived_InstanceDerivedCategoryProtocolProperty2Optional', function () { var instance = TNSDerivedInterface.alloc().init(); instance.derivedCategoryProtocolProperty2Optional = 1; UNUSED(instance.derivedCategoryProtocolProperty2Optional); @@ -1094,14 +1095,14 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance setDerivedCategoryProtocolProperty2Optional: calledinstance derivedCategoryProtocolProperty2Optional called'); }); - it('Derived_DerivedCategoryProtocolProperty2Optional', function () { + it('Derived_StaticDerivedCategoryProtocolProperty2Optional', function () { TNSDerivedInterface.derivedCategoryProtocolProperty2Optional = 1; UNUSED(TNSDerivedInterface.derivedCategoryProtocolProperty2Optional); var actual = TNSGetOutput(); expect(actual).toBe('static setDerivedCategoryProtocolProperty2Optional: calledstatic derivedCategoryProtocolProperty2Optional called'); }); - it('Derived_DerivedCategoryProperty', function () { + it('Derived_InstanceDerivedCategoryProperty', function () { var instance = TNSDerivedInterface.alloc().init(); instance.derivedCategoryProperty = 1; UNUSED(instance.derivedCategoryProperty); @@ -1117,7 +1118,7 @@ describe(module.id, function () { var actual = TNSGetOutput(); expect(actual).toBe('instance setDerivedPropertyReadOnlyMadeWritable: calledinstance derivedPropertyReadOnlyMadeWritable called'); }); - it('Derived_DerivedCategoryProperty', function () { + it('Derived_StaticDerivedCategoryProperty', function () { TNSDerivedInterface.derivedCategoryProperty = 1; UNUSED(TNSDerivedInterface.derivedCategoryProperty); diff --git a/TestRunner/app/tests/Promises.js b/TestRunner/app/tests/Promises.js index 7c681f1e..30297c03 100644 --- a/TestRunner/app/tests/Promises.js +++ b/TestRunner/app/tests/Promises.js @@ -1,3 +1,5 @@ +const { raisesGlobalErrors } = require("../Infrastructure/Jasmine/globalErrorSpy"); + /** TODO: Revisit thread handling with promises regarding these tests */ @@ -193,7 +195,7 @@ describe("unhandled rejections", function () { }, 20); } - it("reports an unhandled Promise.reject", function (done) { + it("reports an unhandled Promise.reject", raisesGlobalErrors(function (done) { const reason = new Error("unhandled-promise-reject"); Promise.reject(reason); afterDrain(function () { @@ -201,7 +203,7 @@ describe("unhandled rejections", function () { expect(reported[0]).toBe(reason); done(); }); - }); + })); it("does not report when .catch is attached synchronously in the same turn", function (done) { const p = Promise.reject(new Error("handled-same-turn")); @@ -212,7 +214,7 @@ describe("unhandled rejections", function () { }); }); - it("reports an uncaught throw from an async function", function (done) { + it("reports an uncaught throw from an async function", raisesGlobalErrors(function (done) { const reason = new Error("async-function-throw"); (async () => { throw reason; @@ -222,9 +224,9 @@ describe("unhandled rejections", function () { expect(reported[0]).toBe(reason); done(); }); - }); + })); - it("reports an unhandled rejection thrown from a .then callback", function (done) { + it("reports an unhandled rejection thrown from a .then callback", raisesGlobalErrors(function (done) { const reason = new Error("then-callback-throw"); Promise.resolve().then(() => { throw reason; @@ -234,9 +236,9 @@ describe("unhandled rejections", function () { expect(reported[0]).toBe(reason); done(); }); - }); + })); - it("ignores a late .catch attached after the rejection was already reported", function (done) { + it("ignores a late .catch attached after the rejection was already reported", raisesGlobalErrors(function (done) { const reason = new Error("late-catch"); const p = Promise.reject(reason); afterDrain(function () { @@ -249,5 +251,5 @@ describe("unhandled rejections", function () { done(); }); }); - }); + })); }); diff --git a/TestRunner/app/tests/Timers.js b/TestRunner/app/tests/Timers.js index 5f3e4e95..a0c8e917 100644 --- a/TestRunner/app/tests/Timers.js +++ b/TestRunner/app/tests/Timers.js @@ -51,14 +51,6 @@ describe("native timer", () => { }, 100); }); - it("triggers timeout", (done) => { - const now = Date.now(); - setTimeout(() => { - expect(Date.now() - now).not.toBeLessThan(100); - done(); - }, 100); - }); - it("triggers interval", (done) => { let calls = 0; const itv = setInterval(() => { diff --git a/TestRunner/app/tests/WorkerResourceLimitsTests.js b/TestRunner/app/tests/WorkerResourceLimitsTests.js index 236ef4e8..2663dc89 100644 --- a/TestRunner/app/tests/WorkerResourceLimitsTests.js +++ b/TestRunner/app/tests/WorkerResourceLimitsTests.js @@ -60,9 +60,11 @@ describe("Worker resourceLimits", function () { expect("worker posted a message").toBe("worker exhausted its heap"); }; + // Returns true so the error is not re-reported on the main global scope, + // where Jasmine would charge it to whatever spec runs next. worker.onerror = function (e) { if (settled) { - return; + return true; } settled = true; var message = String(e && e.message ? e.message : e); @@ -72,6 +74,7 @@ describe("Worker resourceLimits", function () { expect(message).toMatch(/maxOldGenerationSizeMb: 32/); worker.terminate(); done(); + return true; }; }); diff --git a/TestRunner/app/tests/index.js b/TestRunner/app/tests/index.js index 5c64e576..9c9ce44f 100644 --- a/TestRunner/app/tests/index.js +++ b/TestRunner/app/tests/index.js @@ -94,7 +94,7 @@ global.__JUnitSaveResults = function (text) { global.__approot = NSString.stringWithString(NSBundle.mainBundle.bundlePath).stringByResolvingSymlinksInPath; -require("../Infrastructure/Jasmine/jasmine-2.0.1/boot"); +var jasmineBoot = require("../Infrastructure/Jasmine/boot"); require("./Marshalling/Primitives/Function"); require("./Marshalling/Primitives/Static"); @@ -217,6 +217,6 @@ require("../shared/index").runAllTests(); // These can be turned on manually to verify if needed anytime //require("./sdks/MusicKit"); -execute(); +jasmineBoot.execute(); UIApplicationMain(0, null, null, null); diff --git a/TestRunnerTests/QUARANTINED_TESTS.md b/TestRunnerTests/QUARANTINED_TESTS.md index c6f5395c..b262a934 100644 --- a/TestRunnerTests/QUARANTINED_TESTS.md +++ b/TestRunnerTests/QUARANTINED_TESTS.md @@ -1,10 +1,9 @@ # Quarantined runtime tests Specs in the runtime suite (`TestRunner`) can be skipped at the harness level -via a `specFilter` in -`TestRunner/app/Infrastructure/Jasmine/jasmine-2.0.1/boot.js` +via a `specFilter` in `TestRunner/app/Infrastructure/Jasmine/boot.js` (`QUARANTINED_SPEC_SUBSTRINGS`), matched by substring against each spec's full -name. Quarantining touches only the test app — it does **not** edit the shared +name. Quarantined specs show up as skipped in the JUnit report. Quarantining touches only the test app — it does **not** edit the shared `common-runtime-tests-app` submodule. When adding an entry, record *why* and *how to re-enable* it here. From e902e4b652019bb183b502b39d0557cfa35d4576 Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Wed, 11 Nov 2026 16:51:21 -0300 Subject: [PATCH 3/3] test: keep the JUnit report parseable and count non-spec errors in the summary - The JUnit reporter writes characters XML 1.0 cannot represent (control characters other than tab/LF/CR, lone surrogates, U+FFFE/U+FFFF) as \uXXXX text, in attributes and CDATA alike; one such character in a failure message made the whole report unparseable. - The terminal summary lists suite- and run-level errors next to the spec failure count, so a FAILURE caused only by an afterAll no longer reads "0 failures". - The unhandled-rejection specs check that Jasmine's global error spy saw exactly their own rejection, so an unrelated error landing while they run fails them instead of being swallowed. --- .../jasmine-reporters/junit_reporter.js | 12 ++++++++++-- .../jasmine-reporters/terminal_reporter.js | 4 +++- TestRunner/app/tests/Promises.js | 19 +++++++++++++++---- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/TestRunner/app/Infrastructure/Jasmine/jasmine-reporters/junit_reporter.js b/TestRunner/app/Infrastructure/Jasmine/jasmine-reporters/junit_reporter.js index 5770f4f8..199f2a4a 100644 --- a/TestRunner/app/Infrastructure/Jasmine/jasmine-reporters/junit_reporter.js +++ b/TestRunner/app/Infrastructure/Jasmine/jasmine-reporters/junit_reporter.js @@ -32,15 +32,23 @@ pad(d.getMinutes()) + ':' + pad(d.getSeconds()); } + // XML 1.0 has no way to represent these, escaped or not, and a single one + // makes the whole report unparseable; they are written out as \uXXXX text. + var XML_FORBIDDEN = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?/g, ">") .replace(/\"/g, """) .replace(/\'/g, "'"); } function cdata(str) { - return '/g, ']]]]>') + ']]>'; + return '/g, ']]]]>') + ']]>'; } function getQualifiedFilename(path, filename, separator) { if (path && path.substr(-1) !== separator && filename.substr(0) !== separator) { diff --git a/TestRunner/app/Infrastructure/Jasmine/jasmine-reporters/terminal_reporter.js b/TestRunner/app/Infrastructure/Jasmine/jasmine-reporters/terminal_reporter.js index da9a93f9..268e9d7e 100644 --- a/TestRunner/app/Infrastructure/Jasmine/jasmine-reporters/terminal_reporter.js +++ b/TestRunner/app/Infrastructure/Jasmine/jasmine-reporters/terminal_reporter.js @@ -219,7 +219,9 @@ fail_str = totalSpecsFailed + (totalSpecsFailed === 1 ? " failure, " : " failures, "), skip_str = skipped + " skipped, ", disabled_str = disabled + " disabled in ", - summary_str = spec_str + fail_str + skip_str + disabled_str + dur + "s.", + other_str = totalOtherFailures === 0 ? "" : + totalOtherFailures + (totalOtherFailures === 1 ? " suite/run-level error, " : " suite/run-level errors, "), + summary_str = spec_str + fail_str + other_str + skip_str + disabled_str + dur + "s.", result_str = (failedOverall ? "FAILURE: " : "SUCCESS: ") + summary_str, result_color = failedOverall ? "red+bold" : "green+bold"; diff --git a/TestRunner/app/tests/Promises.js b/TestRunner/app/tests/Promises.js index 30297c03..8a01ff90 100644 --- a/TestRunner/app/tests/Promises.js +++ b/TestRunner/app/tests/Promises.js @@ -188,6 +188,13 @@ describe("unhandled rejections", function () { })(); } + // The spec's own rejection must be the only global error raised while it + // ran; anything else the spy caught would otherwise pass unnoticed. + function expectOnlyGlobalError(globalErrorSpy, reason) { + expect(globalErrorSpy.calls.count()).toBe(1); + expect(globalErrorSpy.calls.argsFor(0)[0]).toBe(reason); + } + // Wait a couple of runloop turns to confirm the hook did NOT fire. function afterQuietTurns(cb) { setTimeout(function () { @@ -195,12 +202,13 @@ describe("unhandled rejections", function () { }, 20); } - it("reports an unhandled Promise.reject", raisesGlobalErrors(function (done) { + it("reports an unhandled Promise.reject", raisesGlobalErrors(function (done, globalErrorSpy) { const reason = new Error("unhandled-promise-reject"); Promise.reject(reason); afterDrain(function () { expect(reported.length).toBeGreaterThan(0); expect(reported[0]).toBe(reason); + expectOnlyGlobalError(globalErrorSpy, reason); done(); }); })); @@ -214,7 +222,7 @@ describe("unhandled rejections", function () { }); }); - it("reports an uncaught throw from an async function", raisesGlobalErrors(function (done) { + it("reports an uncaught throw from an async function", raisesGlobalErrors(function (done, globalErrorSpy) { const reason = new Error("async-function-throw"); (async () => { throw reason; @@ -222,11 +230,12 @@ describe("unhandled rejections", function () { afterDrain(function () { expect(reported.length).toBeGreaterThan(0); expect(reported[0]).toBe(reason); + expectOnlyGlobalError(globalErrorSpy, reason); done(); }); })); - it("reports an unhandled rejection thrown from a .then callback", raisesGlobalErrors(function (done) { + it("reports an unhandled rejection thrown from a .then callback", raisesGlobalErrors(function (done, globalErrorSpy) { const reason = new Error("then-callback-throw"); Promise.resolve().then(() => { throw reason; @@ -234,11 +243,12 @@ describe("unhandled rejections", function () { afterDrain(function () { expect(reported.length).toBeGreaterThan(0); expect(reported[0]).toBe(reason); + expectOnlyGlobalError(globalErrorSpy, reason); done(); }); })); - it("ignores a late .catch attached after the rejection was already reported", raisesGlobalErrors(function (done) { + it("ignores a late .catch attached after the rejection was already reported", raisesGlobalErrors(function (done, globalErrorSpy) { const reason = new Error("late-catch"); const p = Promise.reject(reason); afterDrain(function () { @@ -248,6 +258,7 @@ describe("unhandled rejections", function () { p.catch(function () {}); afterQuietTurns(function () { expect(reported.length).toBe(1); + expectOnlyGlobalError(globalErrorSpy, reason); done(); }); });