Skip to content

Commit

Permalink
implement self-typing for better fluent builders
Browse files Browse the repository at this point in the history
  • Loading branch information
bsideup committed Apr 29, 2016
1 parent 9962f8c commit 4cb5a1d
Show file tree
Hide file tree
Showing 12 changed files with 238 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*/
@EqualsAndHashCode(callSuper = false)
@Data
public class AmbassadorContainer extends GenericContainer {
public class AmbassadorContainer<SELF extends AmbassadorContainer<SELF>> extends GenericContainer<SELF> {

private final String otherContainerName;
private final String serviceName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
/**
* Container which launches Docker Compose, for the purposes of launching a defined set of containers.
*/
public class DockerComposeContainer extends GenericContainer implements LinkableContainer {
public class DockerComposeContainer<SELF extends DockerComposeContainer<SELF>> extends GenericContainer<SELF> implements LinkableContainer {

/**
* Random identifier which will become part of spawned containers names, so we can shut them down
Expand Down Expand Up @@ -143,11 +143,11 @@ public void stop() {

@Override
@Deprecated
public GenericContainer withExposedPorts(Integer... ports) {
public SELF withExposedPorts(Integer... ports) {
throw new UnsupportedOperationException("Use withExposedService instead");
}

public DockerComposeContainer withExposedService(String serviceName, int servicePort) {
public SELF withExposedService(String serviceName, int servicePort) {

/**
* For every service/port pair that needs to be exposed, we have to start an 'ambassador container'.
Expand All @@ -164,7 +164,7 @@ public DockerComposeContainer withExposedService(String serviceName, int service
// Ambassador containers will all be started together after docker compose has started
ambassadorContainers.put(serviceName + ":" + servicePort, ambassadorContainer);

return this;
return self();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* <p>Callers are responsible for ensuring that this fixed port is actually available; failure will occur if it is
* not available - which could manifest as flaky or unstable tests.</p>
*/
public class FixedHostPortGenericContainer extends GenericContainer {
public class FixedHostPortGenericContainer<SELF extends FixedHostPortGenericContainer<SELF>> extends GenericContainer<SELF> {
public FixedHostPortGenericContainer(@NotNull String dockerImageName) {
super(dockerImageName);
}
Expand All @@ -22,10 +22,10 @@ public FixedHostPortGenericContainer(@NotNull String dockerImageName) {
* @param containerPort a port in the container
* @return this container
*/
public FixedHostPortGenericContainer withFixedExposedPort(int hostPort, int containerPort) {
public SELF withFixedExposedPort(int hostPort, int containerPort) {

super.addFixedExposedPort(hostPort, containerPort);

return this;
return self();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class GenericContainer extends FailureDetectingExternalResource implements LinkableContainer {
public class GenericContainer<SELF extends GenericContainer<SELF>> extends FailureDetectingExternalResource implements TestContainer<SELF> {

public static final int STARTUP_RETRY_COUNT = 3;

Expand Down Expand Up @@ -360,9 +360,10 @@ private void applyConfiguration(CreateContainerCmd createCommand) {
* @param waitStrategy the WaitStrategy to use
* @return this
*/
public GenericContainer waitingFor(@NonNull WaitStrategy waitStrategy) {
@Override
public SELF waitingFor(@NonNull WaitStrategy waitStrategy) {
this.waitStrategy = waitStrategy;
return this;
return self();
}

/**
Expand Down Expand Up @@ -391,6 +392,7 @@ protected void waitUntilContainerStarted() {
*
* @param command a command in single string format (will automatically be split on spaces)
*/
@Override
public void setCommand(@NonNull String command) {
this.commandParts = command.split(" ");
}
Expand All @@ -400,6 +402,7 @@ public void setCommand(@NonNull String command) {
*
* @param commandParts a command as an array of string parts
*/
@Override
public void setCommand(@NonNull String... commandParts) {
this.commandParts = commandParts;
}
Expand All @@ -410,6 +413,7 @@ public void setCommand(@NonNull String... commandParts) {
* @param key environment variable key
* @param value environment variable value
*/
@Override
public void addEnv(String key, String value) {
env.add(key + "=" + value);
}
Expand All @@ -421,6 +425,7 @@ public void addEnv(String key, String value) {
* @param containerPath the file system path inside the container
* @param mode the bind mode
*/
@Override
public void addFileSystemBind(String hostPath, String containerPath, BindMode mode) {
binds.add(new Bind(hostPath, new Volume(containerPath), mode.accessMode));
}
Expand All @@ -433,19 +438,23 @@ public void addFileSystemBind(String hostPath, String containerPath, BindMode mo
* @param mode the bind mode
* @return this
*/
public GenericContainer withFileSystemBind(String hostPath, String containerPath, BindMode mode) {
@Override
public SELF withFileSystemBind(String hostPath, String containerPath, BindMode mode) {
addFileSystemBind(hostPath, containerPath, mode);
return this;
return self();
}

@Override
public void addLink(LinkableContainer otherContainer, String alias) {
this.linkedContainers.put(alias, otherContainer);
}

@Override
public void addExposedPort(Integer port) {
exposedPorts.add(port);
}

@Override
public void addExposedPorts(int... ports) {
for (int port : ports) {
exposedPorts.add(port);
Expand All @@ -469,9 +478,10 @@ protected void finished(Description description) {
* @param ports an array of TCP ports
* @return this
*/
public GenericContainer withExposedPorts(Integer... ports) {
@Override
public SELF withExposedPorts(Integer... ports) {
this.setExposedPorts(asList(ports));
return this;
return self();

}

Expand All @@ -496,9 +506,10 @@ protected void addFixedExposedPort(int hostPort, int containerPort) {
* @param value environment variable value
* @return this
*/
public GenericContainer withEnv(String key, String value) {
@Override
public SELF withEnv(String key, String value) {
this.addEnv(key, value);
return this;
return self();
}

/**
Expand All @@ -507,9 +518,10 @@ public GenericContainer withEnv(String key, String value) {
* @param cmd a command in single string format (will automatically be split on spaces)
* @return this
*/
public GenericContainer withCommand(String cmd) {
@Override
public SELF withCommand(String cmd) {
this.setCommand(cmd);
return this;
return self();
}

/**
Expand All @@ -518,9 +530,10 @@ public GenericContainer withCommand(String cmd) {
* @param commandParts a command as an array of string parts
* @return this
*/
public GenericContainer withCommand(String... commandParts) {
@Override
public SELF withCommand(String... commandParts) {
this.setCommand(commandParts);
return this;
return self();
}

/**
Expand All @@ -529,9 +542,10 @@ public GenericContainer withCommand(String... commandParts) {
* @param ipAddress
* @return this
*/
public GenericContainer withExtraHost(String hostname, String ipAddress) {
@Override
public SELF withExtraHost(String hostname, String ipAddress) {
this.extraHosts.add(String.format("%s:%s", hostname, ipAddress));
return this;
return self();
}

/**
Expand All @@ -543,7 +557,8 @@ public GenericContainer withExtraHost(String hostname, String ipAddress) {
* @param mode access mode for the file
* @return this
*/
public GenericContainer withClasspathResourceMapping(String resourcePath, String containerPath, BindMode mode) {
@Override
public SELF withClasspathResourceMapping(String resourcePath, String containerPath, BindMode mode) {
URL resource = GenericContainer.class.getClassLoader().getResource(resourcePath);

if (resource == null) {
Expand All @@ -553,7 +568,7 @@ public GenericContainer withClasspathResourceMapping(String resourcePath, String

this.addFileSystemBind(resourceFilePath, containerPath, mode);

return this;
return self();
}

/**
Expand All @@ -563,16 +578,18 @@ public GenericContainer withClasspathResourceMapping(String resourcePath, String
* @param startupTimeout timeout
* @return this
*/
public GenericContainer withStartupTimeout(Duration startupTimeout) {
@Override
public SELF withStartupTimeout(Duration startupTimeout) {
getWaitStrategy().withStartupTimeout(startupTimeout);
return this;
return self();
}

/**
* Get the IP address that this container may be reached on (may not be the local machine).
*
* @return an IP address
*/
@Override
public String getContainerIpAddress() {
return DockerClientFactory.instance().dockerHostIpAddress();
}
Expand All @@ -581,9 +598,10 @@ public String getContainerIpAddress() {
* Only consider a container to have successfully started if it has been running for this duration. The default
* value is null; if that's the value, ignore this check.
*/
public GenericContainer withMinimumRunningDuration(Duration minimumRunningDuration) {
@Override
public SELF withMinimumRunningDuration(Duration minimumRunningDuration) {
this.setMinimumRunningDuration(minimumRunningDuration);
return this;
return self();
}

/**
Expand All @@ -592,6 +610,7 @@ public GenericContainer withMinimumRunningDuration(Duration minimumRunningDurati
* @return an IP address
* @deprecated please use getContainerIpAddress() instead
*/
@Override
@Deprecated
public String getIpAddress() {
return getContainerIpAddress();
Expand All @@ -600,6 +619,7 @@ public String getIpAddress() {
/**
* @return is the container currently running?
*/
@Override
public Boolean isRunning() {
try {
return dockerClient.inspectContainerCmd(containerId).exec().getState().isRunning();
Expand All @@ -614,6 +634,7 @@ public Boolean isRunning() {
* @param originalPort the original TCP port that is exposed
* @return the port that the exposed port is mapped to, or null if it is not exposed
*/
@Override
public Integer getMappedPort(final int originalPort) {

Preconditions.checkState(containerId != null, "Mapped port can only be obtained after the container is started");
Expand All @@ -635,6 +656,7 @@ public Integer getMappedPort(final int originalPort) {
*
* @param dockerImageName image name
*/
@Override
public void setDockerImageName(@NonNull String dockerImageName) {
this.image = new RemoteDockerImage(dockerImageName);

Expand All @@ -647,6 +669,7 @@ public void setDockerImageName(@NonNull String dockerImageName) {
*
* @return image name
*/
@Override
@NonNull
public String getDockerImageName() {
try {
Expand All @@ -666,6 +689,7 @@ public String getDockerImageName() {
*
* @return the IP address of the host machine
*/
@Override
public String getTestHostIpAddress() {
if (DockerMachineClient.instance().isInstalled()) {
try {
Expand Down Expand Up @@ -699,6 +723,7 @@ public String getTestHostIpAddress() {
*
* @param consumer consumer that the frames should be sent to
*/
@Override
public void followOutput(Consumer<OutputFrame> consumer) {
this.followOutput(consumer, OutputFrame.OutputType.STDOUT, OutputFrame.OutputType.STDERR);
}
Expand All @@ -710,6 +735,7 @@ public void followOutput(Consumer<OutputFrame> consumer) {
* @param consumer consumer that the frames should be sent to
* @param types types that should be followed (one or both of STDOUT, STDERR)
*/
@Override
public void followOutput(Consumer<OutputFrame> consumer, OutputFrame.OutputType... types) {
LogContainerCmd cmd = dockerClient.logContainerCmd(containerId)
.withFollowStream(true);
Expand All @@ -724,6 +750,7 @@ public void followOutput(Consumer<OutputFrame> consumer, OutputFrame.OutputType.
cmd.exec(callback);
}

@Override
public synchronized Info fetchDockerDaemonInfo() throws IOException {

if (this.dockerDaemonInfo == null) {
Expand All @@ -738,6 +765,7 @@ public synchronized Info fetchDockerDaemonInfo() throws IOException {
* <p>
* @see #execInContainer(Charset, String...)
*/
@Override
public ExecResult execInContainer(String... command)
throws UnsupportedOperationException, IOException, InterruptedException {

Expand All @@ -756,6 +784,7 @@ public ExecResult execInContainer(String... command)
* @throws InterruptedException if the thread waiting for the response is interrupted
* @throws UnsupportedOperationException if the docker daemon you're connecting to doesn't support "exec".
*/
@Override
public ExecResult execInContainer(Charset outputCharset, String... command)
throws UnsupportedOperationException, IOException, InterruptedException {

Expand Down Expand Up @@ -791,28 +820,6 @@ public ExecResult execInContainer(Charset outputCharset, String... command)
return result;
}

/**
* Class to hold results from a "docker exec" command. Note that, due to the limitations of the
* docker API, there's no easy way to get the result code from the process we ran.
*/
public static class ExecResult {
private final String stdout;
private final String stderr;

public ExecResult(String stdout, String stderr) {
this.stdout = stdout;
this.stderr = stderr;
}

public String getStdout() {
return stdout;
}

public String getStderr() {
return stderr;
}
}

/**
* Convenience class with access to non-public members of GenericContainer.
*/
Expand Down
Loading

2 comments on commit 4cb5a1d

@yawkat
Copy link

@yawkat yawkat commented on 4cb5a1d Mar 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using self references on non-abstract classes means you cannot directly instantiate those classes without using raw types. Using raw classes leads to any method on that class also becoming raw, e.g. you cannot return generic lists from such methods.

@bsideup
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yawkat this commit is 4 years old :) We're working on a new API that will not have any self typing 👍

Please sign in to comment.