mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 08:14:09 -08:00
Add JS test runner that runs tests right in the browser
This commit is contained in:
parent
8b4f401bcb
commit
61db54e848
|
@ -73,7 +73,7 @@ function launchTest(argument, callback) {
|
|||
|
||||
function buildErrorMessage(e) {
|
||||
let stack = "";
|
||||
var je = main.javaException(e);
|
||||
let je = main.javaException(e);
|
||||
if (je && je.constructor.$meta) {
|
||||
stack = je.constructor.$meta.name + ": ";
|
||||
stack += je.getMessage();
|
||||
|
@ -85,8 +85,8 @@ function launchTest(argument, callback) {
|
|||
}
|
||||
|
||||
function launchWasmTest(path, argument, callback) {
|
||||
var output = [];
|
||||
var outputBuffer = "";
|
||||
let output = [];
|
||||
let outputBuffer = "";
|
||||
|
||||
function putwchar(charCode) {
|
||||
if (charCode === 10) {
|
||||
|
|
|
@ -44,6 +44,12 @@
|
|||
<artifactId>commons-io</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.teavm</groupId>
|
||||
<artifactId>teavm-jso-apis</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2019 Alexey Andreev.
|
||||
* Copyright 2021 konsoletyper.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.teavm.devserver.deobfuscate;
|
||||
package org.teavm.tooling.deobfuscate.js;
|
||||
|
||||
import org.teavm.jso.JSFunctor;
|
||||
import org.teavm.jso.JSObject;
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2019 Alexey Andreev.
|
||||
* Copyright 2021 konsoletyper.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.teavm.devserver.deobfuscate;
|
||||
package org.teavm.tooling.deobfuscate.js;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
@ -26,6 +26,7 @@ import org.teavm.debugging.information.SourceLocation;
|
|||
import org.teavm.jso.JSBody;
|
||||
import org.teavm.jso.ajax.XMLHttpRequest;
|
||||
import org.teavm.jso.core.JSArray;
|
||||
import org.teavm.jso.core.JSObjects;
|
||||
import org.teavm.jso.core.JSRegExp;
|
||||
import org.teavm.jso.core.JSString;
|
||||
import org.teavm.jso.typedarrays.ArrayBuffer;
|
||||
|
@ -33,9 +34,16 @@ import org.teavm.jso.typedarrays.Int8Array;
|
|||
import org.teavm.model.MethodReference;
|
||||
|
||||
public final class Deobfuscator {
|
||||
private static final JSRegExp FRAME_PATTERN = JSRegExp.create("^ +at ([^(]+) *\\((.+):([0-9]+):([0-9]+)\\) *$");
|
||||
private static final JSRegExp FRAME_PATTERN = JSRegExp.create(""
|
||||
+ "(^ +at ([^(]+) *\\((.+):([0-9]+):([0-9]+)\\) *$)|"
|
||||
+ "(^([^@]*)@(.+):([0-9]+):([0-9]+)$)");
|
||||
private DebugInformation debugInformation;
|
||||
private String classesFileName;
|
||||
|
||||
private Deobfuscator() {
|
||||
public Deobfuscator(ArrayBuffer buffer, String classesFileName) throws IOException {
|
||||
Int8Array array = Int8Array.create(buffer);
|
||||
debugInformation = DebugInformation.read(new Int8ArrayInputStream(array));
|
||||
this.classesFileName = classesFileName;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
@ -52,17 +60,7 @@ public final class Deobfuscator {
|
|||
xhr.send();
|
||||
}
|
||||
|
||||
private static void installDeobfuscator(ArrayBuffer buffer, String classesFileName) {
|
||||
Int8Array array = Int8Array.create(buffer);
|
||||
DebugInformation debugInformation;
|
||||
try {
|
||||
debugInformation = DebugInformation.read(new Int8ArrayInputStream(array));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
setDeobfuscateFunction(stack -> {
|
||||
public Frame[] deobfuscate(String stack) {
|
||||
List<Frame> frames = new ArrayList<>();
|
||||
for (String line : splitLines(stack)) {
|
||||
JSArray<JSString> groups = FRAME_PATTERN.exec(JSString.valueOf(line));
|
||||
|
@ -70,10 +68,15 @@ public final class Deobfuscator {
|
|||
continue;
|
||||
}
|
||||
|
||||
String functionName = groups.get(1).stringValue();
|
||||
String fileName = groups.get(2).stringValue();
|
||||
int lineNumber = Integer.parseInt(groups.get(3).stringValue());
|
||||
int columnNumber = Integer.parseInt(groups.get(4).stringValue());
|
||||
int groupOffset = 1;
|
||||
if (JSObjects.isUndefined(groups.get(1))) {
|
||||
groupOffset = 6;
|
||||
}
|
||||
|
||||
String functionName = groups.get(1 + groupOffset).stringValue();
|
||||
String fileName = groups.get(2 + groupOffset).stringValue();
|
||||
int lineNumber = Integer.parseInt(groups.get(3 + groupOffset).stringValue());
|
||||
int columnNumber = Integer.parseInt(groups.get(4 + groupOffset).stringValue());
|
||||
List<Frame> framesPerLine = deobfuscateFrames(debugInformation, classesFileName, fileName,
|
||||
lineNumber, columnNumber);
|
||||
if (framesPerLine == null) {
|
||||
|
@ -82,7 +85,18 @@ public final class Deobfuscator {
|
|||
frames.addAll(framesPerLine);
|
||||
}
|
||||
return frames.toArray(new Frame[0]);
|
||||
});
|
||||
}
|
||||
|
||||
private static void installDeobfuscator(ArrayBuffer buffer, String classesFileName) {
|
||||
Deobfuscator deobfuscator;
|
||||
try {
|
||||
deobfuscator = new Deobfuscator(buffer, classesFileName);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
setDeobfuscateFunction(deobfuscator::deobfuscate);
|
||||
DeobfuscatorCallback callback = getCallback();
|
||||
if (callback != null) {
|
||||
callback.run();
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2019 Alexey Andreev.
|
||||
* Copyright 2021 konsoletyper.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.teavm.devserver.deobfuscate;
|
||||
package org.teavm.tooling.deobfuscate.js;
|
||||
|
||||
import org.teavm.jso.JSFunctor;
|
||||
import org.teavm.jso.JSObject;
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
package org.teavm.tooling.deobfuscate.js;
|
||||
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.typedarrays.ArrayBuffer;
|
||||
|
||||
public interface DeobfuscatorJs extends JSObject {
|
||||
DeobfuscateFunction create(ArrayBuffer buffer, String classesFileName);
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
package org.teavm.tooling.deobfuscate.js;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.teavm.jso.JSBody;
|
||||
import org.teavm.jso.typedarrays.ArrayBuffer;
|
||||
|
||||
public final class DeobfuscatorLib implements DeobfuscatorJs {
|
||||
private DeobfuscatorLib() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeobfuscateFunction create(ArrayBuffer buffer, String classesFileName) {
|
||||
try {
|
||||
return new Deobfuscator(buffer, classesFileName)::deobfuscate;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
install(new DeobfuscatorLib());
|
||||
}
|
||||
|
||||
@JSBody(params = "instance", script =
|
||||
"deobfuscator.create = function(buffer, classesFileName) {"
|
||||
+ "return instance.create(buffer, classesFileName);"
|
||||
+ "}"
|
||||
)
|
||||
private static native void install(DeobfuscatorJs js);
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2019 Alexey Andreev.
|
||||
* Copyright 2021 konsoletyper.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.teavm.devserver.deobfuscate;
|
||||
package org.teavm.tooling.deobfuscate.js;
|
||||
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.JSProperty;
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2019 Alexey Andreev.
|
||||
* Copyright 2021 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.teavm.devserver.deobfuscate;
|
||||
package org.teavm.tooling.deobfuscate.js;
|
||||
|
||||
import java.io.InputStream;
|
||||
import org.teavm.jso.typedarrays.Int8Array;
|
|
@ -61,12 +61,6 @@
|
|||
<artifactId>teavm-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.teavm</groupId>
|
||||
<artifactId>teavm-jso-apis</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.teavm</groupId>
|
||||
<artifactId>teavm-tooling</artifactId>
|
||||
|
@ -134,7 +128,7 @@
|
|||
<targetFileName>deobfuscator.js</targetFileName>
|
||||
<minifying>true</minifying>
|
||||
<optimizationLevel>ADVANCED</optimizationLevel>
|
||||
<mainClass>org.teavm.devserver.deobfuscate.Deobfuscator</mainClass>
|
||||
<mainClass>org.teavm.tooling.deobfuscate.js.Deobfuscator</mainClass>
|
||||
<entryPointName>$teavm_deobfuscator</entryPointName>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
|
|
@ -50,6 +50,34 @@
|
|||
<artifactId>htmlunit</artifactId>
|
||||
<version>2.33</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>javax-websocket-server-impl</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-client</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>3.1.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
@ -70,6 +98,41 @@
|
|||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.teavm</groupId>
|
||||
<artifactId>teavm-maven-plugin</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.teavm</groupId>
|
||||
<artifactId>teavm-jso-impl</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.teavm</groupId>
|
||||
<artifactId>teavm-classlib</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>compile-deobfuscator</id>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
</goals>
|
||||
<phase>process-classes</phase>
|
||||
<configuration>
|
||||
<targetDirectory>${project.build.directory}/classes/test-server</targetDirectory>
|
||||
<targetFileName>deobfuscator.js</targetFileName>
|
||||
<minifying>true</minifying>
|
||||
<optimizationLevel>ADVANCED</optimizationLevel>
|
||||
<mainClass>org.teavm.tooling.deobfuscate.js.DeobfuscatorLib</mainClass>
|
||||
<entryPointName>deobfuscator</entryPointName>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
|
@ -0,0 +1,346 @@
|
|||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
package org.teavm.junit;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketBehavior;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
|
||||
|
||||
public class BrowserRunStrategy implements TestRunStrategy {
|
||||
private boolean decodeStack = Boolean.parseBoolean(System.getProperty(TeaVMTestRunner.JS_DECODE_STACK, "true"));
|
||||
private final File baseDir;
|
||||
private final String type;
|
||||
private final Function<String, Process> browserRunner;
|
||||
private Process browserProcess;
|
||||
private Server server;
|
||||
private int port;
|
||||
private AtomicInteger idGenerator = new AtomicInteger(0);
|
||||
private AtomicReference<Session> wsSession = new AtomicReference<>();
|
||||
private CountDownLatch wsSessionReady = new CountDownLatch(1);
|
||||
private ConcurrentMap<Integer, TestRun> awaitingRuns = new ConcurrentHashMap<>();
|
||||
private ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
public BrowserRunStrategy(File baseDir, String type, Function<String, Process> browserRunner) {
|
||||
this.baseDir = baseDir;
|
||||
this.type = type;
|
||||
this.browserRunner = browserRunner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeAll() {
|
||||
runServer();
|
||||
browserProcess = browserRunner.apply("http://localhost:" + port + "/index.html");
|
||||
}
|
||||
|
||||
private void runServer() {
|
||||
server = new Server();
|
||||
ServerConnector connector = new ServerConnector(server);
|
||||
server.addConnector(connector);
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
|
||||
context.setContextPath("/");
|
||||
server.setHandler(context);
|
||||
|
||||
TestCodeServlet servlet = new TestCodeServlet();
|
||||
|
||||
ServletHolder servletHolder = new ServletHolder(servlet);
|
||||
servletHolder.setAsyncSupported(true);
|
||||
context.addServlet(servletHolder, "/*");
|
||||
|
||||
try {
|
||||
server.start();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
port = connector.getLocalPort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterAll() {
|
||||
try {
|
||||
server.stop();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (browserProcess != null) {
|
||||
browserProcess.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeThread() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterThread() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runTest(TestRun run) throws IOException {
|
||||
try {
|
||||
while (!wsSessionReady.await(1L, TimeUnit.SECONDS)) {
|
||||
// keep waiting
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
run.getCallback().error(e);
|
||||
return;
|
||||
}
|
||||
|
||||
Session ws = wsSession.get();
|
||||
if (ws == null) {
|
||||
return;
|
||||
}
|
||||
int id = idGenerator.incrementAndGet();
|
||||
awaitingRuns.put(id, run);
|
||||
|
||||
JsonNodeFactory nf = objectMapper.getNodeFactory();
|
||||
ObjectNode node = nf.objectNode();
|
||||
node.set("id", nf.numberNode(id));
|
||||
|
||||
ArrayNode array = nf.arrayNode();
|
||||
node.set("tests", array);
|
||||
|
||||
File file = new File(run.getBaseDirectory(), run.getFileName()).getAbsoluteFile();
|
||||
String relPath = baseDir.getAbsoluteFile().toPath().relativize(file.toPath()).toString();
|
||||
ObjectNode testNode = nf.objectNode();
|
||||
testNode.set("type", nf.textNode(type));
|
||||
testNode.set("name", nf.textNode(run.getFileName()));
|
||||
testNode.set("file", nf.textNode("tests/" + relPath));
|
||||
if (run.getArgument() != null) {
|
||||
testNode.set("argument", nf.textNode(run.getArgument()));
|
||||
}
|
||||
array.add(testNode);
|
||||
|
||||
String message = node.toString();
|
||||
ws.getRemote().sendStringByFuture(message);
|
||||
}
|
||||
|
||||
class TestCodeServlet extends HttpServlet {
|
||||
private WebSocketServletFactory wsFactory;
|
||||
private Map<String, String> contentCache = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void init(ServletConfig config) throws ServletException {
|
||||
super.init(config);
|
||||
WebSocketPolicy wsPolicy = new WebSocketPolicy(WebSocketBehavior.SERVER);
|
||||
wsFactory = WebSocketServletFactory.Loader.load(config.getServletContext(), wsPolicy);
|
||||
wsFactory.setCreator((req, resp) -> new TestCodeSocket());
|
||||
try {
|
||||
wsFactory.start();
|
||||
} catch (Exception e) {
|
||||
throw new ServletException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
String path = req.getRequestURI();
|
||||
if (path != null) {
|
||||
if (!path.startsWith("/")) {
|
||||
path = "/" + path;
|
||||
}
|
||||
if (req.getMethod().equals("GET")) {
|
||||
switch (path) {
|
||||
case "/index.html":
|
||||
case "/frame.html": {
|
||||
String content = getFromCache(path, "true".equals(req.getParameter("logging")));
|
||||
if (content != null) {
|
||||
resp.setStatus(HttpServletResponse.SC_OK);
|
||||
resp.setContentType("text/html");
|
||||
resp.getOutputStream().write(content.getBytes(StandardCharsets.UTF_8));
|
||||
resp.getOutputStream().flush();
|
||||
return;
|
||||
}
|
||||
}
|
||||
case "/client.js":
|
||||
case "/frame.js":
|
||||
case "/deobfuscator.js": {
|
||||
String content = getFromCache(path, false);
|
||||
if (content != null) {
|
||||
resp.setStatus(HttpServletResponse.SC_OK);
|
||||
resp.setContentType("application/javascript");
|
||||
resp.getOutputStream().write(content.getBytes(StandardCharsets.UTF_8));
|
||||
resp.getOutputStream().flush();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (path.startsWith("/tests/")) {
|
||||
String relPath = path.substring("/tests/".length());
|
||||
File file = new File(baseDir, relPath);
|
||||
if (file.isFile()) {
|
||||
resp.setStatus(HttpServletResponse.SC_OK);
|
||||
if (file.getName().endsWith(".js")) {
|
||||
resp.setContentType("application/javascript");
|
||||
} else if (file.getName().endsWith(".wasm")) {
|
||||
resp.setContentType("application/wasm");
|
||||
}
|
||||
try (FileInputStream input = new FileInputStream(file)) {
|
||||
copy(input, resp.getOutputStream());
|
||||
}
|
||||
resp.getOutputStream().flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (path.equals("/ws") && wsFactory.isUpgradeRequest(req, resp)
|
||||
&& (wsFactory.acceptWebSocket(req, resp) || resp.isCommitted())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||
}
|
||||
|
||||
private String getFromCache(String fileName, boolean logging) {
|
||||
return contentCache.computeIfAbsent(fileName, fn -> {
|
||||
ClassLoader loader = BrowserRunStrategy.class.getClassLoader();
|
||||
try (InputStream input = loader.getResourceAsStream("test-server" + fn);
|
||||
Reader reader = new InputStreamReader(input)) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
char[] buffer = new char[2048];
|
||||
while (true) {
|
||||
int charsRead = reader.read(buffer);
|
||||
if (charsRead < 0) {
|
||||
break;
|
||||
}
|
||||
sb.append(buffer, 0, charsRead);
|
||||
}
|
||||
return sb.toString()
|
||||
.replace("{{PORT}}", String.valueOf(port))
|
||||
.replace("\"{{LOGGING}}\"", String.valueOf(logging))
|
||||
.replace("\"{{DEOBFUSCATION}}\"", String.valueOf(decodeStack));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void copy(InputStream input, OutputStream output) throws IOException {
|
||||
byte[] buffer = new byte[2048];
|
||||
while (true) {
|
||||
int bytes = input.read(buffer);
|
||||
if (bytes < 0) {
|
||||
break;
|
||||
}
|
||||
output.write(buffer, 0, bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TestCodeSocket extends WebSocketAdapter {
|
||||
private AtomicBoolean ready = new AtomicBoolean(false);
|
||||
|
||||
@Override
|
||||
public void onWebSocketConnect(Session sess) {
|
||||
if (wsSession.compareAndSet(null, sess)) {
|
||||
ready.set(true);
|
||||
wsSessionReady.countDown();
|
||||
} else {
|
||||
System.err.println("Link opened in multiple browsers");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketClose(int statusCode, String reason) {
|
||||
if (ready.get()) {
|
||||
System.err.println("Browser has disconnected");
|
||||
for (TestRun run : awaitingRuns.values()) {
|
||||
run.getCallback().error(new RuntimeException("Browser disconnected unexpectedly"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketText(String message) {
|
||||
if (!ready.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
JsonNode node;
|
||||
try {
|
||||
node = objectMapper.readTree(new StringReader(message));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
int id = node.get("id").asInt();
|
||||
TestRun run = awaitingRuns.remove(id);
|
||||
if (run == null) {
|
||||
System.err.println("Unexpected run id: " + id);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonNode resultNode = node.get("result");
|
||||
|
||||
JsonNode log = resultNode.get("log");
|
||||
if (log != null) {
|
||||
for (JsonNode logEntry : log) {
|
||||
String str = logEntry.get("message").asText();
|
||||
switch (logEntry.get("type").asText()) {
|
||||
case "stdout":
|
||||
System.out.println(str);
|
||||
break;
|
||||
case "stderr":
|
||||
System.err.println(str);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String status = resultNode.get("status").asText();
|
||||
if (status.equals("OK")) {
|
||||
run.getCallback().complete();
|
||||
} else {
|
||||
run.getCallback().error(new RuntimeException(resultNode.get("errorMessage").asText()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -33,6 +33,14 @@ class CRunStrategy implements TestRunStrategy {
|
|||
this.compilerCommand = compilerCommand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeAll() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterAll() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeThread() {
|
||||
}
|
||||
|
|
|
@ -41,6 +41,14 @@ class HtmlUnitRunStrategy implements TestRunStrategy {
|
|||
private ThreadLocal<HtmlPage> page = new ThreadLocal<>();
|
||||
private int runs;
|
||||
|
||||
@Override
|
||||
public void beforeAll() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterAll() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeThread() {
|
||||
init();
|
||||
|
|
|
@ -17,10 +17,12 @@ package org.teavm.junit;
|
|||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
|
@ -164,7 +166,12 @@ public class TeaVMTestRunner extends Runner implements Filterable {
|
|||
case "htmlunit":
|
||||
jsRunStrategy = new HtmlUnitRunStrategy();
|
||||
break;
|
||||
case "":
|
||||
case "browser":
|
||||
jsRunStrategy = new BrowserRunStrategy(outputDir, "JAVASCRIPT", this::customBrowser);
|
||||
break;
|
||||
case "browser-chrome":
|
||||
jsRunStrategy = new BrowserRunStrategy(outputDir, "JAVASCRIPT", this::chromeBrowser);
|
||||
break;
|
||||
case "none":
|
||||
jsRunStrategy = null;
|
||||
break;
|
||||
|
@ -180,6 +187,73 @@ public class TeaVMTestRunner extends Runner implements Filterable {
|
|||
}
|
||||
}
|
||||
|
||||
private Process customBrowser(String url) {
|
||||
System.out.println("Open link to run tests: " + url + "?logging=true");
|
||||
return null;
|
||||
}
|
||||
|
||||
private Process chromeBrowser(String url) {
|
||||
File temp;
|
||||
try {
|
||||
temp = File.createTempFile("teavm", "teavm");
|
||||
temp.delete();
|
||||
temp.mkdirs();
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
deleteDir(temp);
|
||||
}));
|
||||
System.out.println("Running chrome with user data dir: " + temp.getAbsolutePath());
|
||||
ProcessBuilder pb = new ProcessBuilder(
|
||||
"google-chrome-stable",
|
||||
"--headless",
|
||||
"--disable-gpu",
|
||||
"--remote-debugging-port=9222",
|
||||
"--no-first-run",
|
||||
"--user-data-dir=" + temp.getAbsolutePath(),
|
||||
url
|
||||
);
|
||||
Process process = pb.start();
|
||||
logStream(process.getInputStream(), "Chrome stdout");
|
||||
logStream(process.getErrorStream(), "Chrome stderr");
|
||||
new Thread(() -> {
|
||||
try {
|
||||
System.out.println("Chrome process terminated with code: " + process.waitFor());
|
||||
} catch (InterruptedException e) {
|
||||
// ignore
|
||||
}
|
||||
});
|
||||
return process;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void logStream(InputStream stream, String name) {
|
||||
new Thread(() -> {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) {
|
||||
while (true) {
|
||||
String line = reader.readLine();
|
||||
if (line == null) {
|
||||
break;
|
||||
}
|
||||
System.out.println(name + ": " + line);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void deleteDir(File dir) {
|
||||
for (File file : dir.listFiles()) {
|
||||
if (file.isDirectory()) {
|
||||
deleteDir(file);
|
||||
} else {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
dir.delete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Description getDescription() {
|
||||
if (suiteDescription == null) {
|
||||
|
@ -707,7 +781,7 @@ public class TeaVMTestRunner extends Runner implements Filterable {
|
|||
}
|
||||
};
|
||||
}
|
||||
return compile(configuration, targetSupplier, TestEntryPoint.class.getName(), path, ".js",
|
||||
return compile(configuration, targetSupplier, TestJsEntryPoint.class.getName(), path, ".js",
|
||||
postBuild, false, additionalProcessing, baseName);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
package org.teavm.junit;
|
||||
|
||||
import org.teavm.jso.JSBody;
|
||||
|
||||
final class TestJsEntryPoint {
|
||||
private TestJsEntryPoint() {
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Throwable {
|
||||
try {
|
||||
TestEntryPoint.run(args.length > 0 ? args[0] : null);
|
||||
} catch (Throwable e) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
printStackTrace(e, sb);
|
||||
saveJavaException(sb.toString());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private static void printStackTrace(Throwable e, StringBuilder stream) {
|
||||
stream.append(e.getClass().getName());
|
||||
String message = e.getLocalizedMessage();
|
||||
if (message != null) {
|
||||
stream.append(": " + message);
|
||||
}
|
||||
stream.append("\n");
|
||||
StackTraceElement[] stackTrace = e.getStackTrace();
|
||||
if (stackTrace != null) {
|
||||
for (StackTraceElement element : stackTrace) {
|
||||
stream.append("\tat ");
|
||||
stream.append(element).append("\n");
|
||||
}
|
||||
}
|
||||
if (e.getCause() != null && e.getCause() != e) {
|
||||
stream.append("Caused by: ");
|
||||
printStackTrace(e.getCause(), stream);
|
||||
}
|
||||
}
|
||||
|
||||
@JSBody(params = "e", script = "window.teavmException = e")
|
||||
private static native void saveJavaException(String e);
|
||||
}
|
|
@ -18,6 +18,10 @@ package org.teavm.junit;
|
|||
import java.io.IOException;
|
||||
|
||||
interface TestRunStrategy {
|
||||
void beforeAll();
|
||||
|
||||
void afterAll();
|
||||
|
||||
void beforeThread();
|
||||
|
||||
void afterThread();
|
||||
|
|
|
@ -37,6 +37,11 @@ class TestRunner {
|
|||
|
||||
public void init() {
|
||||
latch = new CountDownLatch(numThreads);
|
||||
strategy.beforeAll();
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
strategy.afterAll();
|
||||
}));
|
||||
|
||||
for (int i = 0; i < numThreads; ++i) {
|
||||
Thread thread = new Thread(() -> {
|
||||
strategy.beforeThread();
|
||||
|
|
146
tools/junit/src/main/resources/test-server/client.js
Normal file
146
tools/junit/src/main/resources/test-server/client.js
Normal file
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
let logging = false;
|
||||
let deobfuscation = false;
|
||||
deobfuscator();
|
||||
|
||||
function tryConnect() {
|
||||
let ws = new WebSocket("ws://localhost:{{PORT}}/ws");
|
||||
|
||||
ws.onopen = () => {
|
||||
if (logging) {
|
||||
console.log("Connection established");
|
||||
}
|
||||
listen(ws);
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
ws.close();
|
||||
setTimeout(() => {
|
||||
tryConnect();
|
||||
}, 500);
|
||||
};
|
||||
|
||||
ws.onerror = err => {
|
||||
if (logging) {
|
||||
console.log("Could not connect WebSocket", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function listen(ws) {
|
||||
ws.onmessage = (event) => {
|
||||
let request = JSON.parse(event.data);
|
||||
if (logging) {
|
||||
console.log("Request #" + request.id + " received");
|
||||
}
|
||||
runTests(ws, request.id, request.tests, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function runTests(ws, suiteId, tests, index) {
|
||||
if (index === tests.length) {
|
||||
return;
|
||||
}
|
||||
let test = tests[index];
|
||||
runSingleTest(test, result => {
|
||||
if (logging) {
|
||||
console.log("Sending response #" + suiteId);
|
||||
}
|
||||
ws.send(JSON.stringify({
|
||||
id: suiteId,
|
||||
index: index,
|
||||
result: result
|
||||
}));
|
||||
runTests(ws, suiteId, tests, index + 1);
|
||||
});
|
||||
}
|
||||
|
||||
let lastDeobfuscator = null;
|
||||
let lastDeobfuscatorFile = null;
|
||||
let lastDeobfuscatorPromise = null;
|
||||
function runSingleTest(test, callback) {
|
||||
if (logging) {
|
||||
console.log("Running test " + test.name);
|
||||
}
|
||||
if (deobfuscation) {
|
||||
const fileName = test.file + ".teavmdbg";
|
||||
if (lastDeobfuscatorFile === fileName) {
|
||||
if (lastDeobfuscatorPromise === null) {
|
||||
runSingleTestWithDeobfuscator(test, lastDeobfuscator, callback);
|
||||
} else {
|
||||
lastDeobfuscatorPromise.then(value => {
|
||||
runSingleTestWithDeobfuscator(test, value, callback);
|
||||
})
|
||||
}
|
||||
} else {
|
||||
lastDeobfuscatorFile = fileName;
|
||||
lastDeobfuscator = null;
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.responseType = "arraybuffer";
|
||||
lastDeobfuscatorPromise = new Promise(resolve => {
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === 4) {
|
||||
const newDeobfuscator = xhr.status === 200
|
||||
? deobfuscator.create(xhr.response, "http://localhost:{{PORT}}/" + test.file)
|
||||
: null;
|
||||
if (lastDeobfuscatorFile === fileName) {
|
||||
lastDeobfuscator = newDeobfuscator;
|
||||
lastDeobfuscatorPromise = null;
|
||||
}
|
||||
resolve(newDeobfuscator);
|
||||
runSingleTestWithDeobfuscator(test, newDeobfuscator, callback);
|
||||
}
|
||||
}
|
||||
xhr.open("GET", fileName);
|
||||
xhr.send();
|
||||
});
|
||||
|
||||
}
|
||||
} else {
|
||||
runSingleTestWithDeobfuscator(test, null, callback);
|
||||
}
|
||||
}
|
||||
|
||||
function runSingleTestWithDeobfuscator(test, deobfuscator, callback) {
|
||||
let iframe = document.createElement("iframe");
|
||||
document.body.appendChild(iframe);
|
||||
let handshakeListener = handshakeEvent => {
|
||||
if (handshakeEvent.source !== iframe.contentWindow || handshakeEvent.data !== "ready") {
|
||||
return;
|
||||
}
|
||||
window.removeEventListener("message", handshakeListener);
|
||||
|
||||
let listener = event => {
|
||||
if (event.source !== iframe.contentWindow) {
|
||||
return;
|
||||
}
|
||||
window.removeEventListener("message", listener);
|
||||
document.body.removeChild(iframe);
|
||||
callback(event.data);
|
||||
};
|
||||
window.addEventListener("message", listener);
|
||||
|
||||
iframe.contentWindow.$rt_decodeStack = deobfuscator;
|
||||
iframe.contentWindow.postMessage(test, "*");
|
||||
};
|
||||
window.addEventListener("message", handshakeListener);
|
||||
iframe.src = "about:blank";
|
||||
iframe.src = "frame.html";
|
||||
}
|
25
tools/junit/src/main/resources/test-server/frame.html
Normal file
25
tools/junit/src/main/resources/test-server/frame.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
<!--
|
||||
~ Copyright 2021 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.
|
||||
-->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<script src="frame.js"></script>
|
||||
</head>
|
||||
<body onload="start()">
|
||||
</body>
|
||||
</html>
|
190
tools/junit/src/main/resources/test-server/frame.js
Normal file
190
tools/junit/src/main/resources/test-server/frame.js
Normal file
|
@ -0,0 +1,190 @@
|
|||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
window.addEventListener("message", event => {
|
||||
let request = event.data;
|
||||
switch (request.type) {
|
||||
case "JAVASCRIPT":
|
||||
appendFiles([request.file], 0, () => {
|
||||
launchTest(request.argument, response => {
|
||||
event.source.postMessage(response, "*");
|
||||
});
|
||||
}, error => {
|
||||
event.source.postMessage(wrapResponse({ status: "failed", errorMessage: error }), "*");
|
||||
});
|
||||
break;
|
||||
|
||||
case "WASM":
|
||||
const runtimeFile = request.file + "-runtime.js";
|
||||
appendFiles([runtimeFile], 0, () => {
|
||||
launchWasmTest(request.file, equest.argument, response => {
|
||||
event.source.postMessage(response, "*");
|
||||
});
|
||||
}, error => {
|
||||
event.source.postMessage(wrapResponse({ status: "failed", errorMessage: error }), "*");
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
function appendFiles(files, index, callback, errorCallback) {
|
||||
if (index === files.length) {
|
||||
callback();
|
||||
} else {
|
||||
let fileName = files[index];
|
||||
let script = document.createElement("script");
|
||||
script.onload = () => {
|
||||
appendFiles(files, index + 1, callback, errorCallback);
|
||||
};
|
||||
script.onerror = () => {
|
||||
errorCallback("failed to load script " + fileName);
|
||||
};
|
||||
script.src = fileName;
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
}
|
||||
|
||||
function launchTest(argument, callback) {
|
||||
main(argument ? [argument] : [], result => {
|
||||
if (result instanceof Error) {
|
||||
callback(wrapResponse({
|
||||
status: "failed",
|
||||
errorMessage: buildErrorMessage(result)
|
||||
}));
|
||||
} else {
|
||||
callback({ status: "OK" });
|
||||
}
|
||||
});
|
||||
|
||||
function buildErrorMessage(e) {
|
||||
if (typeof $rt_decodeStack === "function" && typeof teavmException == "string") {
|
||||
return teavmException;
|
||||
}
|
||||
let stack = "";
|
||||
let je = main.javaException(e);
|
||||
if (je && je.constructor.$meta) {
|
||||
stack = je.constructor.$meta.name + ": ";
|
||||
stack += je.getMessage();
|
||||
stack += "\n";
|
||||
}
|
||||
stack += e.stack;
|
||||
return stack;
|
||||
}
|
||||
}
|
||||
|
||||
function launchWasmTest(path, argument, callback) {
|
||||
let output = [];
|
||||
let outputBuffer = "";
|
||||
|
||||
function putwchar(charCode) {
|
||||
if (charCode === 10) {
|
||||
switch (outputBuffer) {
|
||||
case "SUCCESS":
|
||||
callback(wrapResponse({ status: "OK" }));
|
||||
break;
|
||||
case "FAILURE":
|
||||
callback(wrapResponse({
|
||||
status: "failed",
|
||||
errorMessage: output.join("\n")
|
||||
}));
|
||||
break;
|
||||
default:
|
||||
output.push(outputBuffer);
|
||||
outputBuffer = "";
|
||||
}
|
||||
} else {
|
||||
outputBuffer += String.fromCharCode(charCode);
|
||||
}
|
||||
}
|
||||
|
||||
TeaVM.wasm.run(path, {
|
||||
installImports: function(o) {
|
||||
o.teavm.putwchar = putwchar;
|
||||
},
|
||||
errorCallback: function(err) {
|
||||
callback(wrapResponse({
|
||||
status: "failed",
|
||||
errorMessage: err.message + '\n' + err.stack
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function start() {
|
||||
window.parent.postMessage("ready", "*");
|
||||
}
|
||||
|
||||
let log = [];
|
||||
|
||||
function wrapResponse(response) {
|
||||
if (log.length > 0) {
|
||||
response.log = log;
|
||||
log = [];
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
let $rt_putStdoutCustom = createOutputFunction(msg => {
|
||||
log.push({ type: "stdout", message: msg });
|
||||
});
|
||||
let $rt_putStderrCustom = createOutputFunction(msg => {
|
||||
log.push({ type: "stderr", message: msg });
|
||||
});
|
||||
|
||||
function createOutputFunction(printFunction) {
|
||||
let buffer = "";
|
||||
let utf8Buffer = 0;
|
||||
let utf8Remaining = 0;
|
||||
|
||||
function putCodePoint(ch) {
|
||||
if (ch === 0xA) {
|
||||
printFunction(buffer);
|
||||
buffer = "";
|
||||
} else if (ch < 0x10000) {
|
||||
buffer += String.fromCharCode(ch);
|
||||
} else {
|
||||
ch = (ch - 0x10000) | 0;
|
||||
var hi = (ch >> 10) + 0xD800;
|
||||
var lo = (ch & 0x3FF) + 0xDC00;
|
||||
buffer += String.fromCharCode(hi, lo);
|
||||
}
|
||||
}
|
||||
|
||||
return ch => {
|
||||
if ((ch & 0x80) === 0) {
|
||||
putCodePoint(ch);
|
||||
} else if ((ch & 0xC0) === 0x80) {
|
||||
if (utf8Buffer > 0) {
|
||||
utf8Remaining <<= 6;
|
||||
utf8Remaining |= ch & 0x3F;
|
||||
if (--utf8Buffer === 0) {
|
||||
putCodePoint(utf8Remaining);
|
||||
}
|
||||
}
|
||||
} else if ((ch & 0xE0) === 0xC0) {
|
||||
utf8Remaining = ch & 0x1F;
|
||||
utf8Buffer = 1;
|
||||
} else if ((ch & 0xF0) === 0xE0) {
|
||||
utf8Remaining = ch & 0x0F;
|
||||
utf8Buffer = 2;
|
||||
} else if ((ch & 0xF8) === 0xF0) {
|
||||
utf8Remaining = ch & 0x07;
|
||||
utf8Buffer = 3;
|
||||
}
|
||||
};
|
||||
}
|
31
tools/junit/src/main/resources/test-server/index.html
Normal file
31
tools/junit/src/main/resources/test-server/index.html
Normal file
|
@ -0,0 +1,31 @@
|
|||
<!--
|
||||
~ Copyright 2021 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.
|
||||
-->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<script src="deobfuscator.js"></script>
|
||||
<script src="client.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
logging = "{{LOGGING}}";
|
||||
deobfuscation = "{{DEOBFUSCATION}}";
|
||||
tryConnect();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user