1From 1d1fe8d96215edb5f1a9b4fe8c305417eca0aac1 Mon Sep 17 00:00:00 2001 2From: John Doe <john.d@corp.com> 3Date: Fri, 26 Jul 2024 22:33:11 +0800 4Subject: [PATCH] 5 6Signed-off-by: John Doe <john.d@corp.com> 7--- 8 lib/Agent.js | 1 + 9 lib/ConsoleAgent.js | 55 +++++++++++++++------------ 10 lib/agents/panda.js | 88 ++++++++++++++++++++++++++++++++++++++++++++ 11 lib/dependencies.js | 6 +-- 12 lib/write-sources.js | 27 +++++++++----- 13 runtimes/panda.js | 44 ++++++++++++++++++++++ 14 6 files changed, 184 insertions(+), 37 deletions(-) 15 create mode 100644 lib/agents/panda.js 16 create mode 100644 runtimes/panda.js 17 18diff --git a/lib/Agent.js b/lib/Agent.js 19index edcdf0e..7e655c5 100644 20--- a/lib/Agent.js 21+++ b/lib/Agent.js 22@@ -7,6 +7,7 @@ class Agent { 23 this.args = options.hostArguments || []; 24 this.transform = options.transform || (x => x); 25 this.out = options.out || ''; 26+ this.test262Dir = options.test262Dir; 27 28 if (typeof this.args === 'string') { 29 this.args = this.args.includes(' ') ? 30diff --git a/lib/ConsoleAgent.js b/lib/ConsoleAgent.js 31index 947c1db..3fcd363 100644 32--- a/lib/ConsoleAgent.js 33+++ b/lib/ConsoleAgent.js 34@@ -19,7 +19,7 @@ const { 35 const cpSym = Symbol.for('cp'); 36 const tpSym = Symbol.for('tp'); 37 38-function generateTempFileName() { 39+function generateTempFileName(file) { 40 const now = Date.now(); 41 return `f-${now}-${process.pid}-${(Math.random() * 0x100000000 + 1).toString(36)}.js`; 42 } 43@@ -47,9 +47,28 @@ class ConsoleAgent extends Agent { 44 } 45 } 46 47+ genTempFileName(code){ 48+ let file = code.file; 49+ let scenario = code.scenario === 'strict mode' ? '' : code.scenario; 50+ let tmps = file.split(this.test262Dir); 51+ let tempFile = path.join(this.out, tmps[1]); 52+ tempFile = tempFile.substring(0, tempFile.indexOf('.js')); 53+ let fileBase = path.basename(tempFile); 54+ if (tempFile.indexOf("/dynamic-import/") != -1 || tempFile.indexOf("\\dynamic-import\\") != -1) { 55+ tempFile = path.join(tempFile, "/", fileBase); 56+ } 57+ tempFile = path.normalize( 58+ `${tempFile}${scenario}.js` 59+ ); 60+ return tempFile; 61+ } 62+ 63 evalScript(code, options = {}) { 64- let tempfile = path.join(this[tpSym], generateTempFileName()); 65- let temppath = this[tpSym]; 66+ let fileBase = path.basename(code.file); 67+ let originalFilePath = code.file.split(fileBase)[0]; 68+ let tempFile = this.genTempFileName(code); 69+ let outputFilePath = path.basename(tempFile); 70+ let depTempPath = tempFile.substring(0, tempFile.indexOf(outputFilePath)); 71 72 let isExpectingRawSource = false; 73 let hasDependencies = false; 74@@ -57,11 +76,6 @@ class ConsoleAgent extends Agent { 75 const sources = []; 76 const dependencies = []; 77 78- if (this.out) { 79- tempfile = tempfile.replace(temppath, this.out); 80- temppath = this.out; 81- } 82- 83 // When evalScript is called with a test262-stream test record: 84 if (typeof code === 'object' && code.contents) { 85 let {attrs, contents, file} = code; 86@@ -84,13 +98,6 @@ class ConsoleAgent extends Agent { 87 hasDependencies = false; 88 } 89 90- if (options.module || attrs.flags.module || 91- hasModuleSpecifier(contents)) { 92- // When testing module or dynamic import code that imports itself, 93- // we must copy the test file with its actual name. 94- tempfile = path.join(temppath, sourcebase); 95- } 96- 97 // The test record in "code" is no longer needed and 98 // all further operations expect the "code" argument to be 99 // a string, make that true for back-compat. 100@@ -112,7 +119,7 @@ class ConsoleAgent extends Agent { 101 // raw source code. 102 // - The file name of the test being executed, but within 103 // the os's temporary file directory 104- sources.push([ tempfile, code ]); 105+ sources.push([ tempFile, code ]); 106 107 // If any dependencies were discovered, there will be 108 if (hasDependencies) { 109@@ -123,23 +130,24 @@ class ConsoleAgent extends Agent { 110 // 3. Push the dependency and source into the sources to be written. 111 // 112 dependencies.forEach(file => { 113- let absname = path.join(temppath, file); 114- let depsource = rawSource.get(path.basename(file)); 115+ let absName = path.join(depTempPath, file); 116+ let depFile = path.join(originalFilePath, file); 117+ let depSource = rawSource.get(depFile); 118 119 // Sometimes a test file might want to import itself, 120 // which is a valid exercise of the import semantics. 121 // Here we avoid adding the test file more than once. 122- if (absname !== tempfile) { 123+ if (absName !== tempFile) { 124 sources.push([ 125- absname, 126- depsource 127+ absName, 128+ depSource 129 ]); 130 } 131 }); 132 } 133 134 this[cpSym] = writeSources(sources) 135- .then(() => this.createChildProcess([tempfile])); 136+ .then(() => this.createChildProcess([tempFile])); 137 138 return this[cpSym].then(child => { 139 let stdout = ''; 140@@ -158,10 +166,9 @@ class ConsoleAgent extends Agent { 141 }); 142 }).then(({stdout, stderr}) => { 143 // Remove _all_ sources 144- sources.forEach(({0: file}) => fs.unlink(file, () => { /* ignore */ })); 145+ // sources.forEach(({0: file}) => fs.unlink(file, () => { /* ignore */ })); 146 147 const result = this.normalizeResult({ stderr, stdout }); 148- 149 result.error = this.parseError(result.stderr); 150 151 return result; 152diff --git a/lib/agents/panda.js b/lib/agents/panda.js 153new file mode 100644 154index 0000000..ab22b47 155--- /dev/null 156+++ b/lib/agents/panda.js 157@@ -0,0 +1,88 @@ 158+'use strict'; 159+ 160+const fs = require('fs'); 161+const runtimePath = require('../runtime-path'); 162+const ConsoleAgent = require('../ConsoleAgent'); 163+ 164+const errorRe = /[(](\d+),(\d+)[)]: (.*)/; 165+const errorRe1 = /^(\w+): (.*)$/m; 166+// const errorRe2 = /^(?:(\w+): (.*))|(?:(\w+))$/m; 167+const errorRe2 = /(\w+): (\w+): (.*)$/m; 168+ 169+function parseSyntaxError(syntaxErrorMessage) { 170+ const matches = syntaxErrorMessage.match(); 171+ if (matches && matches.length) { 172+ return { 173+ message: matches[3], 174+ lineNumber: Number(matches[1]), 175+ columnNumber: Number(matches[2]) 176+ }; 177+ } 178+ return null; 179+} 180+ 181+class PandaAgent extends ConsoleAgent{ 182+ constructor(options) { 183+ super(options); 184+ } 185+ 186+ createChildProcess(args) { 187+ let js_file = args[0] 188+ args = [] 189+ args.unshift(`--js-file=${js_file}`) 190+ return super.createChildProcess(args); 191+ } 192+ 193+ evalScript(code, options = {}) { 194+ return super.evalScript(code, options); 195+ } 196+ 197+ parseError(str) { 198+ let match = str.match(errorRe1); 199+ if (match) { 200+ return { 201+ name: match[1], 202+ message: match[2], 203+ stack: [], 204+ }; 205+ } else { 206+ // Syntax errors don't have nice error messages... 207+ let error = null; 208+ let errors = str.match(/[(](\d+),(\d+)[)]: (.*)/gm); 209+ 210+ if (errors && errors.length) { 211+ error = { 212+ name: 'SyntaxError', 213+ message: errors[0], 214+ stack: [] 215+ }; 216+ 217+ const stack = parseSyntaxError(errors[0]); 218+ 219+ if (stack) { 220+ error.stack.push(stack); 221+ error.message = stack.message; 222+ } 223+ } 224+ 225+ if (error) { 226+ return error; 227+ } 228+ 229+ // Last chance... 230+ errors = str.match(errorRe2); 231+ if (errors && errors.length >3) { 232+ return { 233+ name: errors[2], 234+ message: errors[0], 235+ stack: [], 236+ }; 237+ } 238+ } 239+ 240+ return null; 241+ } 242+} 243+ 244+PandaAgent.runtime = fs.readFileSync(runtimePath.for('panda'), 'utf8'); 245+module.exports = PandaAgent; 246\ No newline at end of file 247diff --git a/lib/dependencies.js b/lib/dependencies.js 248index 00de9a4..3de6002 100644 249--- a/lib/dependencies.js 250+++ b/lib/dependencies.js 251@@ -46,12 +46,12 @@ function getDependencies(file, accum = []) { 252 let basename = path.basename(file); 253 let contents = ''; 254 255- if (rawSourceCache.has(basename)) { 256- contents = rawSourceCache.get(basename); 257+ if (rawSourceCache.has(file)) { 258+ contents = rawSourceCache.get(file); 259 } else { 260 try { 261 contents = fs.readFileSync(file, 'utf8'); 262- rawSourceCache.set(basename, contents); 263+ rawSourceCache.set(file, contents); 264 } catch (error) { 265 accum.splice(accum.indexOf(basename), 1); 266 } 267diff --git a/lib/write-sources.js b/lib/write-sources.js 268index ba7ce71..408091e 100644 269--- a/lib/write-sources.js 270+++ b/lib/write-sources.js 271@@ -1,12 +1,14 @@ 272 'use strict'; 273 274-const fs = require('fs'); 275-const path = require('path'); 276+let fs; 277+ 278+try { 279+ fs = require('fs/promises'); 280+} catch(error) { 281+ fs = require('fs').promises; 282+} 283 284-const promisify = require('./promisify'); 285-const mkdir = promisify(fs.mkdir); 286-const stat = promisify(fs.stat); 287-const writeFile = promisify(fs.writeFile); 288+const path = require('path'); 289 290 module.exports = async function(sources) { 291 let {0: [file]} = sources; 292@@ -17,18 +19,23 @@ module.exports = async function(sources) { 293 first: path to output file 294 second: contents 295 */ 296- return await Promise.all( 297- sources.map(args => writeFile(...args)) 298+ return Promise.all( 299+ sources.map(args => fs.writeFile(...args, { flag: "wx" }).catch( 300+ error => { 301+ if (error && error.code !== 'EEXIST') { 302+ throw error 303+ } 304+ })) 305 ); 306 }; 307 308 async function safeMkdir(dir) { 309 try { 310- await stat(dir); 311+ await fs.stat(dir); 312 } catch (error) { 313 if (error.code === 'ENOENT') { 314 try { 315- await mkdir(dir); 316+ await fs.mkdir(dir); 317 } catch ({}) { 318 // suppressed? 319 } 320diff --git a/runtimes/panda.js b/runtimes/panda.js 321new file mode 100644 322index 0000000..0acbd09 323--- /dev/null 324+++ b/runtimes/panda.js 325@@ -0,0 +1,44 @@ 326+if (!globalThis.$262) { 327+ globalThis.$262 = { 328+ global: globalThis, 329+ evalScript(code) { 330+ try { 331+ global.evalScript(code); 332+ return { type: 'normal', value: undefined }; 333+ } catch (e) { 334+ return { type: 'throw', value: e }; 335+ } 336+ }, 337+ gc() { 338+ throw new Test262Error('gc() not yet supported.'); 339+ }, 340+ getGlobal(name) { 341+ return global[name]; 342+ }, 343+ setGlobal(name, value) { 344+ global[name] = value; 345+ }, 346+ agent: (function() { 347+ function thrower() { 348+ throw new Test262Error('agent.* not yet supported.'); 349+ }; 350+ return { 351+ start: thrower, 352+ broadcast: thrower, 353+ getReport: thrower, 354+ sleep: thrower, 355+ monotonicNow: thrower, 356+ }; 357+ })(), 358+ }; 359+} 360+ 361+$262.IsHTMLDDA = function() {}; 362+$262.destroy = function() {}; 363+$262.getGlobal = function(name) { 364+ return this.global[name]; 365+}; 366+$262.setGlobal = function(name, value) { 367+ this.global[name] = value; 368+}; 369+$262.source = $SOURCE; 370-- 3712.25.1 372 373