@@ -30,6 +30,45 @@ var TerminalReporter = require('../jasmine-reporters/terminal_reporter').Termina
3030 *
3131 * 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.
3232 */
33+ // Jasmine 2.0.1 decides whether a body is asynchronous from its declared
34+ // arity alone (QueueRunner: `fn.length > 0`). A zero-arity `async function`
35+ // is therefore treated as synchronous: its promise is discarded and the spec
36+ // is recorded as passed before the first `await` resumes. Assertions then run
37+ // after the fact — attributed to whichever spec is current by then — and
38+ // rejections escape as unhandled.
39+ //
40+ // Give those bodies the `done` callback Jasmine needs so it waits for the
41+ // promise to settle. Only `async function` bodies are rewritten: handing a
42+ // `done` to every zero-arity body would move the entire suite's synchronous
43+ // specs onto the async path, which changes when the runloop turns between
44+ // them. A synchronous throw is left to propagate — QueueRunner's attemptAsync
45+ // catches it and attributes it to the right spec.
46+ var AsyncFunction = ( async function ( ) { } ) . constructor ;
47+
48+ function awaitPromiseResult ( func ) {
49+ if ( typeof func !== 'function' || func . length > 0 ||
50+ ! ( func instanceof AsyncFunction ) ) {
51+ return func ;
52+ }
53+
54+ return function ( done ) {
55+ var result = func . call ( this ) ;
56+
57+ if ( ! result || typeof result . then !== 'function' ) {
58+ done ( ) ;
59+ return ;
60+ }
61+
62+ result . then ( function ( ) {
63+ done ( ) ;
64+ } , function ( error ) {
65+ var detail = ( error && ( error . stack || error . message ) ) || String ( error ) ;
66+ env . expect ( 'promise rejected: ' + detail ) . toBeUndefined ( ) ;
67+ done ( ) ;
68+ } ) ;
69+ } ;
70+ }
71+
3372 var jasmineInterface = {
3473 describe : function ( description , specDefinitions ) {
3574 return env . describe ( description , specDefinitions ) ;
@@ -40,19 +79,19 @@ var TerminalReporter = require('../jasmine-reporters/terminal_reporter').Termina
4079 } ,
4180
4281 it : function ( desc , func ) {
43- return env . it ( desc , func ) ;
82+ return env . it ( desc , awaitPromiseResult ( func ) ) ;
4483 } ,
4584
4685 xit : function ( desc , func ) {
47- return env . xit ( desc , func ) ;
86+ return env . xit ( desc , awaitPromiseResult ( func ) ) ;
4887 } ,
4988
5089 beforeEach : function ( beforeEachFunction ) {
51- return env . beforeEach ( beforeEachFunction ) ;
90+ return env . beforeEach ( awaitPromiseResult ( beforeEachFunction ) ) ;
5291 } ,
5392
5493 afterEach : function ( afterEachFunction ) {
55- return env . afterEach ( afterEachFunction ) ;
94+ return env . afterEach ( awaitPromiseResult ( afterEachFunction ) ) ;
5695 } ,
5796
5897 expect : function ( actual ) {
@@ -63,6 +102,15 @@ var TerminalReporter = require('../jasmine-reporters/terminal_reporter').Termina
63102 return env . pending ( ) ;
64103 } ,
65104
105+ fail : function ( error ) {
106+ // Jasmine 2.0 fail() – mark current spec as failed with given message
107+ var message = error ;
108+ if ( error && typeof error === 'object' ) {
109+ message = error . message || String ( error ) ;
110+ }
111+ throw new Error ( message ) ;
112+ } ,
113+
66114 spyOn : function ( obj , methodName ) {
67115 return env . spyOn ( obj , methodName ) ;
68116 } ,
@@ -124,7 +172,67 @@ var TerminalReporter = require('../jasmine-reporters/terminal_reporter').Termina
124172 } ) ) ;
125173 jasmine . getEnv ( ) . addReporter ( new JUnitXmlReporter ( ) ) ;
126174
175+ // Progress beacon: fire-and-forget GET of each SUITE name to the XCTest host's
176+ // /progress endpoint. When the run hangs (no JUnit report is ever POSTed), this
177+ // lets the Swift harness name the suite that was running when the JS thread
178+ // stalled. Async via NSURLSession so it never blocks the JS thread; best-effort.
179+ //
180+ // SUITE-level only (not specStarted): the minimal Embassy test server crashed
181+ // in handleNewConnection() under the hundreds-of-connections-per-run flood that
182+ // a per-spec beacon produced on CI's tighter fd limits. Suites number in the
183+ // dozens and fire at suite boundaries, which stays well within those limits
184+ // while still pinpointing a hang to its suite.
185+ ( function installProgressBeacon ( ) {
186+ try {
187+ var reportUrl = NSProcessInfo . processInfo . environment . objectForKey ( "REPORT_BASEURL" ) ;
188+ if ( ! reportUrl ) { return ; }
189+ var origin = new URL ( String ( reportUrl ) ) . origin ;
190+ var beacon = function ( name ) {
191+ try {
192+ var url = origin + "/progress?spec=" + encodeURIComponent ( name || "" ) ;
193+ var req = NSMutableURLRequest . requestWithURL ( NSURL . URLWithString ( url ) ) ;
194+ req . HTTPMethod = "GET" ;
195+ req . timeoutInterval = 2.0 ;
196+ NSURLSession . sharedSession . dataTaskWithRequestCompletionHandler ( req , function ( ) { } ) . resume ( ) ;
197+ } catch ( e ) { /* best-effort */ }
198+ } ;
199+ jasmine . getEnv ( ) . addReporter ( {
200+ suiteStarted : function ( r ) { beacon ( "[suite] " + ( r && r . fullName ? r . fullName : "" ) ) ; }
201+ } ) ;
202+ } catch ( e ) { /* best-effort */ }
203+ } ( ) ) ;
204+
205+ // Quarantined specs — skipped at the harness level (no submodule edit).
206+ // Matched by substring against the spec's full name.
207+ //
208+ // "no crash during or after runtime teardown": the TNS Workers teardown stress
209+ // spec triggers an AB-BA deadlock between the main and a worker V8 isolate lock
210+ // — the main thread holds the main isolate lock and waits on a worker isolate
211+ // (a nil-queue NSNotification observer block the worker registered), while the
212+ // worker holds its isolate lock and waits on the main isolate (a main-extended
213+ // class's +initialize; ClassBuilder.mm). It only manifests when those windows
214+ // overlap, which happens reliably on constrained CI runners but never on fast
215+ // multi-core dev machines. Tracking + native stacks:
216+ // https://github.com/NativeScript/ios/issues/397
217+ // See TestRunnerTests/QUARANTINED_TESTS.md for the full rationale + how to
218+ // re-enable each of these.
219+ var QUARANTINED_SPEC_SUBSTRINGS = [
220+ // Worker-teardown stress spec: AB-BA cross-isolate lock deadlock on
221+ // constrained CI cores (github.com/NativeScript/ios/issues/397).
222+ "no crash during or after runtime teardown" ,
223+ // HTTP-ESM identity specs: require the in-runner Embassy test server to
224+ // answer the runtime's synchronous (NSURLConnection) GET, which it can't
225+ // (getPeerName EINVAL / no response). The loader itself works; this is a
226+ // test-harness limitation. See QUARANTINED_TESTS.md.
227+ "URL Key Canonicalization" ,
228+ ] ;
127229 env . specFilter = function ( spec ) {
230+ var fullName = spec . getFullName ( ) ;
231+ for ( var i = 0 ; i < QUARANTINED_SPEC_SUBSTRINGS . length ; i ++ ) {
232+ if ( fullName . indexOf ( QUARANTINED_SPEC_SUBSTRINGS [ i ] ) !== - 1 ) {
233+ return false ;
234+ }
235+ }
128236 return true ;
129237 } ;
130238
0 commit comments