JS: allow running tests in multiple browser tabs

This commit is contained in:
Alexey Andreev 2021-03-09 10:59:56 +03:00
parent 4ab706f128
commit fb81153ad2
2 changed files with 83 additions and 83 deletions

View File

@ -30,13 +30,13 @@ import java.io.Reader;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
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;
@ -62,9 +62,8 @@ public class BrowserRunStrategy implements TestRunStrategy {
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, TestRunCallback> awaitingRuns = new ConcurrentHashMap<>();
private BlockingQueue<Session> wsSessionQueue = new LinkedBlockingQueue<>();
private ConcurrentMap<Integer, CallbackWrapper> awaitingRuns = new ConcurrentHashMap<>();
private ObjectMapper objectMapper = new ObjectMapper();
public BrowserRunStrategy(File baseDir, String type, Function<String, Process> browserRunner) {
@ -122,36 +121,57 @@ public class BrowserRunStrategy implements TestRunStrategy {
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
public void runTest(TestRun run) throws IOException {
while (!runTestOnce(run)) {
// repeat
}
}
private boolean runTestOnce(TestRun run) {
Session ws;
try {
while (!wsSessionReady.await(1L, TimeUnit.SECONDS)) {
// keep waiting
}
do {
ws = wsSessionQueue.poll(1, TimeUnit.SECONDS);
} while (ws == null || !ws.isOpen());
} catch (InterruptedException e) {
run.getCallback().error(e);
return;
return true;
}
Session ws = wsSession.get();
if (ws == null) {
return;
}
int id = idGenerator.incrementAndGet();
CountDownLatch latch = new CountDownLatch(1);
awaitingRuns.put(id, new TestRunCallback() {
@Override
public void complete() {
latch.countDown();
run.getCallback().complete();
}
@Override
public void error(Throwable e) {
latch.countDown();
run.getCallback().error(e);
}
});
CallbackWrapper callbackWrapper = new CallbackWrapper(latch, run);
awaitingRuns.put(id, callbackWrapper);
JsonNodeFactory nf = objectMapper.getNodeFactory();
ObjectNode node = nf.objectNode();
@ -179,6 +199,12 @@ public class BrowserRunStrategy implements TestRunStrategy {
} catch (InterruptedException e) {
// do nothing
}
if (ws.isOpen()) {
wsSessionQueue.offer(ws);
}
return !callbackWrapper.shouldRepeat;
}
class TestCodeServlet extends HttpServlet {
@ -295,34 +321,20 @@ public class BrowserRunStrategy implements TestRunStrategy {
}
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");
}
wsSessionQueue.offer(sess);
}
@Override
public void onWebSocketClose(int statusCode, String reason) {
if (ready.get()) {
System.err.println("Browser has disconnected");
for (TestRunCallback run : awaitingRuns.values()) {
run.error(new RuntimeException("Browser disconnected unexpectedly"));
}
for (CallbackWrapper run : awaitingRuns.values()) {
run.repeat();
}
}
@Override
public void onWebSocketText(String message) {
if (!ready.get()) {
return;
}
JsonNode node;
try {
node = objectMapper.readTree(new StringReader(message));

View File

@ -43,6 +43,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Stream;
@ -196,63 +197,50 @@ public class TeaVMTestRunner extends Runner implements Filterable {
}
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(
return browserTemplate("chrome", url, (profile, params) -> {
params.addAll(Arrays.asList(
"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);
}
"--user-data-dir=" + profile
));
});
}
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;
try {
temp = File.createTempFile("teavm", "teavm");
temp.delete();
temp.mkdirs();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
deleteDir(temp);
}));
System.out.println("Running firefox with user data dir: " + temp.getAbsolutePath());
ProcessBuilder pb = new ProcessBuilder(
"firefox",
"--headless",
"--profile",
temp.getAbsolutePath(),
url
);
Runtime.getRuntime().addShutdownHook(new Thread(() -> deleteDir(temp)));
System.out.println("Running " + name + " with user data dir: " + temp.getAbsolutePath());
List<String> params = new ArrayList<>();
paramsBuilder.accept(temp.getAbsolutePath(), params);
int tabs = Integer.parseInt(System.getProperty(THREAD_COUNT, "1"));
for (int i = 0; i < tabs; ++i) {
params.add(url);
}
ProcessBuilder pb = new ProcessBuilder(params.toArray(new String[0]));
Process process = pb.start();
logStream(process.getInputStream(), "Firefox stdout");
logStream(process.getErrorStream(), "Firefox stderr");
logStream(process.getInputStream(), name + " stdout");
logStream(process.getErrorStream(), name + " stderr");
new Thread(() -> {
try {
System.out.println("Firefox process terminated with code: " + process.waitFor());
System.out.println(name + " process terminated with code: " + process.waitFor());
} catch (InterruptedException e) {
// ignore
}