Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,13 @@ SELF withClasspathResourceMapping(
*/
SELF withWorkingDirectory(String workDir);

/**
* Set the container alias to distinguish between multiple containers.
*
* @param alias name
*/
SELF withContainerAlias(String alias);

/**
* <b>Resolve</b> Docker image and set it.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ public class GenericContainer<SELF extends GenericContainer<SELF>>
@Nullable
private String workingDirectory = null;

@Nullable
private String containerAlias = null;

/**
* The shared memory size to use when starting the container.
* This value is in bytes.
Expand Down Expand Up @@ -343,7 +346,14 @@ protected void doStart() {
}
);
} catch (Exception e) {
throw new ContainerLaunchException("Container startup failed for image " + getDockerImageName(), e);
String containerAliasStr = "";
if (StringUtils.isNotBlank(containerAlias)) {
containerAliasStr = " (containerAlias='" + containerAlias.trim() + "')";
}
throw new ContainerLaunchException(
"Container startup failed for image " + getDockerImageName() + containerAliasStr,
e
);
}
}

Expand Down Expand Up @@ -663,7 +673,11 @@ public void stop() {
* @return a logger that references the docker image name
*/
protected Logger logger() {
return DockerLoggerFactory.getLogger(this.getDockerImageName());
String loggerName = this.getDockerImageName();
if (StringUtils.isNotBlank(containerAlias)) {
loggerName = loggerName + "--" + containerAlias.trim();
}
return DockerLoggerFactory.getLogger(loggerName);
}

/**
Expand Down Expand Up @@ -1256,6 +1270,12 @@ public SELF withWorkingDirectory(String workDir) {
return self();
}

@Override
public SELF withContainerAlias(String alias) {
this.setContainerAlias(alias);
return self();
}

/**
* {@inheritDoc}
*/
Expand Down
102 changes: 102 additions & 0 deletions core/src/test/java/org/testcontainers/junit/ContainerAliasTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package org.testcontainers.junit;

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.LoggerFactory;
import org.testcontainers.TestImages;
import org.testcontainers.containers.ContainerLaunchException;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class ContainerAliasTest {

private final TestLogger testLogger = new TestLogger();

public GenericContainer container1 = new GenericContainer(TestImages.ALPINE_IMAGE)
.withContainerAlias("potato")
.withStartupCheckStrategy(new OneShotStartupCheckStrategy())
.withCommand("ls", "-al");

public GenericContainer container2 = new GenericContainer(TestImages.ALPINE_IMAGE)
.withContainerAlias("monkey")
.withStartupCheckStrategy(new OneShotStartupCheckStrategy())
.withCommand("non-existing-command");

@BeforeEach
void setUp() {
testLogger.startCapturing();
}

@AfterEach
void tearDown() {
testLogger.stopCapturing();
}

@Test
void checkOutput() {
assertThatNoException()
.isThrownBy(() -> {
container1.start();
});

assertThatThrownBy(() -> {
container2.start();
})
.isInstanceOf(ContainerLaunchException.class)
.hasMessage("Container startup failed for image alpine:3.17 (containerAlias='monkey')");

List<String> container1PotatoLogs = testLogger
.getLogs()
.stream()
.filter(it -> "tc.alpine:3.17--potato".equals(it.getLoggerName()))
.map(ILoggingEvent::getFormattedMessage)
.toList();
assertThat(container1PotatoLogs)
.anyMatch(it -> it.equals("Creating container for image: alpine:3.17"))
.anyMatch(it -> it.startsWith("Container alpine:3.17 is starting: "))
.anyMatch(it -> it.startsWith("Container alpine:3.17 started in P"));

List<String> container2MonkeyLogs = testLogger
.getLogs()
.stream()
.filter(it -> "tc.alpine:3.17--monkey".equals(it.getLoggerName()))
.map(ILoggingEvent::getFormattedMessage)
.toList();
assertThat(container2MonkeyLogs)
.anyMatch(it -> it.equals("Creating container for image: alpine:3.17"))
.anyMatch(it -> it.startsWith("Container alpine:3.17 is starting: "))
.anyMatch(it -> it.equals("Could not start container"))
.anyMatch(it -> it.equals("There are no stdout/stderr logs available for the failed container"));
}
}

class TestLogger {

private final ListAppender<ILoggingEvent> listAppender = new ListAppender<>();

public void startCapturing() {
Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
listAppender.start();
rootLogger.addAppender(listAppender);
}

public void stopCapturing() {
Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.detachAppender(listAppender);
listAppender.stop();
}

public List<ILoggingEvent> getLogs() {
return listAppender.list;
}
}
Loading