ui5-test-runner 5.9.0 → 5.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +3 -4
- package/jest.config.json +1 -0
- package/package.json +9 -9
- package/src/batch.js +3 -3
- package/src/browsers.js +5 -1
- package/src/clean.js +5 -12
- package/src/cors.js +2 -2
- package/src/coverage.js +1 -1
- package/src/defaults/puppeteer.js +11 -0
- package/src/defaults/report/decompress.js +19 -0
- package/src/defaults/report.js +27 -7
- package/src/defaults/scan-ui5.js +7 -1
- package/src/endpoints.js +32 -0
- package/src/handle.js +43 -0
- package/src/inject/jest2qunit.js +289 -0
- package/src/inject/post.js +42 -7
- package/src/inject/qunit-hooks.js +22 -15
- package/src/job.js +15 -4
- package/src/npm.js +3 -1
- package/src/output.js +55 -3
- package/src/reserve.js +1 -1
- package/src/start.js +58 -20
- package/src/tests.js +50 -8
- package/src/ui5.js +105 -102
package/src/start.js
CHANGED
|
@@ -2,9 +2,7 @@ const { exec } = require('child_process')
|
|
|
2
2
|
const { stat, readFile } = require('fs/promises')
|
|
3
3
|
const { join } = require('path')
|
|
4
4
|
const { getOutput } = require('./output')
|
|
5
|
-
const
|
|
6
|
-
const { promisify } = require('util')
|
|
7
|
-
const psTree = promisify(psTreeNodeCb)
|
|
5
|
+
const pidtree = require('pidtree')
|
|
8
6
|
|
|
9
7
|
async function start (job) {
|
|
10
8
|
const { startWaitUrl: url, startWaitMethod: method } = job
|
|
@@ -40,23 +38,23 @@ async function start (job) {
|
|
|
40
38
|
}
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
let
|
|
41
|
+
let startProcessExited = false
|
|
44
42
|
output.debug('start', 'Starting command :', start)
|
|
45
|
-
const
|
|
43
|
+
const startProcess = exec(start, {
|
|
46
44
|
cwd: job.cwd,
|
|
47
45
|
windowsHide: true
|
|
48
46
|
})
|
|
49
|
-
|
|
50
|
-
output.debug('start', '
|
|
51
|
-
|
|
47
|
+
startProcess.on('close', () => {
|
|
48
|
+
output.debug('start', 'Start command process exited')
|
|
49
|
+
startProcessExited = true
|
|
52
50
|
})
|
|
53
|
-
output.monitor(
|
|
51
|
+
output.monitor(startProcess)
|
|
54
52
|
|
|
55
53
|
job.status = 'Waiting for URL to be reachable'
|
|
56
54
|
|
|
57
55
|
const begin = Date.now()
|
|
58
56
|
// eslint-disable-next-line no-unmodified-loop-condition
|
|
59
|
-
while (!
|
|
57
|
+
while (!startProcessExited && Date.now() - begin <= job.startTimeout) {
|
|
60
58
|
try {
|
|
61
59
|
const response = await fetch(url, { method })
|
|
62
60
|
output.debug('start', url, response.status)
|
|
@@ -69,20 +67,60 @@ async function start (job) {
|
|
|
69
67
|
}
|
|
70
68
|
}
|
|
71
69
|
|
|
72
|
-
if (
|
|
73
|
-
throw new Error(`Start command failed with exit code ${
|
|
70
|
+
if (startProcessExited) {
|
|
71
|
+
throw new Error(`Start command failed with exit code ${startProcess.exitCode}`)
|
|
74
72
|
}
|
|
75
73
|
|
|
76
74
|
const stop = async () => {
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
75
|
+
job.status = 'Terminating start command'
|
|
76
|
+
const begin = new Date()
|
|
77
|
+
// eslint-disable-next-line no-unmodified-loop-condition
|
|
78
|
+
while (!startProcessExited && Date.now() - begin <= job.startTimeout) {
|
|
79
|
+
output.debug('start', `Getting start command ${startProcess.pid} child processes...`)
|
|
80
|
+
const childProcesses = await pidtree(startProcess.pid, { advanced: true })
|
|
81
|
+
output.debug('start', 'Child processes', JSON.stringify(childProcesses))
|
|
82
|
+
if (childProcesses.length === 0) {
|
|
83
|
+
try {
|
|
84
|
+
output.debug('start', 'Terminating start command')
|
|
85
|
+
process.kill(startProcess.pid, 'SIGKILL')
|
|
86
|
+
} catch (e) {
|
|
87
|
+
output.debug('start', 'Failed to terminate start command', startProcess.pid, ':', e)
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
const depth = {}
|
|
91
|
+
let deepest = 1
|
|
92
|
+
let deepless = childProcesses.length
|
|
93
|
+
while (deepless > 0) {
|
|
94
|
+
for (const { ppid, pid } of childProcesses) {
|
|
95
|
+
if (ppid === startProcess.pid) {
|
|
96
|
+
depth[pid] = 1
|
|
97
|
+
--deepless
|
|
98
|
+
} else {
|
|
99
|
+
const parentDepth = depth[ppid]
|
|
100
|
+
if (parentDepth !== undefined) {
|
|
101
|
+
depth[pid] = parentDepth + 1
|
|
102
|
+
deepest = Math.max(deepest, parentDepth + 1)
|
|
103
|
+
--deepless
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
output.debug('start', 'Child processes', JSON.stringify(depth), 'terminating', deepest)
|
|
109
|
+
for (const { pid } of childProcesses) {
|
|
110
|
+
if (depth[pid] === deepest) {
|
|
111
|
+
output.debug('start', 'Terminating start child process', pid)
|
|
112
|
+
try {
|
|
113
|
+
process.kill(pid, 'SIGKILL')
|
|
114
|
+
} catch (e) {
|
|
115
|
+
output.debug('start', 'Failed to terminate start child process', pid, ':', e)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
85
119
|
}
|
|
120
|
+
await new Promise(resolve => setTimeout(resolve, 250))
|
|
121
|
+
}
|
|
122
|
+
if (!startProcessExited) {
|
|
123
|
+
output.failedToTerminateStartCommand()
|
|
86
124
|
}
|
|
87
125
|
}
|
|
88
126
|
|
package/src/tests.js
CHANGED
|
@@ -48,9 +48,13 @@ async function probeUrl (job, url) {
|
|
|
48
48
|
let scripts
|
|
49
49
|
if (job.browserCapabilities.scripts) {
|
|
50
50
|
scripts = [
|
|
51
|
+
'(function () { window[\'ui5-test-runner/probe\'] = true }())',
|
|
51
52
|
'post.js',
|
|
52
53
|
'qunit-redirect.js'
|
|
53
54
|
]
|
|
55
|
+
if (job.jest) {
|
|
56
|
+
scripts.push('jest2qunit.js')
|
|
57
|
+
}
|
|
54
58
|
}
|
|
55
59
|
await start(job, url, scripts)
|
|
56
60
|
} catch (error) {
|
|
@@ -64,10 +68,19 @@ async function runTestPage (job, url) {
|
|
|
64
68
|
try {
|
|
65
69
|
let scripts
|
|
66
70
|
if (job.browserCapabilities.scripts) {
|
|
67
|
-
scripts = [
|
|
71
|
+
scripts = []
|
|
72
|
+
if (job.qunitBatchSize) {
|
|
73
|
+
scripts.push(
|
|
74
|
+
`(function () { window['ui5-test-runner/batch'] = ${job.qunitBatchSize} }())`
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
scripts.push(
|
|
68
78
|
'post.js',
|
|
69
79
|
'qunit-hooks.js'
|
|
70
|
-
|
|
80
|
+
)
|
|
81
|
+
if (job.jest) {
|
|
82
|
+
scripts.push('jest2qunit.js')
|
|
83
|
+
}
|
|
71
84
|
if (job.coverage && !job.coverageProxy) {
|
|
72
85
|
scripts.push(
|
|
73
86
|
'opa-iframe-coverage.js',
|
|
@@ -102,13 +115,42 @@ async function process (job) {
|
|
|
102
115
|
await save(job)
|
|
103
116
|
job.testPageUrls = []
|
|
104
117
|
|
|
118
|
+
let probingRound = 0
|
|
119
|
+
const parallel = job.probeParallel || job.parallel
|
|
120
|
+
const confirmedTestPageUrls = []
|
|
105
121
|
job.status = 'Probing urls'
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
122
|
+
do {
|
|
123
|
+
++probingRound
|
|
124
|
+
if (probingRound >= 2) {
|
|
125
|
+
if (job.testPageUrls.length === 0) {
|
|
126
|
+
break
|
|
127
|
+
}
|
|
128
|
+
job.status = `Probing urls (${probingRound})`
|
|
129
|
+
job.url = job.testPageUrls.filter(url => !confirmedTestPageUrls.includes(url))
|
|
130
|
+
if (job.url.length) {
|
|
131
|
+
job.testPageUrls = []
|
|
132
|
+
} else {
|
|
133
|
+
job.testPageUrls = confirmedTestPageUrls
|
|
134
|
+
break
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
await parallelize(task(job, probeUrl), job.url, parallel)
|
|
139
|
+
} catch (e) {
|
|
140
|
+
output.genericError(e)
|
|
141
|
+
job.failed = true
|
|
142
|
+
break
|
|
143
|
+
}
|
|
144
|
+
job.testPageUrls.forEach(url => {
|
|
145
|
+
if ((job.url.includes(url) && !confirmedTestPageUrls.includes(url)) ||
|
|
146
|
+
(url.includes('/resources/sap/ui/test/starter/Test.qunit.html?testsuite=') && url.includes('&test='))
|
|
147
|
+
) {
|
|
148
|
+
confirmedTestPageUrls.push(url)
|
|
149
|
+
getOutput(job).debug('probe', 'confirmed:', url)
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
getOutput(job).debug('probe', 'from', job.url.length, 'to', job.testPageUrls.length, 'confirmed', confirmedTestPageUrls.length)
|
|
153
|
+
} while (job.deepProbe)
|
|
112
154
|
|
|
113
155
|
/* istanbul ignore else */
|
|
114
156
|
if (!job.debugProbeOnly && !job.failed) {
|
package/src/ui5.js
CHANGED
|
@@ -16,6 +16,109 @@ const buildCacheBase = job => {
|
|
|
16
16
|
return join(job.cache || '', hostName.replace(':', '_'), version || '')
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
const ui5mappings = job => {
|
|
20
|
+
const cacheBase = buildCacheBase(job)
|
|
21
|
+
const match = /\/((?:test-)?resources\/.*)/
|
|
22
|
+
const ifCacheEnabled = (request, url, match) => job.cache
|
|
23
|
+
const uncachable = {}
|
|
24
|
+
const cachingInProgress = {}
|
|
25
|
+
|
|
26
|
+
const mappings = [{
|
|
27
|
+
/* Prevent caching issues :
|
|
28
|
+
* - Caching was not possible (99% URL does not exist)
|
|
29
|
+
* - Caching is in progress (must wait for the end of the writing stream)
|
|
30
|
+
*/
|
|
31
|
+
match,
|
|
32
|
+
'if-match': ifCacheEnabled,
|
|
33
|
+
custom: async (request, response, path) => {
|
|
34
|
+
if (uncachable[path]) {
|
|
35
|
+
response.writeHead(404)
|
|
36
|
+
response.end()
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
const cachingPromise = cachingInProgress[path]
|
|
40
|
+
/* istanbul ignore next */ // Hard to reproduce
|
|
41
|
+
if (cachingPromise) {
|
|
42
|
+
await cachingPromise
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}, {
|
|
46
|
+
// UI5 from cache
|
|
47
|
+
match,
|
|
48
|
+
'if-match': ifCacheEnabled,
|
|
49
|
+
cwd: cacheBase,
|
|
50
|
+
file: '$1',
|
|
51
|
+
static: !job.debugDevMode
|
|
52
|
+
}, {
|
|
53
|
+
// UI5 caching
|
|
54
|
+
method: 'GET',
|
|
55
|
+
match,
|
|
56
|
+
'if-match': ifCacheEnabled,
|
|
57
|
+
custom: async (request, response, path) => {
|
|
58
|
+
const filePath = /([^?#]+)/.exec(unescape(path))[1] // filter URL parameters & hash (assuming resources are static)
|
|
59
|
+
const cachePath = join(cacheBase, filePath)
|
|
60
|
+
const cacheFolder = dirname(cachePath)
|
|
61
|
+
await mkdir(cacheFolder, { recursive: true })
|
|
62
|
+
if (cachingInProgress[path]) {
|
|
63
|
+
return request.url // loop back to use cached result
|
|
64
|
+
}
|
|
65
|
+
const file = createWriteStream(cachePath)
|
|
66
|
+
cachingInProgress[path] = capture(response, file)
|
|
67
|
+
.catch(reason => {
|
|
68
|
+
file.end()
|
|
69
|
+
uncachable[path] = true
|
|
70
|
+
if (response.statusCode !== 404) {
|
|
71
|
+
getOutput(job).failedToCacheUI5resource(path, response.statusCode)
|
|
72
|
+
}
|
|
73
|
+
return unlink(cachePath)
|
|
74
|
+
})
|
|
75
|
+
.then(() => {
|
|
76
|
+
delete cachingInProgress[path]
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
}, {
|
|
80
|
+
// UI5 from url
|
|
81
|
+
method: ['GET', 'HEAD'],
|
|
82
|
+
match,
|
|
83
|
+
url: `${job.ui5}/$1`,
|
|
84
|
+
'ignore-unverifiable-certificate': true
|
|
85
|
+
}]
|
|
86
|
+
|
|
87
|
+
for (let { relative, source } of job.libs) {
|
|
88
|
+
if (source.endsWith('/') || source.endsWith('\\')) {
|
|
89
|
+
source = source.substring(0, source.length - 1)
|
|
90
|
+
}
|
|
91
|
+
const relativeUrl = relative.replace(/\//g, '\\/')
|
|
92
|
+
if (source.startsWith(job.webapp)) {
|
|
93
|
+
const relativeAbsoluteUrl = '/' + relativePath(job.webapp, source).replace(/\\/g, '/')
|
|
94
|
+
getOutput(job).debug('libs', `${relative} maps to webapp sub directory, use internal redirection to ${relativeAbsoluteUrl}`)
|
|
95
|
+
mappings.unshift({
|
|
96
|
+
match: new RegExp(`\\/resources\\/${relativeUrl}(.*)`),
|
|
97
|
+
custom: (request, response, $1) => `${relativeAbsoluteUrl}${$1}`
|
|
98
|
+
})
|
|
99
|
+
} else {
|
|
100
|
+
mappings.unshift({
|
|
101
|
+
match: new RegExp(`\\/resources\\/${relativeUrl}(.*)`),
|
|
102
|
+
cwd: source,
|
|
103
|
+
file: '$1',
|
|
104
|
+
static: !job.watch && !job.debugDevMode
|
|
105
|
+
}, {
|
|
106
|
+
match: new RegExp(`\\/resources\\/${relativeUrl}(.*)`),
|
|
107
|
+
custom: (request, response, $1) => {
|
|
108
|
+
if ($1 === undefined) {
|
|
109
|
+
getOutput(job).debug('libs', `Unable to map ${relative} : $1 is undefined`)
|
|
110
|
+
} else {
|
|
111
|
+
getOutput(job).debug('libs', `Unable to map ${relative}/${$1} to ${join(source, $1)}`)
|
|
112
|
+
}
|
|
113
|
+
return 404
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return mappings
|
|
120
|
+
}
|
|
121
|
+
|
|
19
122
|
module.exports = {
|
|
20
123
|
preload: async job => {
|
|
21
124
|
const cacheBase = buildCacheBase(job)
|
|
@@ -57,110 +160,10 @@ module.exports = {
|
|
|
57
160
|
progress.done()
|
|
58
161
|
},
|
|
59
162
|
|
|
60
|
-
mappings: job => {
|
|
163
|
+
mappings: async job => {
|
|
61
164
|
if (job.disableUi5) {
|
|
62
165
|
return []
|
|
63
166
|
}
|
|
64
|
-
|
|
65
|
-
const cacheBase = buildCacheBase(job)
|
|
66
|
-
const match = /\/((?:test-)?resources\/.*)/
|
|
67
|
-
const ifCacheEnabled = (request, url, match) => job.cache
|
|
68
|
-
const uncachable = {}
|
|
69
|
-
const cachingInProgress = {}
|
|
70
|
-
|
|
71
|
-
const mappings = [{
|
|
72
|
-
/* Prevent caching issues :
|
|
73
|
-
* - Caching was not possible (99% URL does not exist)
|
|
74
|
-
* - Caching is in progress (must wait for the end of the writing stream)
|
|
75
|
-
*/
|
|
76
|
-
match,
|
|
77
|
-
'if-match': ifCacheEnabled,
|
|
78
|
-
custom: async (request, response, path) => {
|
|
79
|
-
if (uncachable[path]) {
|
|
80
|
-
response.writeHead(404)
|
|
81
|
-
response.end()
|
|
82
|
-
return
|
|
83
|
-
}
|
|
84
|
-
const cachingPromise = cachingInProgress[path]
|
|
85
|
-
/* istanbul ignore next */ // Hard to reproduce
|
|
86
|
-
if (cachingPromise) {
|
|
87
|
-
await cachingPromise
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}, {
|
|
91
|
-
// UI5 from cache
|
|
92
|
-
match,
|
|
93
|
-
'if-match': ifCacheEnabled,
|
|
94
|
-
cwd: cacheBase,
|
|
95
|
-
file: '$1',
|
|
96
|
-
static: !job.debugDevMode
|
|
97
|
-
}, {
|
|
98
|
-
// UI5 caching
|
|
99
|
-
method: 'GET',
|
|
100
|
-
match,
|
|
101
|
-
'if-match': ifCacheEnabled,
|
|
102
|
-
custom: async (request, response, path) => {
|
|
103
|
-
const filePath = /([^?#]+)/.exec(unescape(path))[1] // filter URL parameters & hash (assuming resources are static)
|
|
104
|
-
const cachePath = join(cacheBase, filePath)
|
|
105
|
-
const cacheFolder = dirname(cachePath)
|
|
106
|
-
await mkdir(cacheFolder, { recursive: true })
|
|
107
|
-
if (cachingInProgress[path]) {
|
|
108
|
-
return request.url // loop back to use cached result
|
|
109
|
-
}
|
|
110
|
-
const file = createWriteStream(cachePath)
|
|
111
|
-
cachingInProgress[path] = capture(response, file)
|
|
112
|
-
.catch(reason => {
|
|
113
|
-
file.end()
|
|
114
|
-
uncachable[path] = true
|
|
115
|
-
if (response.statusCode !== 404) {
|
|
116
|
-
getOutput(job).failedToCacheUI5resource(path, response.statusCode)
|
|
117
|
-
}
|
|
118
|
-
return unlink(cachePath)
|
|
119
|
-
})
|
|
120
|
-
.then(() => {
|
|
121
|
-
delete cachingInProgress[path]
|
|
122
|
-
})
|
|
123
|
-
}
|
|
124
|
-
}, {
|
|
125
|
-
// UI5 from url
|
|
126
|
-
method: ['GET', 'HEAD'],
|
|
127
|
-
match,
|
|
128
|
-
url: `${job.ui5}/$1`,
|
|
129
|
-
'ignore-unverifiable-certificate': true
|
|
130
|
-
}]
|
|
131
|
-
|
|
132
|
-
for (let { relative, source } of job.libs) {
|
|
133
|
-
if (source.endsWith('/') || source.endsWith('\\')) {
|
|
134
|
-
source = source.substring(0, source.length - 1)
|
|
135
|
-
}
|
|
136
|
-
const relativeUrl = relative.replace(/\//g, '\\/')
|
|
137
|
-
if (source.startsWith(job.webapp)) {
|
|
138
|
-
const relativeAbsoluteUrl = '/' + relativePath(job.webapp, source).replace(/\\/g, '/')
|
|
139
|
-
getOutput(job).debug('libs', `${relative} maps to webapp sub directory, use internal redirection to ${relativeAbsoluteUrl}`)
|
|
140
|
-
mappings.unshift({
|
|
141
|
-
match: new RegExp(`\\/resources\\/${relativeUrl}(.*)`),
|
|
142
|
-
custom: (request, response, $1) => `${relativeAbsoluteUrl}${$1}`
|
|
143
|
-
})
|
|
144
|
-
} else {
|
|
145
|
-
mappings.unshift({
|
|
146
|
-
match: new RegExp(`\\/resources\\/${relativeUrl}(.*)`),
|
|
147
|
-
cwd: source,
|
|
148
|
-
file: '$1',
|
|
149
|
-
static: !job.watch && !job.debugDevMode
|
|
150
|
-
}, {
|
|
151
|
-
match: new RegExp(`\\/resources\\/${relativeUrl}(.*)`),
|
|
152
|
-
custom: (request, response, $1) => {
|
|
153
|
-
if ($1 === undefined) {
|
|
154
|
-
getOutput(job).debug('libs', `Unable to map ${relative} : $1 is undefined`)
|
|
155
|
-
} else {
|
|
156
|
-
getOutput(job).debug('libs', `Unable to map ${relative}/${$1} to ${join(source, $1)}`)
|
|
157
|
-
}
|
|
158
|
-
return 404
|
|
159
|
-
}
|
|
160
|
-
})
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return mappings
|
|
167
|
+
return ui5mappings(job)
|
|
165
168
|
}
|
|
166
169
|
}
|