ui5-test-runner 4.2.1 → 4.3.1
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 +2 -1
- package/package.json +5 -5
- package/src/browsers.js +11 -4
- package/src/job.js +3 -0
- package/src/output.js +76 -57
- package/src/qunit-hooks.js +9 -3
- package/src/reserve.js +2 -2
- package/src/symbols.js +3 -1
- package/src/tests.js +12 -1
- package/src/tools.js +18 -6
- package/src/ui5.js +141 -73
package/index.js
CHANGED
|
@@ -58,10 +58,11 @@ async function main () {
|
|
|
58
58
|
job.port = port
|
|
59
59
|
send({ msg: 'ready', port: job.port })
|
|
60
60
|
output.serving(url)
|
|
61
|
+
output.reportOnJobProgress()
|
|
61
62
|
if (job.serveOnly) {
|
|
63
|
+
job.status = 'Serving'
|
|
62
64
|
return
|
|
63
65
|
}
|
|
64
|
-
output.reportOnJobProgress()
|
|
65
66
|
await notifyAndExecuteTests(job)
|
|
66
67
|
if (job.watch) {
|
|
67
68
|
delete job.start
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ui5-test-runner",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.1",
|
|
4
4
|
"description": "Standalone test runner for UI5",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -66,19 +66,19 @@
|
|
|
66
66
|
"mime": "^3.0.0",
|
|
67
67
|
"punybind": "^1.2.1",
|
|
68
68
|
"punyexpr": "^1.0.4",
|
|
69
|
-
"reserve": "^1.15.
|
|
69
|
+
"reserve": "^1.15.8"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@openui5/types": "^1.121.0",
|
|
73
73
|
"@ui5/cli": "^3.9.1",
|
|
74
74
|
"@ui5/middleware-code-coverage": "^1.1.1",
|
|
75
75
|
"jest": "^29.7.0",
|
|
76
|
-
"nock": "^13.5.
|
|
76
|
+
"nock": "^13.5.4",
|
|
77
77
|
"nyc": "^15.1.0",
|
|
78
78
|
"standard": "^17.1.0",
|
|
79
79
|
"start-server-and-test": "^2.0.3",
|
|
80
|
-
"typescript": "^5.
|
|
81
|
-
"ui5-tooling-transpile": "^3.3.
|
|
80
|
+
"typescript": "^5.4.2",
|
|
81
|
+
"ui5-tooling-transpile": "^3.3.7"
|
|
82
82
|
},
|
|
83
83
|
"optionalDependencies": {
|
|
84
84
|
"fsevents": "^2.3.3"
|
package/src/browsers.js
CHANGED
|
@@ -5,7 +5,7 @@ const { join } = require('path')
|
|
|
5
5
|
const { writeFile, readFile, open, stat, unlink } = require('fs/promises')
|
|
6
6
|
const { recreateDir, filename, allocPromise } = require('./tools')
|
|
7
7
|
const { getPageTimeout, pageTimedOut } = require('./timeout')
|
|
8
|
-
const { getOutput } = require('./output')
|
|
8
|
+
const { getOutput, newProgress } = require('./output')
|
|
9
9
|
const { resolvePackage } = require('./npm')
|
|
10
10
|
const { UTRError } = require('./error')
|
|
11
11
|
const { $browsers } = require('./symbols')
|
|
@@ -119,11 +119,14 @@ async function start (job, url, scripts = []) {
|
|
|
119
119
|
resolvedScripts.unshift(`window['ui5-test-runner/base-host'] = 'http://localhost:${job.port}'
|
|
120
120
|
`)
|
|
121
121
|
}
|
|
122
|
+
const progress = newProgress(job)
|
|
123
|
+
progress.label = url
|
|
122
124
|
const pageBrowser = {
|
|
123
125
|
url,
|
|
124
126
|
reportDir,
|
|
125
127
|
scripts: resolvedScripts,
|
|
126
|
-
retry: 0
|
|
128
|
+
retry: 0,
|
|
129
|
+
progress
|
|
127
130
|
}
|
|
128
131
|
const { promise, resolve, reject } = allocPromise()
|
|
129
132
|
pageBrowser.done = value => {
|
|
@@ -136,8 +139,12 @@ async function start (job, url, scripts = []) {
|
|
|
136
139
|
}
|
|
137
140
|
job[$browsers][url] = pageBrowser
|
|
138
141
|
await run(job, pageBrowser)
|
|
139
|
-
|
|
140
|
-
|
|
142
|
+
try {
|
|
143
|
+
await promise
|
|
144
|
+
} finally {
|
|
145
|
+
progress.done()
|
|
146
|
+
output.browserStopped(url)
|
|
147
|
+
}
|
|
141
148
|
}
|
|
142
149
|
|
|
143
150
|
async function run (job, pageBrowser) {
|
package/src/job.js
CHANGED
|
@@ -138,6 +138,7 @@ function getCommand (cwd) {
|
|
|
138
138
|
.option('--libs <lib...>', '[💻] Library mapping (<relative>=<path> or <path>)', arrayOf(lib))
|
|
139
139
|
.option('--mappings <mapping...>', '[💻] Custom mapping (<match>=<file|url>(<config>))', arrayOf(mapping))
|
|
140
140
|
.option('--cache <path>', '[💻] Cache UI5 resources locally in the given folder (empty to disable)')
|
|
141
|
+
.option('--preload <library...>', '[💻] Preload UI5 libraries in the cache folder (only if --cache is used)', arrayOf(string))
|
|
141
142
|
.option('--testsuite <path>', '[💻] Path of the testsuite file (relative to webapp, URL parameters are supported)', 'test/testsuite.qunit.html')
|
|
142
143
|
.option('-w, --watch [flag]', '[💻] Monitor the webapp folder and re-execute tests on change', boolean, false)
|
|
143
144
|
|
|
@@ -225,6 +226,8 @@ function finalize (job) {
|
|
|
225
226
|
.forEach(setting => updateToAbsolute(setting))
|
|
226
227
|
if (job.cache) {
|
|
227
228
|
updateToAbsolute('cache')
|
|
229
|
+
} else if (job.preload) {
|
|
230
|
+
throw new Error('--preload cannot be used without --cache')
|
|
228
231
|
}
|
|
229
232
|
if (job.alternateNpmPath) {
|
|
230
233
|
checkAccess({ path: job.alternateNpmPath, label: 'Alternate NPM path' })
|
package/src/output.js
CHANGED
|
@@ -5,9 +5,8 @@ const { join } = require('path')
|
|
|
5
5
|
const { memoryUsage } = require('process')
|
|
6
6
|
const {
|
|
7
7
|
$browsers,
|
|
8
|
-
$
|
|
9
|
-
$
|
|
10
|
-
$testPagesCompleted
|
|
8
|
+
$statusProgressCount,
|
|
9
|
+
$statusProgressTotal
|
|
11
10
|
} = require('./symbols')
|
|
12
11
|
const { filename, noop, pad } = require('./tools')
|
|
13
12
|
|
|
@@ -15,6 +14,7 @@ const inJest = typeof jest !== 'undefined'
|
|
|
15
14
|
const interactive = process.stdout.columns !== undefined && !inJest
|
|
16
15
|
const $output = Symbol('output')
|
|
17
16
|
const $outputStart = Symbol('output-start')
|
|
17
|
+
const $outputProgress = Symbol('output-progress')
|
|
18
18
|
|
|
19
19
|
if (!interactive) {
|
|
20
20
|
const UTF8_BOM_CODE = '\ufeff'
|
|
@@ -41,66 +41,90 @@ const formatTime = duration => {
|
|
|
41
41
|
|
|
42
42
|
const getElapsed = job => formatTime(Date.now() - job[$outputStart])
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
function clean (job) {
|
|
44
|
+
function * buildCleanSequence (job) {
|
|
47
45
|
const { lines } = job[$output]
|
|
48
46
|
if (!lines) {
|
|
49
47
|
return
|
|
50
48
|
}
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
yield '\x1b[?12l'
|
|
50
|
+
yield `\x1b[${lines.toString()}F`
|
|
53
51
|
for (let line = 0; line < lines; ++line) {
|
|
54
52
|
if (line > 1) {
|
|
55
|
-
|
|
53
|
+
yield '\x1b[1E'
|
|
56
54
|
}
|
|
57
|
-
|
|
55
|
+
yield ''.padEnd(process.stdout.columns, ' ')
|
|
58
56
|
}
|
|
59
57
|
if (lines > 1) {
|
|
60
|
-
|
|
58
|
+
yield `\x1b[${(lines - 1).toString()}F`
|
|
61
59
|
} else {
|
|
62
|
-
|
|
60
|
+
yield '\x1b[1G'
|
|
63
61
|
}
|
|
64
62
|
}
|
|
65
63
|
|
|
64
|
+
function clean (job) {
|
|
65
|
+
process.stdout.write([...buildCleanSequence(job)].join(''))
|
|
66
|
+
}
|
|
67
|
+
|
|
66
68
|
const BAR_WIDTH = 10
|
|
67
69
|
|
|
68
|
-
function bar (ratio, msg) {
|
|
69
|
-
|
|
70
|
+
function * bar (ratio, msg) {
|
|
71
|
+
yield '['
|
|
70
72
|
if (typeof ratio === 'string') {
|
|
71
73
|
if (ratio.length > BAR_WIDTH) {
|
|
72
|
-
|
|
74
|
+
yield ratio.substring(0, BAR_WIDTH - 3)
|
|
75
|
+
yield '...'
|
|
73
76
|
} else {
|
|
74
77
|
const padded = ratio.padStart(BAR_WIDTH - Math.floor((BAR_WIDTH - ratio.length) / 2), '-').padEnd(BAR_WIDTH, '-')
|
|
75
|
-
|
|
78
|
+
yield padded
|
|
76
79
|
}
|
|
77
|
-
|
|
80
|
+
yield '] '
|
|
78
81
|
} else {
|
|
79
82
|
const filled = Math.floor(BAR_WIDTH * ratio)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
yield ''.padEnd(filled, '\u2588')
|
|
84
|
+
yield ''.padEnd(BAR_WIDTH - filled, '\u2591')
|
|
85
|
+
yield '] '
|
|
86
|
+
yield Math.floor(100 * ratio).toString().padStart(3, ' ').toString()
|
|
87
|
+
yield '%'
|
|
83
88
|
}
|
|
84
|
-
|
|
89
|
+
yield ' '
|
|
85
90
|
const spaceLeft = process.stdout.columns - BAR_WIDTH - 14
|
|
86
91
|
if (msg.length > spaceLeft) {
|
|
87
|
-
|
|
92
|
+
yield '...'
|
|
93
|
+
yield msg.substring(msg.length - spaceLeft - 3)
|
|
88
94
|
} else {
|
|
89
|
-
|
|
95
|
+
yield msg
|
|
90
96
|
}
|
|
91
|
-
|
|
97
|
+
yield '\n'
|
|
92
98
|
}
|
|
93
99
|
|
|
94
100
|
const TICKS = ['\u280b', '\u2819', '\u2839', '\u2838', '\u283c', '\u2834', '\u2826', '\u2827', '\u2807', '\u280f']
|
|
95
101
|
|
|
102
|
+
class Progress {
|
|
103
|
+
#job = undefined
|
|
104
|
+
|
|
105
|
+
constructor (job) {
|
|
106
|
+
this.#job = job
|
|
107
|
+
if (!job[$outputProgress]) {
|
|
108
|
+
job[$outputProgress] = []
|
|
109
|
+
}
|
|
110
|
+
job[$outputProgress].push(this)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
done () {
|
|
114
|
+
const pos = this.#job[$outputProgress].indexOf(this)
|
|
115
|
+
this.#job[$outputProgress].splice(pos, 1)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
96
119
|
function progress (job, cleanFirst = true) {
|
|
120
|
+
const sequence = []
|
|
97
121
|
if (interactive) {
|
|
98
122
|
if (cleanFirst) {
|
|
99
|
-
|
|
123
|
+
sequence.push(...buildCleanSequence(job))
|
|
100
124
|
}
|
|
101
125
|
} else {
|
|
102
126
|
if (job[$browsers]) {
|
|
103
|
-
|
|
127
|
+
sequence.push(`${getElapsed(job)} │ Progress\n──────┴──────────\n`)
|
|
104
128
|
} else {
|
|
105
129
|
return
|
|
106
130
|
}
|
|
@@ -112,41 +136,28 @@ function progress (job, cleanFirst = true) {
|
|
|
112
136
|
++output.lines
|
|
113
137
|
const { rss, heapTotal, heapUsed, external, arrayBuffers } = memoryUsage()
|
|
114
138
|
const fmt = size => `${(size / (1024 * 1024)).toFixed(2)}M`
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
if (job[$probeUrlsStarted]) {
|
|
118
|
-
const total = job.url.length + job.testPageUrls.length
|
|
119
|
-
if (job[$testPagesCompleted] !== total) {
|
|
120
|
-
progressRatio = (job[$probeUrlsCompleted] + (job[$testPagesCompleted] || 0)) / total
|
|
121
|
-
}
|
|
139
|
+
sequence.push(`MEM r:${fmt(rss)}, h:${fmt(heapUsed)}/${fmt(heapTotal)}, x:${fmt(external)}, a:${fmt(arrayBuffers)}\n`)
|
|
122
140
|
}
|
|
123
|
-
if (job[$
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (page) {
|
|
131
|
-
const { count, passed, failed } = page
|
|
132
|
-
if (count) {
|
|
133
|
-
const progress = passed + failed
|
|
134
|
-
bar(progress / count, pageUrl)
|
|
135
|
-
starting = false
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
if (starting) {
|
|
140
|
-
bar('starting', pageUrl)
|
|
141
|
+
if (job[$outputProgress]) {
|
|
142
|
+
output.lines += job[$outputProgress].length
|
|
143
|
+
job[$outputProgress].forEach(({ count, total, label }) => {
|
|
144
|
+
if (total !== undefined) {
|
|
145
|
+
sequence.push(...bar((count || 0) / (total || 1), label))
|
|
146
|
+
} else {
|
|
147
|
+
sequence.push(...bar('starting', label))
|
|
141
148
|
}
|
|
142
149
|
})
|
|
143
150
|
}
|
|
151
|
+
if (job[$statusProgressTotal]) {
|
|
152
|
+
progressRatio = (job[$statusProgressCount] || 0) / job[$statusProgressTotal]
|
|
153
|
+
}
|
|
144
154
|
const status = `${TICKS[++output.lastTick % TICKS.length]} ${job.status}`
|
|
145
155
|
if (progressRatio !== undefined) {
|
|
146
|
-
bar(progressRatio, status)
|
|
156
|
+
sequence.push(...bar(progressRatio, status))
|
|
147
157
|
} else {
|
|
148
|
-
|
|
158
|
+
sequence.push(status, '\n')
|
|
149
159
|
}
|
|
160
|
+
process.stdout.write(sequence.join(''))
|
|
150
161
|
}
|
|
151
162
|
|
|
152
163
|
function output (job, ...args) {
|
|
@@ -250,13 +261,15 @@ function build (job) {
|
|
|
250
261
|
log(job, p80()`Server running at ${pad.lt(url)}`)
|
|
251
262
|
},
|
|
252
263
|
|
|
253
|
-
debug:
|
|
264
|
+
debug: (moduleSpecifier, ...args) => {
|
|
254
265
|
const [mainModule] = moduleSpecifier.split('/')
|
|
255
266
|
if (job.debugVerbose && (job.debugVerbose.includes(moduleSpecifier) || job.debugVerbose.includes(mainModule))) {
|
|
256
|
-
|
|
257
|
-
|
|
267
|
+
wrap(() => {
|
|
268
|
+
console.log(`🐞${moduleSpecifier}`, ...args)
|
|
269
|
+
output(job, `🐞${moduleSpecifier}`, ...args)
|
|
270
|
+
})()
|
|
258
271
|
}
|
|
259
|
-
}
|
|
272
|
+
},
|
|
260
273
|
|
|
261
274
|
redirected: wrap(({ method, url, statusCode, timeSpent }) => {
|
|
262
275
|
if (url.startsWith('/_/progress')) {
|
|
@@ -282,6 +295,8 @@ function build (job) {
|
|
|
282
295
|
method(job, '')
|
|
283
296
|
method(job, text)
|
|
284
297
|
method(job, '──────┴'.padEnd(text.length, '─'))
|
|
298
|
+
delete job[$statusProgressCount]
|
|
299
|
+
delete job[$statusProgressTotal]
|
|
285
300
|
},
|
|
286
301
|
|
|
287
302
|
watching: wrap(path => {
|
|
@@ -553,5 +568,9 @@ module.exports = {
|
|
|
553
568
|
job[$output] = build(job)
|
|
554
569
|
}
|
|
555
570
|
return job[$output]
|
|
571
|
+
},
|
|
572
|
+
|
|
573
|
+
newProgress (job) {
|
|
574
|
+
return new Progress(job)
|
|
556
575
|
}
|
|
557
576
|
}
|
package/src/qunit-hooks.js
CHANGED
|
@@ -6,6 +6,7 @@ const { UTRError } = require('./error')
|
|
|
6
6
|
const { getOutput } = require('./output')
|
|
7
7
|
const { basename } = require('path')
|
|
8
8
|
const { filename, stripUrlHash, allocPromise } = require('./tools')
|
|
9
|
+
const { $browsers } = require('./symbols')
|
|
9
10
|
const $doneResolve = Symbol('doneResolve')
|
|
10
11
|
const $doneTimeout = Symbol('doneTimeout')
|
|
11
12
|
|
|
@@ -42,11 +43,12 @@ function merge (targetModules, modules) {
|
|
|
42
43
|
function get (job, urlWithHash, { testId, modules, isOpa } = {}) {
|
|
43
44
|
const url = stripUrlHash(urlWithHash)
|
|
44
45
|
const page = job.qunitPages && job.qunitPages[url]
|
|
46
|
+
const progress = (job[$browsers] && job[$browsers][url] && job[$browsers][url].progress) || { total: 0, count: 0 }
|
|
45
47
|
if (!page) {
|
|
46
48
|
error(job, url, `No QUnit page found for ${urlWithHash}`)
|
|
47
49
|
}
|
|
48
50
|
if (modules && modules.length) {
|
|
49
|
-
page.count = merge(page.modules, modules)
|
|
51
|
+
progress.total = page.count = merge(page.modules, modules)
|
|
50
52
|
}
|
|
51
53
|
if (!page.isOpa && isOpa) {
|
|
52
54
|
page.isOpa = true
|
|
@@ -67,7 +69,7 @@ function get (job, urlWithHash, { testId, modules, isOpa } = {}) {
|
|
|
67
69
|
invalidTestId(job, url, testId)
|
|
68
70
|
}
|
|
69
71
|
}
|
|
70
|
-
return { url, page, testModule, test }
|
|
72
|
+
return { url, page, testModule, test, progress }
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
async function done (job, urlWithHash, report) {
|
|
@@ -117,6 +119,9 @@ module.exports = {
|
|
|
117
119
|
modules
|
|
118
120
|
}
|
|
119
121
|
job.qunitPages[url] = qunitPage
|
|
122
|
+
const { progress } = get(job, url)
|
|
123
|
+
progress.count = 0
|
|
124
|
+
progress.total = totalTests
|
|
120
125
|
},
|
|
121
126
|
|
|
122
127
|
async testStart (job, urlWithHash, details) {
|
|
@@ -155,10 +160,11 @@ module.exports = {
|
|
|
155
160
|
getOutput(job).debug('qunit/testDone', 'testDone', urlWithHash, details)
|
|
156
161
|
const { name, module, testId, assertions, ...report } = details
|
|
157
162
|
const { failed } = report
|
|
158
|
-
const { url, page, test } = get(job, urlWithHash, { testId })
|
|
163
|
+
const { url, page, test, progress } = get(job, urlWithHash, { testId })
|
|
159
164
|
if (!test) {
|
|
160
165
|
invalidTestId(job, url, testId)
|
|
161
166
|
}
|
|
167
|
+
++progress.count
|
|
162
168
|
if (failed) {
|
|
163
169
|
if (job.browserCapabilities.screenshot) {
|
|
164
170
|
try {
|
package/src/reserve.js
CHANGED
|
@@ -2,7 +2,7 @@ const { join } = require('path')
|
|
|
2
2
|
const cors = require('./cors')
|
|
3
3
|
const endpoints = require('./endpoints')
|
|
4
4
|
const { mappings: coverage } = require('./coverage')
|
|
5
|
-
const ui5 = require('./ui5')
|
|
5
|
+
const { mappings: ui5 } = require('./ui5')
|
|
6
6
|
const { check } = require('reserve')
|
|
7
7
|
const unhandled = require('./unhandled')
|
|
8
8
|
|
|
@@ -20,6 +20,6 @@ module.exports = async job => check({
|
|
|
20
20
|
strict: true,
|
|
21
21
|
'ignore-if-not-found': true
|
|
22
22
|
},
|
|
23
|
-
...unhandled(job)
|
|
23
|
+
...job.serveOnly ? [{ status: 404 }] : unhandled(job)
|
|
24
24
|
]
|
|
25
25
|
})
|
package/src/symbols.js
CHANGED
|
@@ -6,5 +6,7 @@ module.exports = {
|
|
|
6
6
|
$testPagesCompleted: Symbol('testPagesCompleted'),
|
|
7
7
|
$valueSources: Symbol('valueSources'),
|
|
8
8
|
$remoteOnLegacy: Symbol('remoteOnLegacy'),
|
|
9
|
-
$proxifiedUrls: Symbol('proxifiedUrls')
|
|
9
|
+
$proxifiedUrls: Symbol('proxifiedUrls'),
|
|
10
|
+
$statusProgressCount: Symbol('statusProgressCount'),
|
|
11
|
+
$statusProgressTotal: Symbol('statusProgressTotal')
|
|
10
12
|
}
|
package/src/tests.js
CHANGED
|
@@ -10,10 +10,13 @@ const {
|
|
|
10
10
|
$probeUrlsStarted,
|
|
11
11
|
$probeUrlsCompleted,
|
|
12
12
|
$testPagesStarted,
|
|
13
|
-
$testPagesCompleted
|
|
13
|
+
$testPagesCompleted,
|
|
14
|
+
$statusProgressTotal,
|
|
15
|
+
$statusProgressCount
|
|
14
16
|
} = require('./symbols')
|
|
15
17
|
const { UTRError } = require('./error')
|
|
16
18
|
const { $proxifiedUrls } = require('./symbols')
|
|
19
|
+
const { preload } = require('./ui5')
|
|
17
20
|
|
|
18
21
|
async function run (task, job) {
|
|
19
22
|
const {
|
|
@@ -25,6 +28,10 @@ async function run (task, job) {
|
|
|
25
28
|
const output = getOutput(job)
|
|
26
29
|
const urls = job[urlsMember]
|
|
27
30
|
const { length } = urls
|
|
31
|
+
if (job[$statusProgressTotal] === undefined) {
|
|
32
|
+
job[$statusProgressTotal] = length
|
|
33
|
+
job[$statusProgressCount] = 0
|
|
34
|
+
}
|
|
28
35
|
if (job[completedMember] === length) {
|
|
29
36
|
return
|
|
30
37
|
}
|
|
@@ -47,6 +54,7 @@ async function run (task, job) {
|
|
|
47
54
|
}
|
|
48
55
|
}
|
|
49
56
|
++job[completedMember]
|
|
57
|
+
++job[$statusProgressCount]
|
|
50
58
|
return run(task, job)
|
|
51
59
|
}
|
|
52
60
|
|
|
@@ -158,6 +166,9 @@ module.exports = {
|
|
|
158
166
|
async execute (job) {
|
|
159
167
|
await recreateDir(job.reportDir)
|
|
160
168
|
getOutput(job).version()
|
|
169
|
+
if (job.preload) {
|
|
170
|
+
await preload(job)
|
|
171
|
+
}
|
|
161
172
|
await probe(job)
|
|
162
173
|
if (job.mode !== 'url') {
|
|
163
174
|
job.url = [`http://localhost:${job.port}/${job.testsuite}`]
|
package/src/tools.js
CHANGED
|
@@ -127,6 +127,7 @@ function allocPromise () {
|
|
|
127
127
|
|
|
128
128
|
async function download (url, filename) {
|
|
129
129
|
const { hostname, port, origin } = new URL(url)
|
|
130
|
+
const error = reason => new Error(`Error downloading ${url} to ${filename}, ${reason}`)
|
|
130
131
|
const options = {
|
|
131
132
|
hostname,
|
|
132
133
|
port,
|
|
@@ -139,16 +140,27 @@ async function download (url, filename) {
|
|
|
139
140
|
const { promise, resolve, reject } = allocPromise()
|
|
140
141
|
const request = protocol.request(options, async response => {
|
|
141
142
|
if (response.statusCode !== 200) {
|
|
142
|
-
reject(response.statusCode)
|
|
143
|
+
reject(error(`server responded with ${response.statusCode}`))
|
|
143
144
|
output.end()
|
|
144
|
-
|
|
145
|
+
try {
|
|
146
|
+
await unlink(filename)
|
|
147
|
+
} catch (e) {
|
|
148
|
+
// ignore
|
|
149
|
+
}
|
|
145
150
|
return
|
|
146
151
|
}
|
|
147
|
-
response.on('error',
|
|
148
|
-
|
|
149
|
-
|
|
152
|
+
response.on('error', reason => {
|
|
153
|
+
reject(error(`response failed : ${reason.toString()}`))
|
|
154
|
+
})
|
|
155
|
+
response
|
|
156
|
+
.pipe(output)
|
|
157
|
+
.on('finish', () => {
|
|
158
|
+
resolve(filename)
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
request.on('error', reason => {
|
|
162
|
+
reject(error(`request failed : ${reason.toString()}`))
|
|
150
163
|
})
|
|
151
|
-
request.on('error', reject)
|
|
152
164
|
request.end()
|
|
153
165
|
return promise
|
|
154
166
|
}
|
package/src/ui5.js
CHANGED
|
@@ -2,90 +2,158 @@
|
|
|
2
2
|
|
|
3
3
|
const { dirname, join } = require('path')
|
|
4
4
|
const { createWriteStream } = require('fs')
|
|
5
|
-
const { mkdir, unlink } = require('fs').promises
|
|
5
|
+
const { mkdir, unlink, stat } = require('fs').promises
|
|
6
6
|
const { capture } = require('reserve')
|
|
7
|
-
const { getOutput } = require('./output')
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if (job.disableUi5) {
|
|
11
|
-
return []
|
|
12
|
-
}
|
|
7
|
+
const { getOutput, newProgress } = require('./output')
|
|
8
|
+
const { download, allocPromise } = require('./tools')
|
|
9
|
+
const { $statusProgressCount, $statusProgressTotal } = require('./symbols')
|
|
13
10
|
|
|
11
|
+
const buildCacheBase = job => {
|
|
14
12
|
const [, hostName] = /https?:\/\/([^/]*)/.exec(job.ui5)
|
|
15
13
|
const [, version] = /(\d+\.\d+\.\d+)?$/.exec(job.ui5)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const ifCacheEnabled = (request, url, match) => job.cache ? match : false
|
|
19
|
-
const uncachable = {}
|
|
20
|
-
const cachingInProgress = {}
|
|
14
|
+
return join(job.cache || '', hostName.replace(':', '_'), version || '')
|
|
15
|
+
}
|
|
21
16
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const cachingPromise = cachingInProgress[path]
|
|
36
|
-
/* istanbul ignore next */ // Hard to reproduce
|
|
37
|
-
if (cachingPromise) {
|
|
38
|
-
await cachingPromise
|
|
17
|
+
module.exports = {
|
|
18
|
+
preload: async job => {
|
|
19
|
+
const cacheBase = buildCacheBase(job)
|
|
20
|
+
|
|
21
|
+
const get = async (path, expectedSize) => {
|
|
22
|
+
const filePath = join(cacheBase, 'resources/' + path)
|
|
23
|
+
try {
|
|
24
|
+
const info = await stat(filePath)
|
|
25
|
+
if (expectedSize !== undefined && info.isFile() && info.size === expectedSize) {
|
|
26
|
+
return filePath
|
|
27
|
+
}
|
|
28
|
+
} catch (e) {
|
|
29
|
+
// ignore
|
|
39
30
|
}
|
|
31
|
+
return download((new URL('resources/' + path, job.ui5)).toString(), filePath)
|
|
40
32
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
33
|
+
|
|
34
|
+
const lib = async name => {
|
|
35
|
+
progress.label = name
|
|
36
|
+
progress.count = 0
|
|
37
|
+
const { promise, resolve/*, reject */ } = allocPromise()
|
|
38
|
+
const libPath = name.replace(/\./g, '/') + '/'
|
|
39
|
+
const { resources } = require(await get(libPath + 'resources.json'))
|
|
40
|
+
progress.total = resources.length
|
|
41
|
+
let index = 0
|
|
42
|
+
let active = 0
|
|
43
|
+
|
|
44
|
+
const task = async () => {
|
|
45
|
+
++active
|
|
46
|
+
const { name, size } = resources[index++]
|
|
47
|
+
await get(libPath + name, size)
|
|
48
|
+
++progress.count
|
|
49
|
+
if (index < resources.length) {
|
|
50
|
+
task()
|
|
51
|
+
}
|
|
52
|
+
if (--active === 0) {
|
|
53
|
+
resolve()
|
|
54
|
+
}
|
|
59
55
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
getOutput(job).failedToCacheUI5resource(path, response.statusCode)
|
|
67
|
-
}
|
|
68
|
-
return unlink(cachePath)
|
|
69
|
-
})
|
|
70
|
-
.then(() => {
|
|
71
|
-
delete cachingInProgress[path]
|
|
72
|
-
})
|
|
56
|
+
|
|
57
|
+
for (let parallel = 0; parallel < 8; ++parallel) {
|
|
58
|
+
task()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return promise
|
|
73
62
|
}
|
|
74
|
-
}, {
|
|
75
|
-
// UI5 from url
|
|
76
|
-
method: ['GET', 'HEAD'],
|
|
77
|
-
match,
|
|
78
|
-
url: `${job.ui5}/$1`,
|
|
79
|
-
'ignore-unverifiable-certificate': true
|
|
80
|
-
}]
|
|
81
63
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
64
|
+
job.status = 'Preloading UI5'
|
|
65
|
+
job[$statusProgressCount] = 0
|
|
66
|
+
job[$statusProgressTotal] = job.preload.length + 1
|
|
67
|
+
await get('sap-ui-version.json')
|
|
68
|
+
await get('sap-ui-core.js')
|
|
69
|
+
const progress = newProgress(job)
|
|
70
|
+
await lib('sap.ui.core')
|
|
71
|
+
for (const name of job.preload) {
|
|
72
|
+
++job[$statusProgressCount]
|
|
73
|
+
await lib(name)
|
|
74
|
+
}
|
|
75
|
+
progress.done()
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
mappings: job => {
|
|
79
|
+
if (job.disableUi5) {
|
|
80
|
+
return []
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const cacheBase = buildCacheBase(job)
|
|
84
|
+
const match = /\/((?:test-)?resources\/.*)/
|
|
85
|
+
const ifCacheEnabled = (request, url, match) => job.cache ? match : false
|
|
86
|
+
const uncachable = {}
|
|
87
|
+
const cachingInProgress = {}
|
|
88
|
+
|
|
89
|
+
const mappings = [{
|
|
90
|
+
/* Prevent caching issues :
|
|
91
|
+
* - Caching was not possible (99% URL does not exist)
|
|
92
|
+
* - Caching is in progress (must wait for the end of the writing stream)
|
|
93
|
+
*/
|
|
94
|
+
match,
|
|
95
|
+
'if-match': ifCacheEnabled,
|
|
96
|
+
custom: async (request, response, path) => {
|
|
97
|
+
if (uncachable[path]) {
|
|
98
|
+
response.writeHead(404)
|
|
99
|
+
response.end()
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
const cachingPromise = cachingInProgress[path]
|
|
103
|
+
/* istanbul ignore next */ // Hard to reproduce
|
|
104
|
+
if (cachingPromise) {
|
|
105
|
+
await cachingPromise
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}, {
|
|
109
|
+
// UI5 from cache
|
|
110
|
+
match,
|
|
111
|
+
'if-match': ifCacheEnabled,
|
|
112
|
+
file: join(cacheBase, '$1'),
|
|
86
113
|
'ignore-if-not-found': true
|
|
114
|
+
}, {
|
|
115
|
+
// UI5 caching
|
|
116
|
+
method: 'GET',
|
|
117
|
+
match,
|
|
118
|
+
'if-match': ifCacheEnabled,
|
|
119
|
+
custom: async (request, response, path) => {
|
|
120
|
+
const filePath = /([^?#]+)/.exec(unescape(path))[1] // filter URL parameters & hash (assuming resources are static)
|
|
121
|
+
const cachePath = join(cacheBase, filePath)
|
|
122
|
+
const cacheFolder = dirname(cachePath)
|
|
123
|
+
await mkdir(cacheFolder, { recursive: true })
|
|
124
|
+
if (cachingInProgress[path]) {
|
|
125
|
+
return request.url // loop back to use cached result
|
|
126
|
+
}
|
|
127
|
+
const file = createWriteStream(cachePath)
|
|
128
|
+
cachingInProgress[path] = capture(response, file)
|
|
129
|
+
.catch(reason => {
|
|
130
|
+
file.end()
|
|
131
|
+
uncachable[path] = true
|
|
132
|
+
if (response.statusCode !== 404) {
|
|
133
|
+
getOutput(job).failedToCacheUI5resource(path, response.statusCode)
|
|
134
|
+
}
|
|
135
|
+
return unlink(cachePath)
|
|
136
|
+
})
|
|
137
|
+
.then(() => {
|
|
138
|
+
delete cachingInProgress[path]
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
}, {
|
|
142
|
+
// UI5 from url
|
|
143
|
+
method: ['GET', 'HEAD'],
|
|
144
|
+
match,
|
|
145
|
+
url: `${job.ui5}/$1`,
|
|
146
|
+
'ignore-unverifiable-certificate': true
|
|
147
|
+
}]
|
|
148
|
+
|
|
149
|
+
job.libs.forEach(({ relative, source }) => {
|
|
150
|
+
mappings.unshift({
|
|
151
|
+
match: new RegExp(`\\/resources\\/${relative.replace(/\//g, '\\/')}(.*)`),
|
|
152
|
+
file: join(source, '$1'),
|
|
153
|
+
'ignore-if-not-found': true
|
|
154
|
+
})
|
|
87
155
|
})
|
|
88
|
-
})
|
|
89
156
|
|
|
90
|
-
|
|
157
|
+
return mappings
|
|
158
|
+
}
|
|
91
159
|
}
|