Class SharedAxonServerContainer

java.lang.Object
io.axoniq.framework.testcontainer.SharedAxonServerContainer

public final class SharedAxonServerContainer extends Object
Holds the single canonical AxonServerContainer configuration meant to be shared by every IT test suite in the build, instead of each test class declaring its own.

Testcontainers' reuse feature (a container-level GenericContainer.withReuse(boolean) combined with testcontainers.reuse.enable=true in ~/.testcontainers.properties) attaches to an already-running container instead of starting a new one, but only when the requested configuration hashes identically to one already running. Every test class that used to hand-roll its own AxonServerContainer therefore started a fresh container, even within the same build. Depending on INSTANCE here instead, rather than constructing a separate AxonServerContainer, is what lets those requests collapse onto the same running container.

This class lives in this module specifically because it is low enough in the dependency graph for every consumer -- connector/axon-server-connector, dependency-injection/spring/spring-boot-autoconfigure, and all integrationtests/* modules -- to reach it directly.

Since:
5.4.0
Author:
John Hendrikx
  • Field Details

    • LICENSE_RESOURCE

      public static final String LICENSE_RESOURCE
      Classpath resource name of the Axon Server Enterprise test license. Only present (and thus only mounted, see INSTANCE) in modules whose test classpath includes it; absent elsewhere. Tests that don't need multi-context features work identically either way.
      See Also:
    • INSTANCE

      public static final AxonServerContainer INSTANCE
      The shared container instance. Byte-identical construction is the entire point: it is what lets Testcontainers' reuse hash match across every module and JVM fork that references it.
  • Method Details

    • ensureStarted

      public static void ensureStarted()
      Starts INSTANCE if it isn't already running.

      GenericContainer.start() is idempotent on its own -- Testcontainers makes it a no-op when the container is already running in this JVM -- so this is a thin, explicitly-named entry point for callers rather than a correctness requirement.

      synchronized alone only guards within one JVM, not across forked processes, and Testcontainers' own findContainerForReuse() has no locking of its own (confirmed via its source: a // TODO locking comment). Two Surefire/Failsafe forks can therefore both decide INSTANCE isn't running yet and both end up creating their own container instead of one attaching to the other's -- confirmed directly: from a clean Docker state, two forks calling this method concurrently produced two separate containers on two different ports, even though both compute the identical reuse hash. A FileLock serializes the whole check-and-start sequence across processes, so the second caller sees the first one's container already running (and matching on hash) by the time it gets to check. Kept synchronized too: a second FileLock acquisition attempt from another thread in the same JVM throws OverlappingFileLockException rather than waiting, so cross-thread safety within this JVM still needs the intrinsic lock.

    • licenseExists

      public static boolean licenseExists()
      Checks whether LICENSE_RESOURCE is present on this JVM's classpath.
      Returns:
      true if the license resource is present and non-empty, false otherwise