ui5-test-runner 4.2.1 → 4.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ui5-test-runner",
3
- "version": "4.2.1",
3
+ "version": "4.3.0",
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.6"
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.3",
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.3.3",
81
- "ui5-tooling-transpile": "^3.3.5"
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
- await promise
140
- output.browserStopped(url)
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
- $probeUrlsStarted,
9
- $probeUrlsCompleted,
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
- const write = (...parts) => parts.forEach(part => process.stdout.write(part))
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
- write('\x1b[?12l')
52
- write(`\x1b[${lines.toString()}F`)
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
- write('\x1b[1E')
53
+ yield '\x1b[1E'
56
54
  }
57
- write(''.padEnd(process.stdout.columns, ' '))
55
+ yield ''.padEnd(process.stdout.columns, ' ')
58
56
  }
59
57
  if (lines > 1) {
60
- write(`\x1b[${(lines - 1).toString()}F`)
58
+ yield `\x1b[${(lines - 1).toString()}F`
61
59
  } else {
62
- write('\x1b[1G')
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
- write('[')
70
+ function * bar (ratio, msg) {
71
+ yield '['
70
72
  if (typeof ratio === 'string') {
71
73
  if (ratio.length > BAR_WIDTH) {
72
- write(ratio.substring(0, BAR_WIDTH - 3), '...')
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
- write(padded)
78
+ yield padded
76
79
  }
77
- write('] ')
80
+ yield '] '
78
81
  } else {
79
82
  const filled = Math.floor(BAR_WIDTH * ratio)
80
- write(''.padEnd(filled, '\u2588'), ''.padEnd(BAR_WIDTH - filled, '\u2591'))
81
- const percent = Math.floor(100 * ratio).toString().padStart(3, ' ')
82
- write('] ', percent, '%')
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
- write(' ')
89
+ yield ' '
85
90
  const spaceLeft = process.stdout.columns - BAR_WIDTH - 14
86
91
  if (msg.length > spaceLeft) {
87
- write('...', msg.substring(msg.length - spaceLeft - 3))
92
+ yield '...'
93
+ yield msg.substring(msg.length - spaceLeft - 3)
88
94
  } else {
89
- write(msg)
95
+ yield msg
90
96
  }
91
- write('\n')
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
- clean(job)
123
+ sequence.push(...buildCleanSequence(job))
100
124
  }
101
125
  } else {
102
126
  if (job[$browsers]) {
103
- write(`${getElapsed(job)} │ Progress\n──────┴──────────\n`)
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
- write(`MEM r:${fmt(rss)}, h:${fmt(heapUsed)}/${fmt(heapTotal)}, x:${fmt(external)}, a:${fmt(arrayBuffers)}\n`)
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[$browsers]) {
124
- const runningPages = Object.keys(job[$browsers])
125
- output.lines += runningPages.length
126
- runningPages.forEach(pageUrl => {
127
- let starting = true
128
- if (job.qunitPages) {
129
- const page = job.qunitPages[pageUrl]
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
- write(status, '\n')
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: wrap((moduleSpecifier, ...args) => {
264
+ debug: (moduleSpecifier, ...args) => {
254
265
  const [mainModule] = moduleSpecifier.split('/')
255
266
  if (job.debugVerbose && (job.debugVerbose.includes(moduleSpecifier) || job.debugVerbose.includes(mainModule))) {
256
- console.log(`🐞${module}`, ...args)
257
- output(job, `🐞${module}`, ...args)
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
  }
@@ -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
 
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
- await unlink(filename)
145
+ try {
146
+ await unlink(filename)
147
+ } catch (e) {
148
+ // ignore
149
+ }
145
150
  return
146
151
  }
147
- response.on('error', reject)
148
- response.on('end', resolve)
149
- response.pipe(output)
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
- module.exports = job => {
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
- const cacheBase = join(job.cache || '', hostName.replace(':', '_'), version || '')
17
- const match = /\/((?:test-)?resources\/.*)/
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
- const mappings = [{
23
- /* Prevent caching issues :
24
- * - Caching was not possible (99% URL does not exist)
25
- * - Caching is in progress (must wait for the end of the writing stream)
26
- */
27
- match,
28
- 'if-match': ifCacheEnabled,
29
- custom: async (request, response, path) => {
30
- if (uncachable[path]) {
31
- response.writeHead(404)
32
- response.end()
33
- return
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
- // UI5 from cache
43
- match,
44
- 'if-match': ifCacheEnabled,
45
- file: join(cacheBase, '$1'),
46
- 'ignore-if-not-found': true
47
- }, {
48
- // UI5 caching
49
- method: 'GET',
50
- match,
51
- 'if-match': ifCacheEnabled,
52
- custom: async (request, response, path) => {
53
- const filePath = /([^?#]+)/.exec(unescape(path))[1] // filter URL parameters & hash (assuming resources are static)
54
- const cachePath = join(cacheBase, filePath)
55
- const cacheFolder = dirname(cachePath)
56
- await mkdir(cacheFolder, { recursive: true })
57
- if (cachingInProgress[path]) {
58
- return request.url // loop back to use cached result
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
- const file = createWriteStream(cachePath)
61
- cachingInProgress[path] = capture(response, file)
62
- .catch(reason => {
63
- file.end()
64
- uncachable[path] = true
65
- if (response.statusCode !== 404) {
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
- job.libs.forEach(({ relative, source }) => {
83
- mappings.unshift({
84
- match: new RegExp(`\\/resources\\/${relative.replace(/\//g, '\\/')}(.*)`),
85
- file: join(source, '$1'),
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
- return mappings
157
+ return mappings
158
+ }
91
159
  }