From 7b8b07200522fa6491951ef02c0fc06e7a693616 Mon Sep 17 00:00:00 2001 From: pabloFuente Date: Wed, 21 Oct 2026 16:33:37 +0200 Subject: [PATCH] openvidu-test-e2e: do not use a shared cache between test runs for server SDK apps --- .../io/openvidu/test/e2e/OpenViduTestE2e.java | 54 ++++++++++++++++--- .../src/test/resources/README.md | 11 ++-- 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/openvidu-test-e2e/src/main/java/io/openvidu/test/e2e/OpenViduTestE2e.java b/openvidu-test-e2e/src/main/java/io/openvidu/test/e2e/OpenViduTestE2e.java index a43bfeb5e..785886370 100644 --- a/openvidu-test-e2e/src/main/java/io/openvidu/test/e2e/OpenViduTestE2e.java +++ b/openvidu-test-e2e/src/main/java/io/openvidu/test/e2e/OpenViduTestE2e.java @@ -16,6 +16,7 @@ import java.nio.file.Paths; import java.time.Duration; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; @@ -43,6 +44,9 @@ import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.containers.wait.strategy.WaitStrategy; import org.testcontainers.utility.DockerImageName; +import com.github.dockerjava.api.model.Mount; +import com.github.dockerjava.api.model.MountType; + import io.livekit.server.AccessToken; import io.livekit.server.CanPublish; import io.livekit.server.IngressServiceClient; @@ -470,6 +474,34 @@ public class OpenViduTestE2e { startServerSdkPublisher(sdk, roomName, codec, 1); } + // Dependency cache volumes of the server SDK publishers, one per SDK, created + // on first use and alive only for this JVM. + private static final Map SDK_CACHE_VOLUMES = new ConcurrentHashMap<>(); + + /** + * Name of the dependency cache volume of the given SDK for this JVM, creating + * it on first use. The volume carries the Testcontainers session labels, so + * Ryuk removes it when the JVM ends, the same way it removes the containers; + * a shutdown hook removes it too for the runs that disable Ryuk. + */ + private static String sdkCacheVolume(String sdk) { + return SDK_CACHE_VOLUMES.computeIfAbsent(sdk, s -> { + String name = "openvidu-e2e-sdk-cache-" + s + "-" + DockerClientFactory.SESSION_ID; + Map labels = new HashMap<>(DockerClientFactory.DEFAULT_LABELS); + labels.put(DockerClientFactory.TESTCONTAINERS_SESSION_ID_LABEL, DockerClientFactory.SESSION_ID); + DockerClientFactory.instance().client().createVolumeCmd().withName(name).withLabels(labels).exec(); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + try { + DockerClientFactory.instance().client().removeVolumeCmd(name).exec(); + } catch (Exception e) { + // Already removed by Ryuk, or still used by a container Ryuk is + // about to remove together with the volume + } + })); + return name; + }); + } + /** * Starts a LiveKit server RTC SDK participant (a minimal program at * src/test/resources/<sdk>-publisher) in a plain runtime container @@ -478,8 +510,11 @@ public class OpenViduTestE2e { * simulcast for VP8/H264 and without SVC (scalabilityMode L1T1) for VP9/AV1. * * The container mounts the program read-only at /app, copies it to /work and - * runs it there, with a per-SDK dependency cache mounted at /cache so - * repeated runs skip downloads. Returns once the program logs + * runs it there. /cache is a Docker volume that exists only for the current + * test JVM (see {@link #sdkCacheVolume}): nothing of a previous run or of + * another job on the same machine reaches the program, while the publishers + * of one SDK still share their downloads (and the rust build, which takes up + * to 30 minutes from cold) within the run. Returns once the program logs * TRACK_PUBLISHED. The container is stopped automatically on test dispose. */ public void startServerSdkPublisher(String sdk, String roomName, String codec, int layers) throws Exception { @@ -542,14 +577,21 @@ public class OpenViduTestE2e { env.put("VIDEO_LAYERS", String.valueOf(layers)); String programDir = Paths.get("src/test/resources/" + sdk + "-publisher").toAbsolutePath().toString(); - Path cacheDir = Paths.get(System.getProperty("java.io.tmpdir"), "openvidu-e2e-sdk-cache", sdk); - Files.createDirectories(cacheDir); + String cacheVolume = sdkCacheVolume(sdk); GenericContainer publisherContainer = new GenericContainer<>(DockerImageName.parse(image)) .withCreateContainerCmdModifier( cmd -> cmd.withName(sdk + "-publisher-" + (int) (Math.random() * 100000))) - .withNetworkMode("host").withFileSystemBind(programDir, "/app", BindMode.READ_ONLY) - .withFileSystemBind(cacheDir.toString(), "/cache").withEnv(env) + // Not withFileSystemBind: it resolves its first argument as a host path + .withCreateContainerCmdModifier(cmd -> { + List mounts = new ArrayList<>(); + if (cmd.getHostConfig().getMounts() != null) { + mounts.addAll(cmd.getHostConfig().getMounts()); + } + mounts.add(new Mount().withType(MountType.VOLUME).withSource(cacheVolume).withTarget("/cache")); + cmd.getHostConfig().withMounts(mounts); + }) + .withNetworkMode("host").withFileSystemBind(programDir, "/app", BindMode.READ_ONLY).withEnv(env) .withCommand("sh", "-c", "mkdir -p /work && cp -r /app/. /work && cd /work && " + runCommand) .withLogConsumer( frame -> log.info("[{}-publisher] {}", sdk, frame.getUtf8String().stripTrailing())) diff --git a/openvidu-test-e2e/src/test/resources/README.md b/openvidu-test-e2e/src/test/resources/README.md index 4c2127b24..495abfd65 100644 --- a/openvidu-test-e2e/src/test/resources/README.md +++ b/openvidu-test-e2e/src/test/resources/README.md @@ -12,9 +12,14 @@ Minimal LiveKit server RTC SDK publisher programs used by | `dotnet-publisher/` | `Livekit.Rtc.Dotnet` | synthetic frames (FFI) | Each program is launched by `OpenViduTestE2e#startServerSdkPublisher` inside a -plain runtime container of its language (see that method for the images, run -commands and per-SDK dependency caches). The directory is mounted read-only at -`/app`, copied to `/work` and run there. The program must print the exact line +plain runtime container of its language (see that method for the images and run +commands). The directory is mounted read-only at `/app`, copied to `/work` and +run there. The dependency cache at `/cache` is a Docker volume that lives only +for the test JVM that created it (one per SDK, removed with the containers when +the JVM ends), so a run never sees the downloads, builds or caches of a previous +run or of another job on the same machine; within a run the publishers of an SDK +share it, which keeps the Rust publisher's libwebrtc build to one per run. The +program must print the exact line `TRACK_PUBLISHED` once the track is published: the Java test waits for it before asserting, and stops the container when the test ends, so the programs never exit on their own.