-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add "VncRecordingContainer" #526
Changes from all commits
7f599e8
0020cba
0f09dbe
fd409b9
176eafe
067f2d2
d283f0e
bd5eb9b
1add62a
f0c6336
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
package org.testcontainers.containers; | ||
|
||
import com.github.dockerjava.api.model.Frame; | ||
import lombok.Getter; | ||
import lombok.NonNull; | ||
import lombok.SneakyThrows; | ||
import lombok.ToString; | ||
import org.apache.commons.codec.binary.Base64; | ||
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; | ||
import org.rnorth.ducttape.TimeoutException; | ||
import org.rnorth.ducttape.unreliables.Unreliables; | ||
import org.testcontainers.containers.output.FrameConsumerResultCallback; | ||
import org.testcontainers.utility.TestcontainersConfiguration; | ||
|
||
import java.io.Closeable; | ||
import java.io.File; | ||
import java.io.InputStream; | ||
import java.nio.file.Files; | ||
import java.nio.file.StandardCopyOption; | ||
import java.util.concurrent.CountDownLatch; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
/** | ||
* 'Sidekick container' with the sole purpose of recording the VNC screen output from another container. | ||
* | ||
*/ | ||
@Getter | ||
@ToString | ||
public class VncRecordingContainer extends GenericContainer<VncRecordingContainer> { | ||
|
||
private static final String RECORDING_FILE_NAME = "/screen.flv"; | ||
|
||
public static final String DEFAULT_VNC_PASSWORD = "secret"; | ||
|
||
public static final int DEFAULT_VNC_PORT = 5900; | ||
|
||
private final String targetNetworkAlias; | ||
|
||
private String vncPassword = DEFAULT_VNC_PASSWORD; | ||
|
||
private int vncPort = 5900; | ||
|
||
private int frameRate = 30; | ||
|
||
public VncRecordingContainer(@NonNull GenericContainer<?> targetContainer) { | ||
this( | ||
targetContainer.getNetwork(), | ||
targetContainer.getNetworkAliases().stream() | ||
.findFirst() | ||
.orElseThrow(() -> new IllegalStateException("Target container must have a network alias")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we could make this less likely to happen by always creating a default (random?) alias for a container whenever you join it to a network? Does this already happen / would it cause problems if we did it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if container already started, we can't modify its aliases I think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. actually, we CAN alias during I'll check if it's possible with docker-java, will make users' life much easier |
||
); | ||
} | ||
|
||
/** | ||
* Create a sidekick container and attach it to another container. The VNC output of that container will be recorded. | ||
*/ | ||
public VncRecordingContainer(@NonNull Network network, @NonNull String targetNetworkAlias) throws IllegalStateException { | ||
super(TestcontainersConfiguration.getInstance().getVncRecordedContainerImage()); | ||
|
||
this.targetNetworkAlias = targetNetworkAlias; | ||
withNetwork(network); | ||
|
||
waitingFor(new AbstractWaitStrategy() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Our standard LogWaitStrategy didn't work here and also requires a Regexp while here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps we could/should ship a log wait strategy that is based on Not saying we have to do that now, but keen for your thoughts. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see your point not needing a Regex, but why didn't |
||
|
||
@Override | ||
protected void waitUntilReady() { | ||
try { | ||
Unreliables.retryUntilTrue((int) startupTimeout.toMillis(), TimeUnit.MILLISECONDS, () -> { | ||
CountDownLatch latch = new CountDownLatch(1); | ||
|
||
FrameConsumerResultCallback callback = new FrameConsumerResultCallback() { | ||
@Override | ||
public void onNext(Frame frame) { | ||
if (frame != null && new String(frame.getPayload()).contains("Connected")) { | ||
latch.countDown(); | ||
} | ||
} | ||
}; | ||
|
||
try ( | ||
Closeable __ = dockerClient.logContainerCmd(containerId) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Starting with Java 9, underscores won't be legal variable names (Java Language Changes for Java SE 9) (okay, maybe double underscore is fine 😜 ) |
||
.withFollowStream(true) | ||
.withSince(0) | ||
.withStdErr(true) | ||
.exec(callback) | ||
) { | ||
return latch.await(1, TimeUnit.SECONDS); | ||
} | ||
}); | ||
} catch (TimeoutException e) { | ||
throw new ContainerLaunchException("Timed out waiting for log output", e); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
public VncRecordingContainer withVncPassword(@NonNull String vncPassword) { | ||
this.vncPassword = vncPassword; | ||
return this; | ||
} | ||
|
||
public VncRecordingContainer withVncPort(int vncPort) { | ||
this.vncPort = vncPort; | ||
return this; | ||
} | ||
|
||
public VncRecordingContainer withFrameRate(int frameRate) { | ||
this.frameRate = frameRate; | ||
return this; | ||
} | ||
|
||
@Override | ||
protected void configure() { | ||
withCreateContainerCmdModifier(it -> it.withEntrypoint("/bin/sh")); | ||
setCommand( | ||
"-c", | ||
"echo '" + Base64.encodeBase64String(vncPassword.getBytes()) + "' | base64 -d > /vnc_password && " + | ||
"flvrec.py -o " + RECORDING_FILE_NAME + " -d -r " + frameRate + " -P /vnc_password " + targetNetworkAlias + " " + vncPort | ||
); | ||
} | ||
|
||
@SneakyThrows | ||
public InputStream streamRecording() { | ||
TarArchiveInputStream archiveInputStream = new TarArchiveInputStream( | ||
dockerClient.copyArchiveFromContainerCmd(containerId, RECORDING_FILE_NAME).exec() | ||
); | ||
archiveInputStream.getNextEntry(); | ||
return archiveInputStream; | ||
} | ||
|
||
@SneakyThrows | ||
public void saveRecordingToFile(File file) { | ||
try(InputStream inputStream = streamRecording()) { | ||
Files.copy(inputStream, file.toPath(), StandardCopyOption.REPLACE_EXISTING); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ | |
import java.util.concurrent.TimeUnit; | ||
|
||
import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals; | ||
import static org.rnorth.visibleassertions.VisibleAssertions.assertTrue; | ||
|
||
/** | ||
* | ||
|
@@ -28,15 +29,18 @@ protected void doSimpleWebdriverTest(BrowserWebDriverContainer rule) { | |
} | ||
|
||
@NotNull | ||
private RemoteWebDriver setupDriverFromRule(BrowserWebDriverContainer rule) { | ||
private static RemoteWebDriver setupDriverFromRule(BrowserWebDriverContainer rule) { | ||
RemoteWebDriver driver = rule.getWebDriver(); | ||
driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS); | ||
return driver; | ||
} | ||
|
||
protected void doSimpleExplore(BrowserWebDriverContainer rule) { | ||
protected static void doSimpleExplore(BrowserWebDriverContainer rule) { | ||
RemoteWebDriver driver = setupDriverFromRule(rule); | ||
driver.get("http://en.wikipedia.org/wiki/Randomness"); | ||
|
||
// Oh! The irony! | ||
assertTrue("Randomness' description has the word 'pattern'", driver.findElementByPartialLinkText("pattern").isDisplayed()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Leading to Wiki authors breaking our tests 🤣 |
||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thought it's nice to have it configurable :) Some people do crazy stuff during their tests :D