-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
30 changed files
with
2,178 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package software.xdev.tci; | ||
|
||
import java.util.Objects; | ||
|
||
import org.rnorth.ducttape.unreliables.Unreliables; | ||
import org.slf4j.LoggerFactory; | ||
import org.testcontainers.containers.GenericContainer; | ||
|
||
import software.xdev.tci.safestart.SafeNamedContainerStarter; | ||
|
||
|
||
/** | ||
* Represents basic Testcontainers based infrastructure. | ||
* <p> | ||
* It can be extended and contain other things like e.g. clients, that may also require initialization during startup. | ||
* </p> | ||
* <p> | ||
* This was created so that you don't have to extend containers and add non-container related stuff like e.g. clients to | ||
* them. ("You should favor composition over inheritance") | ||
* </p> | ||
*/ | ||
@SuppressWarnings("java:S119") | ||
public class TCI<C extends GenericContainer<C>> | ||
{ | ||
private C container; | ||
private String networkAlias; | ||
private Runnable onStopped; | ||
|
||
protected TCI(final C container, final String networkAlias) | ||
{ | ||
this.container = Objects.requireNonNull(container); | ||
this.networkAlias = networkAlias; | ||
} | ||
|
||
public void setOnStopped(final Runnable onStopped) | ||
{ | ||
this.onStopped = onStopped; | ||
} | ||
|
||
public void setNetworkAlias(final String networkAlias) | ||
{ | ||
this.networkAlias = networkAlias; | ||
} | ||
|
||
public void start(final String containerName) | ||
{ | ||
new SafeNamedContainerStarter<>(containerName, this.container).start(); | ||
} | ||
|
||
public void stop() | ||
{ | ||
if(this.container == null) // Already stopped | ||
{ | ||
return; | ||
} | ||
|
||
try | ||
{ | ||
Unreliables.retryUntilSuccess(2, () -> { | ||
this.container.stop(); | ||
return null; | ||
}); | ||
} | ||
catch(final Exception ex) | ||
{ | ||
LoggerFactory.getLogger(this.getClass()) | ||
.warn("Failed to stop container", ex); | ||
} | ||
this.container = null; | ||
this.networkAlias = null; | ||
this.onStopped(); | ||
} | ||
|
||
protected void onStopped() | ||
{ | ||
if(this.onStopped != null) | ||
{ | ||
this.onStopped.run(); | ||
this.onStopped = null; | ||
} | ||
} | ||
|
||
public C getContainer() | ||
{ | ||
return this.container; | ||
} | ||
|
||
public String getNetworkAlias() | ||
{ | ||
return this.networkAlias; | ||
} | ||
} |
121 changes: 121 additions & 0 deletions
121
tci-base/src/main/java/software/xdev/tci/factory/BaseTCIFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
package software.xdev.tci.factory; | ||
|
||
import java.util.Collections; | ||
import java.util.HashSet; | ||
import java.util.Objects; | ||
import java.util.Set; | ||
import java.util.function.BiFunction; | ||
import java.util.function.Supplier; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import org.testcontainers.containers.GenericContainer; | ||
import org.testcontainers.containers.output.Slf4jLogConsumer; | ||
|
||
import software.xdev.tci.TCI; | ||
import software.xdev.tci.tracing.TCITracer; | ||
|
||
|
||
@SuppressWarnings("java:S119") | ||
public abstract class BaseTCIFactory< | ||
C extends GenericContainer<C>, | ||
I extends TCI<C>> | ||
implements TCIFactory<C, I> | ||
{ | ||
private final Logger logger; | ||
protected Set<I> returnedAndInUse = Collections.synchronizedSet(new HashSet<>()); | ||
protected boolean warmedUp; | ||
|
||
protected BiFunction<C, String, I> infraBuilder; | ||
protected final Supplier<C> containerBuilder; | ||
protected final String containerBaseName; | ||
protected final String containerLoggerName; | ||
protected final TCITracer tracer = new TCITracer(); | ||
|
||
protected BaseTCIFactory( | ||
final BiFunction<C, String, I> infraBuilder, | ||
final Supplier<C> containerBuilder, | ||
final String containerBaseName, | ||
final String containerLoggerName) | ||
{ | ||
this.infraBuilder = Objects.requireNonNull(infraBuilder); | ||
|
||
this.containerBuilder = Objects.requireNonNull(containerBuilder); | ||
this.containerBaseName = Objects.requireNonNull(containerBaseName); | ||
this.containerLoggerName = Objects.requireNonNull(containerLoggerName); | ||
|
||
this.logger = LoggerFactory.getLogger(this.getClass()); | ||
|
||
this.register(); | ||
} | ||
|
||
@Override | ||
public void warmUp() | ||
{ | ||
if(!this.warmedUp) | ||
{ | ||
this.warmUpSync(); | ||
} | ||
} | ||
|
||
protected synchronized void warmUpSync() | ||
{ | ||
if(this.warmedUp) | ||
{ | ||
return; | ||
} | ||
|
||
final long startTime = System.currentTimeMillis(); | ||
|
||
this.warmUpInternal(); | ||
this.warmedUp = true; | ||
|
||
this.tracer.timedAdd("warmUp", System.currentTimeMillis() - startTime); | ||
} | ||
|
||
protected void warmUpInternal() | ||
{ | ||
// No OP | ||
} | ||
|
||
protected C buildContainer() | ||
{ | ||
return this.containerBuilder.get() | ||
.withLogConsumer(getLogConsumer(this.containerLoggerName)); | ||
} | ||
|
||
protected I registerReturned(final I infra) | ||
{ | ||
this.returnedAndInUse.add(infra); | ||
infra.setOnStopped(() -> this.returnedAndInUse.remove(infra)); | ||
return infra; | ||
} | ||
|
||
@Override | ||
public void close() | ||
{ | ||
// NO OP | ||
} | ||
|
||
@Override | ||
public Set<I> getReturnedAndInUse() | ||
{ | ||
return new HashSet<>(this.returnedAndInUse); | ||
} | ||
|
||
@Override | ||
public TCITracer getTracer() | ||
{ | ||
return this.tracer; | ||
} | ||
|
||
protected Logger log() | ||
{ | ||
return this.logger; | ||
} | ||
|
||
protected static Slf4jLogConsumer getLogConsumer(final String name) | ||
{ | ||
return new Slf4jLogConsumer(LoggerFactory.getLogger(name)); | ||
} | ||
} |
40 changes: 40 additions & 0 deletions
40
tci-base/src/main/java/software/xdev/tci/factory/TCIFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package software.xdev.tci.factory; | ||
|
||
import java.util.Set; | ||
|
||
import org.testcontainers.containers.GenericContainer; | ||
|
||
import software.xdev.tci.TCI; | ||
import software.xdev.tci.factory.registry.TCIFactoryRegistry; | ||
import software.xdev.tci.tracing.TCITracer; | ||
|
||
|
||
/** | ||
* A factory for {@link TCI} | ||
*/ | ||
public interface TCIFactory<C extends GenericContainer<C>, I extends TCI<C>> extends AutoCloseable | ||
{ | ||
default void register() | ||
{ | ||
TCIFactoryRegistry.instance().register(this); | ||
} | ||
|
||
default void unregister() | ||
{ | ||
TCIFactoryRegistry.instance().unRegister(this); | ||
} | ||
|
||
void warmUp(); | ||
|
||
@Override | ||
void close(); | ||
|
||
Set<I> getReturnedAndInUse(); | ||
|
||
default String getFactoryName() | ||
{ | ||
return this.getClass().getSimpleName(); | ||
} | ||
|
||
TCITracer getTracer(); | ||
} |
74 changes: 74 additions & 0 deletions
74
tci-base/src/main/java/software/xdev/tci/factory/ondemand/OnDemandTCIFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package software.xdev.tci.factory.ondemand; | ||
|
||
import java.util.Optional; | ||
import java.util.concurrent.atomic.AtomicInteger; | ||
import java.util.function.BiFunction; | ||
import java.util.function.Consumer; | ||
import java.util.function.Supplier; | ||
|
||
import org.testcontainers.containers.GenericContainer; | ||
import org.testcontainers.containers.Network; | ||
|
||
import software.xdev.tci.TCI; | ||
import software.xdev.tci.factory.BaseTCIFactory; | ||
|
||
|
||
/** | ||
* A simple implementation of {@link software.xdev.tci.factory.TCIFactory}. | ||
* <p> | ||
* Creates new infrastructure on demand and allows customizing the container. | ||
* </p> | ||
* <p> | ||
* It's recommended to use this for certain infrastructure that is only required for a few tests. | ||
* </p> | ||
*/ | ||
public class OnDemandTCIFactory<C extends GenericContainer<C>, I extends TCI<C>> | ||
extends BaseTCIFactory<C, I> | ||
{ | ||
protected AtomicInteger startCounter = new AtomicInteger(1); | ||
|
||
public OnDemandTCIFactory( | ||
final BiFunction<C, String, I> infraBuilder, | ||
final Supplier<C> containerBuilder, | ||
final String containerBaseName, | ||
final String containerLoggerName) | ||
{ | ||
super(infraBuilder, containerBuilder, containerBaseName, containerLoggerName); | ||
} | ||
|
||
protected I newInternal(final Network network, final Consumer<C> buildContainerCustomizer) | ||
{ | ||
final C c = this.buildContainer() | ||
.withNetwork(network); | ||
Optional.ofNullable(buildContainerCustomizer).ifPresent(customizer -> customizer.accept(c)); | ||
|
||
final I infra = this.infraBuilder.apply(c, c.getNetworkAliases().stream() | ||
.skip(1) // At pos 0 is a generic one from GenericContainer | ||
.findFirst() | ||
.orElseGet(() -> c.getNetworkAliases().stream() | ||
.findFirst() | ||
.orElse(null))); | ||
infra.start(this.containerBaseName + "-" + this.startCounter.getAndIncrement()); | ||
return infra; | ||
} | ||
|
||
public I getNew(final Network network, final Consumer<C> buildContainerCustomizer) | ||
{ | ||
this.log().info("Getting new infra"); | ||
final long startTime = System.currentTimeMillis(); | ||
|
||
final I infra = this.registerReturned(this.newInternal(network, buildContainerCustomizer)); | ||
|
||
final long ms = System.currentTimeMillis() - startTime; | ||
this.log().info("Got new infra, took {}ms", ms); | ||
|
||
this.tracer.timedAdd("getNew", ms); | ||
|
||
return infra; | ||
} | ||
|
||
public I getNew(final Network network) | ||
{ | ||
return this.getNew(network, null); | ||
} | ||
} |
Oops, something went wrong.