mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2025-01-08 07:54:11 -08:00
JS: allow running tests in multiple browser tabs
This commit is contained in:
parent
4ab706f128
commit
fb81153ad2
|
@ -30,13 +30,13 @@ import java.io.Reader;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import javax.servlet.ServletConfig;
|
import javax.servlet.ServletConfig;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
|
@ -62,9 +62,8 @@ public class BrowserRunStrategy implements TestRunStrategy {
|
||||||
private Server server;
|
private Server server;
|
||||||
private int port;
|
private int port;
|
||||||
private AtomicInteger idGenerator = new AtomicInteger(0);
|
private AtomicInteger idGenerator = new AtomicInteger(0);
|
||||||
private AtomicReference<Session> wsSession = new AtomicReference<>();
|
private BlockingQueue<Session> wsSessionQueue = new LinkedBlockingQueue<>();
|
||||||
private CountDownLatch wsSessionReady = new CountDownLatch(1);
|
private ConcurrentMap<Integer, CallbackWrapper> awaitingRuns = new ConcurrentHashMap<>();
|
||||||
private ConcurrentMap<Integer, TestRunCallback> awaitingRuns = new ConcurrentHashMap<>();
|
|
||||||
private ObjectMapper objectMapper = new ObjectMapper();
|
private ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
public BrowserRunStrategy(File baseDir, String type, Function<String, Process> browserRunner) {
|
public BrowserRunStrategy(File baseDir, String type, Function<String, Process> browserRunner) {
|
||||||
|
@ -122,36 +121,57 @@ public class BrowserRunStrategy implements TestRunStrategy {
|
||||||
public void afterThread() {
|
public void afterThread() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class CallbackWrapper implements TestRunCallback {
|
||||||
|
private final CountDownLatch latch;
|
||||||
|
private final TestRun run;
|
||||||
|
volatile boolean shouldRepeat;
|
||||||
|
|
||||||
|
CallbackWrapper(CountDownLatch latch, TestRun run) {
|
||||||
|
this.latch = latch;
|
||||||
|
this.run = run;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void complete() {
|
||||||
|
latch.countDown();
|
||||||
|
run.getCallback().complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void error(Throwable e) {
|
||||||
|
latch.countDown();
|
||||||
|
run.getCallback().error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void repeat() {
|
||||||
|
latch.countDown();
|
||||||
|
shouldRepeat = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void runTest(TestRun run) throws IOException {
|
public void runTest(TestRun run) throws IOException {
|
||||||
|
while (!runTestOnce(run)) {
|
||||||
|
// repeat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean runTestOnce(TestRun run) {
|
||||||
|
Session ws;
|
||||||
try {
|
try {
|
||||||
while (!wsSessionReady.await(1L, TimeUnit.SECONDS)) {
|
do {
|
||||||
// keep waiting
|
ws = wsSessionQueue.poll(1, TimeUnit.SECONDS);
|
||||||
}
|
} while (ws == null || !ws.isOpen());
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
run.getCallback().error(e);
|
run.getCallback().error(e);
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Session ws = wsSession.get();
|
|
||||||
if (ws == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int id = idGenerator.incrementAndGet();
|
int id = idGenerator.incrementAndGet();
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
awaitingRuns.put(id, new TestRunCallback() {
|
|
||||||
@Override
|
|
||||||
public void complete() {
|
|
||||||
latch.countDown();
|
|
||||||
run.getCallback().complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
CallbackWrapper callbackWrapper = new CallbackWrapper(latch, run);
|
||||||
public void error(Throwable e) {
|
awaitingRuns.put(id, callbackWrapper);
|
||||||
latch.countDown();
|
|
||||||
run.getCallback().error(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
JsonNodeFactory nf = objectMapper.getNodeFactory();
|
JsonNodeFactory nf = objectMapper.getNodeFactory();
|
||||||
ObjectNode node = nf.objectNode();
|
ObjectNode node = nf.objectNode();
|
||||||
|
@ -179,6 +199,12 @@ public class BrowserRunStrategy implements TestRunStrategy {
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ws.isOpen()) {
|
||||||
|
wsSessionQueue.offer(ws);
|
||||||
|
}
|
||||||
|
|
||||||
|
return !callbackWrapper.shouldRepeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestCodeServlet extends HttpServlet {
|
class TestCodeServlet extends HttpServlet {
|
||||||
|
@ -295,34 +321,20 @@ public class BrowserRunStrategy implements TestRunStrategy {
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestCodeSocket extends WebSocketAdapter {
|
class TestCodeSocket extends WebSocketAdapter {
|
||||||
private AtomicBoolean ready = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onWebSocketConnect(Session sess) {
|
public void onWebSocketConnect(Session sess) {
|
||||||
if (wsSession.compareAndSet(null, sess)) {
|
wsSessionQueue.offer(sess);
|
||||||
ready.set(true);
|
|
||||||
wsSessionReady.countDown();
|
|
||||||
} else {
|
|
||||||
System.err.println("Link opened in multiple browsers");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onWebSocketClose(int statusCode, String reason) {
|
public void onWebSocketClose(int statusCode, String reason) {
|
||||||
if (ready.get()) {
|
for (CallbackWrapper run : awaitingRuns.values()) {
|
||||||
System.err.println("Browser has disconnected");
|
run.repeat();
|
||||||
for (TestRunCallback run : awaitingRuns.values()) {
|
|
||||||
run.error(new RuntimeException("Browser disconnected unexpectedly"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onWebSocketText(String message) {
|
public void onWebSocketText(String message) {
|
||||||
if (!ready.get()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonNode node;
|
JsonNode node;
|
||||||
try {
|
try {
|
||||||
node = objectMapper.readTree(new StringReader(message));
|
node = objectMapper.readTree(new StringReader(message));
|
||||||
|
|
|
@ -43,6 +43,7 @@ import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
@ -196,63 +197,50 @@ public class TeaVMTestRunner extends Runner implements Filterable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Process chromeBrowser(String url) {
|
private Process chromeBrowser(String url) {
|
||||||
File temp;
|
return browserTemplate("chrome", url, (profile, params) -> {
|
||||||
try {
|
params.addAll(Arrays.asList(
|
||||||
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",
|
"google-chrome-stable",
|
||||||
"--headless",
|
"--headless",
|
||||||
"--disable-gpu",
|
"--disable-gpu",
|
||||||
"--remote-debugging-port=9222",
|
"--remote-debugging-port=9222",
|
||||||
"--no-first-run",
|
"--no-first-run",
|
||||||
"--user-data-dir=" + temp.getAbsolutePath(),
|
"--user-data-dir=" + profile
|
||||||
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 Process firefoxBrowser(String url) {
|
private Process firefoxBrowser(String url) {
|
||||||
|
return browserTemplate("firefox", url, (profile, params) -> {
|
||||||
|
params.addAll(Arrays.asList(
|
||||||
|
"firefox",
|
||||||
|
"--headless",
|
||||||
|
"--profile",
|
||||||
|
profile
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Process browserTemplate(String name, String url, BiConsumer<String, List<String>> paramsBuilder) {
|
||||||
File temp;
|
File temp;
|
||||||
try {
|
try {
|
||||||
temp = File.createTempFile("teavm", "teavm");
|
temp = File.createTempFile("teavm", "teavm");
|
||||||
temp.delete();
|
temp.delete();
|
||||||
temp.mkdirs();
|
temp.mkdirs();
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
Runtime.getRuntime().addShutdownHook(new Thread(() -> deleteDir(temp)));
|
||||||
deleteDir(temp);
|
System.out.println("Running " + name + " with user data dir: " + temp.getAbsolutePath());
|
||||||
}));
|
List<String> params = new ArrayList<>();
|
||||||
System.out.println("Running firefox with user data dir: " + temp.getAbsolutePath());
|
paramsBuilder.accept(temp.getAbsolutePath(), params);
|
||||||
ProcessBuilder pb = new ProcessBuilder(
|
int tabs = Integer.parseInt(System.getProperty(THREAD_COUNT, "1"));
|
||||||
"firefox",
|
for (int i = 0; i < tabs; ++i) {
|
||||||
"--headless",
|
params.add(url);
|
||||||
"--profile",
|
}
|
||||||
temp.getAbsolutePath(),
|
ProcessBuilder pb = new ProcessBuilder(params.toArray(new String[0]));
|
||||||
url
|
|
||||||
);
|
|
||||||
Process process = pb.start();
|
Process process = pb.start();
|
||||||
logStream(process.getInputStream(), "Firefox stdout");
|
logStream(process.getInputStream(), name + " stdout");
|
||||||
logStream(process.getErrorStream(), "Firefox stderr");
|
logStream(process.getErrorStream(), name + " stderr");
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
System.out.println("Firefox process terminated with code: " + process.waitFor());
|
System.out.println(name + " process terminated with code: " + process.waitFor());
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user