aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2020-03-04 21:24:39 -0500
committerGitHub <noreply@github.com>2020-03-04 21:24:39 -0500
commit421b60db0f6f132cbc5993488041846055a76d11 (patch)
treeb2b28efc8ed7651c63ca2e728096ab79c6e6ab06
parent6e47b4e241594d68384f62b1faa34c86f85addbf (diff)
parent7a51a0fbde445e346a83982dd6a16ce164521e26 (diff)
Merge pull request #387 from toasted-nutbread/yomichan-vm
Abstract Yomichan extension script execution
-rw-r--r--test/dictionary-validate.js8
-rw-r--r--test/schema-validate.js6
-rw-r--r--test/test-database.js42
-rw-r--r--test/test-document.js28
-rw-r--r--test/test-schema.js12
-rw-r--r--test/yomichan-test.js13
-rw-r--r--test/yomichan-vm.js174
7 files changed, 225 insertions, 58 deletions
diff --git a/test/dictionary-validate.js b/test/dictionary-validate.js
index 14eee2ed..6496f2ac 100644
--- a/test/dictionary-validate.js
+++ b/test/dictionary-validate.js
@@ -18,10 +18,12 @@
const fs = require('fs');
const path = require('path');
-const yomichanTest = require('./yomichan-test');
+const {JSZip} = require('./yomichan-test');
+const {VM} = require('./yomichan-vm');
-const JSZip = yomichanTest.JSZip;
-const {JsonSchema} = yomichanTest.requireScript('ext/bg/js/json-schema.js', ['JsonSchema']);
+const vm = new VM();
+vm.execute('bg/js/json-schema.js');
+const JsonSchema = vm.get('JsonSchema');
function readSchema(relativeFileName) {
diff --git a/test/schema-validate.js b/test/schema-validate.js
index a4f2d94c..eb31aa8d 100644
--- a/test/schema-validate.js
+++ b/test/schema-validate.js
@@ -17,9 +17,11 @@
*/
const fs = require('fs');
-const yomichanTest = require('./yomichan-test');
+const {VM} = require('./yomichan-vm');
-const {JsonSchema} = yomichanTest.requireScript('ext/bg/js/json-schema.js', ['JsonSchema']);
+const vm = new VM();
+vm.execute('bg/js/json-schema.js');
+const JsonSchema = vm.get('JsonSchema');
function main() {
diff --git a/test/test-database.js b/test/test-database.js
index 9a24a393..833aa75d 100644
--- a/test/test-database.js
+++ b/test/test-database.js
@@ -21,6 +21,7 @@ const url = require('url');
const path = require('path');
const assert = require('assert');
const yomichanTest = require('./yomichan-test');
+const {VM} = require('./yomichan-vm');
require('fake-indexeddb/auto');
const chrome = {
@@ -91,23 +92,24 @@ class XMLHttpRequest {
}
}
-const {JsonSchema} = yomichanTest.requireScript('ext/bg/js/json-schema.js', ['JsonSchema']);
-const {dictFieldSplit, dictTagSanitize} = yomichanTest.requireScript('ext/bg/js/dictionary.js', ['dictFieldSplit', 'dictTagSanitize']);
-const {stringReverse} = yomichanTest.requireScript('ext/mixed/js/core.js', ['stringReverse'], {chrome});
-const {requestJson} = yomichanTest.requireScript('ext/bg/js/request.js', ['requestJson'], {XMLHttpRequest});
-const databaseGlobals = {
+const vm = new VM({
chrome,
- JsonSchema,
- requestJson,
- stringReverse,
- dictFieldSplit,
- dictTagSanitize,
+ XMLHttpRequest,
indexedDB: global.indexedDB,
+ IDBKeyRange: global.IDBKeyRange,
JSZip: yomichanTest.JSZip
-};
-databaseGlobals.window = databaseGlobals;
-const {Database} = yomichanTest.requireScript('ext/bg/js/database.js', ['Database'], databaseGlobals);
+});
+vm.context.window = vm.context;
+
+vm.execute([
+ 'bg/js/json-schema.js',
+ 'bg/js/dictionary.js',
+ 'mixed/js/core.js',
+ 'bg/js/request.js',
+ 'bg/js/database.js'
+]);
+const Database = vm.get('Database');
function countTermsWithExpression(terms, expression) {
@@ -215,20 +217,20 @@ async function testDatabase1() {
},
{prefixWildcardsSupported: true}
);
- assert.deepStrictEqual(errors, []);
- assert.deepStrictEqual(result, expectedSummary);
+ vm.assert.deepStrictEqual(errors, []);
+ vm.assert.deepStrictEqual(result, expectedSummary);
assert.ok(progressEvent);
// Get info summary
const info = await database.getDictionaryInfo();
- assert.deepStrictEqual(info, [expectedSummary]);
+ vm.assert.deepStrictEqual(info, [expectedSummary]);
// Get counts
const counts = await database.getDictionaryCounts(
info.map((v) => v.title),
true
);
- assert.deepStrictEqual(counts, {
+ vm.assert.deepStrictEqual(counts, {
counts: [{kanji: 2, kanjiMeta: 2, terms: 32, termMeta: 3, tagMeta: 12}],
total: {kanji: 2, kanjiMeta: 2, terms: 32, termMeta: 3, tagMeta: 12}
});
@@ -251,10 +253,10 @@ async function testDatabase1() {
async function testDatabaseEmpty1(database) {
const info = await database.getDictionaryInfo();
- assert.deepStrictEqual(info, []);
+ vm.assert.deepStrictEqual(info, []);
const counts = await database.getDictionaryCounts([], true);
- assert.deepStrictEqual(counts, {
+ vm.assert.deepStrictEqual(counts, {
counts: [],
total: {kanji: 0, kanjiMeta: 0, terms: 0, termMeta: 0, tagMeta: 0}
});
@@ -827,7 +829,7 @@ async function testFindTagForTitle1(database, title) {
for (const {inputs, expectedResults} of data) {
for (const {name} of inputs) {
const result = await database.findTagForTitle(name, title);
- assert.deepStrictEqual(result, expectedResults.value);
+ vm.assert.deepStrictEqual(result, expectedResults.value);
}
}
}
diff --git a/test/test-document.js b/test/test-document.js
index edf6bbea..ab5f5716 100644
--- a/test/test-document.js
+++ b/test/test-document.js
@@ -2,7 +2,7 @@ const fs = require('fs');
const path = require('path');
const assert = require('assert');
const {JSDOM} = require('jsdom');
-const yomichanTest = require('./yomichan-test');
+const {VM} = require('./yomichan-vm');
// DOMRect class definition
@@ -74,20 +74,18 @@ async function testDocument1() {
const Node = window.Node;
const Range = window.Range;
- const {DOM} = yomichanTest.requireScript(
- 'ext/mixed/js/dom.js',
- ['DOM']
- );
- const {TextSourceRange, TextSourceElement} = yomichanTest.requireScript(
- 'ext/fg/js/source.js',
- ['TextSourceRange', 'TextSourceElement'],
- {document, window, Range, Node}
- );
- const {docRangeFromPoint, docSentenceExtract} = yomichanTest.requireScript(
- 'ext/fg/js/document.js',
- ['docRangeFromPoint', 'docSentenceExtract'],
- {document, window, Node, TextSourceElement, TextSourceRange, DOM}
- );
+ const vm = new VM({document, window, Range, Node});
+ vm.execute([
+ 'mixed/js/dom.js',
+ 'fg/js/source.js',
+ 'fg/js/document.js'
+ ]);
+ const [TextSourceRange, TextSourceElement, docRangeFromPoint, docSentenceExtract] = vm.get([
+ 'TextSourceRange',
+ 'TextSourceElement',
+ 'docRangeFromPoint',
+ 'docSentenceExtract'
+ ]);
try {
await testDocumentTextScanningFunctions(dom, {docRangeFromPoint, docSentenceExtract, TextSourceRange, TextSourceElement});
diff --git a/test/test-schema.js b/test/test-schema.js
index f4612f86..5f9915fd 100644
--- a/test/test-schema.js
+++ b/test/test-schema.js
@@ -17,9 +17,11 @@
*/
const assert = require('assert');
-const yomichanTest = require('./yomichan-test');
+const {VM} = require('./yomichan-vm');
-const {JsonSchema} = yomichanTest.requireScript('ext/bg/js/json-schema.js', ['JsonSchema']);
+const vm = new VM();
+vm.execute('bg/js/json-schema.js');
+const JsonSchema = vm.get('JsonSchema');
function testValidate1() {
@@ -138,7 +140,7 @@ function testGetValidValueOrDefault1() {
for (const [value, expected] of testData) {
const actual = JsonSchema.getValidValueOrDefault(schema, value);
- assert.deepStrictEqual(actual, expected);
+ vm.assert.deepStrictEqual(actual, expected);
}
}
@@ -177,7 +179,7 @@ function testGetValidValueOrDefault2() {
for (const [value, expected] of testData) {
const actual = JsonSchema.getValidValueOrDefault(schema, value);
- assert.deepStrictEqual(actual, expected);
+ vm.assert.deepStrictEqual(actual, expected);
}
}
@@ -235,7 +237,7 @@ function testGetValidValueOrDefault3() {
for (const [value, expected] of testData) {
const actual = JsonSchema.getValidValueOrDefault(schema, value);
- assert.deepStrictEqual(actual, expected);
+ vm.assert.deepStrictEqual(actual, expected);
}
}
diff --git a/test/yomichan-test.js b/test/yomichan-test.js
index 78bfb9c6..0fc97b4b 100644
--- a/test/yomichan-test.js
+++ b/test/yomichan-test.js
@@ -22,18 +22,6 @@ const path = require('path');
let JSZip = null;
-function requireScript(fileName, exportNames, variables) {
- const absoluteFileName = path.join(__dirname, '..', fileName);
- const source = fs.readFileSync(absoluteFileName, {encoding: 'utf8'});
- const exportNamesString = Array.isArray(exportNames) ? exportNames.join(',') : '';
- const variablesArgumentName = '__variables__';
- let variableString = '';
- if (typeof variables === 'object' && variables !== null) {
- variableString = Object.keys(variables).join(',');
- variableString = `const {${variableString}} = ${variablesArgumentName};`;
- }
- return Function(variablesArgumentName, `'use strict';${variableString}${source}\n;return {${exportNamesString}};`)(variables);
-}
function getJSZip() {
if (JSZip === null) {
@@ -64,7 +52,6 @@ function createTestDictionaryArchive(dictionary, dictionaryName) {
module.exports = {
- requireScript,
createTestDictionaryArchive,
get JSZip() { return getJSZip(); }
};
diff --git a/test/yomichan-vm.js b/test/yomichan-vm.js
new file mode 100644
index 00000000..ff478844
--- /dev/null
+++ b/test/yomichan-vm.js
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2020 Alex Yatskov <alex@foosoft.net>
+ * Author: Alex Yatskov <alex@foosoft.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+const fs = require('fs');
+const vm = require('vm');
+const path = require('path');
+const assert = require('assert');
+
+
+function getContextEnvironmentRecords(context, names) {
+ // Enables export of values from the declarative environment record
+ if (!Array.isArray(names) || names.length === 0) {
+ return [];
+ }
+
+ let scriptSource = '(() => {\n "use strict";\n const results = [];';
+ for (const name of names) {
+ scriptSource += `\n try { results.push(${name}); } catch (e) { results.push(void 0); }`;
+ }
+ scriptSource += '\n return results;\n})();';
+
+ const script = new vm.Script(scriptSource, {filename: 'getContextEnvironmentRecords'});
+
+ const contextHasNames = Object.prototype.hasOwnProperty.call(context, 'names');
+ const contextNames = context.names;
+ context.names = names;
+
+ const results = script.runInContext(context, {});
+
+ if (contextHasNames) {
+ context.names = contextNames;
+ } else {
+ delete context.names;
+ }
+
+ return Array.from(results);
+}
+
+function isDeepStrictEqual(val1, val2) {
+ if (val1 === val2) { return true; }
+
+ if (Array.isArray(val1)) {
+ if (Array.isArray(val2)) {
+ return isArrayDeepStrictEqual(val1, val2);
+ }
+ } else if (typeof val1 === 'object' && val1 !== null) {
+ if (typeof val2 === 'object' && val2 !== null) {
+ return isObjectDeepStrictEqual(val1, val2);
+ }
+ }
+
+ return false;
+}
+
+function isArrayDeepStrictEqual(val1, val2) {
+ const ii = val1.length;
+ if (ii !== val2.length) { return false; }
+
+ for (let i = 0; i < ii; ++i) {
+ if (!isDeepStrictEqual(val1[i], val2[i])) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+function isObjectDeepStrictEqual(val1, val2) {
+ const keys1 = Object.keys(val1);
+ const keys2 = Object.keys(val2);
+
+ if (keys1.length !== keys2.length) { return false; }
+
+ const keySet = new Set(keys1);
+ for (const key of keys2) {
+ if (!keySet.delete(key)) { return false; }
+ }
+
+ for (const key of keys1) {
+ if (!isDeepStrictEqual(val1[key], val2[key])) {
+ return false;
+ }
+ }
+
+ const tag1 = Object.prototype.toString.call(val1);
+ const tag2 = Object.prototype.toString.call(val2);
+ if (tag1 !== tag2) { return false; }
+
+ return true;
+}
+
+function deepStrictEqual(actual, expected) {
+ try {
+ // This will fail on prototype === comparison on cross context objects
+ assert.deepStrictEqual(actual, expected);
+ } catch (e) {
+ if (!isDeepStrictEqual(actual, expected)) {
+ throw e;
+ }
+ }
+}
+
+
+class VM {
+ constructor(context={}) {
+ this._context = vm.createContext(context);
+ this._assert = {
+ deepStrictEqual
+ };
+ }
+
+ get context() {
+ return this._context;
+ }
+
+ get assert() {
+ return this._assert;
+ }
+
+ get(names) {
+ if (typeof names === 'string') {
+ return getContextEnvironmentRecords(this._context, [names])[0];
+ } else if (Array.isArray(names)) {
+ return getContextEnvironmentRecords(this._context, names);
+ } else {
+ throw new Error('Invalid argument');
+ }
+ }
+
+ set(values) {
+ if (typeof values === 'object' && values !== null) {
+ Object.assign(this._context, values);
+ } else {
+ throw new Error('Invalid argument');
+ }
+ }
+
+ execute(fileNames) {
+ const single = !Array.isArray(fileNames);
+ if (single) {
+ fileNames = [fileNames];
+ }
+
+ const results = [];
+ for (const fileName of fileNames) {
+ const absoluteFileName = path.resolve(__dirname, '..', 'ext', fileName);
+ const source = fs.readFileSync(absoluteFileName, {encoding: 'utf8'});
+ const script = new vm.Script(source, {filename: absoluteFileName});
+ results.push(script.runInContext(this._context, {}));
+ }
+
+ return single ? results[0] : results;
+ }
+}
+
+
+module.exports = {
+ VM
+};