Skip to content

Commit

Permalink
Init code and document
Browse files Browse the repository at this point in the history
  • Loading branch information
AB-xdev committed Apr 30, 2024
1 parent 22fb779 commit 7c98453
Show file tree
Hide file tree
Showing 30 changed files with 2,178 additions and 1 deletion.
38 changes: 37 additions & 1 deletion tci-base/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<connection>scm:git:https://github.com/xdev-software/tci-base.git</connection>
</scm>

<inceptionYear>2023</inceptionYear>
<inceptionYear>2024</inceptionYear>

<organization>
<name>XDEV Software</name>
Expand Down Expand Up @@ -84,6 +84,42 @@
</repository>
</distributionManagement>

<dependencies>
<!-- TestContainers -->
<dependency>
<groupId>software.xdev</groupId>
<artifactId>testcontainers-junit4-mock</artifactId>
<version>1.0.1</version>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<scope>compile</scope>
<version>1.19.7</version>
<exclusions>
<!-- The few classes that testcontainers needs have been extracted -->
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- Testcontainers is using outdated v1 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.13</version>
</dependency>

<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<scope>compile</scope>
<version>1.10.2</version>
</dependency>
</dependencies>

<build>
<pluginManagement>
<plugins>
Expand Down
92 changes: 92 additions & 0 deletions tci-base/src/main/java/software/xdev/tci/TCI.java
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 tci-base/src/main/java/software/xdev/tci/factory/BaseTCIFactory.java
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 tci-base/src/main/java/software/xdev/tci/factory/TCIFactory.java
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();
}
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);
}
}
Loading

0 comments on commit 7c98453

Please sign in to comment.