Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 2 additions & 1 deletion .xcode.env
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
85 changes: 85 additions & 0 deletions TestRunner/app/Infrastructure/Jasmine/boot.js
Original file line number Diff line number Diff line change
@@ -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));
});
};
29 changes: 29 additions & 0 deletions TestRunner/app/Infrastructure/Jasmine/globalErrorSpy.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
};
};
237 changes: 0 additions & 237 deletions TestRunner/app/Infrastructure/Jasmine/jasmine-2.0.1/boot.js

This file was deleted.

Loading
Loading