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