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.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));

View File

@ -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
} }