From fb81153ad2208f22f5c241a1a77de76bf084a917 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Tue, 9 Mar 2021 10:59:56 +0300 Subject: [PATCH] JS: allow running tests in multiple browser tabs --- .../org/teavm/junit/BrowserRunStrategy.java | 96 +++++++++++-------- .../java/org/teavm/junit/TeaVMTestRunner.java | 70 ++++++-------- 2 files changed, 83 insertions(+), 83 deletions(-) diff --git a/tools/junit/src/main/java/org/teavm/junit/BrowserRunStrategy.java b/tools/junit/src/main/java/org/teavm/junit/BrowserRunStrategy.java index 75799b121..4c4ade11c 100644 --- a/tools/junit/src/main/java/org/teavm/junit/BrowserRunStrategy.java +++ b/tools/junit/src/main/java/org/teavm/junit/BrowserRunStrategy.java @@ -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 wsSession = new AtomicReference<>(); - private CountDownLatch wsSessionReady = new CountDownLatch(1); - private ConcurrentMap awaitingRuns = new ConcurrentHashMap<>(); + private BlockingQueue wsSessionQueue = new LinkedBlockingQueue<>(); + private ConcurrentMap awaitingRuns = new ConcurrentHashMap<>(); private ObjectMapper objectMapper = new ObjectMapper(); public BrowserRunStrategy(File baseDir, String type, Function 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)); diff --git a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java index 9a4e718f4..6ffca38a6 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java +++ b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java @@ -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> 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 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 }