mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 08:14:09 -08:00
Add test runner on headless chrome
This commit is contained in:
parent
653caa00b3
commit
e6606302cc
7
tests/src/test/js/.babelrc
Normal file
7
tests/src/test/js/.babelrc
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"presets": [["env", {
|
||||||
|
"targets": {
|
||||||
|
"node": "current"
|
||||||
|
}
|
||||||
|
}]]
|
||||||
|
}
|
3
tests/src/test/js/.gitignore
vendored
Normal file
3
tests/src/test/js/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/node_modules/
|
||||||
|
/bin/
|
||||||
|
/tmp/
|
13
tests/src/test/js/package.json
Normal file
13
tests/src/test/js/package.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"name": "teavm-tests",
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-cli": "6.24.1",
|
||||||
|
"babel-core": "6.24.1",
|
||||||
|
"babel-polyfill": "6.23.0",
|
||||||
|
"babel-preset-env": "1.4.0",
|
||||||
|
"chrome-remote-interface": "0.20.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "babel src -d bin --source-maps"
|
||||||
|
}
|
||||||
|
}
|
40
tests/src/test/js/src/promise-fs.js
Normal file
40
tests/src/test/js/src/promise-fs.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2017 Alexey Andreev.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as fs from "fs";
|
||||||
|
|
||||||
|
function wrapFsFunction(fsFunction) {
|
||||||
|
return function() {
|
||||||
|
const self = this;
|
||||||
|
const args = Array.prototype.slice.call(arguments);
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
args.push((error, result) => {
|
||||||
|
if (!error) {
|
||||||
|
resolve(result);
|
||||||
|
} else {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fsFunction.apply(self, args);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export let
|
||||||
|
readdir = wrapFsFunction(fs.readdir),
|
||||||
|
readFile = wrapFsFunction(fs.readFile),
|
||||||
|
stat = wrapFsFunction(fs.stat),
|
||||||
|
open = wrapFsFunction(fs.open);
|
243
tests/src/test/js/src/run-tests.js
Normal file
243
tests/src/test/js/src/run-tests.js
Normal file
|
@ -0,0 +1,243 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2017 Alexey Andreev.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as fs from "./promise-fs.js";
|
||||||
|
import { default as CDP } from 'chrome-remote-interface';
|
||||||
|
|
||||||
|
const TEST_FILE_NAME = "test.js";
|
||||||
|
const RUNTIME_FILE_NAME = "runtime.js";
|
||||||
|
const TEST_FILES = [
|
||||||
|
{ file: TEST_FILE_NAME, name: "simple" },
|
||||||
|
{ file: "test-min.js", name: "minified" },
|
||||||
|
{ file: "test-optimized.js", name: "optimized" }
|
||||||
|
];
|
||||||
|
|
||||||
|
class TestSuite {
|
||||||
|
constructor(name) {
|
||||||
|
this.name = name;
|
||||||
|
this.testSuites = [];
|
||||||
|
this.testCases = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class TestCase {
|
||||||
|
constructor(name, files) {
|
||||||
|
this.name = name;
|
||||||
|
this.files = files;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runAll() {
|
||||||
|
const rootSuite = new TestSuite("root");
|
||||||
|
console.log("Searching tests");
|
||||||
|
await walkDir(process.argv[2], "root", rootSuite);
|
||||||
|
|
||||||
|
console.log("Running tests");
|
||||||
|
const stats = { testRun: 0, testsFailed: [] };
|
||||||
|
const startTime = new Date().getTime();
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
CDP(async (client) => {
|
||||||
|
try {
|
||||||
|
const {Page, Runtime} = client;
|
||||||
|
await Promise.all([Runtime.enable(), Page.enable()]);
|
||||||
|
await Page.navigate({url: "about:blank"});
|
||||||
|
//await Page.loadEventFired();
|
||||||
|
|
||||||
|
const runner = new TestRunner(Page, Runtime);
|
||||||
|
await runner.runTests(rootSuite, "", 0);
|
||||||
|
stats.testRun = runner.testsRun;
|
||||||
|
stats.testsFailed = runner.testsFailed;
|
||||||
|
await client.close();
|
||||||
|
resolve();
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
}).on("error", err => {
|
||||||
|
reject(err);
|
||||||
|
}).on("disconnect", () => {
|
||||||
|
reject("disconnected from chrome");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const endTime = new Date().getTime();
|
||||||
|
for (let i = 0; i < stats.testsFailed.length; i++) {
|
||||||
|
const failedTest = stats.testsFailed[i];
|
||||||
|
console.log("(" + (i + 1) + ") " + failedTest.path +":");
|
||||||
|
console.log(failedTest.message);
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Tests run: " + stats.testRun + ", failed: " + stats.testsFailed.length
|
||||||
|
+ ", took " + (endTime - startTime) + " millisecond(s)");
|
||||||
|
|
||||||
|
if (stats.testsFailed.length > 0) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function walkDir(path, name, suite) {
|
||||||
|
const files = await fs.readdir(path);
|
||||||
|
if (files.includes(TEST_FILE_NAME) && files.includes(RUNTIME_FILE_NAME)) {
|
||||||
|
for (const { file: fileName, name: profileName } of TEST_FILES) {
|
||||||
|
if (files.includes(fileName)) {
|
||||||
|
suite.testCases.push(new TestCase(
|
||||||
|
name + " " + profileName,
|
||||||
|
[path + "/" + RUNTIME_FILE_NAME, path + "/" + fileName]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (files) {
|
||||||
|
await Promise.all(files.map(async file => {
|
||||||
|
const filePath = path + "/" + file;
|
||||||
|
const stat = await fs.stat(filePath);
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
const childSuite = new TestSuite(file);
|
||||||
|
suite.testSuites.push(childSuite);
|
||||||
|
await walkDir(filePath, file, childSuite);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestRunner {
|
||||||
|
constructor(page, runtime) {
|
||||||
|
this.page = page;
|
||||||
|
this.runtime = runtime;
|
||||||
|
this.testsRun = 0;
|
||||||
|
this.testsFailed = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async runTests(suite, path, depth) {
|
||||||
|
let prefix = "";
|
||||||
|
for (let i = 0; i < depth; i++) {
|
||||||
|
prefix += " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(prefix + suite.name + "/");
|
||||||
|
for (const testCase of suite.testCases) {
|
||||||
|
this.testsRun++;
|
||||||
|
process.stdout.write(prefix + " " + testCase.name + "... ");
|
||||||
|
try {
|
||||||
|
const testRun = Promise.race([
|
||||||
|
this.runTeaVMTest(testCase),
|
||||||
|
new Promise(resolve => {
|
||||||
|
setTimeout(() => resolve({ status: "failed", errorMessage: "timeout" }), 1000);
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
const result = await testRun;
|
||||||
|
switch (result.status) {
|
||||||
|
case "OK":
|
||||||
|
process.stdout.write("OK");
|
||||||
|
break;
|
||||||
|
case "failed":
|
||||||
|
this.logFailure(path, testCase, result.errorMessage);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.logFailure(path, testCase, e.stack);
|
||||||
|
}
|
||||||
|
process.stdout.write("\n");
|
||||||
|
}
|
||||||
|
for (const childSuite of suite.testSuites) {
|
||||||
|
await this.runTests(childSuite, path + "/" + suite.name, depth + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logFailure(path, testCase, message) {
|
||||||
|
process.stdout.write("failure (" + (this.testsFailed.length + 1) + ")");
|
||||||
|
this.testsFailed.push({
|
||||||
|
path: path + "/" + testCase.name,
|
||||||
|
message: message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async runTeaVMTest(testCase) {
|
||||||
|
await this.page.reload();
|
||||||
|
//await this.page.loadEventFired();
|
||||||
|
|
||||||
|
const fileContents = await Promise.all(testCase.files.map(async (file) => {
|
||||||
|
return fs.readFile(file, 'utf8');
|
||||||
|
}));
|
||||||
|
for (let i = 0; i < testCase.files.length; i++) {
|
||||||
|
const fileName = testCase.files[i];
|
||||||
|
const contents = fileContents[i];
|
||||||
|
const { scriptId } = await this.runtime.compileScript({
|
||||||
|
expression: contents,
|
||||||
|
sourceURL: fileName,
|
||||||
|
persistScript: true
|
||||||
|
});
|
||||||
|
TestRunner.checkScriptResult(await this.runtime.runScript({ scriptId : scriptId }), fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { scriptId: runScriptId } = await this.runtime.compileScript({
|
||||||
|
expression: "(" + runTeaVM.toString() + ")()",
|
||||||
|
sourceURL: " ",
|
||||||
|
persistScript: true
|
||||||
|
});
|
||||||
|
const result = TestRunner.checkScriptResult(await this.runtime.runScript({
|
||||||
|
scriptId: runScriptId,
|
||||||
|
awaitPromise: true,
|
||||||
|
returnByValue: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
return result.result.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static checkScriptResult(scriptResult, scriptUrl) {
|
||||||
|
if (scriptResult.result.subtype === "error") {
|
||||||
|
throw new Error("Exception caught from script " + scriptUrl + ":\n" + scriptResult.result.description);
|
||||||
|
}
|
||||||
|
return scriptResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function runTeaVM() {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
$rt_startThread(() => {
|
||||||
|
const thread = $rt_nativeThread();
|
||||||
|
let instance;
|
||||||
|
if (thread.isResuming()) {
|
||||||
|
instance = thread.pop();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
runTest();
|
||||||
|
} catch (e) {
|
||||||
|
resolve({ status: "failed", errorMessage: buildErrorMessage(e) });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (thread.isSuspending()) {
|
||||||
|
thread.push(instance);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve({ status: "OK" });
|
||||||
|
});
|
||||||
|
|
||||||
|
function buildErrorMessage(e) {
|
||||||
|
let stack = e.stack;
|
||||||
|
if (e.$javaException && e.$javaException.constructor.$meta) {
|
||||||
|
stack = e.$javaException.constructor.$meta.name + ": ";
|
||||||
|
const exceptionMessage = extractException(e.$javaException);
|
||||||
|
stack += exceptionMessage ? $rt_ustr(exceptionMessage) : "";
|
||||||
|
}
|
||||||
|
stack += "\n" + stack;
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
runAll().catch(e => {
|
||||||
|
console.log(e.stack);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
20
tests/src/test/js/start.js
Normal file
20
tests/src/test/js/start.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2017 Alexey Andreev.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
require("babel-core/register");
|
||||||
|
require("babel-polyfill");
|
||||||
|
|
||||||
|
require("./bin/run-tests");
|
Loading…
Reference in New Issue
Block a user