@@ -2,95 +2,147 @@ import { LogMessageWithStack } from '../../internal/logging/log_message.js';
2
2
import { TransferredTestCaseResult , LiveTestCaseResult } from '../../internal/logging/result.js' ;
3
3
import { TestCaseRecorder } from '../../internal/logging/test_case_recorder.js' ;
4
4
import { TestQueryWithExpectation } from '../../internal/query/query.js' ;
5
+ import { timeout } from '../../util/timeout.js' ;
6
+ import { assert } from '../../util/util.js' ;
5
7
6
8
import { CTSOptions , kDefaultCTSOptions } from './options.js' ;
9
+ import { WorkerTestRunRequest } from './utils_worker.js' ;
7
10
8
- export class TestDedicatedWorker {
9
- private readonly ctsOptions : CTSOptions ;
10
- private readonly worker : Worker ;
11
- private readonly resolvers = new Map < string , ( result : LiveTestCaseResult ) => void > ( ) ;
11
+ /** Query all currently-registered service workers, and unregister them. */
12
+ function unregisterAllServiceWorkers ( ) {
13
+ void navigator . serviceWorker . getRegistrations ( ) . then ( registrations => {
14
+ for ( const registration of registrations ) {
15
+ void registration . unregister ( ) ;
16
+ }
17
+ } ) ;
18
+ }
12
19
13
- constructor ( ctsOptions ?: CTSOptions ) {
14
- this . ctsOptions = { ...( ctsOptions || kDefaultCTSOptions ) , ...{ worker : 'dedicated' } } ;
15
- const selfPath = import . meta. url ;
16
- const selfPathDir = selfPath . substring ( 0 , selfPath . lastIndexOf ( '/' ) ) ;
17
- const workerPath = selfPathDir + '/test_worker-worker.js' ;
18
- this . worker = new Worker ( workerPath , { type : 'module' } ) ;
19
- this . worker . onmessage = ev => {
20
- const query : string = ev . data . query ;
21
- const result : TransferredTestCaseResult = ev . data . result ;
22
- if ( result . logs ) {
23
- for ( const l of result . logs ) {
24
- Object . setPrototypeOf ( l , LogMessageWithStack . prototype ) ;
25
- }
20
+ // NOTE: This code runs on startup for any runtime with worker support. Here, we use that chance to
21
+ // delete any leaked service workers, and register to clean up after ourselves at shutdown.
22
+ unregisterAllServiceWorkers ( ) ;
23
+ window . addEventListener ( 'beforeunload' , ( ) => {
24
+ unregisterAllServiceWorkers ( ) ;
25
+ } ) ;
26
+
27
+ class TestBaseWorker {
28
+ protected readonly ctsOptions : CTSOptions ;
29
+ protected readonly resolvers = new Map < string , ( result : LiveTestCaseResult ) => void > ( ) ;
30
+
31
+ constructor ( worker : CTSOptions [ 'worker' ] , ctsOptions ?: CTSOptions ) {
32
+ this . ctsOptions = { ...( ctsOptions || kDefaultCTSOptions ) , ...{ worker } } ;
33
+ }
34
+
35
+ onmessage ( ev : MessageEvent ) {
36
+ const query : string = ev . data . query ;
37
+ const result : TransferredTestCaseResult = ev . data . result ;
38
+ if ( result . logs ) {
39
+ for ( const l of result . logs ) {
40
+ Object . setPrototypeOf ( l , LogMessageWithStack . prototype ) ;
26
41
}
27
- this . resolvers . get ( query ) ! ( result as LiveTestCaseResult ) ;
42
+ }
43
+ this . resolvers . get ( query ) ! ( result as LiveTestCaseResult ) ;
44
+ this . resolvers . delete ( query ) ;
28
45
29
- // MAINTENANCE_TODO(kainino0x): update the Logger with this result (or don't have a logger and
30
- // update the entire results JSON somehow at some point).
31
- } ;
46
+ // MAINTENANCE_TODO(kainino0x): update the Logger with this result (or don't have a logger and
47
+ // update the entire results JSON somehow at some point).
32
48
}
33
49
34
- async run (
50
+ async makeRequestAndRecordResult (
51
+ target : MessagePort | Worker | ServiceWorker ,
35
52
rec : TestCaseRecorder ,
36
53
query : string ,
37
- expectations : TestQueryWithExpectation [ ] = [ ]
38
- ) : Promise < void > {
39
- this . worker . postMessage ( {
54
+ expectations : TestQueryWithExpectation [ ]
55
+ ) {
56
+ const request : WorkerTestRunRequest = {
40
57
query,
41
58
expectations,
42
59
ctsOptions : this . ctsOptions ,
43
- } ) ;
60
+ } ;
61
+ target . postMessage ( request ) ;
62
+
44
63
const workerResult = await new Promise < LiveTestCaseResult > ( resolve => {
64
+ assert ( ! this . resolvers . has ( query ) , "can't request same query twice simultaneously" ) ;
45
65
this . resolvers . set ( query , resolve ) ;
46
66
} ) ;
47
67
rec . injectResult ( workerResult ) ;
48
68
}
49
69
}
50
70
71
+ export class TestDedicatedWorker extends TestBaseWorker {
72
+ private readonly worker : Worker ;
73
+
74
+ constructor ( ctsOptions ?: CTSOptions ) {
75
+ super ( 'dedicated' , ctsOptions ) ;
76
+ const selfPath = import . meta. url ;
77
+ const selfPathDir = selfPath . substring ( 0 , selfPath . lastIndexOf ( '/' ) ) ;
78
+ const workerPath = selfPathDir + '/test_worker-worker.js' ;
79
+ this . worker = new Worker ( workerPath , { type : 'module' } ) ;
80
+ this . worker . onmessage = ev => this . onmessage ( ev ) ;
81
+ }
82
+
83
+ async run (
84
+ rec : TestCaseRecorder ,
85
+ query : string ,
86
+ expectations : TestQueryWithExpectation [ ] = [ ]
87
+ ) : Promise < void > {
88
+ await this . makeRequestAndRecordResult ( this . worker , rec , query , expectations ) ;
89
+ }
90
+ }
91
+
51
92
export class TestWorker extends TestDedicatedWorker { }
52
93
53
- export class TestSharedWorker {
54
- private readonly ctsOptions : CTSOptions ;
94
+ export class TestSharedWorker extends TestBaseWorker {
55
95
private readonly port : MessagePort ;
56
- private readonly resolvers = new Map < string , ( result : LiveTestCaseResult ) => void > ( ) ;
57
96
58
97
constructor ( ctsOptions ?: CTSOptions ) {
59
- this . ctsOptions = { ... ( ctsOptions || kDefaultCTSOptions ) , ... { worker : 'shared' } } ;
98
+ super ( 'shared' , ctsOptions ) ;
60
99
const selfPath = import . meta. url ;
61
100
const selfPathDir = selfPath . substring ( 0 , selfPath . lastIndexOf ( '/' ) ) ;
62
101
const workerPath = selfPathDir + '/test_worker-worker.js' ;
63
102
const worker = new SharedWorker ( workerPath , { type : 'module' } ) ;
64
103
this . port = worker . port ;
65
104
this . port . start ( ) ;
66
- this . port . onmessage = ev => {
67
- const query : string = ev . data . query ;
68
- const result : TransferredTestCaseResult = ev . data . result ;
69
- if ( result . logs ) {
70
- for ( const l of result . logs ) {
71
- Object . setPrototypeOf ( l , LogMessageWithStack . prototype ) ;
72
- }
73
- }
74
- this . resolvers . get ( query ) ! ( result as LiveTestCaseResult ) ;
105
+ this . port . onmessage = ev => this . onmessage ( ev ) ;
106
+ }
75
107
76
- // MAINTENANCE_TODO(kainino0x): update the Logger with this result (or don't have a logger and
77
- // update the entire results JSON somehow at some point).
78
- } ;
108
+ async run (
109
+ rec : TestCaseRecorder ,
110
+ query : string ,
111
+ expectations : TestQueryWithExpectation [ ] = [ ]
112
+ ) : Promise < void > {
113
+ await this . makeRequestAndRecordResult ( this . port , rec , query , expectations ) ;
114
+ }
115
+ }
116
+
117
+ export class TestServiceWorker extends TestBaseWorker {
118
+ constructor ( ctsOptions ?: CTSOptions ) {
119
+ super ( 'service' , ctsOptions ) ;
79
120
}
80
121
81
122
async run (
82
123
rec : TestCaseRecorder ,
83
124
query : string ,
84
125
expectations : TestQueryWithExpectation [ ] = [ ]
85
126
) : Promise < void > {
86
- this . port . postMessage ( {
87
- query,
88
- expectations,
89
- ctsOptions : this . ctsOptions ,
90
- } ) ;
91
- const workerResult = await new Promise < LiveTestCaseResult > ( resolve => {
92
- this . resolvers . set ( query , resolve ) ;
127
+ const [ suite , name ] = query . split ( ':' , 2 ) ;
128
+ const fileName = name . split ( ',' ) . join ( '/' ) ;
129
+ const serviceWorkerURL = new URL (
130
+ `/out/${ suite } /webworker/${ fileName } .worker.js` ,
131
+ window . location . href
132
+ ) . toString ( ) ;
133
+
134
+ // If a registration already exists for this path, it will be ignored.
135
+ const registration = await navigator . serviceWorker . register ( serviceWorkerURL , {
136
+ type : 'module' ,
93
137
} ) ;
94
- rec . injectResult ( workerResult ) ;
138
+ // Make sure the registration we just requested is active. (We don't worry about it being
139
+ // outdated from a previous page load, because we wipe all service workers on shutdown/startup.)
140
+ while ( ! registration . active || registration . active . scriptURL !== serviceWorkerURL ) {
141
+ await new Promise ( resolve => timeout ( resolve , 0 ) ) ;
142
+ }
143
+ const serviceWorker = registration . active ;
144
+
145
+ navigator . serviceWorker . onmessage = ev => this . onmessage ( ev ) ;
146
+ await this . makeRequestAndRecordResult ( serviceWorker , rec , query , expectations ) ;
95
147
}
96
148
}
0 commit comments