Abstract Yomichan extension script execution
This commit is contained in:
parent
46fee07d36
commit
7a51a0fbde
@ -18,10 +18,12 @@
|
|||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const yomichanTest = require('./yomichan-test');
|
const {JSZip} = require('./yomichan-test');
|
||||||
|
const {VM} = require('./yomichan-vm');
|
||||||
|
|
||||||
const JSZip = yomichanTest.JSZip;
|
const vm = new VM();
|
||||||
const {JsonSchema} = yomichanTest.requireScript('ext/bg/js/json-schema.js', ['JsonSchema']);
|
vm.execute('bg/js/json-schema.js');
|
||||||
|
const JsonSchema = vm.get('JsonSchema');
|
||||||
|
|
||||||
|
|
||||||
function readSchema(relativeFileName) {
|
function readSchema(relativeFileName) {
|
||||||
|
@ -17,9 +17,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const fs = require('fs');
|
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() {
|
function main() {
|
||||||
|
@ -21,6 +21,7 @@ const url = require('url');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const yomichanTest = require('./yomichan-test');
|
const yomichanTest = require('./yomichan-test');
|
||||||
|
const {VM} = require('./yomichan-vm');
|
||||||
require('fake-indexeddb/auto');
|
require('fake-indexeddb/auto');
|
||||||
|
|
||||||
const chrome = {
|
const chrome = {
|
||||||
@ -88,23 +89,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,
|
chrome,
|
||||||
JsonSchema,
|
XMLHttpRequest,
|
||||||
requestJson,
|
|
||||||
stringReverse,
|
|
||||||
dictFieldSplit,
|
|
||||||
dictTagSanitize,
|
|
||||||
indexedDB: global.indexedDB,
|
indexedDB: global.indexedDB,
|
||||||
|
IDBKeyRange: global.IDBKeyRange,
|
||||||
JSZip: yomichanTest.JSZip
|
JSZip: yomichanTest.JSZip
|
||||||
};
|
});
|
||||||
databaseGlobals.window = databaseGlobals;
|
vm.context.window = vm.context;
|
||||||
const {Database} = yomichanTest.requireScript('ext/bg/js/database.js', ['Database'], databaseGlobals);
|
|
||||||
|
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) {
|
function countTermsWithExpression(terms, expression) {
|
||||||
@ -212,20 +214,20 @@ async function testDatabase1() {
|
|||||||
},
|
},
|
||||||
{prefixWildcardsSupported: true}
|
{prefixWildcardsSupported: true}
|
||||||
);
|
);
|
||||||
assert.deepStrictEqual(errors, []);
|
vm.assert.deepStrictEqual(errors, []);
|
||||||
assert.deepStrictEqual(result, expectedSummary);
|
vm.assert.deepStrictEqual(result, expectedSummary);
|
||||||
assert.ok(progressEvent);
|
assert.ok(progressEvent);
|
||||||
|
|
||||||
// Get info summary
|
// Get info summary
|
||||||
const info = await database.getDictionaryInfo();
|
const info = await database.getDictionaryInfo();
|
||||||
assert.deepStrictEqual(info, [expectedSummary]);
|
vm.assert.deepStrictEqual(info, [expectedSummary]);
|
||||||
|
|
||||||
// Get counts
|
// Get counts
|
||||||
const counts = await database.getDictionaryCounts(
|
const counts = await database.getDictionaryCounts(
|
||||||
info.map((v) => v.title),
|
info.map((v) => v.title),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
assert.deepStrictEqual(counts, {
|
vm.assert.deepStrictEqual(counts, {
|
||||||
counts: [{kanji: 2, kanjiMeta: 2, terms: 32, termMeta: 3, tagMeta: 12}],
|
counts: [{kanji: 2, kanjiMeta: 2, terms: 32, termMeta: 3, tagMeta: 12}],
|
||||||
total: {kanji: 2, kanjiMeta: 2, terms: 32, termMeta: 3, tagMeta: 12}
|
total: {kanji: 2, kanjiMeta: 2, terms: 32, termMeta: 3, tagMeta: 12}
|
||||||
});
|
});
|
||||||
@ -248,10 +250,10 @@ async function testDatabase1() {
|
|||||||
|
|
||||||
async function testDatabaseEmpty1(database) {
|
async function testDatabaseEmpty1(database) {
|
||||||
const info = await database.getDictionaryInfo();
|
const info = await database.getDictionaryInfo();
|
||||||
assert.deepStrictEqual(info, []);
|
vm.assert.deepStrictEqual(info, []);
|
||||||
|
|
||||||
const counts = await database.getDictionaryCounts([], true);
|
const counts = await database.getDictionaryCounts([], true);
|
||||||
assert.deepStrictEqual(counts, {
|
vm.assert.deepStrictEqual(counts, {
|
||||||
counts: [],
|
counts: [],
|
||||||
total: {kanji: 0, kanjiMeta: 0, terms: 0, termMeta: 0, tagMeta: 0}
|
total: {kanji: 0, kanjiMeta: 0, terms: 0, termMeta: 0, tagMeta: 0}
|
||||||
});
|
});
|
||||||
@ -824,7 +826,7 @@ async function testFindTagForTitle1(database, title) {
|
|||||||
for (const {inputs, expectedResults} of data) {
|
for (const {inputs, expectedResults} of data) {
|
||||||
for (const {name} of inputs) {
|
for (const {name} of inputs) {
|
||||||
const result = await database.findTagForTitle(name, title);
|
const result = await database.findTagForTitle(name, title);
|
||||||
assert.deepStrictEqual(result, expectedResults.value);
|
vm.assert.deepStrictEqual(result, expectedResults.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ const fs = require('fs');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const {JSDOM} = require('jsdom');
|
const {JSDOM} = require('jsdom');
|
||||||
const yomichanTest = require('./yomichan-test');
|
const {VM} = require('./yomichan-vm');
|
||||||
|
|
||||||
|
|
||||||
// DOMRect class definition
|
// DOMRect class definition
|
||||||
@ -74,20 +74,18 @@ async function testDocument1() {
|
|||||||
const Node = window.Node;
|
const Node = window.Node;
|
||||||
const Range = window.Range;
|
const Range = window.Range;
|
||||||
|
|
||||||
const {DOM} = yomichanTest.requireScript(
|
const vm = new VM({document, window, Range, Node});
|
||||||
'ext/mixed/js/dom.js',
|
vm.execute([
|
||||||
['DOM']
|
'mixed/js/dom.js',
|
||||||
);
|
'fg/js/source.js',
|
||||||
const {TextSourceRange, TextSourceElement} = yomichanTest.requireScript(
|
'fg/js/document.js'
|
||||||
'ext/fg/js/source.js',
|
]);
|
||||||
['TextSourceRange', 'TextSourceElement'],
|
const [TextSourceRange, TextSourceElement, docRangeFromPoint, docSentenceExtract] = vm.get([
|
||||||
{document, window, Range, Node}
|
'TextSourceRange',
|
||||||
);
|
'TextSourceElement',
|
||||||
const {docRangeFromPoint, docSentenceExtract} = yomichanTest.requireScript(
|
'docRangeFromPoint',
|
||||||
'ext/fg/js/document.js',
|
'docSentenceExtract'
|
||||||
['docRangeFromPoint', 'docSentenceExtract'],
|
]);
|
||||||
{document, window, Node, TextSourceElement, TextSourceRange, DOM}
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await testDocumentTextScanningFunctions(dom, {docRangeFromPoint, docSentenceExtract, TextSourceRange, TextSourceElement});
|
await testDocumentTextScanningFunctions(dom, {docRangeFromPoint, docSentenceExtract, TextSourceRange, TextSourceElement});
|
||||||
|
@ -17,9 +17,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const assert = require('assert');
|
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() {
|
function testValidate1() {
|
||||||
@ -138,7 +140,7 @@ function testGetValidValueOrDefault1() {
|
|||||||
|
|
||||||
for (const [value, expected] of testData) {
|
for (const [value, expected] of testData) {
|
||||||
const actual = JsonSchema.getValidValueOrDefault(schema, value);
|
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) {
|
for (const [value, expected] of testData) {
|
||||||
const actual = JsonSchema.getValidValueOrDefault(schema, value);
|
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) {
|
for (const [value, expected] of testData) {
|
||||||
const actual = JsonSchema.getValidValueOrDefault(schema, value);
|
const actual = JsonSchema.getValidValueOrDefault(schema, value);
|
||||||
assert.deepStrictEqual(actual, expected);
|
vm.assert.deepStrictEqual(actual, expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,18 +22,6 @@ const path = require('path');
|
|||||||
|
|
||||||
let JSZip = null;
|
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() {
|
function getJSZip() {
|
||||||
if (JSZip === null) {
|
if (JSZip === null) {
|
||||||
@ -64,7 +52,6 @@ function createTestDictionaryArchive(dictionary, dictionaryName) {
|
|||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
requireScript,
|
|
||||||
createTestDictionaryArchive,
|
createTestDictionaryArchive,
|
||||||
get JSZip() { return getJSZip(); }
|
get JSZip() { return getJSZip(); }
|
||||||
};
|
};
|
||||||
|
174
test/yomichan-vm.js
Normal file
174
test/yomichan-vm.js
Normal file
@ -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
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user