diff --git a/docs/sourcey/css/extra.css b/docs/sourcey/css/extra.css new file mode 100644 index 00000000000..ff780cb25ea --- /dev/null +++ b/docs/sourcey/css/extra.css @@ -0,0 +1,128 @@ +h1, h2, h3, h4, h5, h6 { + font-family: 'Rubik', sans-serif; +} + +[data-md-color-scheme="testcontainers"] { + --md-primary-fg-color: #00bac2; + --md-accent-fg-color: #361E5B; + --md-typeset-a-color: #0C94AA; + --md-primary-fg-color--dark: #291A3F; + --md-default-fg-color--lightest: #F2F4FE; + --md-footer-fg-color: #361E5B; + --md-footer-fg-color--light: #746C8F; + --md-footer-fg-color--lighter: #C3BEDE; + --md-footer-bg-color: #F7F9FD; + --md-footer-bg-color--dark: #F7F9FD; +} + +.card-grid { + display: grid; + gap: 10px; +} + +.tc-version { + font-size: 1.1em; + text-align: center; + margin: 0; +} + +@media (min-width: 680px) { + .card-grid { + grid-template-columns: repeat(3, 1fr); + } +} + +body .card-grid-item { + display: flex; + align-items: center; + gap: 20px; + border: 1px solid #C3BEDE; + border-radius: 6px; + padding: 16px; + font-weight: 600; + color: #9991B5; + background: #F2F4FE; +} + +body .card-grid-item:hover, +body .card-grid-item:focus { + color: #9991B5; +} + +.card-grid-item[href] { + color: var(--md-primary-fg-color--dark); + background: transparent; +} + +.card-grid-item[href]:hover, +.card-grid-item[href]:focus { + background: #F2F4FE; + color: var(--md-primary-fg-color--dark); +} + +.community-callout-wrapper { + padding: 30px 10px 0 10px; +} + +.community-callout { + color: #F2F4FE; + background: linear-gradient(10.88deg, rgba(102, 56, 242, 0.4) 9.56%, #6638F2 100%), #291A3F; + box-shadow: 0px 20px 45px rgba(#9991B5, 0.75); + border-radius: 10px; + padding: 20px; +} + +.community-callout h2 { + font-size: 1.15em; + margin: 0 0 20px 0; + color: #F2F4FE; + text-align: center; +} + +.community-callout ul { + list-style: none; + padding: 0; + display: flex; + justify-content: space-between; + gap: 10px; + margin-top: 20px; + margin-bottom: 0; +} + +.community-callout a { + transition: opacity 0.2s ease; +} + +.community-callout a:hover { + opacity: 0.5; +} + +.community-callout a img { + height: 1.75em; + width: auto; + aspect-ratio: 1; +} + +@media (min-width: 1220px) { + .community-callout-wrapper { + padding: 40px 0 0; + } + + .community-callout h2 { + font-size: 1.25em; + } + + .community-callout a img { + height: 2em; + } +} + +@media (min-width: 1600px) { + .community-callout h2 { + font-size: 1.15em; + } + + .community-callout a img { + height: 1.75em; + } +} \ No newline at end of file diff --git a/docs/sourcey/css/tc-header.css b/docs/sourcey/css/tc-header.css new file mode 100644 index 00000000000..de78d636e9c --- /dev/null +++ b/docs/sourcey/css/tc-header.css @@ -0,0 +1,389 @@ + +:root { + --color-catskill: #F2F4FE; + --color-catskill-45: rgba(242, 244, 254, 0.45); + --color-mist: #E7EAFB; + --color-fog: #C3C7E6; + --color-smoke: #9991B5; + --color-smoke-75: rgba(153, 145, 181, 0.75); + --color-storm: #746C8F; + --color-topaz: #00BAC2; + --color-pacific: #17A6B2; + --color-teal: #027F9E; + --color-eggplant: #291A3F; + --color-plum: #361E5B; + +} + +#site-header { + color: var(--color-storm); + background: #fff; + font-family: 'Rubik', Arial, Helvetica, sans-serif; + font-size: 12px; + line-height: 1.5; + position: relative; + width: 100%; + z-index: 4; + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; + padding: 20px; +} + +body.tc-header-active #site-header { + z-index: 5; +} + +#site-header .brand { + display: flex; + justify-content: space-between; + gap: 20px; + width: 100%; +} + +#site-header .logo { + display: flex; +} + +#site-header .logo img, +#site-header .logo svg { + height: 30px; + width: auto; + max-width: 100%; +} + +#site-header #mobile-menu-toggle { + background: none; + border: none; + display: flex; + align-items: center; + gap: 10px; + cursor: pointer; + color: var(--color-eggplant); + padding: 0; + margin: 0; + font-weight: 500; +} + +body.mobile-menu #site-header #mobile-menu-toggle { + color: var(--color-topaz); +} + +#site-header ul { + list-style: none; + padding: 0; + margin: 0; +} + +#site-header nav { + display: none; +} + +#site-header .menu-item { + display: flex; +} + +#site-header .menu-item button, +#site-header .menu-item a { + min-height: 30px; + display: flex; + gap: 6px; + align-items: center; + border: none; + background: none; + cursor: pointer; + padding: 0; + font-weight: 500; + color: var(--color-eggplant); + text-decoration: none; + font-size: 14px; + transition: color 0.2s ease; + white-space: nowrap; +} + +#site-header .menu-item .badge { + color: white; + font-size: 10px; + padding: 2px 6px; + background-color: #0FD5C6; // somehow $topaz is too dark for me. +text-align: center; + text-decoration: none; + display: inline-block; + border-radius: 6px; + &:hover { + + } +} + +#site-header .menu-item button:hover, +#site-header .menu-item a:hover { + color: var(--color-topaz); +} + +#site-header .menu-item button .icon-external, +#site-header .menu-item a .icon-externa { + margin-left: auto; + opacity: .3; + flex-shrink: 0; +} + +#site-header .menu-item button .icon-caret, +#site-header .menu-item a .icon-caret { + opacity: .3; + height: 8px; +} + +#site-header .menu-item button .icon-slack, +#site-header .menu-item a .icon-slack, +#site-header .menu-item button .icon-github, +#site-header .menu-item a .icon-github { + height: 18px; +} + +#site-header .menu-item .menu-dropdown { + flex-direction: column; +} + +body #site-header .menu-item .menu-dropdown { + display: none; +} + +#site-header .menu-item.has-children.active .menu-dropdown { + display: flex; + z-index: 10; +} + +#site-header .menu-dropdown-item + .menu-dropdown-item { + border-top: 1px solid var(--color-mist); +} + +#site-header .menu-dropdown-item a { + display: flex; + gap: 10px; + align-items: center; + padding: 10px 20px; + font-weight: 500; + color: var(--color-eggplant); + text-decoration: none; + transition: + color 0.2s ease, + background 0.2s ease; +} + +#site-header .menu-dropdown-item a .icon-external { + margin-left: auto; + color: var(--color-fog); + flex-shrink: 0; + opacity: 1; +} + +#site-header .menu-dropdown-item a:hover { + background-color: var(--color-catskill-45); +} + +#site-header .menu-dropdown-item a:hover .icon-external { + color: var(--color-topaz); +} + +#site-header .menu-dropdown-item a img { + height: 24px; +} + +.md-header { + background-color: var(--color-catskill); + color: var(--color-eggplant); +} + +.md-header.md-header--shadow { + box-shadow: none; +} + +.md-header__inner.md-grid { + max-width: 100%; + padding: 1.5px 20px; +} + +[dir=ltr] .md-header__title { + margin: 0; +} + +.md-header__topic:first-child { + font-size: 16px; + font-weight: 500; + font-family: 'Rubik', Arial, Helvetica, sans-serif; +} + +.md-header__title.md-header__title--active .md-header__topic, +.md-header__title[data-md-state=active] .md-header__topic { + opacity: 1; + pointer-events: all; + transform: translateX(0); + transition: none; + z-index: 0; +} + +.md-header__topic a { + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + transition: color .2s ease; +} + +.md-header__topic a:hover { + color: var(--color-topaz); +} + +div.md-header__source { + width: auto; +} + +div.md-source__repository { + max-width: 100%; +} + +.md-main { + padding: 0 12px; +} + +@media screen and (min-width: 60em) { + form.md-search__form { + background-color: #FBFBFF; + color: var(--color-storm); + } + + form.md-search__form:hover { + background-color: #fff; + } + + .md-search__input + .md-search__icon { + color: var(--color-plum); + } + + .md-search__input::placeholder { + color: var(--color-smoke); + } +} + +@media (min-width: 500px) { + #site-header { + font-size: 16px; + padding: 20px 40px; + } + #site-header .logo img, + #site-header .logo svg { + height: 48px; + } + + #site-header .menu-item button .icon-caret, + #site-header .menu-item a .icon-caret { + height: 10px; + } + + #site-header .menu-item button .icon-slack, + #site-header .menu-item a .icon-slack, + #site-header .menu-item button .icon-github, + #site-header .menu-item a .icon-github { + height: 24px; + } + + .md-header__inner.md-grid { + padding: 5px 40px; + } + + .md-main { + padding: 0 32px; + } +} + +@media (min-width: 1024px) { + #site-header #mobile-menu-toggle { + display: none; + } + + #site-header nav { + display: block; + } + + #site-header .menu { + display: flex; + justify-content: center; + gap: 30px; + } + + #site-header .menu-item { + align-items: center; + position: relative; + } + + #site-header .menu-item button, + #site-header .menu-item a { + min-height: 48px; + gap: 8px; + font-size: 16px; + } + + #site-header .menu-item .menu-dropdown { + position: absolute; + top: 100%; + right: -8px; + border: 1px solid var(--color-mist); + border-radius: 6px; + background: #fff; + box-shadow: 0px 30px 35px var(--color-smoke-75); + min-width: 200px; + } +} + + +@media (max-width: 1023px) { + #site-header { + flex-direction: column; + } + + body.mobile-tc-header-active #site-header { + z-index: 5; + } + + body.mobile-menu #site-header nav { + display: flex; + } + + #site-header nav { + position: absolute; + top: calc(100% - 5px); + width: calc(100% - 80px); + flex-direction: column; + border: 1px solid var(--color-mist); + border-radius: 6px; + background: #fff; + box-shadow: 0px 30px 35px var(--color-smoke-75); + min-width: 200px; + } + + #site-header .menu-item { + flex-direction: column; + } + #site-header .menu-item + .menu-item { + border-top: 1px solid var(--color-mist); + } + + #site-header .menu-item button, + #site-header .menu-item a { + padding: 10px 20px; + } + + #site-header .menu-item.has-children.active .menu-dropdown { + border-top: 1px solid var(--color-mist); + } + + #site-header .menu-dropdown-item a { + padding: 10px 20px 10px 30px; + } +} + +@media (max-width: 499px) { + #site-header nav { + width: calc(100% - 40px); + } +} diff --git a/docs/sourcey/examples.html b/docs/sourcey/examples.html new file mode 100644 index 00000000000..f646e6e923a --- /dev/null +++ b/docs/sourcey/examples.html @@ -0,0 +1,41 @@ + +Examples - Testcontainers for Java Sourcey Reference
Pages

Examples

Examples of different use cases provided by Testcontainers can be found below:
\ No newline at end of file diff --git a/docs/sourcey/favicon.ico b/docs/sourcey/favicon.ico new file mode 100644 index 00000000000..311a0acaa37 Binary files /dev/null and b/docs/sourcey/favicon.ico differ diff --git a/docs/sourcey/features/advanced-options.html b/docs/sourcey/features/advanced-options.html new file mode 100644 index 00000000000..46eaf0924ce --- /dev/null +++ b/docs/sourcey/features/advanced-options.html @@ -0,0 +1,97 @@ + +Advanced options - Testcontainers for Java Sourcey Reference
Core concepts

Advanced options

To add a custom label to the container, use withLabel:

Container labels

+

To add a custom label to the container, use withLabel:

+ +

Adding a single label inside_block:single_label

+ + +

Additionally, multiple labels may be applied together from a map:

+ +

Adding multiple labels inside_block:multiple_labels

+ + +

Image Pull Policy

+

By default, the container image is retrieved from the local Docker images cache. +This works well when running against a specific version, but for images with a static tag (i.e. 'latest') this may lead to a newer version not being pulled.

+

It is possible to specify an Image Pull Policy to determine at runtime whether an image should be pulled or not:

+ +

Setting image pull policy inside_block:built_in_image_pull_policy

+ + +

... or providing a function:

+ +

Custom image pull policy inside_block:custom_image_pull_policy

+ + +

You can also configure Testcontainers to use your custom implementation by using pull.policy

+

src/test/resources/testcontainers.properties

+
+
+ +
+
pull.policy=com.mycompany.testcontainers.ExampleImagePullPolicy
+

You can also use the provided implementation to always pull images

+

src/test/resources/testcontainers.properties

+
+
+ +
+
pull.policy=org.testcontainers.images.AlwaysPullPolicy
+

Please see the documentation on configuration mechanisms for more information.

+

Customizing the container

+

Using docker-java

+

It is possible to use the docker-java API directly to customize containers before creation. This is useful if there is a need to use advanced Docker features that are not exposed by the Testcontainers API. Any customizations you make using withCreateContainerCmdModifier will be applied on top of the container definition that Testcontainers creates, but before it is created.

+

For example, this can be used to change the container hostname:

+ +

Using modifier to change hostname inside_block:hostname

+ + +

... or modify container memory (see this if it does not appear to work):

+ +

Using modifier to change memory limits inside_block:memory

+ + +
+

Note +It is recommended to use this sparingly, and follow changes to the docker-java API if you choose to use this. +It is typically quite stable, though.

+
+

For what is possible, consult the docker-java CreateContainerCmd source code.

+

Using CreateContainerCmdModifier

+

Testcontainers provides a CreateContainerCmdModifier to customize docker-java CreateContainerCmd +via Service Provider Interface (SPI) mechanism.

+ +

CreateContainerCmd example implementation

+ + +

The previous implementation should be registered in META-INF/services/org.testcontainers.core.CreateContainerCmdModifier file.

+
+

Warning +CreateContainerCmdModifier implementation will apply to all containers created by Testcontainers.

+
+

Parallel Container Startup

+

Usually, containers are started sequentially when more than one container is used. +Using Startables.deepStart(container1, container2, ...).join() will start all containers in parallel. +This can be advantageous to reduce the impact of the container startup overhead.

+
\ No newline at end of file diff --git a/docs/sourcey/features/commands.html b/docs/sourcey/features/commands.html new file mode 100644 index 00000000000..a629cbe7eb5 --- /dev/null +++ b/docs/sourcey/features/commands.html @@ -0,0 +1,48 @@ + +Executing commands - Testcontainers for Java Sourcey Reference
Core concepts

Executing commands

By default the container will execute whatever command is specified in the image's Dockerfile. To override this, and specify a different command, use withCommand. For example:

Container startup command

+

By default the container will execute whatever command is specified in the image's Dockerfile. To override this, and specify a different command, use withCommand. For example:

+ +

Specifying a startup command inside_block:startupCommand

+ + +

Executing a command

+

Your test can execute a command inside a running container, similar to a docker exec call:

+ +

Executing a command inside a running container inside_block:standaloneExec

+ + +

This can be useful for software that has a command line administration tool. You can also get the output (stdout/stderr) and exit code from the command - for example:

+ +

Executing a command inside a running container and reading the result inside_block:execReadingStdout

+ + +

Environment variables

+

To add environment variables to the container, use withEnv:

+
+
+ +
+
new GenericContainer(...)
+		.withEnv("API_TOKEN", "foo")
+
\ No newline at end of file diff --git a/docs/sourcey/features/configuration.html b/docs/sourcey/features/configuration.html new file mode 100644 index 00000000000..648ed8cb636 --- /dev/null +++ b/docs/sourcey/features/configuration.html @@ -0,0 +1,144 @@ + +Custom configuration - Testcontainers for Java Sourcey Reference
Core concepts

Custom configuration

You can override some default properties if your environment requires that.

You can override some default properties if your environment requires that.

+

Configuration locations

+

The configuration will be loaded from multiple locations. Properties are considered in the following order:

+
    +
  1. Environment variables
  2. +
  3. .testcontainers.properties in user's home folder. Example locations:
    Linux: /home/myuser/.testcontainers.properties
    Windows: C:/Users/myuser/.testcontainers.properties
    macOS: /Users/myuser/.testcontainers.properties
  4. +
  5. testcontainers.properties on the classpath.
  6. +
+

Note that when using environment variables, configuration property names should be set in upper +case with underscore separators, preceded by TESTCONTAINERS_ - e.g. checks.disable becomes +TESTCONTAINERS_CHECKS_DISABLE.

+

The classpath testcontainers.properties file may exist within the local codebase (e.g. within the src/test/resources directory) or within library dependencies that you may have. +Any such configuration files will have their contents merged. +If any keys conflict, the value will be taken on the basis of the first value found in:

+
    +
  • 'local' classpath (i.e. where the URL of the file on the classpath begins with file:), then
  • +
  • other classpath locations (i.e. JAR files) - considered in alphabetical order of path to provide deterministic ordering.
  • +
+

Disabling the startup checks

+
+

checks.disable = [true|false]

+
+

Before running any containers Testcontainers will perform a set of startup checks to ensure that your environment is configured correctly. Usually they look like this:

+
+
+ +
+
        ℹ︎ Checking the system...
+        ✔ Docker version should be at least 1.6.0
+        ✔ File should be mountable
+        ✔ A port exposed by a docker container should be accessible
+

It takes a couple of seconds, but if you want to speed up your tests, you can disable the checks once you have everything configured. Add checks.disable=true to your $HOME/.testcontainers.properties to completely disable them.

+

Customizing images

+
+

Note +This approach is discouraged and deprecated, but is documented for completeness. +Overriding individual image names via configuration may be removed in 2021. +See Image Name Substitution for other strategies for substituting image names to pull from other registries.

+
+

Testcontainers uses public Docker images to perform different actions like startup checks, VNC recording and others. +Some companies disallow the usage of Docker Hub, but you can override *.image properties with your own images from your private registry to workaround that.

+
+

ryuk.container.image = testcontainers/ryuk:0.3.3 +Performs fail-safe cleanup of containers, and always required (unless Ryuk is disabled)

+
+
+

tinyimage.container.image = alpine:3.17
Used to check whether images can be pulled at startup, and always required (unless startup checks are disabled)

+
+
+

sshd.container.image = testcontainers/sshd:1.4.0
Required if exposing host ports to containers

+
+
+

vncrecorder.container.image = testcontainers/vnc-recorder:1.3.0 +Used by VNC recorder in Testcontainers' Selenium integration

+
+
+

socat.container.image = alpine/socat
compose.container.image = docker/compose:1.8.0
Required if using Docker Compose

+
+
+

kafka.container.image = confluentinc/cp-kafka
Used by KafkaContainer

+
+
+

localstack.container.image = localstack/localstack
Used by LocalStack

+
+
+

pulsar.container.image = apachepulsar/pulsar:2.2.0
Used by Apache Pulsar

+
+

Customizing Ryuk resource reaper

+
+

ryuk.container.image = testcontainers/ryuk:0.3.3 +The resource reaper is responsible for container removal and automatic cleanup of dead containers at JVM shutdown

+
+
+

ryuk.container.privileged = true +In some environments ryuk must be started in privileged mode to work properly (--privileged flag)

+
+

Disabling Ryuk

+

Ryuk must be started as a privileged container.
If your environment already implements automatic cleanup of containers after the execution, +but does not allow starting privileged containers, you can turn off the Ryuk container by setting +TESTCONTAINERS_RYUK_DISABLED environment variable to true.

+

!!!tip + Note that Testcontainers will continue doing the cleanup at JVM's shutdown, unless you kill -9 your JVM process.

+

Customizing image pull behaviour

+
+

pull.timeout = 120 +By default Testcontainers will timeout if pull takes more than this duration (in seconds)

+
+
+

pull.pause.timeout = 30 +By default Testcontainers will abort the pull of an image if the pull appears stalled (no data transferred) for longer than this duration (in seconds).

+
+

Customizing client ping behaviour

+
+

client.ping.timeout = 10 +Specifies for how long Testcontainers will try to connect to the Docker client to obtain valid info about the client before giving up and trying next strategy, if applicable (in seconds).

+
+

Customizing Docker host detection

+

Testcontainers will attempt to detect the Docker environment and configure everything to work automatically.

+

However, sometimes customization is required. Testcontainers will respect the following environment variables:

+
+

DOCKER_HOST = unix:///var/run/docker.sock
See Docker environment variables

+

TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE
Path to Docker's socket. Used by Ryuk, Docker Compose, and a few other containers that need to perform Docker actions.
Example: /var/run/docker-alt.sock

+

TESTCONTAINERS_HOST_OVERRIDE
Docker's host on which ports are exposed.
Example: docker.svc.local

+
+

For advanced users, the Docker host connection can be configured via configuration in ~/.testcontainers.properties. +Note that these settings require use of the EnvironmentAndSystemPropertyClientProviderStrategy. The example below +illustrates usage:

+
+
+ +
+
docker.client.strategy=org.testcontainers.dockerclient.EnvironmentAndSystemPropertyClientProviderStrategy
+docker.host=tcp\://my.docker.host\:1234     # Equivalent to the DOCKER_HOST environment variable. Colons should be escaped.
+docker.tls.verify=1                         # Equivalent to the DOCKER_TLS_VERIFY environment variable
+docker.cert.path=/some/path                 # Equivalent to the DOCKER_CERT_PATH environment variable
+

In addition, you can deactivate this behaviour by specifying:

+
+
+ +
+
dockerconfig.source=autoIgnoringUserProperties # 'auto' by default
+
\ No newline at end of file diff --git a/docs/sourcey/features/container-logs.html b/docs/sourcey/features/container-logs.html new file mode 100644 index 00000000000..8e63edc16fa --- /dev/null +++ b/docs/sourcey/features/container-logs.html @@ -0,0 +1,115 @@ + +Accessing container logs - Testcontainers for Java Sourcey Reference
Core concepts

Accessing container logs

It is possible to capture container output using:

It is possible to capture container output using:

+
    +
  • the getLogs() method, which simply returns a String snapshot of a container's entire log output
  • +
  • the followOutput() method. This method accepts a Consumer and (optionally) +a varargs list stating which of STDOUT, STDERR, or both, should be followed. If not specified, both will be followed.
  • +
+

At present, container output will always begin from the time of container creation.

+

Reading all logs (from startup time to present)

+

getLogs() is the simplest mechanism for accessing container logs, and can be used as follows:

+ +

Accessing all output (stdout and stderr) inside_block:docsGetAllLogs

+ + + +

Accessing just stdout inside_block:docsGetStdOut

+ + + +

Accessing just stderr inside_block:docsGetStdErr

+ + +

Streaming logs

+

Testcontainers includes some out-of-the-box Consumer implementations that can be used with the streaming followOutput() model; examples follow.

+

Streaming container output to an SLF4J logger

+

Given an existing SLF4J logger instance named LOGGER:

+
+
+ +
+
Slf4jLogConsumer logConsumer = new Slf4jLogConsumer(LOGGER);
+container.followOutput(logConsumer);
+

By default both standard out and standard error will both be emitted at INFO level. +Standard error may be emitted at ERROR level, if desired:

+
+
+ +
+
Slf4jLogConsumer logConsumer = new Slf4jLogConsumer(LOGGER).withSeparateOutputStreams();
+

The Mapped Diagnostic Context (MDC) for emitted messages may be configured using the withMdc(...) option:

+
+
+ +
+
Slf4jLogConsumer logConsumer = new Slf4jLogConsumer(LOGGER).withMdc("key", "value");
+

or using an existing map of key-value pairs:

+
+
+ +
+
Slf4jLogConsumer logConsumer = new Slf4jLogConsumer(LOGGER).withMdc(map);
+

Capturing container output as a String

+

To stream logs live or customize the decoding, ToStringConsumer may be used:

+
+
+ +
+
ToStringConsumer toStringConsumer = new ToStringConsumer();
+container.followOutput(toStringConsumer, OutputType.STDOUT);
+
+String utf8String = toStringConsumer.toUtf8String();
+
+// Or if the container output is not UTF-8
+String otherString = toStringConsumer.toString(CharSet.forName("ISO-8859-1"));
+

Waiting for container output to contain expected content

+

WaitingConsumer will block until a frame of container output (usually a line) matches a provided predicate.

+

A timeout may be specified, as shown in this example.

+
+
+ +
+
WaitingConsumer consumer = new WaitingConsumer();
+
+container.followOutput(consumer, STDOUT);
+
+consumer.waitUntil(frame -> 
+    frame.getUtf8String().contains("STARTED"), 30, TimeUnit.SECONDS);
+

Additionally, as the Java 8 Consumer functional interface is used, Consumers may be composed together. This is +useful, for example, to capture all the container output but only when a matching string has been found. e.g.:

+
+
+ +
+
WaitingConsumer waitingConsumer = new WaitingConsumer();
+ToStringConsumer toStringConsumer = new ToStringConsumer();
+
+Consumer<OutputFrame> composedConsumer = toStringConsumer.andThen(waitingConsumer);
+container.followOutput(composedConsumer);
+
+waitingConsumer.waitUntil(frame -> 
+    frame.getUtf8String().contains("STARTED"), 30, TimeUnit.SECONDS);
+
+String utf8String = toStringConsumer.toUtf8String();
+
\ No newline at end of file diff --git a/docs/sourcey/features/creating-container.html b/docs/sourcey/features/creating-container.html new file mode 100644 index 00000000000..25489ac794d --- /dev/null +++ b/docs/sourcey/features/creating-container.html @@ -0,0 +1,56 @@ + +Creating a container - Testcontainers for Java Sourcey Reference
Core concepts

Creating a container

Testcontainers' generic container support offers the most flexibility, and makes it easy to use virtually any container images as temporary test dependencies. For example, if you might use it to test interactions with:

Creating a generic container based on an image

+

Testcontainers' generic container support offers the most flexibility, and makes it easy to use virtually any container +images as temporary test dependencies. For example, if you might use it to test interactions with:

+
    +
  • NoSQL databases or other data stores (e.g. redis, elasticsearch, mongo)
  • +
  • Web servers/proxies (e.g. nginx, apache)
  • +
  • Log services (e.g. logstash, kibana)
  • +
  • Other services developed by your team/organization which are already dockerized
  • +
+

With a generic container, you set the container image using a parameter to the rule constructor, e.g.:

+
+
+ +
+
new GenericContainer(DockerImageName.parse("jboss/wildfly:9.0.1.Final"))
+

Specifying an image

+

Many Container classes in Testcontainers have historically supported:

+
    +
  • a no-args constructor - for example new GenericContainer() and new ElasticsearchContainer(). With these constructors, Testcontainers has traditionally used a default image name (including a fixed image tag/version). This has caused a conflict between the need to keep the defaults sane (i.e. up to date) and the need to avoid silently upgrading these dependencies along with new versions of Testcontainers.
  • +
  • a single string-argument constructor, which has taken either a version or an image name as a String. This has caused some ambiguity and confusion.
  • +
+

Since v1.15.0, both of these constructor types have been deprecated, for the reasons given above.

+

Instead, it is highly recommended that all containers be constructed using a constructor that accepts a DockerImageName object. +The DockerImageName class is an unambiguous reference to a docker image.

+

It is suggested that developers treat DockerImageNames as you would any other potentially-constant value - consider defining a constant in your test codebase that matches the production version of the dependency you are using.

+

Examples

+

A generic container rule can be used with any public docker image; for example:

+ +

Creating a Redis container inside_block:container

+ + +

The container, as @Container, will be started before any tests in the class run, and will be destroyed after all +tests have run.

+
\ No newline at end of file diff --git a/docs/sourcey/features/creating-images.html b/docs/sourcey/features/creating-images.html new file mode 100644 index 00000000000..1a543913572 --- /dev/null +++ b/docs/sourcey/features/creating-images.html @@ -0,0 +1,96 @@ + +Creating images on-the-fly - Testcontainers for Java Sourcey Reference
Core concepts

Creating images on-the-fly

In situations where there is no pre-existing Docker image, Testcontainers can create a new temporary image on-the-fly from a Dockerfile. For example, when the component under test is the Docker image itself, or when an existing base image needs to be customized for specific test(s).

Overview

+

In situations where there is no pre-existing Docker image, Testcontainers can create a new temporary image on-the-fly +from a Dockerfile. For example, when the component under test is the Docker image itself, or when an existing base +image needs to be customized for specific test(s).

+

Simply pass a configured instance of ImageFromDockerfile as a constructor parameter to GenericContainer. +Testcontainers will docker build a temporary container image, and will use it when creating the container.

+

Dockerfile from String, file or classpath resource

+

ImageFromDockerfile accepts arbitrary files, strings or classpath resources to be used as files in the build context. +At least one of these needs to be a Dockerfile.

+
+
+ +
+
GenericContainer container = new GenericContainer(
+    new ImageFromDockerfile()
+            .withFileFromString("folder/someFile.txt", "hello")
+            .withFileFromClasspath("test.txt", "mappable-resource/test-resource.txt")
+            .withFileFromClasspath("Dockerfile", "mappable-dockerfile/Dockerfile"))
+

The following methods may be used to provide the Dockerfile and any other required build context files:

+
    +
  • withFileFromString(buildContextPath, content)
  • +
  • withFileFromClasspath(buildContextPath, classpathPath)
  • +
  • withFileFromPath(buildContextPath, filesystemPath)
  • +
  • withFileFromFile(buildContextPath, filesystemFile)
  • +
+
+

Info +In older versions of Testcontainers (before 1.3.0) it was necessary to explicitly declare each file that needed to +be present in the Docker build context. +This can be replaced with the following syntax:

+ +

Passing an entire directory of files to the Dockerfile build context inside_block:docsShowRecursiveFileInclusion

+ + +

Where RESOURCE_PATH is the path to a directory containing a Dockerfile and any files that it needs to refer to. +Doing this is equivalent to docker build RESOURCE_PATH on the command line.

+

To mimic docker build ., RESOURCE_PATH would simply be set to . as well.

+
+

Dockerfile DSL

+

If a static Dockerfile is not sufficient (e.g. your test needs to cover many variations that are best generated +programmatically), there is a DSL available to allow Dockerfiles to be defined in code. e.g.:

+
+
+ +
+
new GenericContainer(
+        new ImageFromDockerfile()
+                .withDockerfileFromBuilder(builder ->
+                        builder
+                                .from("alpine:3.17")
+                                .run("apk add --update nginx")
+                                .cmd("nginx", "-g", "daemon off;")
+                                .build()))
+                .withExposedPorts(80);
+

See ParameterizedDockerfileContainerTest for a very basic example of using this in conjunction with JUnit +parameterized testing.

+

Automatic deletion

+

Temporary container images will be automatically removed when the test JVM shuts down. If this is not desired and +the image should be retained between tests, pass a stable image name and false flag to the ImageFromDockerfile +constructor.

+

Retaining the image between tests will use Docker's image cache to accelerate subsequent test runs.

+

By default the no-args constructor will use an image name of the form testcontainers/ + random string:

+
    +
  • public ImageFromDockerfile()
  • +
  • public ImageFromDockerfile(String dockerImageName)
  • +
  • public ImageFromDockerfile(String dockerImageName, boolean deleteOnExit)
  • +
+

Alternative Dockerfiles

+

Normally Docker will automatically build an image from any /Dockerfile that it finds in the root of the build context. +To override this behaviour, use .withDockerfilePath("./Name-Of-Other-Dockerfile").

+

Build Args

+

Build Args may be used to allow lightweight parameterization.

+

To specify build args, use .withBuildArg("varname", "value") or provide a Map of args using .withBuildArgs(map).

+
\ No newline at end of file diff --git a/docs/sourcey/features/files.html b/docs/sourcey/features/files.html new file mode 100644 index 00000000000..61aa84b6a14 --- /dev/null +++ b/docs/sourcey/features/files.html @@ -0,0 +1,48 @@ + +Files and volumes - Testcontainers for Java Sourcey Reference
Core concepts

Files and volumes

Files can be copied into the container before startup, or can be copied from the container after the container has started.

Copying files

+

Files can be copied into the container before startup, or can be copied from the container after the container has started.

+
+

Note +This is the recommended approach for portability cross-docker environments.

+
+

Copying to a container before startup

+ +

Copying files using MountableFile inside_block:copyToContainer

+ + +

Using Transferable, file content will be placed in the specified location.

+ +

Copying files using Transferable inside_block:transferableFile

+ + +

Setting file mode is also possible.

+ +

Copying files using Transferable with file mode inside_block:transferableWithFileMode

+ + +

Copying a file from a running container

+ +

Copying files from a container inside_block:copyFileFromContainer

+ +
\ No newline at end of file diff --git a/docs/sourcey/features/image-name-substitution.html b/docs/sourcey/features/image-name-substitution.html new file mode 100644 index 00000000000..57a377e3827 --- /dev/null +++ b/docs/sourcey/features/image-name-substitution.html @@ -0,0 +1,158 @@ + +Image name substitution - Testcontainers for Java Sourcey Reference
Core concepts

Image name substitution

Testcontainers supports automatic substitution of Docker image names.

Testcontainers supports automatic substitution of Docker image names.

+

This allows replacement of an image name specified in test code with an alternative name - for example, to replace the +name of a Docker Hub image dependency with an alternative hosted on a private image registry.

+

This is advisable to avoid Docker Hub rate limiting, and some companies will prefer this for policy reasons.

+

This page describes four approaches for image name substitution:

+ +

It is assumed that you have already set up a private registry hosting all the Docker images your build requires.

+

Manual substitution

+

Consider this if:

+
    +
  • You use only a few images and updating code is not a chore
  • +
  • All developers and CI machines in your organisation have access to a common registry server
  • +
  • You also use one of the automated mechanisms to substitute the images that Testcontainers itself requires
  • +
+

This approach simply entails modifying test code manually, e.g. changing:

+

For example, you may have a test that uses the mysql container image from Docker Hub:

+ +

Direct Docker Hub image name inside_block:directDockerHubReference

+ + +

to:

+ +

Private registry image name inside_block:hardcodedMirror

+ + + + + + +

Automatically modifying Docker Hub image names

+

Testcontainers can be configured to modify Docker Hub image names on the fly to apply a prefix string.

+

Consider this if:

+
    +
  • Developers and CI machines need to use different image names. For example, developers are able to pull images from Docker Hub, but CI machines need to pull from a private registry
  • +
  • Your private registry has copies of images from Docker Hub where the names are predictable, and just adding a prefix is enough. +For example, registry.mycompany.com/mirror/mysql:8.0.36 can be derived from the original Docker Hub image name (mysql:8.0.36) with a consistent prefix string: registry.mycompany.com/mirror/
  • +
+

In this case, image name references in code are unchanged. +i.e. you would leave as-is:

+ +

Unchanged direct Docker Hub image name inside_block:directDockerHubReference

+ + +

You can then configure Testcontainers to apply the prefix registry.mycompany.com/mirror/ to every image that it tries to pull from Docker Hub. +This can be done in one of two ways:

+
    +
  • Setting environment variables TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX=registry.mycompany.com/mirror/
  • +
  • Via config file, setting hub.image.name.prefix in either:
      +
    • the ~/.testcontainers.properties file in your user home directory, or
    • +
    • a file named testcontainers.properties on the classpath
    • +
    +
  • +
+

Testcontainers will automatically apply the prefix to every image that it pulls from Docker Hub - please verify that all the required images exist in your registry.

+

Testcontainers will not apply the prefix to:

+
    +
  • non-Hub image names (e.g. where another registry is set)
  • +
  • Docker Hub image names where the hub registry is explicitly part of the name (i.e. anything with a docker.io or registry.hub.docker.com host part)
  • +
+

Developing a custom function for transforming image names on the fly

+

Consider this if:

+
    +
  • You have complex rules about which private registry images should be used as substitutes, e.g.:
      +
    • non-deterministic mapping of names meaning that a name prefix cannot be used
    • +
    • rules depending upon developer identity or location
    • +
    +
  • +
  • or you wish to add audit logging of images used in the build
  • +
  • or you wish to prevent accidental usage of images that are not on an approved list
  • +
+

In this case, image name references in code are unchanged. +i.e. you would leave as-is:

+ +

Unchanged direct Docker Hub image name inside_block:directDockerHubReference

+ + +

You can implement a custom image name substitutor by:

+
    +
  • subclassing org.testcontainers.utility.ImageNameSubstitutor
  • +
  • configuring Testcontainers to use your custom implementation
  • +
+

The following is an example image substitutor implementation:

+ +

Example Image Substitutor block:ExampleImageNameSubstitutor

+ + +

Testcontainers can be configured to find it at runtime via configuration. +To do this, create or modify a file on the classpath named testcontainers.properties.

+

For example:

+

src/test/resources/testcontainers.properties

+
+
+ +
+
image.substitutor=com.mycompany.testcontainers.ExampleImageNameSubstitutor
+

Note that it is also possible to provide this same configuration property:

+
    +
  • in a testcontainers.properties file at the root of a library JAR file (useful if you wish to distribute a drop-in image substitutor JAR within an organization)
  • +
  • in a properties file in the user's home directory (~/.testcontainers.properties; note the leading .)
  • +
  • or as an environment variable (e.g. TESTCONTAINERS_IMAGE_SUBSTITUTOR=com.mycompany.testcontainers.ExampleImageNameSubstitutor).
  • +
+

Please see the documentation on configuration mechanisms for more information.

+

Also, you can use the ServiceLoader mechanism to provide the fully qualified class name of the ImageNameSubstitutor implementation:

+

src/test/resources/META-INF/services/org.testcontainers.utility.ImageNameSubstitutor

+
+
+ +
+
com.mycompany.testcontainers.ExampleImageNameSubstitutor
+

Overriding image names individually in configuration

+
+

Note +This approach is discouraged and deprecated, but is documented for completeness. +Please consider one of the other approaches outlined in this page instead. +Overriding individual image names via configuration may be removed in the future.

+
+

Consider this if:

+
    +
  • You have many references to image names in code and changing them is impractical, and
  • +
  • None of the other options are practical for you
  • +
+

In this case, image name references in code are left unchanged. +i.e. you would leave as-is:

+ +

Unchanged direct Docker Hub image name inside_block:directDockerHubReference

+ + +

You can force Testcontainers to substitute in a different image using a configuration file, which allows some (but not all) container names to be substituted.

+
\ No newline at end of file diff --git a/docs/sourcey/features/networking.html b/docs/sourcey/features/networking.html new file mode 100644 index 00000000000..0d13100903b --- /dev/null +++ b/docs/sourcey/features/networking.html @@ -0,0 +1,99 @@ + +Networking and communicating with containers - Testcontainers for Java Sourcey Reference
Core concepts

Networking and communicating with containers

It is common to want to connect to a container from your test process, running on the test 'host' machine. For example, you may be testing a class that needs to connect to a backend or data store container.

Exposing container ports to the host

+

It is common to want to connect to a container from your test process, running on the test 'host' machine. +For example, you may be testing a class that needs to connect to a backend or data store container.

+

Generally, each required port needs to be explicitly exposed. For example, we can specify one or more ports as follows:

+ +

Exposing ports inside_block:rule

+ + +

Note that this exposed port number is from the perspective of the container.

+

From the host's perspective Testcontainers actually exposes this on a random free port. +This is by design, to avoid port collisions that may arise with locally running software or in between parallel test runs.

+

Because there is this layer of indirection, it is necessary to ask Testcontainers for the actual mapped port at runtime. +This can be done using the getMappedPort method, which takes the original (container) port as an argument:

+ +

Retrieving actual ports at runtime inside_block:fetchPortsByNumber

+ + +
+

Warning +Because the randomised port mapping happens during container startup, the container must be running at the time getMappedPort is called. +You may need to ensure that the startup order of components in your tests caters for this.

+
+

There is also a getFirstMappedPort method for convenience, for the fairly common scenario of a container that only exposes one port:

+ +

Retrieving the first mapped port inside_block:fetchFirstMappedPort

+ + +

Getting the container host

+

When running with a local Docker daemon, exposed ports will usually be reachable on localhost. +However, in some CI environments they may instead be reachable on a different host.

+

As such, Testcontainers provides a convenience method to obtain an address on which the container should be reachable from the host machine.

+ +

Getting the container host inside_block:getHostOnly

+ + +

It is normally advisable to use getHost and getMappedPort together when constructing addresses - for example:

+ +

Getting the container host and mapped port inside_block:getHostAndMappedPort

+ + +
+

Tip +getHost() is a replacement for getContainerIpAddress() and returns the same result. +getContainerIpAddress() is believed to be confusingly named, and will eventually be deprecated.

+
+

Exposing host ports to the container

+

In some cases it is necessary to make a network connection from a container to a socket that is listening on the host machine. +Natively, Docker has limited support for this model across platforms. +Testcontainers, however, makes this possible.

+

In this example, assume that localServerPort is a port on our test host machine where a server (e.g. a web application) is running.

+

We need to tell Testcontainers to prepare to expose this port to containers:

+ +

Exposing the host port inside_block:exposePort

+ + +
+

Warning +Note that the above command should be invoked before containers are started, but after the server on the host was started.
Alternatively, use container.withAccessToHost(true) to force the host access mechanism (you still need to call exposeHostPorts to make the port available).

+
+

Having done so, we can now access this port from any containers that are launched. +From a container's perspective, the hostname will be host.testcontainers.internal and the port will be the same value as localServerPort.

+

For example, here we construct an HTTP URL for our local web application and tell a Selenium container to get a page from it:

+ +

Accessing the exposed host port from a container inside_block:useHostExposedPort

+ + + +

Advanced networking

+

Docker provides the ability for you to create custom networks and place containers on one or more networks. Then, communication can occur between networked containers without the need of exposing ports through the host. With Testcontainers, you can do this as well.

+
+

Warning +Note that Testcontainers currently only allows a container to be on a single network.

+
+ +

Creating custom networks inside_block:useCustomNetwork

+ +
\ No newline at end of file diff --git a/docs/sourcey/features/reuse.html b/docs/sourcey/features/reuse.html new file mode 100644 index 00000000000..999c8275ee2 --- /dev/null +++ b/docs/sourcey/features/reuse.html @@ -0,0 +1,62 @@ + +Reusable Containers (Experimental) - Testcontainers for Java Sourcey Reference
Core concepts

Reusable Containers (Experimental)

The Reusable feature keeps the containers running and next executions with the same container configuration will reuse it. To use it, start the container manually by calling start() method, do not call stop() method directly or indirectly via try-with-resources or JUnit integration, and enable it manually through an opt-in mechanism per environment. To reuse a container, the container configuration must be the same.
+

Warning +Reusable Containers is still an experimental feature and the behavior can change. +Those containers won't stop after all tests are finished.

+
+

The Reusable feature keeps the containers running and next executions with the same container configuration +will reuse it. To use it, start the container manually by calling start() method, do not call stop() method +directly or indirectly via try-with-resources or JUnit integration, and enable it manually through an +opt-in mechanism per environment. To reuse a container, the container configuration must be the same.

+
+

Note +Reusable containers are not suited for CI usage and as an experimental feature +not all Testcontainers features are fully working (e.g., resource cleanup +or networking).

+
+

How to use it

+
    +
  • Enable Reusable Containers
      +
    • through environment variable TESTCONTAINERS_REUSE_ENABLE=true
    • +
    • through user property file ~/.testcontainers.properties, by adding testcontainers.reuse.enable=true
    • +
    • not through classpath properties file see this comment
    • +
    +
  • +
  • Define a container and subscribe to reuse the container using withReuse(true)
  • +
+
+
+ +
+
GenericContainer container = new GenericContainer("redis:6-alpine")
+    .withExposedPorts(6379)
+    .withReuse(true)
+
    +
  • Start the container manually by using container.start()
  • +
+

Reusable Container with Testcontainers JDBC URL

+

If using the Testcontainers JDBC URL support +the URL must follow the pattern of jdbc:tc:mysql:8.0.36:///databasename?TC_REUSABLE=true. +TC_REUSABLE=true is set as a parameter of the JDBC URL.

+
\ No newline at end of file diff --git a/docs/sourcey/features/startup-and-waits.html b/docs/sourcey/features/startup-and-waits.html new file mode 100644 index 00000000000..67e36c7d8c2 --- /dev/null +++ b/docs/sourcey/features/startup-and-waits.html @@ -0,0 +1,119 @@ + +Waiting for containers to start or be ready - Testcontainers for Java Sourcey Reference
Core concepts

Waiting for containers to start or be ready

Ordinarily Testcontainers will wait for up to 60 seconds for the container's first mapped network port to start listening.
+

Wait strategies vs Startup strategies

+

Wait strategy: is the container in a state that is useful for testing. This is generally approximated as 'can we talk to this container over the network'. However, there are quite a few variations and nuances.

+

Startup strategy: did a container reach the desired running state. Almost always this just means 'wait until the container is running' - for a daemon process in a container this is the goal. Sometimes we need to wait until the container reaches a running state and then exits - this is the 'one shot startup' strategy, only used for cases where we need to run a one off command in a container but not a daemon.

+
+

Wait Strategies

+

Ordinarily Testcontainers will wait for up to 60 seconds for the container's first mapped network port to start listening.

+

This simple measure provides a basic check whether a container is ready for use.

+ +

Waiting for the first exposed port to start listening inside_block:waitForNetworkListening

+ + +

If the default 60s timeout is not sufficient, it can be altered with the withStartupTimeout() method.

+

If waiting for a listening TCP port is not sufficient to establish whether the container is ready, you can use the +waitingFor() method with other [WaitStrategy](http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/wait/strategy/WaitStrategy.html) implementations as shown below.

+

HTTP Wait strategy examples

+

You can choose to wait for an HTTP(S) endpoint to return a particular status code.

+

Waiting for 200 OK

+ +

inside_block:waitForSimpleHttp

+ + +

Variations on the HTTP wait strategy are supported, including:

+

Waiting for multiple possible status codes

+ +

inside_block:waitForHttpWithMultipleStatusCodes

+ + +

Waiting for a status code that matches a predicate

+ +

Waiting for a status code that matches a predicate inside_block:waitForHttpWithStatusCodePredicate

+ + +

Using TLS

+ +

inside_block:waitForHttpWithTls

+ + +

Healthcheck Wait strategy examples

+

If the used image supports Docker's Healthcheck feature, you can directly leverage the healthy state of the container as your wait condition:

+ +

inside_block:healthcheckWait

+ + +

Log output Wait Strategy

+

In some situations a container's log output is a simple way to determine if it is ready or not. +For example, we can wait for a `Ready' message in the container's logs as follows:

+ +

inside_block:logMessageWait

+ + +

Other Wait Strategies

+

For further options, check out the [Wait](http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/wait/strategy/Wait.html) convenience class, or the various subclasses of [WaitStrategy](http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/wait/strategy/WaitStrategy.html).

+

If none of these options meet your requirements, you can create your own subclass of +[AbstractWaitStrategy](http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/wait/strategy/AbstractWaitStrategy.html) with an +appropriate wait mechanism in waitUntilReady(). +The GenericContainer.waitingFor() method accepts any valid [WaitStrategy](http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/wait/strategy/WaitStrategy.html).

+

Startup check Strategies

+

Ordinarily Testcontainers will check that the container has reached the running state and has not exited. +In order to do that inspect is executed against the container and state parameter is extracted.

+

All logic is implemented in [StartupCheckStrategy](http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/startupcheck/StartupCheckStrategy.html) child classes.

+

Running startup strategy example

+

This is the strategy used by default. Testcontainers just checks if container is running.

+

Implemented in [IsRunningStartupCheckStrategy](http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/startupcheck/IsRunningStartupCheckStrategy.html) class.

+

One shot startup strategy example

+

This strategy is intended for use with containers that only run briefly and exit of their own accord. As such, success is deemed to be when +the container has stopped with exit code 0.

+ +

Using one shot startup strategy inside_block:withOneShotStrategy

+ + +

Indefinite one shot startup strategy example

+

Variant of one shot strategy that does not impose a timeout. Intended for situation such as when a long running task forms part of + container startup.

+

It has to be assumed that the container will stop of its own accord, either with a success or failure exit code.

+ +

Using indefinite one shot startup strategy inside_block:withIndefiniteOneShotStrategy

+ + +

Minimum duration startup strategy example

+

Checks that the container is running and has been running for a defined minimum period of time.

+ +

Using minimum duration strategy inside_block:withMinimumDurationStrategy

+ + +

Other startup strategies

+

If none of these options meet your requirements, you can create your own subclass of +[StartupCheckStrategy](http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers +/startupcheck/StartupCheckStrategy.html) with an appropriate startup check mechanism in waitUntilStartupSuccessful(). +Or you can leave it as is and just implement the checkStartupState(DockerClient dockerClient, String containerId) if you still want to check state + periodically.

+

Depending on another container

+

Sometimes, a container relies on another container to be ready before it should start itself. An example of this might be a database that needs to be started before your application container can link to it. You can tell a container that it depends on another container by using the dependsOn method:

+ +

Depending on another container inside_block:dependsOn

+ +
\ No newline at end of file diff --git a/docs/sourcey/icons/github.svg b/docs/sourcey/icons/github.svg new file mode 100644 index 00000000000..d563c83aa0a --- /dev/null +++ b/docs/sourcey/icons/github.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/docs/sourcey/icons/slack.svg b/docs/sourcey/icons/slack.svg new file mode 100644 index 00000000000..942265e1d23 --- /dev/null +++ b/docs/sourcey/icons/slack.svg @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/docs/sourcey/icons/stackoverflow.svg b/docs/sourcey/icons/stackoverflow.svg new file mode 100644 index 00000000000..c3307627c4e --- /dev/null +++ b/docs/sourcey/icons/stackoverflow.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/docs/sourcey/icons/twitter.svg b/docs/sourcey/icons/twitter.svg new file mode 100644 index 00000000000..ef3fbc6217e --- /dev/null +++ b/docs/sourcey/icons/twitter.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/docs/sourcey/index.html b/docs/sourcey/index.html new file mode 100644 index 00000000000..e749ae96d53 --- /dev/null +++ b/docs/sourcey/index.html @@ -0,0 +1,247 @@ + +Testcontainers for Java - Testcontainers for Java Sourcey Reference
Pages

Testcontainers for Java

Testcontainers for Java is a Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container.

Not using Java? Here are other supported languages!

+
+ Java + Go + .NET + Node.js + Python + Rust + Haskell + Ruby +
+ +

About Testcontainers for Java

+

Testcontainers for Java is a Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container.

+

Testcontainers make the following kinds of tests easier:

+
    +
  • Data access layer integration tests: use a containerized instance of a MySQL, PostgreSQL or Oracle database to test your data access layer code for complete compatibility, but without requiring complex setup on developers' machines and safe in the knowledge that your tests will always start with a known DB state. Any other database type that can be containerized can also be used.
  • +
  • Application integration tests: for running your application in a short-lived test mode with dependencies, such as databases, message queues or web servers.
  • +
  • UI/Acceptance tests: use containerized web browsers, compatible with Selenium, for conducting automated UI tests. Each test can get a fresh instance of the browser, with no browser state, plugin variations or automated browser upgrades to worry about. And you get a video recording of each test session, or just each session where tests failed.
  • +
  • Much more! Check out the various contributed modules or create your own custom container classes using GenericContainer as a base.
  • +
+

Prerequisites

+ +

Maven dependencies

+

Testcontainers is distributed as separate JARs with a common version number:

+
    +
  • A core JAR file for core functionality, generic containers and docker-compose support
  • +
  • A separate JAR file for each of the specialised modules. Each module's documentation describes the Maven/Gradle dependency to add to your project's build.
  • +
+

For the core library, the latest Maven/Gradle dependency is as follows:

+

Gradle

+
+
+ +
+
testImplementation "org.testcontainers:testcontainers:{{latest_version}}"
+

Maven

+
+
+ +
+
<dependency>
+    &lt;groupId&gt;org.testcontainers</groupId>
+    &lt;artifactId&gt;testcontainers</artifactId>
+    &lt;version&gt;{{latest_version}}</version>
+    &lt;scope&gt;test</scope>
+</dependency>
+

You can also check the latest version available on Maven Central.

+

Managing versions for multiple Testcontainers dependencies

+

To avoid specifying the version of each dependency, you can use a BOM or Bill Of Materials.

+

Using Maven you can add the following to dependencyManagement section in your pom.xml: +Maven

+
+
+ +
+
&lt;dependencyManagement&gt;
+    &lt;dependencies&gt;
+        &lt;dependency&gt;
+            &lt;groupId&gt;org.testcontainers</groupId>
+            &lt;artifactId&gt;testcontainers-bom</artifactId>
+            &lt;version&gt;{{latest_version}}</version>
+            &lt;type&gt;pom</type>
+            &lt;scope&gt;import</scope>
+        </dependency>
+    </dependencies>
+</dependencyManagement>
+

and then use dependencies without specifying a version: +Maven

+
+
+ +
+
&lt;dependency&gt;
+    &lt;groupId&gt;org.testcontainers</groupId>
+    &lt;artifactId&gt;testcontainers-mysql</artifactId>
+    &lt;scope&gt;test</scope>
+</dependency>
+

Using Gradle 5.0 or higher, you can add the following to the dependencies section in your build.gradle:

+

Gradle

+
+
+ +
+
implementation platform('org.testcontainers:testcontainers-bom:{{latest_version}}') //import bom
+testImplementation('org.testcontainers:testcontainers-mysql') //no version specified
+

JitPack builds are available for pre-release versions.

+
+

Shaded dependencies +Testcontainers depends on other libraries (like docker-java) for it to work.
Some of them (JUnit, docker-java-{api,transport} and its transitive dependencies, JNA, visible-assertions and others) are part of our public API.
But there are also "private", implementation detail dependencies (e.g., docker-java-core, Guava, OkHttp, etc.) that are not exposed to public API but prone to conflicts with test code/application under test code. +As such, these libraries are 'shaded' into the core Testcontainers JAR and relocated under org.testcontainers.shaded to prevent class conflicts.

+
+

Sponsors

+

A huge thank you to our sponsors:

+

Bronze sponsors

+ + + + + + + + + + +

Donors

+ + + + +

Backers

+ +

Who is using Testcontainers?

+
    +
  • ZeroTurnaround - Testing of the Java Agents, micro-services, Selenium browser automation
  • +
  • Zipkin - MySQL and Cassandra testing
  • +
  • Apache Gora - CouchDB testing
  • +
  • Apache James - LDAP and Cassandra integration testing
  • +
  • StreamSets - LDAP, MySQL Vault, MongoDB, Redis integration testing
  • +
  • Playtika - Kafka, Couchbase, MariaDB, Redis, Neo4j, Aerospike, MemSQL
  • +
  • JetBrains - Testing of the TeamCity plugin for HashiCorp Vault
  • +
  • Plumbr - Integration testing of data processing pipeline micro-services
  • +
  • Streamlio - Integration and Chaos Testing of our fast data platform based on Apache Pulsar, Apache BookKeeper and Apache Heron.
  • +
  • Spring Session - Redis, PostgreSQL, MySQL and MariaDB integration testing
  • +
  • Apache Camel - Testing Camel against native services such as Consul, Etcd and so on
  • +
  • Infinispan - Testing the Infinispan Server as well as integration tests with databases, LDAP and KeyCloak
  • +
  • Instana - Testing agents and stream processing backends
  • +
  • eBay Marketing - Testing for MySQL, Cassandra, Redis, Couchbase, Kafka, etc.
  • +
  • Skyscanner - Integration testing against HTTP service mocks and various data stores
  • +
  • Neo4j-OGM - Testing with Neo4j
  • +
  • Spring Data Neo4j - Testing imperative and reactive implementations with Neo4j
  • +
  • Lightbend - Testing Alpakka Kafka and support in Alpakka Kafka Testkit
  • +
  • Zalando SE - Testing core business services
  • +
  • Europace AG - Integration testing for databases and micro services
  • +
  • Micronaut Data - Testing of Micronaut Data JDBC, a database access toolkit
  • +
  • Vert.x SQL Client - Testing with PostgreSQL, MySQL, MariaDB, SQL Server, etc.
  • +
  • JHipster - Couchbase and Cassandra integration testing
  • +
  • wescale - Integration testing against HTTP service mocks and various data stores
  • +
  • Marquez - PostgreSQL integration testing
  • +
  • Wise (formerly TransferWise) - Integration testing for different RDBMS, kafka and micro services
  • +
  • XWiki - Testing XWiki under all supported configurations
  • +
  • Apache SkyWalking - End-to-end testing of the Apache SkyWalking, and plugin tests of its subproject, Apache SkyWalking Python, and of its eco-system built by the community, like SkyAPM NodeJS Agent
  • +
  • jOOQ - Integration testing all of jOOQ with a variety of RDBMS
  • +
  • Trino (formerly Presto SQL) - Integration testing all Trino core & connectors, including tests of multi-node deployments and security configurations.
  • +
  • Google - Various open source projects: OpenTelemetry, Universal Application Tool, CloudBowl
  • +
  • Backbase - Unit, Integration and Acceptance testing for different the databases supported (Oracle, SQL Server, MySQL), the different messaging systems supported (Kafka, Rabbit, AMQ) and other microservices and HTTP mocks.
  • +
  • CloudBees - Integration testing of products, including but not limited to database and AWS/Localstack integration testing.
  • +
  • Jenkins - Integration testing of multiple plugins and the Trilead SSH2 fork maintained by the Jenkins community +(query).
  • +
  • Elastic - Integration testing of the Java APM agent
  • +
  • Alkira - Testing of multiple micro-services using Kafka, PostgreSQL, Apache Zookeeper, Etcd and so on.
  • +
  • Togglz - Feature Flags for the Java platform
  • +
  • Byzer - Integration tests for Data and AI platforms are based on multiple versions of Byzer, Ray and Apache Spark.
  • +
  • Apache SeaTunnel - Integration testing with different datasource.
  • +
  • Bucket4j - Java rate-limiting library based on the token-bucket algorithm.
  • +
  • Spark ClickHouse Connector - Integration tests for Apache Spark with both single node ClickHouse instance and multi-node ClickHouse cluster.
  • +
  • Quarkus - Testcontainers is used extensively for Quarkus' DevServices feature.
  • +
  • Apache Kyuubi - Integration testing with Trino as data source engine, Kafka, etc.
  • +
  • Dash0 - Integration testing for OpenTelemetry Observability product.
  • +
+

License

+

See LICENSE.

+

Attributions

+

This project includes a modified class (ScriptUtils) taken from the Spring JDBC project, adapted under the terms of the Apache license. Copyright for that class remains with the original authors.

+

This project was initially inspired by a gist by Moshe Eshel.

+ +

Copyright (c) 2015-2021 Richard North and other authors.

+

See AUTHORS for contributors.

+
\ No newline at end of file diff --git a/docs/sourcey/js/tc-header.js b/docs/sourcey/js/tc-header.js new file mode 100644 index 00000000000..4186b6ca59b --- /dev/null +++ b/docs/sourcey/js/tc-header.js @@ -0,0 +1,45 @@ +const mobileToggle = document.getElementById("mobile-menu-toggle"); +const mobileSubToggle = document.getElementById("mobile-submenu-toggle"); +function toggleMobileMenu() { + document.body.classList.toggle('mobile-menu'); + document.body.classList.toggle("mobile-tc-header-active"); +} +function toggleMobileSubmenu() { + document.body.classList.toggle('mobile-submenu'); +} +if (mobileToggle) + mobileToggle.addEventListener("click", toggleMobileMenu); +if (mobileSubToggle) + mobileSubToggle.addEventListener("click", toggleMobileSubmenu); + +const allParentMenuItems = document.querySelectorAll("#site-header .menu-item.has-children"); +function clearActiveMenuItem() { + document.body.classList.remove("tc-header-active"); + allParentMenuItems.forEach((item) => { + item.classList.remove("active"); + }); +} +function setActiveMenuItem(e) { + clearActiveMenuItem(); + e.currentTarget.closest(".menu-item").classList.add("active"); + document.body.classList.add("tc-header-active"); +} +allParentMenuItems.forEach((item) => { + const trigger = item.querySelector(":scope > a, :scope > button"); + + trigger.addEventListener("click", (e) => { + if (e.currentTarget.closest(".menu-item").classList.contains("active")) { + clearActiveMenuItem(); + } else { + setActiveMenuItem(e); + } + }); + + trigger.addEventListener("mouseenter", (e) => { + setActiveMenuItem(e); + }); + + item.addEventListener("mouseleave", (e) => { + clearActiveMenuItem(); + }); +}); \ No newline at end of file diff --git a/docs/sourcey/language-logos/dotnet.svg b/docs/sourcey/language-logos/dotnet.svg new file mode 100644 index 00000000000..2fb163d4d40 --- /dev/null +++ b/docs/sourcey/language-logos/dotnet.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/docs/sourcey/language-logos/go.svg b/docs/sourcey/language-logos/go.svg new file mode 100644 index 00000000000..bfcca48ceda --- /dev/null +++ b/docs/sourcey/language-logos/go.svg @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/docs/sourcey/language-logos/haskell.svg b/docs/sourcey/language-logos/haskell.svg new file mode 100644 index 00000000000..eb6de3776ec --- /dev/null +++ b/docs/sourcey/language-logos/haskell.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/docs/sourcey/language-logos/java.svg b/docs/sourcey/language-logos/java.svg new file mode 100644 index 00000000000..590da12d96a --- /dev/null +++ b/docs/sourcey/language-logos/java.svg @@ -0,0 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/docs/sourcey/language-logos/nodejs.svg b/docs/sourcey/language-logos/nodejs.svg new file mode 100644 index 00000000000..08c6ea7bdd4 --- /dev/null +++ b/docs/sourcey/language-logos/nodejs.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/docs/sourcey/language-logos/python.svg b/docs/sourcey/language-logos/python.svg new file mode 100644 index 00000000000..d06a313c96b --- /dev/null +++ b/docs/sourcey/language-logos/python.svg @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/docs/sourcey/language-logos/ruby.svg b/docs/sourcey/language-logos/ruby.svg new file mode 100644 index 00000000000..05537cedf72 --- /dev/null +++ b/docs/sourcey/language-logos/ruby.svg @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/sourcey/language-logos/rust.svg b/docs/sourcey/language-logos/rust.svg new file mode 100644 index 00000000000..89039333533 --- /dev/null +++ b/docs/sourcey/language-logos/rust.svg @@ -0,0 +1,57 @@ + + + \ No newline at end of file diff --git a/docs/sourcey/llms-full.txt b/docs/sourcey/llms-full.txt new file mode 100644 index 00000000000..136609c6b34 --- /dev/null +++ b/docs/sourcey/llms-full.txt @@ -0,0 +1,205 @@ +# Testcontainers for Java Sourcey Reference + +*Testcontainers for Java* is a Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container. + +## Java Guide + +### Testcontainers for Java + +Path: `/sourcey/` + +*Testcontainers for Java* is a Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container. + +Not using Java? Here are other supported languages! Java Go .NET Node.js Python Rust Haskell Ruby About Testcontainers for Java Testcontainers for Java is a Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container. Testcontainers make the following kinds of tests easier: Data access layer integration tests : use a containerized instance of a MySQL, PostgreSQL or Oracle database to test your data access layer code for complete compatibility, but without requiring complex setup on developers' machines and safe in the knowledge that your tests will always start with a known DB state. Any other database type that can be containerized can also be used. Application integration tests : for running your application in a short-lived test mode with dependencies, such as databases, message queues or web servers. UI/Acceptance tests : use containerized web browsers , compatible with Selenium, for conducting automated UI tests. Each test can get a fresh instance of the browser, with no browser state, plugin variations or automated browser upgrades to worry about. And you get a video recording of each test session, or just each session where tests failed. Much more! Check out the various contributed modules or create your own custom container classes using GenericContainer as a base. Prerequisites Docker - please see General Docker requirements A supported JVM testing framework: JUnit 4 - See the JUnit 4 Quickstart Guide Jupiter/JUnit 5 Spock Or manually add code to control the container/test lifecycle (See hints for this approach ) Maven dependencies Testcontainers is distributed as separate JARs with a common version number: A core JAR file for core functionality, generic containers and docker-compose support A separate JAR file for each of the specialised modules. Each module's documentation describes the Maven/Gradle dependency to add to your project's build. For the core library, the latest Maven/Gradle dependency is as follows: Gradle Copy testImplementation "org.testcontainers:testcontainers:{{latest_version}}" Maven Copy dependency > groupId > org.testcontainers groupId > artifactId > testcontainers artifactId > version > {{latest_version}} version > scope > test scope > dependency > You can also check the latest version available on Maven Central . Managing versions for multiple Testcontainers dependencies To avoid specifying the version of each dependency, you can use a BOM or Bill Of Materials . Using Maven you can add the following to dependencyManagement section in your pom.xml : Maven Copy dependencyManagement > dependencies > dependency > groupId > org.testcontainers groupId > artifactId > testcontainers-bom artifactId > version > {{latest_version}} version > type > pom type > scope > import scope > dependency > dependencies > dependencyManagement > and then use dependencies without specifying a version: Maven Copy dependency > groupId > org.testcontainers groupId > artifactId > testcontainers-mysql artifactId > scope > test scope > dependency > Using Gradle 5.0 or higher, you can add the following to the dependencies section in your build.gradle : Gradle Copy implementation platform('org.testcontainers:testcontainers-bom:{{latest_version}}') //import bom testImplementation('org.testcontainers:testcontainers-mysql') //no version specified JitPack builds are available for pre-release versions. Shaded dependencies Testcontainers depends on other libraries (like docker-java) for it to work. Some of them (JUnit, docker-java-{api,transport} and its transitive dependencies, JNA, visible-assertions and others) are part of our public API. But there are also "private", implementation detail dependencies (e.g., docker-java-core, Guava, OkHttp, etc.) that are not exposed to public API but prone to conflicts with test code/application under test code. As such, these libraries are 'shaded' into the core Testcontainers JAR and relocated under org.testcontainers.shaded to prevent class conflicts. Sponsors A huge thank you to our sponsors: Bronze sponsors Cirrus CI Vivy jOOQ Backbase Elastic Donors Red Hat Spotify Backers Philip Riecks (@rieckpil) Karl Heinz Marbaise (@khmarbaise) Sascha Frinken (@sascha-frinken) Christoph Dreis (@dreis2211) Nikita Zhevnitskiy (@zhenik) Bas Stoker (@bastoker) Oleg Nenashev (@oleg-nenashev) Rik Glover (@rikglover) Amitosh Swain Mahapatra (@recrsn) Paris Apostolopoulos Who is using Testcontainers? ZeroTurnaround - Testing of the Java Agents, micro-services, Selenium browser automation Zipkin - MySQL and Cassandra testing Apache Gora - CouchDB testing Apache James - LDAP and Cassandra integration testing StreamSets - LDAP, MySQL Vault, MongoDB, Redis integration testing Playtika - Kafka, Couchbase, MariaDB, Redis, Neo4j, Aerospike, MemSQL JetBrains - Testing of the TeamCity plugin for HashiCorp Vault Plumbr - Integration testing of data processing pipeline micro-services Streamlio - Integration and Chaos Testing of our fast data platform based on Apache Pulsar, Apache BookKeeper and Apache Heron. Spring Session - Redis, PostgreSQL, MySQL and MariaDB integration testing Apache Camel - Testing Camel against native services such as Consul, Etcd and so on Infinispan - Testing the Infinispan Server as well as integration tests with databases, LDAP and KeyCloak Instana - Testing agents and stream processing backends eBay Marketing - Testing for MySQL, Cassandra, Redis, Couchbase, Kafka, etc. Skyscanner - Integration testing against HTTP service mocks and various data stores Neo4j-OGM - Testing with Neo4j Spring Data Neo4j - Testing imperative and reactive implementations with Neo4j Lightbend - Testing Alpakka Kafka and support in Alpakka Kafka Testkit Zalando SE - Testing core business services Europace AG - Integration testing for databases and micro services Micronaut Data - Testing of Micronaut Data JDBC, a database access toolkit Vert.x SQL Client - Testing with PostgreSQL, MySQL, MariaDB, SQL Server, etc. JHipster - Couchbase and Cassandra integration testing wescale - Integration testing against HTTP service mocks and various data stores Marquez - PostgreSQL integration testing Wise (formerly TransferWise) - Integration testing for different RDBMS, kafka and micro services XWiki - Testing XWiki under all supported configurations Apache SkyWalking - End-to-end testing of the Apache SkyWalking, and plugin tests of its subproject, Apache SkyWalking Python , and of its eco-system built by the community, like SkyAPM NodeJS Agent jOOQ - Integration testing all of jOOQ with a variety of RDBMS Trino (formerly Presto SQL) - Integration testing all Trino core & connectors, including tests of multi-node deployments and security configurations. Google - Various open source projects: OpenTelemetry , Universal Application Tool , CloudBowl Backbase - Unit, Integration and Acceptance testing for different the databases supported (Oracle, SQL Server, MySQL), the different messaging systems supported (Kafka, Rabbit, AMQ) and other microservices and HTTP mocks. CloudBees - Integration testing of products, including but not limited to database and AWS/Localstack integration testing. Jenkins - Integration testing of multiple plugins and the Trilead SSH2 fork maintained by the Jenkins community ( query ). Elastic - Integration testing of the Java APM agent Alkira - Testing of multiple micro-services using Kafka, PostgreSQL, Apache Zookeeper, Etcd and so on. Togglz - Feature Flags for the Java platform Byzer - Integration tests for Data and AI platforms are based on multiple versions of Byzer, Ray and Apache Spark. Apache SeaTunnel - Integration testing with different datasource. Bucket4j - Java rate-limiting library based on the token-bucket algorithm. Spark ClickHouse Connector - Integration tests for Apache Spark with both single node ClickHouse instance and multi-node ClickHouse cluster. Quarkus - Testcontainers is used extensively for Quarkus' DevServices feature. Apache Kyuubi - Integration testing with Trino as data source engine, Kafka, etc. Dash0 - Integration testing for OpenTelemetry Observability product. License See LICENSE . Attributions This project includes a modified class (ScriptUtils) taken from the Spring JDBC project, adapted under the terms of the Apache license. Copyright for that class remains with the original authors. This project was initially inspired by a gist by Moshe Eshel . Copyright Copyright (c) 2015-2021 Richard North and other authors. See AUTHORS for contributors. + +### Examples + +Path: `/sourcey/examples.html` + +Examples of different use cases provided by Testcontainers can be found below: + +Examples of different use cases provided by Testcontainers can be found below: Hazelcast Kafka Cluster with multiple brokers Neo4j Redis Selenium Selenium Module with Cucumber Singleton Container Pattern Solr Spring Boot Spring Boot with Kotlin TestNG ImmuDb Zookeeper NATS SFTP + +### JUnit 4 Quickstart + +Path: `/sourcey/quickstart/junit-4-quickstart.html` + +It's easy to add Testcontainers to your project - let's walk through a quick example to see how. + +It's easy to add Testcontainers to your project - let's walk through a quick example to see how. Let's imagine we have a simple program that has a dependency on Redis, and we want to add some tests for it. In our imaginary program, there is a RedisBackedCache class which stores data in Redis. You can see an example test that could have been written for it (without using Testcontainers): Pre-Testcontainers test code block:RedisBackedCacheIntTestStep0 Notice that the existing test has a problem - it's relying on a local installation of Redis, which is a red flag for test reliability. This may work if we were sure that every developer and CI machine had Redis installed, but would fail otherwise. We might also have problems if we attempted to run tests in parallel, such as state bleeding between tests, or port clashes. Let's start from here, and see how to improve the test with Testcontainers: 1. Add Testcontainers as a test-scoped dependency First, add Testcontainers as a dependency as follows: Gradle Copy testImplementation "org.testcontainers:testcontainers:{{latest_version}}" Maven Copy dependency > groupId > org.testcontainers groupId > artifactId > testcontainers artifactId > version > {{latest_version}} version > scope > test scope > dependency > 2. Get Testcontainers to run a Redis container during our tests Simply add the following to the body of our test class: JUnit 4 Rule inside_block:rule The @Rule annotation tells JUnit to notify this field about various events in the test lifecycle. In this case, our rule object is a Testcontainers GenericContainer , configured to use a specific Redis image from Docker Hub, and configured to expose a port. If we run our test as-is, then regardless of the actual test outcome, we'll see logs showing us that Testcontainers: was activated before our test method ran discovered and quickly tested our local Docker setup pulled the image if necessary started a new container and waited for it to be ready shut down and deleted the container after the test 3. Make sure our code can talk to the container Before Testcontainers, we might have hardcoded an address like localhost:6379 into our tests. Testcontainers uses randomized ports for each container it starts, but makes it easy to obtain the actual port at runtime. We can do this in our test setUp method, to set up our component under test: Obtaining a mapped port inside_block:setUp Tip Notice that we also ask Testcontainers for the container's actual address with redis.getHost(); , rather than hard-coding localhost . localhost may work in some environments but not others - for example it may not work on your current or future CI environment. As such, avoid hard-coding the address, and use getHost() instead. 4. Run the tests! That's it! Let's look at our complete test class to see how little we had to add to get up and running with Testcontainers: RedisBackedCacheIntTest block:RedisBackedCacheIntTest + +### JUnit 5 Quickstart + +Path: `/sourcey/quickstart/junit-5-quickstart.html` + +It's easy to add Testcontainers to your project - let's walk through a quick example to see how. + +It's easy to add Testcontainers to your project - let's walk through a quick example to see how. Let's imagine we have a simple program that has a dependency on Redis, and we want to add some tests for it. In our imaginary program, there is a RedisBackedCache class which stores data in Redis. You can see an example test that could have been written for it (without using Testcontainers): Pre-Testcontainers test code block:RedisBackedCacheIntTestStep0 Notice that the existing test has a problem - it's relying on a local installation of Redis, which is a red flag for test reliability. This may work if we were sure that every developer and CI machine had Redis installed, but would fail otherwise. We might also have problems if we attempted to run tests in parallel, such as state bleeding between tests, or port clashes. Let's start from here, and see how to improve the test with Testcontainers: 1. Add Testcontainers as a test-scoped dependency First, add Testcontainers as a dependency as follows: Gradle Copy testImplementation "org.junit.jupiter:junit-jupiter:5.8.1" testImplementation "org.testcontainers:testcontainers:{{latest_version}}" testImplementation "org.testcontainers:testcontainers-junit-jupiter:{{latest_version}}" Maven Copy dependency > groupId > org.junit.jupiter groupId > artifactId > junit-jupiter artifactId > version > 5.8.1 version > scope > test scope > dependency > dependency > groupId > org.testcontainers groupId > artifactId > testcontainers artifactId > version > {{latest_version}} version > scope > test scope > dependency > dependency > groupId > org.testcontainers groupId > artifactId > testcontainers-junit-jupiter artifactId > version > {{latest_version}} version > scope > test scope > dependency > 2. Get Testcontainers to run a Redis container during our tests First, you'll need to annotate the test class with @Testcontainers . Furthermore, add the following to the body of our test class: JUnit 5 Rule inside_block:container The @Container annotation tells JUnit to notify this field about various events in the test lifecycle. In this case, our rule object is a Testcontainers GenericContainer , configured to use a specific Redis image from Docker Hub, and configured to expose a port. If we run our test as-is, then regardless of the actual test outcome, we'll see logs showing us that Testcontainers: was activated before our test method ran discovered and quickly tested our local Docker setup pulled the image if necessary started a new container and waited for it to be ready shut down and deleted the container after the test 3. Make sure our code can talk to the container Before Testcontainers, we might have hardcoded an address like localhost:6379 into our tests. Testcontainers uses randomized ports for each container it starts, but makes it easy to obtain the actual port at runtime. We can do this in our test setUp method, to set up our component under test: Obtaining a mapped port inside_block:setUp Tip Notice that we also ask Testcontainers for the container's actual address with redis.getHost(); , rather than hard-coding localhost . localhost may work in some environments but not others - for example it may not work on your current or future CI environment. As such, avoid hard-coding the address, and use getHost() instead. 4. Additional attributes Additional attributes are available for the @Testcontainers annotation. Those attributes can be helpful when: Tests should be skipped instead of failing because Docker is unavailable in the current environment. Set disabledWithoutDocker to true . Enable parallel container initialization instead of sequential (by default). Set parallel to true . 5. Run the tests! That's it! Let's look at our complete test class to see how little we had to add to get up and running with Testcontainers: RedisBackedCacheIntTest inside_block:class + +### Spock Quickstart + +Path: `/sourcey/quickstart/spock-quickstart.html` + +It's easy to add Testcontainers to your project - let's walk through a quick example to see how. + +It's easy to add Testcontainers to your project - let's walk through a quick example to see how. Let's imagine we have a simple program that has a dependency on Redis, and we want to add some tests for it. In our imaginary program, there is a RedisBackedCache class which stores data in Redis. You can see an example test that could have been written for it (without using Testcontainers): Pre-Testcontainers test code block:RedisBackedCacheIntTestStep0 Notice that the existing test has a problem - it's relying on a local installation of Redis, which is a red flag for test reliability. This may work if we were sure that every developer and CI machine had Redis installed, but would fail otherwise. We might also have problems if we attempted to run tests in parallel, such as state bleeding between tests, or port clashes. Let's start from here, and see how to improve the test with Testcontainers: 1. Add Testcontainers as a test-scoped dependency First, add Testcontainers as a dependency as follows: Gradle Copy testImplementation "org.testcontainers:testcontainers-spock:{{latest_version}}" Maven Copy dependency > groupId > org.testcontainers groupId > artifactId > testcontainers-spock artifactId > version > {{latest_version}} version > scope > test scope > dependency > 2. Get Testcontainers to run a Redis container during our tests Annotate the Spock specification class with the Testcontainers extension: Spock Testcontainers annotation Copy @org.testcontainers.spock.Testcontainers class RedisBackedCacheIntTest extends Specification { And add the following field to the body of our test class: Spock Testcontainers init inside_block:init This tells Spock to start a Testcontainers GenericContainer , configured to use a specific Redis image from Docker Hub, and configured to expose a port. If we run our test as-is, then regardless of the actual test outcome, we'll see logs showing us that Testcontainers: was activated before our test method ran discovered and quickly tested our local Docker setup pulled the image if necessary started a new container and waited for it to be ready shut down and deleted the container after the test 3. Make sure our code can talk to the container Before Testcontainers, we might have hardcoded an address like localhost:6379 into our tests. Testcontainers uses randomized ports for each container it starts, but makes it easy to obtain the actual port at runtime. We can do this in our test setup method, to set up our component under test: Obtaining a mapped port inside_block:setup Tip Notice that we also ask Testcontainers for the container's actual address with redis.containerIpAddress , rather than hard-coding localhost . localhost may work in some environments but not others - for example it may not work on your current or future CI environment. As such, avoid hard-coding the address, and use containerIpAddress instead. 4. Run the tests! That's it! Let's look at our complete test class to see how little we had to add to get up and running with Testcontainers: RedisBackedCacheIntTest block:complete + +### Creating a container + +Path: `/sourcey/features/creating-container.html` + +Testcontainers' generic container support offers the most flexibility, and makes it easy to use virtually any container images as temporary test dependencies. For example, if you might use it to test interactions with: + +Creating a generic container based on an image Testcontainers' generic container support offers the most flexibility, and makes it easy to use virtually any container images as temporary test dependencies. For example, if you might use it to test interactions with: NoSQL databases or other data stores (e.g. redis, elasticsearch, mongo) Web servers/proxies (e.g. nginx, apache) Log services (e.g. logstash, kibana) Other services developed by your team/organization which are already dockerized With a generic container, you set the container image using a parameter to the rule constructor, e.g.: Copy new GenericContainer ( DockerImageName . parse ( "jboss/wildfly:9.0.1.Final" )) Specifying an image Many Container classes in Testcontainers have historically supported: a no-args constructor - for example new GenericContainer() and new ElasticsearchContainer() . With these constructors, Testcontainers has traditionally used a default image name (including a fixed image tag/version). This has caused a conflict between the need to keep the defaults sane (i.e. up to date) and the need to avoid silently upgrading these dependencies along with new versions of Testcontainers. a single string-argument constructor, which has taken either a version or an image name as a String. This has caused some ambiguity and confusion. Since v1.15.0, both of these constructor types have been deprecated, for the reasons given above. Instead, it is highly recommended that all containers be constructed using a constructor that accepts a DockerImageName object. The DockerImageName class is an unambiguous reference to a docker image. It is suggested that developers treat DockerImageName s as you would any other potentially-constant value - consider defining a constant in your test codebase that matches the production version of the dependency you are using. Examples A generic container rule can be used with any public docker image; for example: Creating a Redis container inside_block:container The container, as @Container , will be started before any tests in the class run, and will be destroyed after all tests have run. + +### Networking and communicating with containers + +Path: `/sourcey/features/networking.html` + +It is common to want to connect to a container from your test process, running on the test 'host' machine. For example, you may be testing a class that needs to connect to a backend or data store container. + +Exposing container ports to the host It is common to want to connect to a container from your test process, running on the test 'host' machine. For example, you may be testing a class that needs to connect to a backend or data store container. Generally, each required port needs to be explicitly exposed. For example, we can specify one or more ports as follows: Exposing ports inside_block:rule Note that this exposed port number is from the perspective of the container . From the host's perspective Testcontainers actually exposes this on a random free port. This is by design, to avoid port collisions that may arise with locally running software or in between parallel test runs. Because there is this layer of indirection, it is necessary to ask Testcontainers for the actual mapped port at runtime. This can be done using the getMappedPort method, which takes the original (container) port as an argument: Retrieving actual ports at runtime inside_block:fetchPortsByNumber Warning Because the randomised port mapping happens during container startup, the container must be running at the time getMappedPort is called. You may need to ensure that the startup order of components in your tests caters for this. There is also a getFirstMappedPort method for convenience, for the fairly common scenario of a container that only exposes one port: Retrieving the first mapped port inside_block:fetchFirstMappedPort Getting the container host When running with a local Docker daemon, exposed ports will usually be reachable on localhost . However, in some CI environments they may instead be reachable on a different host. As such, Testcontainers provides a convenience method to obtain an address on which the container should be reachable from the host machine. Getting the container host inside_block:getHostOnly It is normally advisable to use getHost and getMappedPort together when constructing addresses - for example: Getting the container host and mapped port inside_block:getHostAndMappedPort Tip getHost() is a replacement for getContainerIpAddress() and returns the same result. getContainerIpAddress() is believed to be confusingly named, and will eventually be deprecated. Exposing host ports to the container In some cases it is necessary to make a network connection from a container to a socket that is listening on the host machine. Natively, Docker has limited support for this model across platforms. Testcontainers, however, makes this possible. In this example, assume that localServerPort is a port on our test host machine where a server (e.g. a web application) is running. We need to tell Testcontainers to prepare to expose this port to containers: Exposing the host port inside_block:exposePort Warning Note that the above command should be invoked before containers are started, but after the server on the host was started. Alternatively, use container.withAccessToHost(true) to force the host access mechanism (you still need to call exposeHostPorts to make the port available). Having done so, we can now access this port from any containers that are launched. From a container's perspective, the hostname will be host.testcontainers.internal and the port will be the same value as localServerPort . For example, here we construct an HTTP URL for our local web application and tell a Selenium container to get a page from it: Accessing the exposed host port from a container inside_block:useHostExposedPort Advanced networking Docker provides the ability for you to create custom networks and place containers on one or more networks. Then, communication can occur between networked containers without the need of exposing ports through the host. With Testcontainers, you can do this as well. Warning Note that Testcontainers currently only allows a container to be on a single network. Creating custom networks inside_block:useCustomNetwork + +### Executing commands + +Path: `/sourcey/features/commands.html` + +By default the container will execute whatever command is specified in the image's Dockerfile. To override this, and specify a different command, use `withCommand`. For example: + +Container startup command By default the container will execute whatever command is specified in the image's Dockerfile. To override this, and specify a different command, use withCommand . For example: Specifying a startup command inside_block:startupCommand Executing a command Your test can execute a command inside a running container, similar to a docker exec call: Executing a command inside a running container inside_block:standaloneExec This can be useful for software that has a command line administration tool. You can also get the output (stdout/stderr) and exit code from the command - for example: Executing a command inside a running container and reading the result inside_block:execReadingStdout Environment variables To add environment variables to the container, use withEnv : Copy new GenericContainer (...) . withEnv ( "API_TOKEN" , "foo" ) + +### Files and volumes + +Path: `/sourcey/features/files.html` + +Files can be copied into the container before startup, or can be copied from the container after the container has started. + +Copying files Files can be copied into the container before startup, or can be copied from the container after the container has started. Note This is the recommended approach for portability cross-docker environments. Copying to a container before startup Copying files using MountableFile inside_block:copyToContainer Using Transferable , file content will be placed in the specified location. Copying files using Transferable inside_block:transferableFile Setting file mode is also possible. Copying files using Transferable with file mode inside_block:transferableWithFileMode Copying a file from a running container Copying files from a container inside_block:copyFileFromContainer + +### Waiting for containers to start or be ready + +Path: `/sourcey/features/startup-and-waits.html` + +Ordinarily Testcontainers will wait for up to 60 seconds for the container's first mapped network port to start listening. + +Wait strategies vs Startup strategies Wait strategy: is the container in a state that is useful for testing. This is generally approximated as 'can we talk to this container over the network'. However, there are quite a few variations and nuances. Startup strategy: did a container reach the desired running state. Almost always this just means 'wait until the container is running' - for a daemon process in a container this is the goal. Sometimes we need to wait until the container reaches a running state and then exits - this is the 'one shot startup' strategy, only used for cases where we need to run a one off command in a container but not a daemon. Wait Strategies Ordinarily Testcontainers will wait for up to 60 seconds for the container's first mapped network port to start listening. This simple measure provides a basic check whether a container is ready for use. Waiting for the first exposed port to start listening inside_block:waitForNetworkListening If the default 60s timeout is not sufficient, it can be altered with the withStartupTimeout() method. If waiting for a listening TCP port is not sufficient to establish whether the container is ready, you can use the waitingFor() method with other [ WaitStrategy ]( http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/wait/strategy/WaitStrategy.html) implementations as shown below. HTTP Wait strategy examples You can choose to wait for an HTTP(S) endpoint to return a particular status code. Waiting for 200 OK inside_block:waitForSimpleHttp Variations on the HTTP wait strategy are supported, including: Waiting for multiple possible status codes inside_block:waitForHttpWithMultipleStatusCodes Waiting for a status code that matches a predicate Waiting for a status code that matches a predicate inside_block:waitForHttpWithStatusCodePredicate Using TLS inside_block:waitForHttpWithTls Healthcheck Wait strategy examples If the used image supports Docker's Healthcheck feature, you can directly leverage the healthy state of the container as your wait condition: inside_block:healthcheckWait Log output Wait Strategy In some situations a container's log output is a simple way to determine if it is ready or not. For example, we can wait for a `Ready' message in the container's logs as follows: inside_block:logMessageWait Other Wait Strategies For further options, check out the [ Wait ]( http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/wait/strategy/Wait.html) convenience class, or the various subclasses of [ WaitStrategy ]( http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/wait/strategy/WaitStrategy.html). If none of these options meet your requirements, you can create your own subclass of [ AbstractWaitStrategy ]( http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/wait/strategy/AbstractWaitStrategy.html) with an appropriate wait mechanism in waitUntilReady() . The GenericContainer.waitingFor() method accepts any valid [ WaitStrategy ]( http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/wait/strategy/WaitStrategy.html). Startup check Strategies Ordinarily Testcontainers will check that the container has reached the running state and has not exited. In order to do that inspect is executed against the container and state parameter is extracted. All logic is implemented in [ StartupCheckStrategy ]( http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/startupcheck/StartupCheckStrategy.html) child classes. Running startup strategy example This is the strategy used by default. Testcontainers just checks if container is running. Implemented in [ IsRunningStartupCheckStrategy ]( http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/startupcheck/IsRunningStartupCheckStrategy.html) class. One shot startup strategy example This strategy is intended for use with containers that only run briefly and exit of their own accord. As such, success is deemed to be when the container has stopped with exit code 0. Using one shot startup strategy inside_block:withOneShotStrategy Indefinite one shot startup strategy example Variant of one shot strategy that does not impose a timeout. Intended for situation such as when a long running task forms part of container startup. It has to be assumed that the container will stop of its own accord, either with a success or failure exit code. Using indefinite one shot startup strategy inside_block:withIndefiniteOneShotStrategy Minimum duration startup strategy example Checks that the container is running and has been running for a defined minimum period of time. Using minimum duration strategy inside_block:withMinimumDurationStrategy Other startup strategies If none of these options meet your requirements, you can create your own subclass of [ StartupCheckStrategy ]( http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers /startupcheck/StartupCheckStrategy.html) with an appropriate startup check mechanism in waitUntilStartupSuccessful() . Or you can leave it as is and just implement the checkStartupState(DockerClient dockerClient, String containerId) if you still want to check state periodically. Depending on another container Sometimes, a container relies on another container to be ready before it should start itself. An example of this might be a database that needs to be started before your application container can link to it. You can tell a container that it depends on another container by using the dependsOn method: Depending on another container inside_block:dependsOn + +### Accessing container logs + +Path: `/sourcey/features/container-logs.html` + +It is possible to capture container output using: + +It is possible to capture container output using: the getLogs() method, which simply returns a String snapshot of a container's entire log output the followOutput() method. This method accepts a Consumer and (optionally) a varargs list stating which of STDOUT, STDERR, or both, should be followed. If not specified, both will be followed. At present, container output will always begin from the time of container creation. Reading all logs (from startup time to present) getLogs() is the simplest mechanism for accessing container logs, and can be used as follows: Accessing all output (stdout and stderr) inside_block:docsGetAllLogs Accessing just stdout inside_block:docsGetStdOut Accessing just stderr inside_block:docsGetStdErr Streaming logs Testcontainers includes some out-of-the-box Consumer implementations that can be used with the streaming followOutput() model; examples follow. Streaming container output to an SLF4J logger Given an existing SLF4J logger instance named LOGGER: Copy Slf4jLogConsumer logConsumer = new Slf4jLogConsumer (LOGGER); container . followOutput (logConsumer); By default both standard out and standard error will both be emitted at INFO level. Standard error may be emitted at ERROR level, if desired: Copy Slf4jLogConsumer logConsumer = new Slf4jLogConsumer (LOGGER). withSeparateOutputStreams (); The Mapped Diagnostic Context (MDC) for emitted messages may be configured using the withMdc(...) option: Copy Slf4jLogConsumer logConsumer = new Slf4jLogConsumer (LOGGER). withMdc ( "key" , "value" ); or using an existing map of key-value pairs: Copy Slf4jLogConsumer logConsumer = new Slf4jLogConsumer (LOGGER). withMdc (map); Capturing container output as a String To stream logs live or customize the decoding, ToStringConsumer may be used: Copy ToStringConsumer toStringConsumer = new ToStringConsumer (); container . followOutput (toStringConsumer, OutputType . STDOUT ); String utf8String = toStringConsumer . toUtf8String (); // Or if the container output is not UTF-8 String otherString = toStringConsumer . toString ( CharSet . forName ( "ISO-8859-1" )); Waiting for container output to contain expected content WaitingConsumer will block until a frame of container output (usually a line) matches a provided predicate. A timeout may be specified, as shown in this example. Copy WaitingConsumer consumer = new WaitingConsumer (); container . followOutput (consumer, STDOUT); consumer . waitUntil (frame -> frame . getUtf8String (). contains ( "STARTED" ), 30 , TimeUnit . SECONDS ); Additionally, as the Java 8 Consumer functional interface is used, Consumers may be composed together. This is useful, for example, to capture all the container output but only when a matching string has been found. e.g.: Copy WaitingConsumer waitingConsumer = new WaitingConsumer (); ToStringConsumer toStringConsumer = new ToStringConsumer (); Consumer & lt;OutputFrame & gt; composedConsumer = toStringConsumer . andThen (waitingConsumer); container . followOutput (composedConsumer); waitingConsumer . waitUntil (frame -> frame . getUtf8String (). contains ( "STARTED" ), 30 , TimeUnit . SECONDS ); String utf8String = toStringConsumer . toUtf8String (); + +### Creating images on-the-fly + +Path: `/sourcey/features/creating-images.html` + +In situations where there is no pre-existing Docker image, Testcontainers can create a new temporary image on-the-fly from a Dockerfile. For example, when the component under test is the Docker image itself, or when an existing base image needs to be customized for specific test(s). + +Overview In situations where there is no pre-existing Docker image, Testcontainers can create a new temporary image on-the-fly from a Dockerfile. For example, when the component under test is the Docker image itself, or when an existing base image needs to be customized for specific test(s). Simply pass a configured instance of ImageFromDockerfile as a constructor parameter to GenericContainer . Testcontainers will docker build a temporary container image, and will use it when creating the container. Dockerfile from String, file or classpath resource ImageFromDockerfile accepts arbitrary files, strings or classpath resources to be used as files in the build context. At least one of these needs to be a Dockerfile . Copy GenericContainer container = new GenericContainer ( new ImageFromDockerfile () . withFileFromString ( "folder/someFile.txt" , "hello" ) . withFileFromClasspath ( "test.txt" , "mappable-resource/test-resource.txt" ) . withFileFromClasspath ( "Dockerfile" , "mappable-dockerfile/Dockerfile" )) The following methods may be used to provide the Dockerfile and any other required build context files: withFileFromString(buildContextPath, content) withFileFromClasspath(buildContextPath, classpathPath) withFileFromPath(buildContextPath, filesystemPath) withFileFromFile(buildContextPath, filesystemFile) Info In older versions of Testcontainers (before 1.3.0) it was necessary to explicitly declare each file that needed to be present in the Docker build context. This can be replaced with the following syntax: Passing an entire directory of files to the Dockerfile build context inside_block:docsShowRecursiveFileInclusion Where RESOURCE_PATH is the path to a directory containing a Dockerfile and any files that it needs to refer to. Doing this is equivalent to docker build RESOURCE_PATH on the command line. To mimic docker build . , RESOURCE_PATH would simply be set to . as well. Dockerfile DSL If a static Dockerfile is not sufficient (e.g. your test needs to cover many variations that are best generated programmatically), there is a DSL available to allow Dockerfiles to be defined in code. e.g.: Copy new GenericContainer ( new ImageFromDockerfile () . withDockerfileFromBuilder (builder -> builder . from ( "alpine:3.17" ) . run ( "apk add --update nginx" ) . cmd ( "nginx" , "-g" , "daemon off;" ) . build ())) . withExposedPorts ( 80 ); See ParameterizedDockerfileContainerTest for a very basic example of using this in conjunction with JUnit parameterized testing. Automatic deletion Temporary container images will be automatically removed when the test JVM shuts down. If this is not desired and the image should be retained between tests, pass a stable image name and false flag to the ImageFromDockerfile constructor. Retaining the image between tests will use Docker's image cache to accelerate subsequent test runs. By default the no-args constructor will use an image name of the form testcontainers/ + random string: public ImageFromDockerfile() public ImageFromDockerfile(String dockerImageName) public ImageFromDockerfile(String dockerImageName, boolean deleteOnExit) Alternative Dockerfiles Normally Docker will automatically build an image from any /Dockerfile that it finds in the root of the build context. To override this behaviour, use .withDockerfilePath("./Name-Of-Other-Dockerfile") . Build Args Build Args may be used to allow lightweight parameterization. To specify build args, use .withBuildArg("varname", "value") or provide a Map of args using .withBuildArgs(map) . + +### Custom configuration + +Path: `/sourcey/features/configuration.html` + +You can override some default properties if your environment requires that. + +You can override some default properties if your environment requires that. Configuration locations The configuration will be loaded from multiple locations. Properties are considered in the following order: Environment variables .testcontainers.properties in user's home folder. Example locations: Linux: /home/myuser/.testcontainers.properties Windows: C:/Users/myuser/.testcontainers.properties macOS: /Users/myuser/.testcontainers.properties testcontainers.properties on the classpath. Note that when using environment variables, configuration property names should be set in upper case with underscore separators, preceded by TESTCONTAINERS_ - e.g. checks.disable becomes TESTCONTAINERS_CHECKS_DISABLE . The classpath testcontainers.properties file may exist within the local codebase (e.g. within the src/test/resources directory) or within library dependencies that you may have. Any such configuration files will have their contents merged. If any keys conflict, the value will be taken on the basis of the first value found in: 'local' classpath (i.e. where the URL of the file on the classpath begins with file: ), then other classpath locations (i.e. JAR files) - considered in alphabetical order of path to provide deterministic ordering. Disabling the startup checks checks.disable = [true|false] Before running any containers Testcontainers will perform a set of startup checks to ensure that your environment is configured correctly. Usually they look like this: Copy ℹ︎ Checking the system... ✔ Docker version should be at least 1.6.0 ✔ File should be mountable ✔ A port exposed by a docker container should be accessible It takes a couple of seconds, but if you want to speed up your tests, you can disable the checks once you have everything configured. Add checks.disable=true to your $HOME/.testcontainers.properties to completely disable them. Customizing images Note This approach is discouraged and deprecated, but is documented for completeness. Overriding individual image names via configuration may be removed in 2021. See Image Name Substitution for other strategies for substituting image names to pull from other registries. Testcontainers uses public Docker images to perform different actions like startup checks, VNC recording and others. Some companies disallow the usage of Docker Hub, but you can override *.image properties with your own images from your private registry to workaround that. ryuk.container.image = testcontainers/ryuk:0.3.3 Performs fail-safe cleanup of containers, and always required (unless Ryuk is disabled ) tinyimage.container.image = alpine:3.17 Used to check whether images can be pulled at startup, and always required (unless startup checks are disabled ) sshd.container.image = testcontainers/sshd:1.4.0 Required if exposing host ports to containers vncrecorder.container.image = testcontainers/vnc-recorder:1.3.0 Used by VNC recorder in Testcontainers' Selenium integration socat.container.image = alpine/socat compose.container.image = docker/compose:1.8.0 Required if using Docker Compose kafka.container.image = confluentinc/cp-kafka Used by KafkaContainer localstack.container.image = localstack/localstack Used by LocalStack pulsar.container.image = apachepulsar/pulsar:2.2.0 Used by Apache Pulsar Customizing Ryuk resource reaper ryuk.container.image = testcontainers/ryuk:0.3.3 The resource reaper is responsible for container removal and automatic cleanup of dead containers at JVM shutdown ryuk.container.privileged = true In some environments ryuk must be started in privileged mode to work properly (--privileged flag) Disabling Ryuk Ryuk must be started as a privileged container. If your environment already implements automatic cleanup of containers after the execution, but does not allow starting privileged containers, you can turn off the Ryuk container by setting TESTCONTAINERS_RYUK_DISABLED environment variable to true . !!!tip Note that Testcontainers will continue doing the cleanup at JVM's shutdown, unless you kill -9 your JVM process. Customizing image pull behaviour pull.timeout = 120 By default Testcontainers will timeout if pull takes more than this duration (in seconds) pull.pause.timeout = 30 By default Testcontainers will abort the pull of an image if the pull appears stalled (no data transferred) for longer than this duration (in seconds). Customizing client ping behaviour client.ping.timeout = 10 Specifies for how long Testcontainers will try to connect to the Docker client to obtain valid info about the client before giving up and trying next strategy, if applicable (in seconds). Customizing Docker host detection Testcontainers will attempt to detect the Docker environment and configure everything to work automatically. However, sometimes customization is required. Testcontainers will respect the following environment variables : DOCKER_HOST = unix:///var/run/docker.sock See Docker environment variables TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE Path to Docker's socket. Used by Ryuk, Docker Compose, and a few other containers that need to perform Docker actions. Example: /var/run/docker-alt.sock TESTCONTAINERS_HOST_OVERRIDE Docker's host on which ports are exposed. Example: docker.svc.local For advanced users, the Docker host connection can be configured via configuration in ~/.testcontainers.properties . Note that these settings require use of the EnvironmentAndSystemPropertyClientProviderStrategy . The example below illustrates usage: Copy docker.client.strategy=org.testcontainers.dockerclient.EnvironmentAndSystemPropertyClientProviderStrategy docker.host=tcp\://my.docker.host\:1234 # Equivalent to the DOCKER_HOST environment variable. Colons should be escaped. docker.tls.verify=1 # Equivalent to the DOCKER_TLS_VERIFY environment variable docker.cert.path=/some/path # Equivalent to the DOCKER_CERT_PATH environment variable In addition, you can deactivate this behaviour by specifying: Copy dockerconfig.source=autoIgnoringUserProperties # 'auto' by default + +### Image name substitution + +Path: `/sourcey/features/image-name-substitution.html` + +Testcontainers supports automatic substitution of Docker image names. + +Testcontainers supports automatic substitution of Docker image names. This allows replacement of an image name specified in test code with an alternative name - for example, to replace the name of a Docker Hub image dependency with an alternative hosted on a private image registry. This is advisable to avoid Docker Hub rate limiting , and some companies will prefer this for policy reasons. This page describes four approaches for image name substitution: Manual substitution - not relying upon an automated approach Using an Image Name Substitutor: Developing a custom function for transforming image names on the fly Overriding image names individually in configuration It is assumed that you have already set up a private registry hosting all the Docker images your build requires . Manual substitution Consider this if: You use only a few images and updating code is not a chore All developers and CI machines in your organisation have access to a common registry server You also use one of the automated mechanisms to substitute the images that Testcontainers itself requires This approach simply entails modifying test code manually, e.g. changing: For example, you may have a test that uses the mysql container image from Docker Hub: Direct Docker Hub image name inside_block:directDockerHubReference to: Private registry image name inside_block:hardcodedMirror Automatically modifying Docker Hub image names Testcontainers can be configured to modify Docker Hub image names on the fly to apply a prefix string. Consider this if: Developers and CI machines need to use different image names. For example, developers are able to pull images from Docker Hub, but CI machines need to pull from a private registry Your private registry has copies of images from Docker Hub where the names are predictable, and just adding a prefix is enough. For example, registry.mycompany.com/mirror/mysql:8.0.36 can be derived from the original Docker Hub image name ( mysql:8.0.36 ) with a consistent prefix string: registry.mycompany.com/mirror/ In this case, image name references in code are unchanged . i.e. you would leave as-is: Unchanged direct Docker Hub image name inside_block:directDockerHubReference You can then configure Testcontainers to apply the prefix registry.mycompany.com/mirror/ to every image that it tries to pull from Docker Hub. This can be done in one of two ways: Setting environment variables TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX=registry.mycompany.com/mirror/ Via config file, setting hub.image.name.prefix in either: the ~/.testcontainers.properties file in your user home directory, or a file named testcontainers.properties on the classpath Testcontainers will automatically apply the prefix to every image that it pulls from Docker Hub - please verify that all the required images exist in your registry. Testcontainers will not apply the prefix to: non-Hub image names (e.g. where another registry is set) Docker Hub image names where the hub registry is explicitly part of the name (i.e. anything with a docker.io or registry.hub.docker.com host part) Developing a custom function for transforming image names on the fly Consider this if: You have complex rules about which private registry images should be used as substitutes, e.g.: non-deterministic mapping of names meaning that a name prefix cannot be used rules depending upon developer identity or location or you wish to add audit logging of images used in the build or you wish to prevent accidental usage of images that are not on an approved list In this case, image name references in code are unchanged . i.e. you would leave as-is: Unchanged direct Docker Hub image name inside_block:directDockerHubReference You can implement a custom image name substitutor by: subclassing org.testcontainers.utility.ImageNameSubstitutor configuring Testcontainers to use your custom implementation The following is an example image substitutor implementation: Example Image Substitutor block:ExampleImageNameSubstitutor Testcontainers can be configured to find it at runtime via configuration. To do this, create or modify a file on the classpath named testcontainers.properties . For example: src/test/resources/testcontainers.properties Copy image.substitutor=com.mycompany.testcontainers.ExampleImageNameSubstitutor Note that it is also possible to provide this same configuration property: in a testcontainers.properties file at the root of a library JAR file (useful if you wish to distribute a drop-in image substitutor JAR within an organization) in a properties file in the user's home directory ( ~/.testcontainers.properties ; note the leading . ) or as an environment variable (e.g. TESTCONTAINERS_IMAGE_SUBSTITUTOR=com.mycompany.testcontainers.ExampleImageNameSubstitutor ). Please see the documentation on configuration mechanisms for more information. Also, you can use the ServiceLoader mechanism to provide the fully qualified class name of the ImageNameSubstitutor implementation: src/test/resources/META-INF/services/org.testcontainers.utility.ImageNameSubstitutor Copy com.mycompany.testcontainers.ExampleImageNameSubstitutor Overriding image names individually in configuration Note This approach is discouraged and deprecated, but is documented for completeness. Please consider one of the other approaches outlined in this page instead. Overriding individual image names via configuration may be removed in the future. Consider this if: You have many references to image names in code and changing them is impractical, and None of the other options are practical for you In this case, image name references in code are left unchanged . i.e. you would leave as-is: Unchanged direct Docker Hub image name inside_block:directDockerHubReference You can force Testcontainers to substitute in a different image using a configuration file , which allows some (but not all) container names to be substituted. + +### Advanced options + +Path: `/sourcey/features/advanced-options.html` + +To add a custom label to the container, use `withLabel`: + +Container labels To add a custom label to the container, use withLabel : Adding a single label inside_block:single_label Additionally, multiple labels may be applied together from a map: Adding multiple labels inside_block:multiple_labels Image Pull Policy By default, the container image is retrieved from the local Docker images cache. This works well when running against a specific version, but for images with a static tag (i.e. 'latest') this may lead to a newer version not being pulled. It is possible to specify an Image Pull Policy to determine at runtime whether an image should be pulled or not: Setting image pull policy inside_block:built_in_image_pull_policy ... or providing a function: Custom image pull policy inside_block:custom_image_pull_policy You can also configure Testcontainers to use your custom implementation by using pull.policy src/test/resources/testcontainers.properties Copy pull.policy=com.mycompany.testcontainers.ExampleImagePullPolicy You can also use the provided implementation to always pull images src/test/resources/testcontainers.properties Copy pull.policy=org.testcontainers.images.AlwaysPullPolicy Please see the documentation on configuration mechanisms for more information. Customizing the container Using docker-java It is possible to use the docker-java API directly to customize containers before creation. This is useful if there is a need to use advanced Docker features that are not exposed by the Testcontainers API. Any customizations you make using withCreateContainerCmdModifier will be applied on top of the container definition that Testcontainers creates, but before it is created. For example, this can be used to change the container hostname: Using modifier to change hostname inside_block:hostname ... or modify container memory (see this if it does not appear to work): Using modifier to change memory limits inside_block:memory Note It is recommended to use this sparingly, and follow changes to the docker-java API if you choose to use this. It is typically quite stable, though. For what is possible, consult the docker-java CreateContainerCmd source code . Using CreateContainerCmdModifier Testcontainers provides a CreateContainerCmdModifier to customize docker-java CreateContainerCmd via Service Provider Interface (SPI) mechanism. CreateContainerCmd example implementation The previous implementation should be registered in META-INF/services/org.testcontainers.core.CreateContainerCmdModifier file. Warning CreateContainerCmdModifier implementation will apply to all containers created by Testcontainers. Parallel Container Startup Usually, containers are started sequentially when more than one container is used. Using Startables.deepStart(container1, container2, ...).join() will start all containers in parallel. This can be advantageous to reduce the impact of the container startup overhead. + +### Reusable Containers (Experimental) + +Path: `/sourcey/features/reuse.html` + +The *Reusable* feature keeps the containers running and next executions with the same container configuration will reuse it. To use it, start the container manually by calling `start()` method, do not call `stop()` method directly or indirectly via `try-with-resources` or `JUnit integration`, and enable it manually through an opt-in mechanism per environment. To reuse a container, the container configuration **must be the same**. + +Warning Reusable Containers is still an experimental feature and the behavior can change. Those containers won't stop after all tests are finished. The Reusable feature keeps the containers running and next executions with the same container configuration will reuse it. To use it, start the container manually by calling start() method, do not call stop() method directly or indirectly via try-with-resources or JUnit integration , and enable it manually through an opt-in mechanism per environment. To reuse a container, the container configuration must be the same . Note Reusable containers are not suited for CI usage and as an experimental feature not all Testcontainers features are fully working (e.g., resource cleanup or networking). How to use it Enable Reusable Containers through environment variable TESTCONTAINERS_REUSE_ENABLE=true through user property file ~/.testcontainers.properties , by adding testcontainers.reuse.enable=true not through classpath properties file see this comment Define a container and subscribe to reuse the container using withReuse(true) Copy GenericContainer container = new GenericContainer ( "redis:6-alpine" ) . withExposedPorts ( 6379 ) . withReuse ( true ) Start the container manually by using container.start() Reusable Container with Testcontainers JDBC URL If using the Testcontainers JDBC URL support the URL must follow the pattern of jdbc:tc:mysql:8.0.36:///databasename?TC_REUSABLE=true . TC_REUSABLE=true is set as a parameter of the JDBC URL. + +### Database containers + +Path: `/sourcey/modules/databases/` + +You might want to use Testcontainers' database support: + +Overview You might want to use Testcontainers' database support: Instead of H2 database for DAO unit tests that depend on database features that H2 doesn't emulate. Testcontainers is not as performant as H2, but does give you the benefit of 100% database compatibility (since it runs a real DB inside of a container). Instead of a database running on the local machine or in a VM for DAO unit tests or end-to-end integration tests that need a database to be present. In this context, the benefit of Testcontainers is that the database always starts in a known state, without any contamination between test runs or on developers' local machines. Note Of course, it's still important to have as few tests that hit the database as possible, and make good use of mocks for components higher up the stack. See JDBC and R2DBC for information on how to use Testcontainers with SQL-like databases. + +### JDBC support + +Path: `/sourcey/modules/databases/jdbc.html` + +You can obtain a temporary database in one of two ways: + +You can obtain a temporary database in one of two ways: Using a specially modified JDBC URL : after making a very simple modification to your system's JDBC URL string, Testcontainers will provide a disposable stand-in database that can be used without requiring modification to your application code. JUnit @Rule/@ClassRule : this mode starts a database inside a container before your tests and tears it down afterwards. Database containers launched via JDBC URL scheme As long as you have Testcontainers and the appropriate JDBC driver on your classpath, you can simply modify regular JDBC connection URLs to get a fresh containerized instance of the database each time your application starts up. N.B: TC needs to be on your application's classpath at runtime for this to work For Spring Boot (Before version 2.3.0 ) you need to specify the driver manually spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver Original URL : jdbc:mysql://localhost:3306/databasename Insert tc: after jdbc: as follows. Note that the hostname, port and database name will be ignored; you can leave these as-is or set them to any value. Note We will use /// (host-less URIs) from now on to emphasis the unimportance of the host:port pair. From Testcontainers' perspective, jdbc:mysql:8.0.36://localhost:3306/databasename and jdbc:mysql:8.0.36:///databasename is the same URI. Warning If you're using the JDBC URL support, there is no need to instantiate an instance of the container - Testcontainers will do it automagically. JDBC URL examples Using ClickHouse jdbc:tc:clickhouse:18.10.3:///databasename Using CockroachDB jdbc:tc:cockroach:v21.2.3:///databasename Using CrateDB jdbc:tc:cratedb:5.2.3:///databasename Using DB2 jdbc:tc:db2:11.5.0.0a:///databasename Using MariaDB jdbc:tc:mariadb:10.3.39:///databasename Using MySQL jdbc:tc:mysql:8.0.36:///databasename Using MSSQL Server jdbc:tc:sqlserver:2017-CU12:///databasename Using OceanBase jdbc:tc:oceanbasece:4.2.1-lts:///databasename Using Oracle jdbc:tc:oracle:21-slim-faststart:///databasename Using PostGIS jdbc:tc:postgis:9.6-2.5:///databasename Using PostgreSQL jdbc:tc:postgresql:9.6.8:///databasename Using QuestDB jdbc:tc:questdb:6.5.3:///databasename Using TimescaleDB jdbc:tc:timescaledb:2.1.0-pg13:///databasename Using PGVector jdbc:tc:pgvector:pg16:///databasename Using TiDB jdbc:tc:tidb:v6.1.0:///databasename Using Timeplus jdbc:tc:timeplus:2.3.21:///databasename Using Trino jdbc:tc:trino:352://localhost/memory/default Using YugabyteDB jdbc:tc:yugabyte:2.14.4.0-b26:///databasename Using a classpath init script Testcontainers can run an init script after the database container is started, but before your code is given a connection to it. The script must be on the classpath, and is referenced as follows: jdbc:tc:mysql:8.0.36:///databasename?TC_INITSCRIPT=somepath/init_mysql.sql This is useful if you have a fixed script for setting up database schema, etc. Using an init script from a file If the init script path is prefixed file: , it will be loaded from a file (relative to the working directory, which will usually be the project root). jdbc:tc:mysql:8.0.36:///databasename?TC_INITSCRIPT=file:src/main/resources/init_mysql.sql Using an init function Instead of running a fixed script for DB setup, it may be useful to call a Java function that you define. This is intended to allow you to trigger database schema migration tools. To do this, add TC_INITFUNCTION to the URL as follows, passing a full path to the class name and method: jdbc:tc:mysql:8.0.36:///databasename?TC_INITFUNCTION=org.testcontainers.jdbc.JDBCDriverTest::sampleInitFunction The init function must be a public static method which takes a java.sql.Connection as its only parameter, e.g. Copy public class JDBCDriverTest { public static void sampleInitFunction ( Connection connection ) throws SQLException { // e.g. run schema setup or Flyway/liquibase/etc DB migrations here... } ... Running container in daemon mode By default database container is being stopped as soon as last connection is closed. There are cases when you might need to start container and keep it running till you stop it explicitly or JVM is shutdown. To do this, add TC_DAEMON parameter to the URL as follows: jdbc:tc:mysql:8.0.36:///databasename?TC_DAEMON=true With this parameter database container will keep running even when there's no open connections. Running container with tmpfs options Container can have tmpfs mounts for storing data in host memory. This is useful if you want to speed up your database tests. Be aware that the data will be lost when the container stops. To pass this option to the container, add TC_TMPFS parameter to the URL as follows: jdbc:tc:postgresql:9.6.8:///databasename?TC_TMPFS=/testtmpfs:rw If you need more than one option, separate them by comma (e.g. TC_TMPFS=key:value,key1:value1&other_parameters=foo ). For more information about tmpfs mount, see the official Docker documentation . Database container objects In case you can't use the URL support, or need to fine-tune the container, you can instantiate it yourself. Add a @Rule or @ClassRule to your test class, e.g.: Copy public class SimpleMySQLTest { @ Rule public MySQLContainer mysql = new MySQLContainer (); Now, in your test code (or a suitable setup method), you can obtain details necessary to connect to this database: mysql.getJdbcUrl() provides a JDBC URL your code can connect to mysql.getUsername() provides the username your code should pass to the driver mysql.getPassword() provides the password your code should pass to the driver Note that if you use @Rule , you will be given an isolated container for each test method. If you use @ClassRule , you will get on isolated container for all the methods in the test class. Examples/Tests: MySQL PostgreSQL + +### Postgres Module + +Path: `/sourcey/modules/databases/postgres.html` + +Testcontainers module for PostgresSQL + +Testcontainers module for PostgresSQL Usage example You can start a PostgreSQL container instance from any Java application by using: Container creation inside_block:container See Database containers for documentation and usage that is common to all relational database container types. Testcontainers JDBC URL PostgreSQL: jdbc:tc:postgresql:9.6.8:///databasename PostGIS: jdbc:tc:postgis:9.6-2.5:///databasename TimescaleDB: jdbc:tc:timescaledb:2.1.0-pg13:///databasename PGvector: jdbc:tc:pgvector:pg16:///databasename See JDBC for documentation. Compatible images PostgreSQLContainer can also be used with the following images: pgvector/pgvector Using pgvector inside_block:pgvectorContainer postgis/postgis Using PostGIS inside_block:postgisContainer timescale/timescaledb Using TimescaleDB inside_block:timescaledbContainer Adding this module to your project dependencies Add the following dependency to your pom.xml / build.gradle file: Gradle Copy testImplementation "org.testcontainers:testcontainers-postgresql:{{latest_version}}" Maven Copy dependency > groupId > org.testcontainers groupId > artifactId > testcontainers-postgresql artifactId > version > {{latest_version}} version > scope > test scope > dependency > Hint Adding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency. + +### MySQL Module + +Path: `/sourcey/modules/databases/mysql.html` + +Testcontainers module for MySQL + +Testcontainers module for MySQL Usage example You can start a MySQL container instance from any Java application by using: Container definition inside_block:container See Database containers for documentation and usage that is common to all relational database container types. Testcontainers JDBC URL jdbc:tc:mysql:8.0.36:///databasename See JDBC for documentation. Overriding MySQL my.cnf settings For MySQL databases, it is possible to override configuration settings using resources on the classpath. Assuming somepath/mysql_conf_override is a directory on the classpath containing .cnf files, the following URL can be used: jdbc:tc:mysql:8.0.36://hostname/databasename?TC_MY_CNF=somepath/mysql_conf_override Any .cnf files in this classpath directory will be mapped into the database container's /etc/mysql/conf.d directory, and will be able to override server settings when the container starts. MySQL root user password If no custom password is specified, the container will use the default user password test for the root user as well. When you specify a custom password for the database user, this will also act as the password of the MySQL root user automatically. Adding this module to your project dependencies Add the following dependency to your pom.xml / build.gradle file: Gradle Copy testImplementation "org.testcontainers:testcontainers-mysql:{{latest_version}}" Maven Copy dependency > groupId > org.testcontainers groupId > artifactId > testcontainers-mysql artifactId > version > {{latest_version}} version > scope > test scope > dependency > Hint Adding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency. + +### MongoDB Module + +Path: `/sourcey/modules/databases/mongodb.html` + +The MongoDB module provides two Testcontainers for MongoDB unit testing: + +The MongoDB module provides two Testcontainers for MongoDB unit testing: MongoDBContainer - the core MongoDB database MongoDBAtlasLocalContainer - the core MongoDB database combined with MongoDB Atlas Search + Atlas Vector Search MongoDBContainer Usage example The following example shows how to create a MongoDBContainer: Creating a MongoDB container inside_block:creatingMongoDBContainer And how to start it: Starting a MongoDB container inside_block:startingMongoDBContainer Note To construct a multi-node MongoDB cluster, consider the mongodb-replica-set project Motivation Implement a reusable, cross-platform, simple to install solution that doesn't depend on fixed ports to test MongoDB transactions. General info MongoDB starting from version 4 supports multi-document transactions only for a replica set. For instance, to initialize a single node replica set on fixed ports via Docker, one has to do the following: Run a MongoDB container of version 4 and up specifying --replSet command Initialize a single replica set via executing a proper command Wait for the initialization to complete Provide a special url for a user to employ with a MongoDB driver without specifying replicaSet As we can see, there is a lot of operations to execute and we even haven't touched a non-fixed port approach. That's where the MongoDBContainer might come in handy. MongoDBAtlasLocalContainer Usage example The following example shows how to create a MongoDBAtlasLocalContainer: Creating a MongoDB Atlas Local Container inside_block:creatingAtlasLocalContainer And how to start it: Start the Container inside_block:startingAtlasLocalContainer The connection string provided by the MongoDBAtlasLocalContainer's getConnectionString() method includes the dynamically allocated port: Get the Connection String inside_block:getConnectionStringAtlasLocalContainer e.g. mongodb://localhost:12345/?directConnection=true References MongoDB Atlas Local combines the MongoDB database engine with MongoT, a sidecar process for advanced searching capabilities built by MongoDB and powered by Apache Lucene . The container (mongodb/mongodb-atlas-local) documentation can be found here . General information about Atlas Search can be found here . Adding this module to your project dependencies Add the following dependency to your pom.xml / build.gradle file: Gradle Copy testImplementation "org.testcontainers:testcontainers-mongodb:{{latest_version}}" Maven Copy dependency > groupId > org.testcontainers groupId > artifactId > testcontainers-mongodb artifactId > version > {{latest_version}} version > scope > test scope > dependency > Hint Adding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency Copyright Copyright (c) 2019 Konstantin Silaev silaev256@gmail.com + +### Kafka Module + +Path: `/sourcey/modules/kafka.html` + +Testcontainers can be used to automatically instantiate and manage Apache Kafka containers. + +Testcontainers can be used to automatically instantiate and manage Apache Kafka containers. Currently, two different Kafka images are supported: org.testcontainers.kafka.ConfluentKafkaContainer supports confluentinc/cp-kafka org.testcontainers.kafka.KafkaContainer supports apache/kafka and apache/kafka-native Note org.testcontainers.containers.KafkaContainer is deprecated. Please use org.testcontainers.kafka.ConfluentKafkaContainer or org.testcontainers.kafka.KafkaContainer instead, depending on the used image. Benefits Running a single node Kafka installation with just one line of code No need to manage external Zookeeper installation, required by Kafka. Example Using org.testcontainers.kafka.KafkaContainer Create a KafkaContainer to use it in your tests: Creating a KafkaContainer inside_block:constructorWithVersion Now your tests or any other process running on your machine can get access to running Kafka broker by using the following bootstrap server location: Bootstrap Servers inside_block:getBootstrapServers Using org.testcontainers.kafka.ConfluentKafkaContainer Note Compatible with confluentinc/cp-kafka images version 7.4.0 and later. Create a ConfluentKafkaContainer to use it in your tests: Creating a ConfluentKafkaContainer inside_block:constructorWithVersion Options Using Kraft mode Note Only available for org.testcontainers.containers.KafkaContainer KRaft mode was declared production ready in 3.3.1 (confluentinc/cp-kafka:7.3.x) Kraft mode inside_block:withKraftMode See the versions interoperability matrix for more details. Register listeners There are scenarios where additional listeners are needed because the consumer/producer can be in another container in the same network or a different process where the port to connect differs from the default exposed port. E.g Toxiproxy . Register additional listener inside_block:registerListener Container defined in the same network: Create kcat container inside_block:createKCatContainer Client using the new registered listener: Produce/Consume via new listener inside_block:produceConsumeMessage Adding this module to your project dependencies Add the following dependency to your pom.xml / build.gradle file: Gradle Copy testImplementation "org.testcontainers:testcontainers-kafka:{{latest_version}}" Maven Copy dependency > groupId > org.testcontainers groupId > artifactId > testcontainers-kafka artifactId > version > {{latest_version}} version > scope > test scope > dependency > + +### LocalStack Module + +Path: `/sourcey/modules/localstack.html` + +Testcontainers module for LocalStack, 'a fully functional local AWS cloud stack', to develop and test your cloud and serverless apps without actually using the cloud. + +Testcontainers module for LocalStack , 'a fully functional local AWS cloud stack', to develop and test your cloud and serverless apps without actually using the cloud. Usage example You can start a LocalStack container instance from any Java application by using: Container creation inside_block:container Environment variables listed in the LocalStack configuration documentation may be used to customize LocalStack's configuration. Use the .withEnv(key, value) method on LocalStackContainer to apply configuration settings. Note Starting March 23, 2026, localstack/localstack requires authentication via a LOCALSTACK_AUTH_TOKEN environment variable. Without it, the container will fail to start. Use .withEnv("LOCALSTACK_AUTH_TOKEN", System.getenv("LOCALSTACK_AUTH_TOKEN")) to pass the token. See the LocalStack blog post for more details. Creating a client using AWS SDK AWS SDK V2 inside_block:with_aws_sdk_v2 Adding this module to your project dependencies Add the following dependency to your pom.xml / build.gradle file: Gradle Copy testImplementation "org.testcontainers:testcontainers-localstack:{{latest_version}}" Maven Copy dependency > groupId > org.testcontainers groupId > artifactId > testcontainers-localstack artifactId > version > {{latest_version}} version > scope > test scope > dependency > + +### Toxiproxy Module + +Path: `/sourcey/modules/toxiproxy.html` + +Testcontainers module for Shopify's Toxiproxy. This TCP proxy can be used to simulate network failure conditions. + +Testcontainers module for Shopify's Toxiproxy . This TCP proxy can be used to simulate network failure conditions. You can simulate network failures: between Java code and containers, ideal for testing resilience features of client code between containers, for testing resilience and emergent behaviour of multi-container systems if desired, between Java code/containers and external resources (non-Dockerized!), for scenarios where not all dependencies can be/have been dockerized Testcontainers Toxiproxy support allows resilience features to be easily verified as part of isolated dev/CI testing. This allows earlier testing of resilience features, and broader sets of failure conditions to be covered. Usage example A Toxiproxy container can be placed in between test code and a container, or in between containers. In either scenario, it is necessary to create a ToxiproxyContainer instance on the same Docker network, as follows: Creating a Toxiproxy container inside_block:creatingProxy Next, it is necessary to instruct Toxiproxy to start proxying connections. Each ToxiproxyContainer can proxy to many target containers if necessary. We do this as follows: Starting proxying connections to a target container inside_block:obtainProxyObject To establish a connection from the test code (on the host machine) to the target container via Toxiproxy, we obtain Toxiproxy's proxy host IP and port: Obtaining proxied host and port inside_block:obtainProxiedHostAndPortForHostMachine Code under test should connect to this proxied host IP and port. Note Currently, ToxiProxyContainer will reserve 31 ports, starting at 8666. Other containers should connect to this proxied host and port. Having done this, it is possible to trigger failure conditions ('Toxics') through the proxy.toxics() object: bandwidth - Limit a connection to a maximum number of kilobytes per second. latency - Add a delay to all data going through the proxy. The delay is equal to latency +/- jitter . slicer - Slices TCP data up into small bits, optionally adding a delay between each sliced "packet". slowClose - Delay the TCP socket from closing until delay milliseconds has elapsed. timeout - Stops all data from getting through, and closes the connection after timeout . If timeout is 0 , the connection won't close, and data will be delayed until the toxic is removed. limitData - Closes connection when transmitted data exceeded limit. Please see the Toxiproxy documentation for full details on the available Toxics. As one example, we can introduce latency and random jitter to proxied connections as follows: Adding latency to a connection inside_block:addingLatency Additionally we can disable the proxy to simulate a complete interruption to the network connection: Cutting a connection inside_block:disableProxy Adding this module to your project dependencies Add the following dependency to your pom.xml / build.gradle file: Gradle Copy testImplementation "org.testcontainers:testcontainers-toxiproxy:{{latest_version}}" Maven Copy dependency > groupId > org.testcontainers groupId > artifactId > testcontainers-toxiproxy artifactId > version > {{latest_version}} version > scope > test scope > dependency > Acknowledgements This module was inspired by a hotels.com blog post . + +### Jupiter / JUnit 5 + +Path: `/sourcey/test-framework-integration/junit-5.html` + +This module provides an API that is based on the JUnit Jupiter extension model. + +This module provides an API that is based on the JUnit Jupiter extension model. The extension supports two modes: containers that are restarted for every test method containers that are shared between all methods of a test class Note that Jupiter/JUnit 5 integration is packaged as a separate library JAR; see below for details. Extension Jupiter integration is provided by means of the @Testcontainers annotation. The extension finds all fields that are annotated with @Container and calls their container lifecycle methods (methods on the Startable interface). Containers declared as static fields will be shared between test methods. They will be started only once before any test method is executed and stopped after the last test method has executed. Containers declared as instance fields will be started and stopped for every test method. Note: This extension has only been tested with sequential test execution. Using it with parallel test execution is unsupported and may have unintended side effects. Example: Mixed Lifecycle inside_block:testClass Examples To use the Testcontainers extension annotate your test class with @Testcontainers . Restarted containers To define a restarted container, define an instance field inside your test class and annotate it with the @Container annotation. Restarted Containers inside_block:testClass Shared containers Shared containers are defined as static fields in a top level test class and have to be annotated with @Container . Note that shared containers can't be declared inside nested test classes. This is because nested test classes have to be defined non-static and can't therefore have static fields. Shared Container lines:18-23,32-33,35-36 Singleton containers Note that the singleton container pattern is also an option when using JUnit 5. Limitations This extension has only been tested with sequential test execution. Using it with parallel test execution is unsupported and may have unintended side effects. Adding Testcontainers JUnit 5 support to your project dependencies Add the following dependency to your pom.xml / build.gradle file: Gradle Copy testImplementation "org.testcontainers:testcontainers-junit-jupiter:{{latest_version}}" Maven Copy dependency > groupId > org.testcontainers groupId > artifactId > testcontainers-junit-jupiter artifactId > version > {{latest_version}} version > scope > test scope > dependency > diff --git a/docs/sourcey/llms.txt b/docs/sourcey/llms.txt new file mode 100644 index 00000000000..17c0d5088bd --- /dev/null +++ b/docs/sourcey/llms.txt @@ -0,0 +1,31 @@ +# Testcontainers for Java Sourcey Reference + +> *Testcontainers for Java* is a Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container. + +## Java Guide + +- [Testcontainers for Java](/sourcey/): *Testcontainers for Java* is a Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container. +- [Examples](/sourcey/examples.html): Examples of different use cases provided by Testcontainers can be found below: +- [JUnit 4 Quickstart](/sourcey/quickstart/junit-4-quickstart.html): It's easy to add Testcontainers to your project - let's walk through a quick example to see how. +- [JUnit 5 Quickstart](/sourcey/quickstart/junit-5-quickstart.html): It's easy to add Testcontainers to your project - let's walk through a quick example to see how. +- [Spock Quickstart](/sourcey/quickstart/spock-quickstart.html): It's easy to add Testcontainers to your project - let's walk through a quick example to see how. +- [Creating a container](/sourcey/features/creating-container.html): Testcontainers' generic container support offers the most flexibility, and makes it easy to use virtually any container images as temporary test dependencies. For example, if you might use it to test interactions with: +- [Networking and communicating with containers](/sourcey/features/networking.html): It is common to want to connect to a container from your test process, running on the test 'host' machine. For example, you may be testing a class that needs to connect to a backend or data store container. +- [Executing commands](/sourcey/features/commands.html): By default the container will execute whatever command is specified in the image's Dockerfile. To override this, and specify a different command, use `withCommand`. For example: +- [Files and volumes](/sourcey/features/files.html): Files can be copied into the container before startup, or can be copied from the container after the container has started. +- [Waiting for containers to start or be ready](/sourcey/features/startup-and-waits.html): Ordinarily Testcontainers will wait for up to 60 seconds for the container's first mapped network port to start listening. +- [Accessing container logs](/sourcey/features/container-logs.html): It is possible to capture container output using: +- [Creating images on-the-fly](/sourcey/features/creating-images.html): In situations where there is no pre-existing Docker image, Testcontainers can create a new temporary image on-the-fly from a Dockerfile. For example, when the component under test is the Docker image itself, or when an existing base image needs to be customized for specific test(s). +- [Custom configuration](/sourcey/features/configuration.html): You can override some default properties if your environment requires that. +- [Image name substitution](/sourcey/features/image-name-substitution.html): Testcontainers supports automatic substitution of Docker image names. +- [Advanced options](/sourcey/features/advanced-options.html): To add a custom label to the container, use `withLabel`: +- [Reusable Containers (Experimental)](/sourcey/features/reuse.html): The *Reusable* feature keeps the containers running and next executions with the same container configuration will reuse it. To use it, start the container manually by calling `start()` method, do not call `stop()` method directly or indirectly via `try-with-resources` or `JUnit integration`, and enable it manually through an opt-in mechanism per environment. To reuse a container, the container configuration **must be the same**. +- [Database containers](/sourcey/modules/databases/): You might want to use Testcontainers' database support: +- [JDBC support](/sourcey/modules/databases/jdbc.html): You can obtain a temporary database in one of two ways: +- [Postgres Module](/sourcey/modules/databases/postgres.html): Testcontainers module for PostgresSQL +- [MySQL Module](/sourcey/modules/databases/mysql.html): Testcontainers module for MySQL +- [MongoDB Module](/sourcey/modules/databases/mongodb.html): The MongoDB module provides two Testcontainers for MongoDB unit testing: +- [Kafka Module](/sourcey/modules/kafka.html): Testcontainers can be used to automatically instantiate and manage Apache Kafka containers. +- [LocalStack Module](/sourcey/modules/localstack.html): Testcontainers module for LocalStack, 'a fully functional local AWS cloud stack', to develop and test your cloud and serverless apps without actually using the cloud. +- [Toxiproxy Module](/sourcey/modules/toxiproxy.html): Testcontainers module for Shopify's Toxiproxy. This TCP proxy can be used to simulate network failure conditions. +- [Jupiter / JUnit 5](/sourcey/test-framework-integration/junit-5.html): This module provides an API that is based on the JUnit Jupiter extension model. diff --git a/docs/sourcey/logo.png b/docs/sourcey/logo.png new file mode 100644 index 00000000000..88961b3e3f0 Binary files /dev/null and b/docs/sourcey/logo.png differ diff --git a/docs/sourcey/logo.svg b/docs/sourcey/logo.svg new file mode 100644 index 00000000000..bac0c391a59 --- /dev/null +++ b/docs/sourcey/logo.svg @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/sourcey/modules/databases/index.html b/docs/sourcey/modules/databases/index.html new file mode 100644 index 00000000000..40372e368a1 --- /dev/null +++ b/docs/sourcey/modules/databases/index.html @@ -0,0 +1,34 @@ + +Database containers - Testcontainers for Java Sourcey Reference
Modules

Database containers

You might want to use Testcontainers' database support:

Overview

+

You might want to use Testcontainers' database support:

+
    +
  • Instead of H2 database for DAO unit tests that depend on database features that H2 doesn't emulate. Testcontainers is not as performant as H2, but does give you the benefit of 100% database compatibility (since it runs a real DB inside of a container).
  • +
  • Instead of a database running on the local machine or in a VM for DAO unit tests or end-to-end integration tests that need a database to be present. In this context, the benefit of Testcontainers is that the database always starts in a known state, without any contamination between test runs or on developers' local machines.
  • +
+
+

Note +Of course, it's still important to have as few tests that hit the database as possible, and make good use of mocks for components higher up the stack.

+
+

See JDBC and R2DBC for information on how to use Testcontainers with SQL-like databases.

+
\ No newline at end of file diff --git a/docs/sourcey/modules/databases/jdbc.html b/docs/sourcey/modules/databases/jdbc.html new file mode 100644 index 00000000000..a3286596514 --- /dev/null +++ b/docs/sourcey/modules/databases/jdbc.html @@ -0,0 +1,132 @@ + +JDBC support - Testcontainers for Java Sourcey Reference
Modules

JDBC support

You can obtain a temporary database in one of two ways:

You can obtain a temporary database in one of two ways:

+
    +
  • Using a specially modified JDBC URL: after making a very simple modification to your system's JDBC URL string, Testcontainers will provide a disposable stand-in database that can be used without requiring modification to your application code.
  • +
  • JUnit @Rule/@ClassRule: this mode starts a database inside a container before your tests and tears it down afterwards.
  • +
+

Database containers launched via JDBC URL scheme

+

As long as you have Testcontainers and the appropriate JDBC driver on your classpath, you can simply modify regular JDBC connection URLs to get a fresh containerized instance of the database each time your application starts up.

+

N.B:

+
    +
  • TC needs to be on your application's classpath at runtime for this to work
  • +
  • For Spring Boot (Before version 2.3.0) you need to specify the driver manually spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver
  • +
+

Original URL: jdbc:mysql://localhost:3306/databasename

+

Insert tc: after jdbc: as follows. Note that the hostname, port and database name will be ignored; you can leave these as-is or set them to any value.

+
+

Note +We will use /// (host-less URIs) from now on to emphasis the unimportance of the host:port pair.
From Testcontainers' perspective, jdbc:mysql:8.0.36://localhost:3306/databasename and jdbc:mysql:8.0.36:///databasename is the same URI.

+

Warning +If you're using the JDBC URL support, there is no need to instantiate an instance of the container - Testcontainers will do it automagically.

+
+

JDBC URL examples

+

Using ClickHouse

+

jdbc:tc:clickhouse:18.10.3:///databasename

+

Using CockroachDB

+

jdbc:tc:cockroach:v21.2.3:///databasename

+

Using CrateDB

+

jdbc:tc:cratedb:5.2.3:///databasename

+

Using DB2

+

jdbc:tc:db2:11.5.0.0a:///databasename

+

Using MariaDB

+

jdbc:tc:mariadb:10.3.39:///databasename

+

Using MySQL

+

jdbc:tc:mysql:8.0.36:///databasename

+

Using MSSQL Server

+

jdbc:tc:sqlserver:2017-CU12:///databasename

+

Using OceanBase

+

jdbc:tc:oceanbasece:4.2.1-lts:///databasename

+

Using Oracle

+

jdbc:tc:oracle:21-slim-faststart:///databasename

+

Using PostGIS

+

jdbc:tc:postgis:9.6-2.5:///databasename

+

Using PostgreSQL

+

jdbc:tc:postgresql:9.6.8:///databasename

+

Using QuestDB

+

jdbc:tc:questdb:6.5.3:///databasename

+

Using TimescaleDB

+

jdbc:tc:timescaledb:2.1.0-pg13:///databasename

+

Using PGVector

+

jdbc:tc:pgvector:pg16:///databasename

+

Using TiDB

+

jdbc:tc:tidb:v6.1.0:///databasename

+

Using Timeplus

+

jdbc:tc:timeplus:2.3.21:///databasename

+

Using Trino

+

jdbc:tc:trino:352://localhost/memory/default

+

Using YugabyteDB

+

jdbc:tc:yugabyte:2.14.4.0-b26:///databasename

+

Using a classpath init script

+

Testcontainers can run an init script after the database container is started, but before your code is given a connection to it. The script must be on the classpath, and is referenced as follows:

+

jdbc:tc:mysql:8.0.36:///databasename?TC_INITSCRIPT=somepath/init_mysql.sql

+

This is useful if you have a fixed script for setting up database schema, etc.

+

Using an init script from a file

+

If the init script path is prefixed file:, it will be loaded from a file (relative to the working directory, which will usually be the project root).

+

jdbc:tc:mysql:8.0.36:///databasename?TC_INITSCRIPT=file:src/main/resources/init_mysql.sql

+

Using an init function

+

Instead of running a fixed script for DB setup, it may be useful to call a Java function that you define. This is intended to allow you to trigger database schema migration tools. To do this, add TC_INITFUNCTION to the URL as follows, passing a full path to the class name and method:

+

jdbc:tc:mysql:8.0.36:///databasename?TC_INITFUNCTION=org.testcontainers.jdbc.JDBCDriverTest::sampleInitFunction

+

The init function must be a public static method which takes a java.sql.Connection as its only parameter, e.g.

+
+
+ +
+
public class JDBCDriverTest {
+    public static void sampleInitFunction(Connection connection) throws SQLException {
+        // e.g. run schema setup or Flyway/liquibase/etc DB migrations here...
+    }
+    ...
+

Running container in daemon mode

+

By default database container is being stopped as soon as last connection is closed. There are cases when you might need to start container and keep it running till you stop it explicitly or JVM is shutdown. To do this, add TC_DAEMON parameter to the URL as follows:

+

jdbc:tc:mysql:8.0.36:///databasename?TC_DAEMON=true

+

With this parameter database container will keep running even when there's no open connections.

+

Running container with tmpfs options

+

Container can have tmpfs mounts for storing data in host memory. This is useful if you want to speed up your database tests. Be aware that the data will be lost when the container stops.

+

To pass this option to the container, add TC_TMPFS parameter to the URL as follows:

+

jdbc:tc:postgresql:9.6.8:///databasename?TC_TMPFS=/testtmpfs:rw

+

If you need more than one option, separate them by comma (e.g. TC_TMPFS=key:value,key1:value1&other_parameters=foo).

+

For more information about tmpfs mount, see the official Docker documentation.

+

Database container objects

+

In case you can't use the URL support, or need to fine-tune the container, you can instantiate it yourself.

+

Add a @Rule or @ClassRule to your test class, e.g.:

+
+
+ +
+
public class SimpleMySQLTest {
+    @Rule
+    public MySQLContainer mysql = new MySQLContainer();
+

Now, in your test code (or a suitable setup method), you can obtain details necessary to connect to this database:

+
    +
  • mysql.getJdbcUrl() provides a JDBC URL your code can connect to
  • +
  • mysql.getUsername() provides the username your code should pass to the driver
  • +
  • mysql.getPassword() provides the password your code should pass to the driver
  • +
+

Note that if you use @Rule, you will be given an isolated container for each test method. If you use @ClassRule, you will get on isolated container for all the methods in the test class.

+

Examples/Tests:

+ +
\ No newline at end of file diff --git a/docs/sourcey/modules/databases/mongodb.html b/docs/sourcey/modules/databases/mongodb.html new file mode 100644 index 00000000000..d9ac98e9fde --- /dev/null +++ b/docs/sourcey/modules/databases/mongodb.html @@ -0,0 +1,105 @@ + +MongoDB Module - Testcontainers for Java Sourcey Reference
Modules

MongoDB Module

The MongoDB module provides two Testcontainers for MongoDB unit testing:

The MongoDB module provides two Testcontainers for MongoDB unit testing:

+ +

MongoDBContainer

+

Usage example

+

The following example shows how to create a MongoDBContainer:

+ +

Creating a MongoDB container inside_block:creatingMongoDBContainer

+ + +

And how to start it:

+ +

Starting a MongoDB container inside_block:startingMongoDBContainer

+ + +
+

Note +To construct a multi-node MongoDB cluster, consider the mongodb-replica-set project

+
+

Motivation

+

Implement a reusable, cross-platform, simple to install solution that doesn't depend on +fixed ports to test MongoDB transactions.

+

General info

+

MongoDB starting from version 4 supports multi-document transactions only for a replica set. +For instance, to initialize a single node replica set on fixed ports via Docker, one has to do the following:

+
    +
  • Run a MongoDB container of version 4 and up specifying --replSet command
  • +
  • Initialize a single replica set via executing a proper command
  • +
  • Wait for the initialization to complete
  • +
  • Provide a special url for a user to employ with a MongoDB driver without specifying replicaSet
  • +
+

As we can see, there is a lot of operations to execute and we even haven't touched a non-fixed port approach. +That's where the MongoDBContainer might come in handy.

+

MongoDBAtlasLocalContainer

+

Usage example

+

The following example shows how to create a MongoDBAtlasLocalContainer:

+ +

Creating a MongoDB Atlas Local Container inside_block:creatingAtlasLocalContainer

+ + +

And how to start it:

+ +

Start the Container inside_block:startingAtlasLocalContainer

+ + +

The connection string provided by the MongoDBAtlasLocalContainer's getConnectionString() method includes the dynamically allocated port:

+ +

Get the Connection String inside_block:getConnectionStringAtlasLocalContainer

+ + +

e.g. mongodb://localhost:12345/?directConnection=true

+

References

+

MongoDB Atlas Local combines the MongoDB database engine with MongoT, a sidecar process for advanced searching capabilities built by MongoDB and powered by Apache Lucene.

+

The container (mongodb/mongodb-atlas-local) documentation can be found here.

+

General information about Atlas Search can be found here.

+

Adding this module to your project dependencies

+

Add the following dependency to your pom.xml/build.gradle file:

+

Gradle

+
+
+ +
+
testImplementation "org.testcontainers:testcontainers-mongodb:{{latest_version}}"
+

Maven

+
+
+ +
+
&lt;dependency&gt;
+    &lt;groupId&gt;org.testcontainers</groupId>
+    &lt;artifactId&gt;testcontainers-mongodb</artifactId>
+    &lt;version&gt;{{latest_version}}</version>
+    &lt;scope&gt;test</scope>
+</dependency>
+
+

Hint +Adding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency

+
+ +

Copyright (c) 2019 Konstantin Silaev silaev256@gmail.com

+
\ No newline at end of file diff --git a/docs/sourcey/modules/databases/mysql.html b/docs/sourcey/modules/databases/mysql.html new file mode 100644 index 00000000000..ed6f7204622 --- /dev/null +++ b/docs/sourcey/modules/databases/mysql.html @@ -0,0 +1,67 @@ + +MySQL Module - Testcontainers for Java Sourcey Reference
Modules

MySQL Module

Testcontainers module for MySQL

Testcontainers module for MySQL

+

Usage example

+

You can start a MySQL container instance from any Java application by using:

+ +

Container definition inside_block:container

+ + +

See Database containers for documentation and usage that is common to all relational database container types.

+

Testcontainers JDBC URL

+

jdbc:tc:mysql:8.0.36:///databasename

+

See JDBC for documentation.

+

Overriding MySQL my.cnf settings

+

For MySQL databases, it is possible to override configuration settings using resources on the classpath. Assuming somepath/mysql_conf_override +is a directory on the classpath containing .cnf files, the following URL can be used:

+

jdbc:tc:mysql:8.0.36://hostname/databasename?TC_MY_CNF=somepath/mysql_conf_override

+

Any .cnf files in this classpath directory will be mapped into the database container's /etc/mysql/conf.d directory, +and will be able to override server settings when the container starts.

+

MySQL root user password

+

If no custom password is specified, the container will use the default user password test for the root user as well. +When you specify a custom password for the database user, +this will also act as the password of the MySQL root user automatically.

+

Adding this module to your project dependencies

+

Add the following dependency to your pom.xml/build.gradle file:

+

Gradle

+
+
+ +
+
testImplementation "org.testcontainers:testcontainers-mysql:{{latest_version}}"
+

Maven

+
+
+ +
+
&lt;dependency&gt;
+    &lt;groupId&gt;org.testcontainers</groupId>
+    &lt;artifactId&gt;testcontainers-mysql</artifactId>
+    &lt;version&gt;{{latest_version}}</version>
+    &lt;scope&gt;test</scope>
+</dependency>
+
+

Hint +Adding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency.

+
+
\ No newline at end of file diff --git a/docs/sourcey/modules/databases/postgres.html b/docs/sourcey/modules/databases/postgres.html new file mode 100644 index 00000000000..68c46ac39ce --- /dev/null +++ b/docs/sourcey/modules/databases/postgres.html @@ -0,0 +1,85 @@ + +Postgres Module - Testcontainers for Java Sourcey Reference
Modules

Postgres Module

Testcontainers module for PostgresSQL

Testcontainers module for PostgresSQL

+

Usage example

+

You can start a PostgreSQL container instance from any Java application by using:

+ +

Container creation inside_block:container

+ + +

See Database containers for documentation and usage that is common to all relational database container types.

+

Testcontainers JDBC URL

+
    +
  • PostgreSQL: jdbc:tc:postgresql:9.6.8:///databasename
  • +
  • PostGIS: jdbc:tc:postgis:9.6-2.5:///databasename
  • +
  • TimescaleDB: jdbc:tc:timescaledb:2.1.0-pg13:///databasename
  • +
  • PGvector: jdbc:tc:pgvector:pg16:///databasename
  • +
+

See JDBC for documentation.

+

Compatible images

+

PostgreSQLContainer can also be used with the following images:

+ + +

Using pgvector inside_block:pgvectorContainer

+ + + + +

Using PostGIS inside_block:postgisContainer

+ + + + +

Using TimescaleDB inside_block:timescaledbContainer

+ + +

Adding this module to your project dependencies

+

Add the following dependency to your pom.xml/build.gradle file:

+

Gradle

+
+
+ +
+
testImplementation "org.testcontainers:testcontainers-postgresql:{{latest_version}}"
+

Maven

+
+
+ +
+
&lt;dependency&gt;
+    &lt;groupId&gt;org.testcontainers</groupId>
+    &lt;artifactId&gt;testcontainers-postgresql</artifactId>
+    &lt;version&gt;{{latest_version}}</version>
+    &lt;scope&gt;test</scope>
+</dependency>
+
+

Hint +Adding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency.

+
+
\ No newline at end of file diff --git a/docs/sourcey/modules/kafka.html b/docs/sourcey/modules/kafka.html new file mode 100644 index 00000000000..0fc1876dc0f --- /dev/null +++ b/docs/sourcey/modules/kafka.html @@ -0,0 +1,110 @@ + +Kafka Module - Testcontainers for Java Sourcey Reference
Modules

Kafka Module

Testcontainers can be used to automatically instantiate and manage Apache Kafka containers.

Testcontainers can be used to automatically instantiate and manage Apache Kafka containers.

+

Currently, two different Kafka images are supported:

+ +
+

Note +org.testcontainers.containers.KafkaContainer is deprecated. +Please use org.testcontainers.kafka.ConfluentKafkaContainer or org.testcontainers.kafka.KafkaContainer instead, depending on the used image.

+
+

Benefits

+
    +
  • Running a single node Kafka installation with just one line of code
  • +
  • No need to manage external Zookeeper installation, required by Kafka.
  • +
+

Example

+

Using org.testcontainers.kafka.KafkaContainer

+

Create a KafkaContainer to use it in your tests:

+ +

Creating a KafkaContainer inside_block:constructorWithVersion

+ + +

Now your tests or any other process running on your machine can get access to running Kafka broker by using the following bootstrap server location:

+ +

Bootstrap Servers inside_block:getBootstrapServers

+ + +

Using org.testcontainers.kafka.ConfluentKafkaContainer

+
+

Note +Compatible with confluentinc/cp-kafka images version 7.4.0 and later.

+
+

Create a ConfluentKafkaContainer to use it in your tests:

+ +

Creating a ConfluentKafkaContainer inside_block:constructorWithVersion

+ + +

Options

+

Using Kraft mode

+
+

Note +Only available for org.testcontainers.containers.KafkaContainer

+
+

KRaft mode was declared production ready in 3.3.1 (confluentinc/cp-kafka:7.3.x)

+ +

Kraft mode inside_block:withKraftMode

+ + +

See the versions interoperability matrix for more details.

+

Register listeners

+

There are scenarios where additional listeners are needed because the consumer/producer can be in another +container in the same network or a different process where the port to connect differs from the default exposed port. E.g Toxiproxy.

+ +

Register additional listener inside_block:registerListener

+ + +

Container defined in the same network:

+ +

Create kcat container inside_block:createKCatContainer

+ + +

Client using the new registered listener:

+ +

Produce/Consume via new listener inside_block:produceConsumeMessage

+ + +

Adding this module to your project dependencies

+

Add the following dependency to your pom.xml/build.gradle file:

+

Gradle

+
+
+ +
+
testImplementation "org.testcontainers:testcontainers-kafka:{{latest_version}}"
+

Maven

+
+
+ +
+
&lt;dependency&gt;
+    &lt;groupId&gt;org.testcontainers</groupId>
+    &lt;artifactId&gt;testcontainers-kafka</artifactId>
+    &lt;version&gt;{{latest_version}}</version>
+    &lt;scope&gt;test</scope>
+</dependency>
+
\ No newline at end of file diff --git a/docs/sourcey/modules/localstack.html b/docs/sourcey/modules/localstack.html new file mode 100644 index 00000000000..58bfbbb8a83 --- /dev/null +++ b/docs/sourcey/modules/localstack.html @@ -0,0 +1,62 @@ + +LocalStack Module - Testcontainers for Java Sourcey Reference
Modules

LocalStack Module

Testcontainers module for LocalStack, 'a fully functional local AWS cloud stack', to develop and test your cloud and serverless apps without actually using the cloud.

Testcontainers module for LocalStack, 'a fully functional local AWS cloud stack', to develop and test your cloud and serverless apps without actually using the cloud.

+

Usage example

+

You can start a LocalStack container instance from any Java application by using:

+ +

Container creation inside_block:container

+ + +

Environment variables listed in the LocalStack configuration documentation may be used to customize LocalStack's configuration. +Use the .withEnv(key, value) method on LocalStackContainer to apply configuration settings.

+
+

Note +Starting March 23, 2026, localstack/localstack requires authentication via a LOCALSTACK_AUTH_TOKEN environment variable. Without it, the container will fail to start.

+

Use .withEnv("LOCALSTACK_AUTH_TOKEN", System.getenv("LOCALSTACK_AUTH_TOKEN")) to pass the token. +See the LocalStack blog post for more details.

+
+

Creating a client using AWS SDK

+ +

AWS SDK V2 inside_block:with_aws_sdk_v2

+ + +

Adding this module to your project dependencies

+

Add the following dependency to your pom.xml/build.gradle file:

+

Gradle

+
+
+ +
+
testImplementation "org.testcontainers:testcontainers-localstack:{{latest_version}}"
+

Maven

+
+
+ +
+
&lt;dependency&gt;
+    &lt;groupId&gt;org.testcontainers</groupId>
+    &lt;artifactId&gt;testcontainers-localstack</artifactId>
+    &lt;version&gt;{{latest_version}}</version>
+    &lt;scope&gt;test</scope>
+</dependency>
+
\ No newline at end of file diff --git a/docs/sourcey/modules/toxiproxy.html b/docs/sourcey/modules/toxiproxy.html new file mode 100644 index 00000000000..b28c9d8a360 --- /dev/null +++ b/docs/sourcey/modules/toxiproxy.html @@ -0,0 +1,98 @@ + +Toxiproxy Module - Testcontainers for Java Sourcey Reference
Modules

Toxiproxy Module

Testcontainers module for Shopify's Toxiproxy. This TCP proxy can be used to simulate network failure conditions.

Testcontainers module for Shopify's Toxiproxy. +This TCP proxy can be used to simulate network failure conditions.

+

You can simulate network failures:

+
    +
  • between Java code and containers, ideal for testing resilience features of client code
  • +
  • between containers, for testing resilience and emergent behaviour of multi-container systems
  • +
  • if desired, between Java code/containers and external resources (non-Dockerized!), for scenarios where not all dependencies can be/have been dockerized
  • +
+

Testcontainers Toxiproxy support allows resilience features to be easily verified as part of isolated dev/CI testing. This allows earlier testing of resilience features, and broader sets of failure conditions to be covered.

+

Usage example

+

A Toxiproxy container can be placed in between test code and a container, or in between containers. +In either scenario, it is necessary to create a ToxiproxyContainer instance on the same Docker network, as follows:

+ +

Creating a Toxiproxy container inside_block:creatingProxy

+ + +

Next, it is necessary to instruct Toxiproxy to start proxying connections. +Each ToxiproxyContainer can proxy to many target containers if necessary.

+

We do this as follows:

+ +

Starting proxying connections to a target container inside_block:obtainProxyObject

+ + +

To establish a connection from the test code (on the host machine) to the target container via Toxiproxy, we obtain Toxiproxy's proxy host IP and port:

+ +

Obtaining proxied host and port inside_block:obtainProxiedHostAndPortForHostMachine

+ + +

Code under test should connect to this proxied host IP and port.

+
+

Note +Currently, ToxiProxyContainer will reserve 31 ports, starting at 8666.

+
+

Other containers should connect to this proxied host and port.

+

Having done this, it is possible to trigger failure conditions ('Toxics') through the proxy.toxics() object:

+
    +
  • bandwidth - Limit a connection to a maximum number of kilobytes per second.
  • +
  • latency - Add a delay to all data going through the proxy. The delay is equal to latency +/- jitter.
  • +
  • slicer - Slices TCP data up into small bits, optionally adding a delay between each sliced "packet".
  • +
  • slowClose - Delay the TCP socket from closing until delay milliseconds has elapsed.
  • +
  • timeout - Stops all data from getting through, and closes the connection after timeout. If timeout is 0, the connection won't close, and data will be delayed until the toxic is removed.
  • +
  • limitData - Closes connection when transmitted data exceeded limit.
  • +
+

Please see the Toxiproxy documentation for full details on the available Toxics.

+

As one example, we can introduce latency and random jitter to proxied connections as follows:

+ +

Adding latency to a connection inside_block:addingLatency

+ + +

Additionally we can disable the proxy to simulate a complete interruption to the network connection:

+ +

Cutting a connection inside_block:disableProxy

+ + +

Adding this module to your project dependencies

+

Add the following dependency to your pom.xml/build.gradle file:

+

Gradle

+
+
+ +
+
testImplementation "org.testcontainers:testcontainers-toxiproxy:{{latest_version}}"
+

Maven

+
+
+ +
+
&lt;dependency&gt;
+    &lt;groupId&gt;org.testcontainers</groupId>
+    &lt;artifactId&gt;testcontainers-toxiproxy</artifactId>
+    &lt;version&gt;{{latest_version}}</version>
+    &lt;scope&gt;test</scope>
+</dependency>
+

Acknowledgements

+

This module was inspired by a hotels.com blog post.

+
\ No newline at end of file diff --git a/docs/sourcey/modules_logos/hivemq-module.png b/docs/sourcey/modules_logos/hivemq-module.png new file mode 100644 index 00000000000..55c0ee1644a Binary files /dev/null and b/docs/sourcey/modules_logos/hivemq-module.png differ diff --git a/docs/sourcey/quickstart/junit-4-quickstart.html b/docs/sourcey/quickstart/junit-4-quickstart.html new file mode 100644 index 00000000000..a1913f770bc --- /dev/null +++ b/docs/sourcey/quickstart/junit-4-quickstart.html @@ -0,0 +1,92 @@ + +JUnit 4 Quickstart - Testcontainers for Java Sourcey Reference
Quickstart

JUnit 4 Quickstart

It's easy to add Testcontainers to your project - let's walk through a quick example to see how.

It's easy to add Testcontainers to your project - let's walk through a quick example to see how.

+

Let's imagine we have a simple program that has a dependency on Redis, and we want to add some tests for it. +In our imaginary program, there is a RedisBackedCache class which stores data in Redis.

+

You can see an example test that could have been written for it (without using Testcontainers):

+ +

Pre-Testcontainers test code block:RedisBackedCacheIntTestStep0

+ + +

Notice that the existing test has a problem - it's relying on a local installation of Redis, which is a red flag for test reliability. +This may work if we were sure that every developer and CI machine had Redis installed, but would fail otherwise. +We might also have problems if we attempted to run tests in parallel, such as state bleeding between tests, or port clashes.

+

Let's start from here, and see how to improve the test with Testcontainers:

+

1. Add Testcontainers as a test-scoped dependency

+

First, add Testcontainers as a dependency as follows:

+

Gradle

+
+
+ +
+
testImplementation "org.testcontainers:testcontainers:{{latest_version}}"
+

Maven

+
+
+ +
+
&lt;dependency&gt;
+    &lt;groupId&gt;org.testcontainers</groupId>
+    &lt;artifactId&gt;testcontainers</artifactId>
+    &lt;version&gt;{{latest_version}}</version>
+    &lt;scope&gt;test</scope>
+</dependency>
+

2. Get Testcontainers to run a Redis container during our tests

+

Simply add the following to the body of our test class:

+ +

JUnit 4 Rule inside_block:rule

+ + +

The @Rule annotation tells JUnit to notify this field about various events in the test lifecycle. +In this case, our rule object is a Testcontainers GenericContainer, configured to use a specific Redis image from Docker Hub, and configured to expose a port.

+

If we run our test as-is, then regardless of the actual test outcome, we'll see logs showing us that Testcontainers:

+
    +
  • was activated before our test method ran
  • +
  • discovered and quickly tested our local Docker setup
  • +
  • pulled the image if necessary
  • +
  • started a new container and waited for it to be ready
  • +
  • shut down and deleted the container after the test
  • +
+

3. Make sure our code can talk to the container

+

Before Testcontainers, we might have hardcoded an address like localhost:6379 into our tests.

+

Testcontainers uses randomized ports for each container it starts, but makes it easy to obtain the actual port at runtime. +We can do this in our test setUp method, to set up our component under test:

+ +

Obtaining a mapped port inside_block:setUp

+ + +
+

Tip +Notice that we also ask Testcontainers for the container's actual address with redis.getHost();, +rather than hard-coding localhost. localhost may work in some environments but not others - for example it may +not work on your current or future CI environment. As such, avoid hard-coding the address, and use +getHost() instead.

+
+

4. Run the tests!

+

That's it!

+

Let's look at our complete test class to see how little we had to add to get up and running with Testcontainers:

+ +

RedisBackedCacheIntTest block:RedisBackedCacheIntTest

+ + +
\ No newline at end of file diff --git a/docs/sourcey/quickstart/junit-5-quickstart.html b/docs/sourcey/quickstart/junit-5-quickstart.html new file mode 100644 index 00000000000..7f9fe8962b3 --- /dev/null +++ b/docs/sourcey/quickstart/junit-5-quickstart.html @@ -0,0 +1,114 @@ + +JUnit 5 Quickstart - Testcontainers for Java Sourcey Reference
Quickstart

JUnit 5 Quickstart

It's easy to add Testcontainers to your project - let's walk through a quick example to see how.

It's easy to add Testcontainers to your project - let's walk through a quick example to see how.

+

Let's imagine we have a simple program that has a dependency on Redis, and we want to add some tests for it. +In our imaginary program, there is a RedisBackedCache class which stores data in Redis.

+

You can see an example test that could have been written for it (without using Testcontainers):

+ +

Pre-Testcontainers test code block:RedisBackedCacheIntTestStep0

+ + +

Notice that the existing test has a problem - it's relying on a local installation of Redis, which is a red flag for test reliability. +This may work if we were sure that every developer and CI machine had Redis installed, but would fail otherwise. +We might also have problems if we attempted to run tests in parallel, such as state bleeding between tests, or port clashes.

+

Let's start from here, and see how to improve the test with Testcontainers:

+

1. Add Testcontainers as a test-scoped dependency

+

First, add Testcontainers as a dependency as follows:

+

Gradle

+
+
+ +
+
testImplementation "org.junit.jupiter:junit-jupiter:5.8.1"
+testImplementation "org.testcontainers:testcontainers:{{latest_version}}"
+testImplementation "org.testcontainers:testcontainers-junit-jupiter:{{latest_version}}"
+

Maven

+
+
+ +
+
&lt;dependency&gt;
+    &lt;groupId&gt;org.junit.jupiter</groupId>
+    &lt;artifactId&gt;junit-jupiter</artifactId>
+    &lt;version&gt;5.8.1</version>
+    &lt;scope&gt;test</scope>
+</dependency>
+&lt;dependency&gt;
+    &lt;groupId&gt;org.testcontainers</groupId>
+    &lt;artifactId&gt;testcontainers</artifactId>
+    &lt;version&gt;{{latest_version}}</version>
+    &lt;scope&gt;test</scope>
+</dependency>
+&lt;dependency&gt;
+    &lt;groupId&gt;org.testcontainers</groupId>
+    &lt;artifactId&gt;testcontainers-junit-jupiter</artifactId>
+    &lt;version&gt;{{latest_version}}</version>
+    &lt;scope&gt;test</scope>
+</dependency>
+

2. Get Testcontainers to run a Redis container during our tests

+

First, you'll need to annotate the test class with @Testcontainers. Furthermore, add the following to the body of our test class:

+ +

JUnit 5 Rule inside_block:container

+ + +

The @Container annotation tells JUnit to notify this field about various events in the test lifecycle. +In this case, our rule object is a Testcontainers GenericContainer, configured to use a specific Redis image from Docker Hub, and configured to expose a port.

+

If we run our test as-is, then regardless of the actual test outcome, we'll see logs showing us that Testcontainers:

+
    +
  • was activated before our test method ran
  • +
  • discovered and quickly tested our local Docker setup
  • +
  • pulled the image if necessary
  • +
  • started a new container and waited for it to be ready
  • +
  • shut down and deleted the container after the test
  • +
+

3. Make sure our code can talk to the container

+

Before Testcontainers, we might have hardcoded an address like localhost:6379 into our tests.

+

Testcontainers uses randomized ports for each container it starts, but makes it easy to obtain the actual port at runtime. +We can do this in our test setUp method, to set up our component under test:

+ +

Obtaining a mapped port inside_block:setUp

+ + +
+

Tip +Notice that we also ask Testcontainers for the container's actual address with redis.getHost();, +rather than hard-coding localhost. localhost may work in some environments but not others - for example it may +not work on your current or future CI environment. As such, avoid hard-coding the address, and use +getHost() instead.

+
+

4. Additional attributes

+

Additional attributes are available for the @Testcontainers annotation. +Those attributes can be helpful when:

+
    +
  • Tests should be skipped instead of failing because Docker is unavailable in the +current environment. Set disabledWithoutDocker to true.
  • +
  • Enable parallel container initialization instead of sequential (by default). Set parallel to true.
  • +
+

5. Run the tests!

+

That's it!

+

Let's look at our complete test class to see how little we had to add to get up and running with Testcontainers:

+ +

RedisBackedCacheIntTest inside_block:class

+ + +
\ No newline at end of file diff --git a/docs/sourcey/quickstart/spock-quickstart.html b/docs/sourcey/quickstart/spock-quickstart.html new file mode 100644 index 00000000000..239d87a66ba --- /dev/null +++ b/docs/sourcey/quickstart/spock-quickstart.html @@ -0,0 +1,98 @@ + +Spock Quickstart - Testcontainers for Java Sourcey Reference
Quickstart

Spock Quickstart

It's easy to add Testcontainers to your project - let's walk through a quick example to see how.

It's easy to add Testcontainers to your project - let's walk through a quick example to see how.

+

Let's imagine we have a simple program that has a dependency on Redis, and we want to add some tests for it. +In our imaginary program, there is a RedisBackedCache class which stores data in Redis.

+

You can see an example test that could have been written for it (without using Testcontainers):

+ +

Pre-Testcontainers test code block:RedisBackedCacheIntTestStep0

+ + +

Notice that the existing test has a problem - it's relying on a local installation of Redis, which is a red flag for test reliability. +This may work if we were sure that every developer and CI machine had Redis installed, but would fail otherwise. +We might also have problems if we attempted to run tests in parallel, such as state bleeding between tests, or port clashes.

+

Let's start from here, and see how to improve the test with Testcontainers:

+

1. Add Testcontainers as a test-scoped dependency

+

First, add Testcontainers as a dependency as follows:

+

Gradle

+
+
+ +
+
testImplementation "org.testcontainers:testcontainers-spock:{{latest_version}}"
+

Maven

+
+
+ +
+
&lt;dependency&gt;
+    &lt;groupId&gt;org.testcontainers</groupId>
+    &lt;artifactId&gt;testcontainers-spock</artifactId>
+    &lt;version&gt;{{latest_version}}</version>
+    &lt;scope&gt;test</scope>
+</dependency>
+

2. Get Testcontainers to run a Redis container during our tests

+

Annotate the Spock specification class with the Testcontainers extension:

+

Spock Testcontainers annotation

+
+
+ +
+
@org.testcontainers.spock.Testcontainers
+class RedisBackedCacheIntTest extends Specification {
+

And add the following field to the body of our test class:

+ +

Spock Testcontainers init inside_block:init

+ + +

This tells Spock to start a Testcontainers GenericContainer, configured to use a specific Redis image from Docker Hub, and configured to expose a port.

+

If we run our test as-is, then regardless of the actual test outcome, we'll see logs showing us that Testcontainers:

+
    +
  • was activated before our test method ran
  • +
  • discovered and quickly tested our local Docker setup
  • +
  • pulled the image if necessary
  • +
  • started a new container and waited for it to be ready
  • +
  • shut down and deleted the container after the test
  • +
+

3. Make sure our code can talk to the container

+

Before Testcontainers, we might have hardcoded an address like localhost:6379 into our tests.

+

Testcontainers uses randomized ports for each container it starts, but makes it easy to obtain the actual port at runtime. +We can do this in our test setup method, to set up our component under test:

+ +

Obtaining a mapped port inside_block:setup

+ + +
+

Tip +Notice that we also ask Testcontainers for the container's actual address with redis.containerIpAddress, +rather than hard-coding localhost. localhost may work in some environments but not others - for example it may +not work on your current or future CI environment. As such, avoid hard-coding the address, and use +containerIpAddress instead.

+
+

4. Run the tests!

+

That's it!

+

Let's look at our complete test class to see how little we had to add to get up and running with Testcontainers:

+ +

RedisBackedCacheIntTest block:complete

+ +
\ No newline at end of file diff --git a/docs/sourcey/search-index.json b/docs/sourcey/search-index.json new file mode 100644 index 00000000000..7763f94d128 --- /dev/null +++ b/docs/sourcey/search-index.json @@ -0,0 +1 @@ +[{"title":"Testcontainers for Java","content":"*Testcontainers for Java* is a Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container.","url":"/sourcey/","tab":"Java Guide","category":"Pages"},{"title":"About Testcontainers for Java","content":"Testcontainers for Java - About Testcontainers for Java","url":"/sourcey/#about-testcontainers-for-java","tab":"Java Guide","category":"Sections"},{"title":"Prerequisites","content":"Testcontainers for Java - Prerequisites","url":"/sourcey/#prerequisites","tab":"Java Guide","category":"Sections"},{"title":"Maven dependencies","content":"Testcontainers for Java - Maven dependencies","url":"/sourcey/#maven-dependencies","tab":"Java Guide","category":"Sections"},{"title":"Managing versions for multiple Testcontainers dependencies","content":"Testcontainers for Java - Managing versions for multiple Testcontainers dependencies","url":"/sourcey/#managing-versions-for-multiple-testcontainers-dependencies","tab":"Java Guide","category":"Sections"},{"title":"Sponsors","content":"Testcontainers for Java - Sponsors","url":"/sourcey/#sponsors","tab":"Java Guide","category":"Sections"},{"title":"Bronze sponsors","content":"Testcontainers for Java - Bronze sponsors","url":"/sourcey/#bronze-sponsors","tab":"Java Guide","category":"Sections"},{"title":"Donors","content":"Testcontainers for Java - Donors","url":"/sourcey/#donors","tab":"Java Guide","category":"Sections"},{"title":"Backers","content":"Testcontainers for Java - Backers","url":"/sourcey/#backers","tab":"Java Guide","category":"Sections"},{"title":"Who is using Testcontainers?","content":"Testcontainers for Java - Who is using Testcontainers?","url":"/sourcey/#who-is-using-testcontainers","tab":"Java Guide","category":"Sections"},{"title":"License","content":"Testcontainers for Java - License","url":"/sourcey/#license","tab":"Java Guide","category":"Sections"},{"title":"Attributions","content":"Testcontainers for Java - Attributions","url":"/sourcey/#attributions","tab":"Java Guide","category":"Sections"},{"title":"Copyright","content":"Testcontainers for Java - Copyright","url":"/sourcey/#copyright","tab":"Java Guide","category":"Sections"},{"title":"Examples","content":"Examples of different use cases provided by Testcontainers can be found below:","url":"/sourcey/examples.html","tab":"Java Guide","category":"Pages"},{"title":"JUnit 4 Quickstart","content":"It's easy to add Testcontainers to your project - let's walk through a quick example to see how.","url":"/sourcey/quickstart/junit-4-quickstart.html","tab":"Java Guide","category":"Pages"},{"title":"1. Add Testcontainers as a test-scoped dependency","content":"JUnit 4 Quickstart - 1. Add Testcontainers as a test-scoped dependency","url":"/sourcey/quickstart/junit-4-quickstart.html#1-add-testcontainers-as-a-test-scoped-dependency","tab":"Java Guide","category":"Sections"},{"title":"2. Get Testcontainers to run a Redis container during our tests","content":"JUnit 4 Quickstart - 2. Get Testcontainers to run a Redis container during our tests","url":"/sourcey/quickstart/junit-4-quickstart.html#2-get-testcontainers-to-run-a-redis-container-during-our-tests","tab":"Java Guide","category":"Sections"},{"title":"3. Make sure our code can talk to the container","content":"JUnit 4 Quickstart - 3. Make sure our code can talk to the container","url":"/sourcey/quickstart/junit-4-quickstart.html#3-make-sure-our-code-can-talk-to-the-container","tab":"Java Guide","category":"Sections"},{"title":"4. Run the tests!","content":"JUnit 4 Quickstart - 4. Run the tests!","url":"/sourcey/quickstart/junit-4-quickstart.html#4-run-the-tests","tab":"Java Guide","category":"Sections"},{"title":"JUnit 5 Quickstart","content":"It's easy to add Testcontainers to your project - let's walk through a quick example to see how.","url":"/sourcey/quickstart/junit-5-quickstart.html","tab":"Java Guide","category":"Pages"},{"title":"1. Add Testcontainers as a test-scoped dependency","content":"JUnit 5 Quickstart - 1. Add Testcontainers as a test-scoped dependency","url":"/sourcey/quickstart/junit-5-quickstart.html#1-add-testcontainers-as-a-test-scoped-dependency","tab":"Java Guide","category":"Sections"},{"title":"2. Get Testcontainers to run a Redis container during our tests","content":"JUnit 5 Quickstart - 2. Get Testcontainers to run a Redis container during our tests","url":"/sourcey/quickstart/junit-5-quickstart.html#2-get-testcontainers-to-run-a-redis-container-during-our-tests","tab":"Java Guide","category":"Sections"},{"title":"3. Make sure our code can talk to the container","content":"JUnit 5 Quickstart - 3. Make sure our code can talk to the container","url":"/sourcey/quickstart/junit-5-quickstart.html#3-make-sure-our-code-can-talk-to-the-container","tab":"Java Guide","category":"Sections"},{"title":"4. Additional attributes","content":"JUnit 5 Quickstart - 4. Additional attributes","url":"/sourcey/quickstart/junit-5-quickstart.html#4-additional-attributes","tab":"Java Guide","category":"Sections"},{"title":"5. Run the tests!","content":"JUnit 5 Quickstart - 5. Run the tests!","url":"/sourcey/quickstart/junit-5-quickstart.html#5-run-the-tests","tab":"Java Guide","category":"Sections"},{"title":"Spock Quickstart","content":"It's easy to add Testcontainers to your project - let's walk through a quick example to see how.","url":"/sourcey/quickstart/spock-quickstart.html","tab":"Java Guide","category":"Pages"},{"title":"1. Add Testcontainers as a test-scoped dependency","content":"Spock Quickstart - 1. Add Testcontainers as a test-scoped dependency","url":"/sourcey/quickstart/spock-quickstart.html#1-add-testcontainers-as-a-test-scoped-dependency","tab":"Java Guide","category":"Sections"},{"title":"2. Get Testcontainers to run a Redis container during our tests","content":"Spock Quickstart - 2. Get Testcontainers to run a Redis container during our tests","url":"/sourcey/quickstart/spock-quickstart.html#2-get-testcontainers-to-run-a-redis-container-during-our-tests","tab":"Java Guide","category":"Sections"},{"title":"3. Make sure our code can talk to the container","content":"Spock Quickstart - 3. Make sure our code can talk to the container","url":"/sourcey/quickstart/spock-quickstart.html#3-make-sure-our-code-can-talk-to-the-container","tab":"Java Guide","category":"Sections"},{"title":"4. Run the tests!","content":"Spock Quickstart - 4. Run the tests!","url":"/sourcey/quickstart/spock-quickstart.html#4-run-the-tests","tab":"Java Guide","category":"Sections"},{"title":"Creating a container","content":"Testcontainers' generic container support offers the most flexibility, and makes it easy to use virtually any container images as temporary test dependencies. For example, if you might use it to test interactions with:","url":"/sourcey/features/creating-container.html","tab":"Java Guide","category":"Pages"},{"title":"Creating a generic container based on an image","content":"Creating a container - Creating a generic container based on an image","url":"/sourcey/features/creating-container.html#creating-a-generic-container-based-on-an-image","tab":"Java Guide","category":"Sections"},{"title":"Specifying an image","content":"Creating a container - Specifying an image","url":"/sourcey/features/creating-container.html#specifying-an-image","tab":"Java Guide","category":"Sections"},{"title":"Examples","content":"Creating a container - Examples","url":"/sourcey/features/creating-container.html#examples","tab":"Java Guide","category":"Sections"},{"title":"Networking and communicating with containers","content":"It is common to want to connect to a container from your test process, running on the test 'host' machine. For example, you may be testing a class that needs to connect to a backend or data store container.","url":"/sourcey/features/networking.html","tab":"Java Guide","category":"Pages"},{"title":"Exposing container ports to the host","content":"Networking and communicating with containers - Exposing container ports to the host","url":"/sourcey/features/networking.html#exposing-container-ports-to-the-host","tab":"Java Guide","category":"Sections"},{"title":"Getting the container host","content":"Networking and communicating with containers - Getting the container host","url":"/sourcey/features/networking.html#getting-the-container-host","tab":"Java Guide","category":"Sections"},{"title":"Exposing host ports to the container","content":"Networking and communicating with containers - Exposing host ports to the container","url":"/sourcey/features/networking.html#exposing-host-ports-to-the-container","tab":"Java Guide","category":"Sections"},{"title":"Advanced networking","content":"Networking and communicating with containers - Advanced networking","url":"/sourcey/features/networking.html#advanced-networking","tab":"Java Guide","category":"Sections"},{"title":"Executing commands","content":"By default the container will execute whatever command is specified in the image's Dockerfile. To override this, and specify a different command, use `withCommand`. For example:","url":"/sourcey/features/commands.html","tab":"Java Guide","category":"Pages"},{"title":"Container startup command","content":"Executing commands - Container startup command","url":"/sourcey/features/commands.html#container-startup-command","tab":"Java Guide","category":"Sections"},{"title":"Executing a command","content":"Executing commands - Executing a command","url":"/sourcey/features/commands.html#executing-a-command","tab":"Java Guide","category":"Sections"},{"title":"Environment variables","content":"Executing commands - Environment variables","url":"/sourcey/features/commands.html#environment-variables","tab":"Java Guide","category":"Sections"},{"title":"Files and volumes","content":"Files can be copied into the container before startup, or can be copied from the container after the container has started.","url":"/sourcey/features/files.html","tab":"Java Guide","category":"Pages"},{"title":"Copying files","content":"Files and volumes - Copying files","url":"/sourcey/features/files.html#copying-files","tab":"Java Guide","category":"Sections"},{"title":"Copying to a container before startup","content":"Files and volumes - Copying to a container before startup","url":"/sourcey/features/files.html#copying-to-a-container-before-startup","tab":"Java Guide","category":"Sections"},{"title":"Copying a file from a running container","content":"Files and volumes - Copying a file from a running container","url":"/sourcey/features/files.html#copying-a-file-from-a-running-container","tab":"Java Guide","category":"Sections"},{"title":"Waiting for containers to start or be ready","content":"Ordinarily Testcontainers will wait for up to 60 seconds for the container's first mapped network port to start listening.","url":"/sourcey/features/startup-and-waits.html","tab":"Java Guide","category":"Pages"},{"title":"Wait Strategies","content":"Waiting for containers to start or be ready - Wait Strategies","url":"/sourcey/features/startup-and-waits.html#wait-strategies","tab":"Java Guide","category":"Sections"},{"title":"HTTP Wait strategy examples","content":"Waiting for containers to start or be ready - HTTP Wait strategy examples","url":"/sourcey/features/startup-and-waits.html#http-wait-strategy-examples","tab":"Java Guide","category":"Sections"},{"title":"Healthcheck Wait strategy examples","content":"Waiting for containers to start or be ready - Healthcheck Wait strategy examples","url":"/sourcey/features/startup-and-waits.html#healthcheck-wait-strategy-examples","tab":"Java Guide","category":"Sections"},{"title":"Log output Wait Strategy","content":"Waiting for containers to start or be ready - Log output Wait Strategy","url":"/sourcey/features/startup-and-waits.html#log-output-wait-strategy","tab":"Java Guide","category":"Sections"},{"title":"Other Wait Strategies","content":"Waiting for containers to start or be ready - Other Wait Strategies","url":"/sourcey/features/startup-and-waits.html#other-wait-strategies","tab":"Java Guide","category":"Sections"},{"title":"Startup check Strategies","content":"Waiting for containers to start or be ready - Startup check Strategies","url":"/sourcey/features/startup-and-waits.html#startup-check-strategies","tab":"Java Guide","category":"Sections"},{"title":"Running startup strategy example","content":"Waiting for containers to start or be ready - Running startup strategy example","url":"/sourcey/features/startup-and-waits.html#running-startup-strategy-example","tab":"Java Guide","category":"Sections"},{"title":"One shot startup strategy example","content":"Waiting for containers to start or be ready - One shot startup strategy example","url":"/sourcey/features/startup-and-waits.html#one-shot-startup-strategy-example","tab":"Java Guide","category":"Sections"},{"title":"Indefinite one shot startup strategy example","content":"Waiting for containers to start or be ready - Indefinite one shot startup strategy example","url":"/sourcey/features/startup-and-waits.html#indefinite-one-shot-startup-strategy-example","tab":"Java Guide","category":"Sections"},{"title":"Minimum duration startup strategy example","content":"Waiting for containers to start or be ready - Minimum duration startup strategy example","url":"/sourcey/features/startup-and-waits.html#minimum-duration-startup-strategy-example","tab":"Java Guide","category":"Sections"},{"title":"Other startup strategies","content":"Waiting for containers to start or be ready - Other startup strategies","url":"/sourcey/features/startup-and-waits.html#other-startup-strategies","tab":"Java Guide","category":"Sections"},{"title":"Depending on another container","content":"Waiting for containers to start or be ready - Depending on another container","url":"/sourcey/features/startup-and-waits.html#depending-on-another-container","tab":"Java Guide","category":"Sections"},{"title":"Accessing container logs","content":"It is possible to capture container output using:","url":"/sourcey/features/container-logs.html","tab":"Java Guide","category":"Pages"},{"title":"Reading all logs (from startup time to present)","content":"Accessing container logs - Reading all logs (from startup time to present)","url":"/sourcey/features/container-logs.html#reading-all-logs-from-startup-time-to-present","tab":"Java Guide","category":"Sections"},{"title":"Streaming logs","content":"Accessing container logs - Streaming logs","url":"/sourcey/features/container-logs.html#streaming-logs","tab":"Java Guide","category":"Sections"},{"title":"Streaming container output to an SLF4J logger","content":"Accessing container logs - Streaming container output to an SLF4J logger","url":"/sourcey/features/container-logs.html#streaming-container-output-to-an-slf4j-logger","tab":"Java Guide","category":"Sections"},{"title":"Capturing container output as a String","content":"Accessing container logs - Capturing container output as a String","url":"/sourcey/features/container-logs.html#capturing-container-output-as-a-string","tab":"Java Guide","category":"Sections"},{"title":"Waiting for container output to contain expected content","content":"Accessing container logs - Waiting for container output to contain expected content","url":"/sourcey/features/container-logs.html#waiting-for-container-output-to-contain-expected-content","tab":"Java Guide","category":"Sections"},{"title":"Creating images on-the-fly","content":"In situations where there is no pre-existing Docker image, Testcontainers can create a new temporary image on-the-fly from a Dockerfile. For example, when the component under test is the Docker image itself, or when an existing base image needs to be customized for specific test(s).","url":"/sourcey/features/creating-images.html","tab":"Java Guide","category":"Pages"},{"title":"Overview","content":"Creating images on-the-fly - Overview","url":"/sourcey/features/creating-images.html#overview","tab":"Java Guide","category":"Sections"},{"title":"Dockerfile from String, file or classpath resource","content":"Creating images on-the-fly - Dockerfile from String, file or classpath resource","url":"/sourcey/features/creating-images.html#dockerfile-from-string-file-or-classpath-resource","tab":"Java Guide","category":"Sections"},{"title":"Dockerfile DSL","content":"Creating images on-the-fly - Dockerfile DSL","url":"/sourcey/features/creating-images.html#dockerfile-dsl","tab":"Java Guide","category":"Sections"},{"title":"Automatic deletion","content":"Creating images on-the-fly - Automatic deletion","url":"/sourcey/features/creating-images.html#automatic-deletion","tab":"Java Guide","category":"Sections"},{"title":"Alternative Dockerfiles","content":"Creating images on-the-fly - Alternative Dockerfiles","url":"/sourcey/features/creating-images.html#alternative-dockerfiles","tab":"Java Guide","category":"Sections"},{"title":"Build Args","content":"Creating images on-the-fly - Build Args","url":"/sourcey/features/creating-images.html#build-args","tab":"Java Guide","category":"Sections"},{"title":"Custom configuration","content":"You can override some default properties if your environment requires that.","url":"/sourcey/features/configuration.html","tab":"Java Guide","category":"Pages"},{"title":"Configuration locations","content":"Custom configuration - Configuration locations","url":"/sourcey/features/configuration.html#configuration-locations","tab":"Java Guide","category":"Sections"},{"title":"Disabling the startup checks","content":"Custom configuration - Disabling the startup checks","url":"/sourcey/features/configuration.html#disabling-the-startup-checks","tab":"Java Guide","category":"Sections"},{"title":"Customizing images","content":"Custom configuration - Customizing images","url":"/sourcey/features/configuration.html#customizing-images","tab":"Java Guide","category":"Sections"},{"title":"Customizing Ryuk resource reaper","content":"Custom configuration - Customizing Ryuk resource reaper","url":"/sourcey/features/configuration.html#customizing-ryuk-resource-reaper","tab":"Java Guide","category":"Sections"},{"title":"Disabling Ryuk","content":"Custom configuration - Disabling Ryuk","url":"/sourcey/features/configuration.html#disabling-ryuk","tab":"Java Guide","category":"Sections"},{"title":"Customizing image pull behaviour","content":"Custom configuration - Customizing image pull behaviour","url":"/sourcey/features/configuration.html#customizing-image-pull-behaviour","tab":"Java Guide","category":"Sections"},{"title":"Customizing client ping behaviour","content":"Custom configuration - Customizing client ping behaviour","url":"/sourcey/features/configuration.html#customizing-client-ping-behaviour","tab":"Java Guide","category":"Sections"},{"title":"Customizing Docker host detection","content":"Custom configuration - Customizing Docker host detection","url":"/sourcey/features/configuration.html#customizing-docker-host-detection","tab":"Java Guide","category":"Sections"},{"title":"Image name substitution","content":"Testcontainers supports automatic substitution of Docker image names.","url":"/sourcey/features/image-name-substitution.html","tab":"Java Guide","category":"Pages"},{"title":"Manual substitution","content":"Image name substitution - Manual substitution","url":"/sourcey/features/image-name-substitution.html#manual-substitution","tab":"Java Guide","category":"Sections"},{"title":"Automatically modifying Docker Hub image names","content":"Image name substitution - Automatically modifying Docker Hub image names","url":"/sourcey/features/image-name-substitution.html#automatically-modifying-docker-hub-image-names","tab":"Java Guide","category":"Sections"},{"title":"Developing a custom function for transforming image names on the fly","content":"Image name substitution - Developing a custom function for transforming image names on the fly","url":"/sourcey/features/image-name-substitution.html#developing-a-custom-function-for-transforming-image-names-on-the-fly","tab":"Java Guide","category":"Sections"},{"title":"Overriding image names individually in configuration","content":"Image name substitution - Overriding image names individually in configuration","url":"/sourcey/features/image-name-substitution.html#overriding-image-names-individually-in-configuration","tab":"Java Guide","category":"Sections"},{"title":"Advanced options","content":"To add a custom label to the container, use `withLabel`:","url":"/sourcey/features/advanced-options.html","tab":"Java Guide","category":"Pages"},{"title":"Container labels","content":"Advanced options - Container labels","url":"/sourcey/features/advanced-options.html#container-labels","tab":"Java Guide","category":"Sections"},{"title":"Image Pull Policy","content":"Advanced options - Image Pull Policy","url":"/sourcey/features/advanced-options.html#image-pull-policy","tab":"Java Guide","category":"Sections"},{"title":"Customizing the container","content":"Advanced options - Customizing the container","url":"/sourcey/features/advanced-options.html#customizing-the-container","tab":"Java Guide","category":"Sections"},{"title":"Using docker-java","content":"Advanced options - Using docker-java","url":"/sourcey/features/advanced-options.html#using-docker-java","tab":"Java Guide","category":"Sections"},{"title":"Using CreateContainerCmdModifier","content":"Advanced options - Using CreateContainerCmdModifier","url":"/sourcey/features/advanced-options.html#using-createcontainercmdmodifier","tab":"Java Guide","category":"Sections"},{"title":"Parallel Container Startup","content":"Advanced options - Parallel Container Startup","url":"/sourcey/features/advanced-options.html#parallel-container-startup","tab":"Java Guide","category":"Sections"},{"title":"Reusable Containers (Experimental)","content":"The *Reusable* feature keeps the containers running and next executions with the same container configuration will reuse it. To use it, start the container manually by calling `start()` method, do not call `stop()` method directly or indirectly via `try-with-resources` or `JUnit integration`, and enable it manually through an opt-in mechanism per environment. To reuse a container, the container configuration **must be the same**.","url":"/sourcey/features/reuse.html","tab":"Java Guide","category":"Pages"},{"title":"How to use it","content":"Reusable Containers (Experimental) - How to use it","url":"/sourcey/features/reuse.html#how-to-use-it","tab":"Java Guide","category":"Sections"},{"title":"Reusable Container with Testcontainers JDBC URL","content":"Reusable Containers (Experimental) - Reusable Container with Testcontainers JDBC URL","url":"/sourcey/features/reuse.html#reusable-container-with-testcontainers-jdbc-url","tab":"Java Guide","category":"Sections"},{"title":"Database containers","content":"You might want to use Testcontainers' database support:","url":"/sourcey/modules/databases/","tab":"Java Guide","category":"Pages"},{"title":"Overview","content":"Database containers - Overview","url":"/sourcey/modules/databases/#overview","tab":"Java Guide","category":"Sections"},{"title":"JDBC support","content":"You can obtain a temporary database in one of two ways:","url":"/sourcey/modules/databases/jdbc.html","tab":"Java Guide","category":"Pages"},{"title":"Database containers launched via JDBC URL scheme","content":"JDBC support - Database containers launched via JDBC URL scheme","url":"/sourcey/modules/databases/jdbc.html#database-containers-launched-via-jdbc-url-scheme","tab":"Java Guide","category":"Sections"},{"title":"JDBC URL examples","content":"JDBC support - JDBC URL examples","url":"/sourcey/modules/databases/jdbc.html#jdbc-url-examples","tab":"Java Guide","category":"Sections"},{"title":"Using a classpath init script","content":"JDBC support - Using a classpath init script","url":"/sourcey/modules/databases/jdbc.html#using-a-classpath-init-script","tab":"Java Guide","category":"Sections"},{"title":"Using an init script from a file","content":"JDBC support - Using an init script from a file","url":"/sourcey/modules/databases/jdbc.html#using-an-init-script-from-a-file","tab":"Java Guide","category":"Sections"},{"title":"Using an init function","content":"JDBC support - Using an init function","url":"/sourcey/modules/databases/jdbc.html#using-an-init-function","tab":"Java Guide","category":"Sections"},{"title":"Running container in daemon mode","content":"JDBC support - Running container in daemon mode","url":"/sourcey/modules/databases/jdbc.html#running-container-in-daemon-mode","tab":"Java Guide","category":"Sections"},{"title":"Running container with tmpfs options","content":"JDBC support - Running container with tmpfs options","url":"/sourcey/modules/databases/jdbc.html#running-container-with-tmpfs-options","tab":"Java Guide","category":"Sections"},{"title":"Database container objects","content":"JDBC support - Database container objects","url":"/sourcey/modules/databases/jdbc.html#database-container-objects","tab":"Java Guide","category":"Sections"},{"title":"Postgres Module","content":"Testcontainers module for PostgresSQL","url":"/sourcey/modules/databases/postgres.html","tab":"Java Guide","category":"Pages"},{"title":"Usage example","content":"Postgres Module - Usage example","url":"/sourcey/modules/databases/postgres.html#usage-example","tab":"Java Guide","category":"Sections"},{"title":"Testcontainers JDBC URL","content":"Postgres Module - Testcontainers JDBC URL","url":"/sourcey/modules/databases/postgres.html#testcontainers-jdbc-url","tab":"Java Guide","category":"Sections"},{"title":"Compatible images","content":"Postgres Module - Compatible images","url":"/sourcey/modules/databases/postgres.html#compatible-images","tab":"Java Guide","category":"Sections"},{"title":"Adding this module to your project dependencies","content":"Postgres Module - Adding this module to your project dependencies","url":"/sourcey/modules/databases/postgres.html#adding-this-module-to-your-project-dependencies","tab":"Java Guide","category":"Sections"},{"title":"MySQL Module","content":"Testcontainers module for MySQL","url":"/sourcey/modules/databases/mysql.html","tab":"Java Guide","category":"Pages"},{"title":"Usage example","content":"MySQL Module - Usage example","url":"/sourcey/modules/databases/mysql.html#usage-example","tab":"Java Guide","category":"Sections"},{"title":"Testcontainers JDBC URL","content":"MySQL Module - Testcontainers JDBC URL","url":"/sourcey/modules/databases/mysql.html#testcontainers-jdbc-url","tab":"Java Guide","category":"Sections"},{"title":"Overriding MySQL my.cnf settings","content":"MySQL Module - Overriding MySQL my.cnf settings","url":"/sourcey/modules/databases/mysql.html#overriding-mysql-my-cnf-settings","tab":"Java Guide","category":"Sections"},{"title":"MySQL root user password","content":"MySQL Module - MySQL root user password","url":"/sourcey/modules/databases/mysql.html#mysql-root-user-password","tab":"Java Guide","category":"Sections"},{"title":"Adding this module to your project dependencies","content":"MySQL Module - Adding this module to your project dependencies","url":"/sourcey/modules/databases/mysql.html#adding-this-module-to-your-project-dependencies","tab":"Java Guide","category":"Sections"},{"title":"MongoDB Module","content":"The MongoDB module provides two Testcontainers for MongoDB unit testing:","url":"/sourcey/modules/databases/mongodb.html","tab":"Java Guide","category":"Pages"},{"title":"MongoDBContainer","content":"MongoDB Module - MongoDBContainer","url":"/sourcey/modules/databases/mongodb.html#mongodbcontainer","tab":"Java Guide","category":"Sections"},{"title":"Usage example","content":"MongoDB Module - Usage example","url":"/sourcey/modules/databases/mongodb.html#usage-example","tab":"Java Guide","category":"Sections"},{"title":"MongoDBAtlasLocalContainer","content":"MongoDB Module - MongoDBAtlasLocalContainer","url":"/sourcey/modules/databases/mongodb.html#mongodbatlaslocalcontainer","tab":"Java Guide","category":"Sections"},{"title":"Usage example","content":"MongoDB Module - Usage example","url":"/sourcey/modules/databases/mongodb.html#usage-example","tab":"Java Guide","category":"Sections"},{"title":"References","content":"MongoDB Module - References","url":"/sourcey/modules/databases/mongodb.html#references","tab":"Java Guide","category":"Sections"},{"title":"Adding this module to your project dependencies","content":"MongoDB Module - Adding this module to your project dependencies","url":"/sourcey/modules/databases/mongodb.html#adding-this-module-to-your-project-dependencies","tab":"Java Guide","category":"Sections"},{"title":"Kafka Module","content":"Testcontainers can be used to automatically instantiate and manage Apache Kafka containers.","url":"/sourcey/modules/kafka.html","tab":"Java Guide","category":"Pages"},{"title":"Benefits","content":"Kafka Module - Benefits","url":"/sourcey/modules/kafka.html#benefits","tab":"Java Guide","category":"Sections"},{"title":"Example","content":"Kafka Module - Example","url":"/sourcey/modules/kafka.html#example","tab":"Java Guide","category":"Sections"},{"title":"Using org.testcontainers.kafka.KafkaContainer","content":"Kafka Module - Using org.testcontainers.kafka.KafkaContainer","url":"/sourcey/modules/kafka.html#using-org-testcontainers-kafka-kafkacontainer","tab":"Java Guide","category":"Sections"},{"title":"Using org.testcontainers.kafka.ConfluentKafkaContainer","content":"Kafka Module - Using org.testcontainers.kafka.ConfluentKafkaContainer","url":"/sourcey/modules/kafka.html#using-org-testcontainers-kafka-confluentkafkacontainer","tab":"Java Guide","category":"Sections"},{"title":"Options","content":"Kafka Module - Options","url":"/sourcey/modules/kafka.html#options","tab":"Java Guide","category":"Sections"},{"title":"Using Kraft mode","content":"Kafka Module - Using Kraft mode","url":"/sourcey/modules/kafka.html#using-kraft-mode","tab":"Java Guide","category":"Sections"},{"title":"Register listeners","content":"Kafka Module - Register listeners","url":"/sourcey/modules/kafka.html#register-listeners","tab":"Java Guide","category":"Sections"},{"title":"Adding this module to your project dependencies","content":"Kafka Module - Adding this module to your project dependencies","url":"/sourcey/modules/kafka.html#adding-this-module-to-your-project-dependencies","tab":"Java Guide","category":"Sections"},{"title":"LocalStack Module","content":"Testcontainers module for LocalStack, 'a fully functional local AWS cloud stack', to develop and test your cloud and serverless apps without actually using the cloud.","url":"/sourcey/modules/localstack.html","tab":"Java Guide","category":"Pages"},{"title":"Usage example","content":"LocalStack Module - Usage example","url":"/sourcey/modules/localstack.html#usage-example","tab":"Java Guide","category":"Sections"},{"title":"Creating a client using AWS SDK","content":"LocalStack Module - Creating a client using AWS SDK","url":"/sourcey/modules/localstack.html#creating-a-client-using-aws-sdk","tab":"Java Guide","category":"Sections"},{"title":"Adding this module to your project dependencies","content":"LocalStack Module - Adding this module to your project dependencies","url":"/sourcey/modules/localstack.html#adding-this-module-to-your-project-dependencies","tab":"Java Guide","category":"Sections"},{"title":"Toxiproxy Module","content":"Testcontainers module for Shopify's Toxiproxy. This TCP proxy can be used to simulate network failure conditions.","url":"/sourcey/modules/toxiproxy.html","tab":"Java Guide","category":"Pages"},{"title":"Usage example","content":"Toxiproxy Module - Usage example","url":"/sourcey/modules/toxiproxy.html#usage-example","tab":"Java Guide","category":"Sections"},{"title":"Adding this module to your project dependencies","content":"Toxiproxy Module - Adding this module to your project dependencies","url":"/sourcey/modules/toxiproxy.html#adding-this-module-to-your-project-dependencies","tab":"Java Guide","category":"Sections"},{"title":"Acknowledgements","content":"Toxiproxy Module - Acknowledgements","url":"/sourcey/modules/toxiproxy.html#acknowledgements","tab":"Java Guide","category":"Sections"},{"title":"Jupiter / JUnit 5","content":"This module provides an API that is based on the JUnit Jupiter extension model.","url":"/sourcey/test-framework-integration/junit-5.html","tab":"Java Guide","category":"Pages"},{"title":"Extension","content":"Jupiter / JUnit 5 - Extension","url":"/sourcey/test-framework-integration/junit-5.html#extension","tab":"Java Guide","category":"Sections"},{"title":"Examples","content":"Jupiter / JUnit 5 - Examples","url":"/sourcey/test-framework-integration/junit-5.html#examples","tab":"Java Guide","category":"Sections"},{"title":"Restarted containers","content":"Jupiter / JUnit 5 - Restarted containers","url":"/sourcey/test-framework-integration/junit-5.html#restarted-containers","tab":"Java Guide","category":"Sections"},{"title":"Shared containers","content":"Jupiter / JUnit 5 - Shared containers","url":"/sourcey/test-framework-integration/junit-5.html#shared-containers","tab":"Java Guide","category":"Sections"},{"title":"Singleton containers","content":"Jupiter / JUnit 5 - Singleton containers","url":"/sourcey/test-framework-integration/junit-5.html#singleton-containers","tab":"Java Guide","category":"Sections"},{"title":"Limitations","content":"Jupiter / JUnit 5 - Limitations","url":"/sourcey/test-framework-integration/junit-5.html#limitations","tab":"Java Guide","category":"Sections"},{"title":"Adding Testcontainers JUnit 5 support to your project dependencies","content":"Jupiter / JUnit 5 - Adding Testcontainers JUnit 5 support to your project dependencies","url":"/sourcey/test-framework-integration/junit-5.html#adding-testcontainers-junit-5-support-to-your-project-dependencies","tab":"Java Guide","category":"Sections"}] \ No newline at end of file diff --git a/docs/sourcey/sitemap.xml b/docs/sourcey/sitemap.xml new file mode 100644 index 00000000000..c006c8f47f9 --- /dev/null +++ b/docs/sourcey/sitemap.xml @@ -0,0 +1,29 @@ + + + + https://java.testcontainers.org/sourcey/ + https://java.testcontainers.org/sourcey/examples.html + https://java.testcontainers.org/sourcey/quickstart/junit-4-quickstart.html + https://java.testcontainers.org/sourcey/quickstart/junit-5-quickstart.html + https://java.testcontainers.org/sourcey/quickstart/spock-quickstart.html + https://java.testcontainers.org/sourcey/features/creating-container.html + https://java.testcontainers.org/sourcey/features/networking.html + https://java.testcontainers.org/sourcey/features/commands.html + https://java.testcontainers.org/sourcey/features/files.html + https://java.testcontainers.org/sourcey/features/startup-and-waits.html + https://java.testcontainers.org/sourcey/features/container-logs.html + https://java.testcontainers.org/sourcey/features/creating-images.html + https://java.testcontainers.org/sourcey/features/configuration.html + https://java.testcontainers.org/sourcey/features/image-name-substitution.html + https://java.testcontainers.org/sourcey/features/advanced-options.html + https://java.testcontainers.org/sourcey/features/reuse.html + https://java.testcontainers.org/sourcey/modules/databases/ + https://java.testcontainers.org/sourcey/modules/databases/jdbc.html + https://java.testcontainers.org/sourcey/modules/databases/postgres.html + https://java.testcontainers.org/sourcey/modules/databases/mysql.html + https://java.testcontainers.org/sourcey/modules/databases/mongodb.html + https://java.testcontainers.org/sourcey/modules/kafka.html + https://java.testcontainers.org/sourcey/modules/localstack.html + https://java.testcontainers.org/sourcey/modules/toxiproxy.html + https://java.testcontainers.org/sourcey/test-framework-integration/junit-5.html + \ No newline at end of file diff --git a/docs/sourcey/sourcey.css b/docs/sourcey/sourcey.css new file mode 100644 index 00000000000..d275553db22 --- /dev/null +++ b/docs/sourcey/sourcey.css @@ -0,0 +1,2210 @@ +/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */ +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:"Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--font-mono:ui-monospace, "SF Mono", "Cascadia Code", Consolas, "Liberation Mono", Menlo, monospace;--color-red-100:oklch(93.6% .032 17.717);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-600:oklch(57.7% .245 27.325);--color-red-800:oklch(44.4% .177 26.899);--color-orange-100:oklch(95.4% .038 75.164);--color-orange-300:oklch(83.7% .128 66.29);--color-orange-400:oklch(75% .183 55.934);--color-orange-900:oklch(40.8% .123 38.172);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-800:oklch(47.3% .137 46.201);--color-amber-900:oklch(41.4% .112 45.904);--color-yellow-300:oklch(90.5% .182 98.111);--color-yellow-400:oklch(85.2% .199 91.936);--color-green-100:oklch(96.2% .044 156.743);--color-green-300:oklch(87.1% .15 154.449);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-emerald-50:oklch(97.9% .021 166.113);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-500:oklch(69.6% .17 162.48);--color-emerald-900:oklch(37.8% .077 168.94);--color-blue-100:oklch(93.2% .032 255.585);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-indigo-300:oklch(78.5% .115 274.713);--color-indigo-600:oklch(51.1% .262 276.966);--color-purple-400:oklch(71.4% .203 305.504);--color-purple-500:oklch(62.7% .265 303.9);--color-purple-700:oklch(49.6% .265 301.924);--color-rose-50:oklch(96.9% .015 12.422);--color-rose-100:oklch(94.1% .03 12.58);--color-rose-200:oklch(89.2% .058 10.001);--color-rose-300:oklch(81% .117 11.638);--color-rose-600:oklch(58.6% .253 17.585);--color-rose-800:oklch(45.5% .188 13.697);--color-rose-900:oklch(41% .159 10.272);--color-gray-50:245 245 250;--color-gray-100:241 241 245;--color-gray-200:225 225 229;--color-gray-300:209 209 213;--color-gray-400:161 161 165;--color-gray-500:115 115 119;--color-gray-600:83 83 87;--color-gray-700:65 65 69;--color-gray-800:40 40 44;--color-gray-900:25 25 30;--color-gray-950:13 13 17;--color-stone-50:250 250 249;--color-stone-100:245 245 244;--color-stone-200:231 229 228;--color-stone-400:168 162 158;--color-stone-500:120 113 108;--color-stone-600:87 83 78;--color-stone-700:68 64 60;--color-stone-900:oklch(21.6% .006 56.043);--color-stone-950:12 10 9;--color-white:#fff;--spacing:.25rem;--container-2xl:42rem;--container-3xl:48rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--leading-tight:1.25;--radius-md:.375rem;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--radius:.75rem;--sidebar-width:18rem;--header-height:7rem;--toc-width:19rem;--toc-inner-width:16.5rem;--content-padding:2.5rem;--content-max-width:44rem;--color-primary:99 102 241;--color-primary-light:129 140 248;--color-primary-dark:79 70 229;--color-primary-ink:79 70 229;--color-background-light:255 255 255;--color-background-dark:11 12 16;--color-code-block-light:255 255 255;--color-code-block-dark:11 12 14;--color-success:34 197 94;--color-overlay:0 0 0;--color-border-dark-subtle:255 255 255;--color-surface-dark-tint:255 255 255;--method-get:#16a34a;--method-post:#2563eb;--method-put:#d97706;--method-delete:#dc2626;--method-patch:#9333ea;--method-tool:#9333ea;--method-resource:#16a34a;--method-prompt:#2563eb}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:0}.top-0{top:0}.top-3{top:calc(var(--spacing) * 3)}.top-\[2\.75rem\]{top:2.75rem}.top-full{top:100%}.right-0{right:0}.right-4{right:calc(var(--spacing) * 4)}.right-auto{right:auto}.bottom-0{bottom:0}.left-0{left:0}.left-1\/2{left:50%}.-z-10{z-index:calc(10 * -1)}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-\[21\]{z-index:21}.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.mx-4{margin-inline:calc(var(--spacing) * 4)}.mx-auto{margin-inline:auto}.my-1{margin-block:var(--spacing)}.prose{color:var(--tw-prose-body);max-width:65ch}.prose :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em}.prose :where([class~=lead]):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-lead);margin-top:1.2em;margin-bottom:1.2em;font-size:1.25em;line-height:1.6}.prose :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-links);font-weight:500;text-decoration:underline}.prose :where(strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-bold);font-weight:600}.prose :where(a strong):not(:where([class~=not-prose],[class~=not-prose] *)),.prose :where(blockquote strong):not(:where([class~=not-prose],[class~=not-prose] *)),.prose :where(thead th strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em;padding-inline-start:1.625em;list-style-type:decimal}.prose :where(ol[type=A]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=A s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=I]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type=I s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type="1"]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal}.prose :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em;padding-inline-start:1.625em;list-style-type:disc}.prose :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-counters);font-weight:400}.prose :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-bullets)}.prose :where(dt):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);margin-top:1.25em;font-weight:600}.prose :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:var(--tw-prose-hr);border-top-width:1px;margin-top:3em;margin-bottom:3em}.prose :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-quotes);border-inline-start-width:.25rem;border-inline-start-color:var(--tw-prose-quote-borders);quotes:"“""”""‘""’";margin-top:1.6em;margin-bottom:1.6em;padding-inline-start:1em;font-style:italic;font-weight:500}.prose :where(blockquote p:first-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:open-quote}.prose :where(blockquote p:last-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:close-quote}.prose :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);margin-top:0;margin-bottom:.888889em;font-size:2.25em;font-weight:800;line-height:1.11111}.prose :where(h1 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:900}.prose :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);margin-top:2em;margin-bottom:1em;font-size:1.5em;font-weight:700;line-height:1.33333}.prose :where(h2 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:800}.prose :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);margin-top:1.6em;margin-bottom:.6em;font-size:1.25em;font-weight:600;line-height:1.6}.prose :where(h3 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:700}.prose :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);margin-top:1.5em;margin-bottom:.5em;font-weight:600;line-height:1.5}.prose :where(h4 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:700}.prose :where(img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em;display:block}.prose :where(video):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-kbd);box-shadow:0 0 0 1px var(--tw-prose-kbd-shadows), 0 3px 0 var(--tw-prose-kbd-shadows);padding-top:.1875em;padding-inline-end:.375em;padding-bottom:.1875em;border-radius:.3125rem;padding-inline-start:.375em;font-family:inherit;font-size:.875em;font-weight:500}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-code);font-size:.875em;font-weight:600}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):before,.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:"`"}.prose :where(a code):not(:where([class~=not-prose],[class~=not-prose] *)),.prose :where(h1 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(h2 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.875em}.prose :where(h3 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.9em}.prose :where(h4 code):not(:where([class~=not-prose],[class~=not-prose] *)),.prose :where(blockquote code):not(:where([class~=not-prose],[class~=not-prose] *)),.prose :where(thead th code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(pre):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-pre-code);background-color:var(--tw-prose-pre-bg);padding-top:.857143em;padding-inline-end:1.14286em;padding-bottom:.857143em;border-radius:.375rem;margin-top:1.71429em;margin-bottom:1.71429em;padding-inline-start:1.14286em;font-size:.875em;font-weight:400;line-height:1.71429;overflow-x:auto}.prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:inherit;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit;background-color:#0000;border-width:0;border-radius:0;padding:0}.prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):before,.prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:none}.prose :where(table):not(:where([class~=not-prose],[class~=not-prose] *)){table-layout:auto;width:100%;margin-top:2em;margin-bottom:2em;font-size:.875em;line-height:1.71429}.prose :where(thead):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-th-borders)}.prose :where(thead th):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);vertical-align:bottom;padding-inline-end:.571429em;padding-bottom:.571429em;padding-inline-start:.571429em;font-weight:600}.prose :where(tbody tr):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-td-borders)}.prose :where(tbody tr:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:0}.prose :where(tbody td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:baseline}.prose :where(tfoot):not(:where([class~=not-prose],[class~=not-prose] *)){border-top-width:1px;border-top-color:var(--tw-prose-th-borders)}.prose :where(tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:top}.prose :where(th,td):not(:where([class~=not-prose],[class~=not-prose] *)){text-align:start}.prose :where(figure>*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:0}.prose :where(figcaption):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-captions);margin-top:.857143em;font-size:.875em;line-height:1.42857}.prose{--tw-prose-body:oklch(37.3% .034 259.733);--tw-prose-headings:oklch(21% .034 264.665);--tw-prose-lead:oklch(44.6% .03 256.802);--tw-prose-links:oklch(21% .034 264.665);--tw-prose-bold:oklch(21% .034 264.665);--tw-prose-counters:oklch(55.1% .027 264.364);--tw-prose-bullets:oklch(87.2% .01 258.338);--tw-prose-hr:oklch(92.8% .006 264.531);--tw-prose-quotes:oklch(21% .034 264.665);--tw-prose-quote-borders:oklch(92.8% .006 264.531);--tw-prose-captions:oklch(55.1% .027 264.364);--tw-prose-kbd:oklch(21% .034 264.665);--tw-prose-kbd-shadows:oklab(21% -.00316127 -.0338527/.1);--tw-prose-code:oklch(21% .034 264.665);--tw-prose-pre-code:oklch(92.8% .006 264.531);--tw-prose-pre-bg:oklch(27.8% .033 256.848);--tw-prose-th-borders:oklch(87.2% .01 258.338);--tw-prose-td-borders:oklch(92.8% .006 264.531);--tw-prose-invert-body:oklch(87.2% .01 258.338);--tw-prose-invert-headings:#fff;--tw-prose-invert-lead:oklch(70.7% .022 261.325);--tw-prose-invert-links:#fff;--tw-prose-invert-bold:#fff;--tw-prose-invert-counters:oklch(70.7% .022 261.325);--tw-prose-invert-bullets:oklch(44.6% .03 256.802);--tw-prose-invert-hr:oklch(37.3% .034 259.733);--tw-prose-invert-quotes:oklch(96.7% .003 264.542);--tw-prose-invert-quote-borders:oklch(37.3% .034 259.733);--tw-prose-invert-captions:oklch(70.7% .022 261.325);--tw-prose-invert-kbd:#fff;--tw-prose-invert-kbd-shadows:#ffffff1a;--tw-prose-invert-code:#fff;--tw-prose-invert-pre-code:oklch(87.2% .01 258.338);--tw-prose-invert-pre-bg:#00000080;--tw-prose-invert-th-borders:oklch(44.6% .03 256.802);--tw-prose-invert-td-borders:oklch(37.3% .034 259.733);font-size:1rem;line-height:1.75}.prose :where(picture>img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:0}.prose :where(li):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.5em;margin-bottom:.5em}.prose :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *)),.prose :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.375em}.prose :where(.prose>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.prose :where(.prose>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose :where(.prose>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.prose :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em}.prose :where(dd):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.5em;padding-inline-start:1.625em}.prose :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)),.prose :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)),.prose :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)),.prose :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(thead th:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.prose :where(thead th:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.prose :where(tbody td,tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){padding-top:.571429em;padding-inline-end:.571429em;padding-bottom:.571429em;padding-inline-start:.571429em}.prose :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.prose :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.prose :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(.prose>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(.prose>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}.prose-sm{font-size:.875rem;line-height:1.71429}.prose-sm :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.14286em;margin-bottom:1.14286em}.prose-sm :where([class~=lead]):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.888889em;margin-bottom:.888889em;font-size:1.28571em;line-height:1.55556}.prose-sm :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.33333em;margin-bottom:1.33333em;padding-inline-start:1.11111em}.prose-sm :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:.8em;font-size:2.14286em;line-height:1.2}.prose-sm :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.6em;margin-bottom:.8em;font-size:1.42857em;line-height:1.4}.prose-sm :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.55556em;margin-bottom:.444444em;font-size:1.28571em;line-height:1.55556}.prose-sm :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.42857em;margin-bottom:.571429em;line-height:1.42857}.prose-sm :where(img):not(:where([class~=not-prose],[class~=not-prose] *)),.prose-sm :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.71429em;margin-bottom:1.71429em}.prose-sm :where(picture>img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:0}.prose-sm :where(video):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.71429em;margin-bottom:1.71429em}.prose-sm :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){padding-top:.142857em;padding-inline-end:.357143em;padding-bottom:.142857em;border-radius:.3125rem;padding-inline-start:.357143em;font-size:.857143em}.prose-sm :where(code):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.857143em}.prose-sm :where(h2 code):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.9em}.prose-sm :where(h3 code):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.888889em}.prose-sm :where(pre):not(:where([class~=not-prose],[class~=not-prose] *)){padding-top:.666667em;padding-inline-end:1em;padding-bottom:.666667em;border-radius:.25rem;margin-top:1.66667em;margin-bottom:1.66667em;padding-inline-start:1em;font-size:.857143em;line-height:1.66667}.prose-sm :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)),.prose-sm :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.14286em;margin-bottom:1.14286em;padding-inline-start:1.57143em}.prose-sm :where(li):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.285714em;margin-bottom:.285714em}.prose-sm :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *)),.prose-sm :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.428571em}.prose-sm :where(.prose-sm>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.571429em;margin-bottom:.571429em}.prose-sm :where(.prose-sm>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.14286em}.prose-sm :where(.prose-sm>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.14286em}.prose-sm :where(.prose-sm>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.14286em}.prose-sm :where(.prose-sm>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.14286em}.prose-sm :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.571429em;margin-bottom:.571429em}.prose-sm :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.14286em;margin-bottom:1.14286em}.prose-sm :where(dt):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.14286em}.prose-sm :where(dd):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.285714em;padding-inline-start:1.57143em}.prose-sm :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2.85714em;margin-bottom:2.85714em}.prose-sm :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)),.prose-sm :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)),.prose-sm :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)),.prose-sm :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose-sm :where(table):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.857143em;line-height:1.5}.prose-sm :where(thead th):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:1em;padding-bottom:.666667em;padding-inline-start:1em}.prose-sm :where(thead th:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.prose-sm :where(thead th:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.prose-sm :where(tbody td,tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){padding-top:.666667em;padding-inline-end:1em;padding-bottom:.666667em;padding-inline-start:1em}.prose-sm :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.prose-sm :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.prose-sm :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.71429em;margin-bottom:1.71429em}.prose-sm :where(figure>*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:0}.prose-sm :where(figcaption):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.666667em;font-size:.857143em;line-height:1.33333}.prose-sm :where(.prose-sm>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose-sm :where(.prose-sm>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}.-mt-10{margin-top:calc(var(--spacing) * -10)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-6{margin-top:calc(var(--spacing) * 6)}.mt-8{margin-top:calc(var(--spacing) * 8)}.mt-12{margin-top:calc(var(--spacing) * 12)}.mt-16{margin-top:calc(var(--spacing) * 16)}.mr-0\.5{margin-right:calc(var(--spacing) * .5)}.mb-0{margin-bottom:0}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.mb-14{margin-bottom:calc(var(--spacing) * 14)}.ml-1{margin-left:var(--spacing)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-auto{margin-left:auto}.box-border{box-sizing:border-box}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.size-3\.5{width:calc(var(--spacing) * 3.5);height:calc(var(--spacing) * 3.5)}.size-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.size-\[26px\]{width:26px;height:26px}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-3{height:calc(var(--spacing) * 3)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-12{height:calc(var(--spacing) * 12)}.h-14{height:calc(var(--spacing) * 14)}.h-16{height:calc(var(--spacing) * 16)}.h-\[1\.5px\]{height:1.5px}.h-\[1lh\]{height:1lh}.h-full{height:100%}.max-h-full{max-height:100%}.w-1\.5{width:calc(var(--spacing) * 1.5)}.w-3{width:calc(var(--spacing) * 3)}.w-4{width:calc(var(--spacing) * 4)}.w-5{width:calc(var(--spacing) * 5)}.w-8{width:calc(var(--spacing) * 8)}.w-\[16\.5rem\]{width:16.5rem}.w-\[18rem\]{width:18rem}.w-\[19rem\]{width:19rem}.w-\[28rem\]{width:28rem}.w-auto{width:auto}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-3xl{max-width:var(--container-3xl)}.max-w-\[28rem\]{max-width:28rem}.max-w-\[92rem\]{max-width:92rem}.max-w-none{max-width:none}.min-w-0{min-width:0}.min-w-4{min-width:calc(var(--spacing) * 4)}.min-w-\[42px\]{min-width:42px}.min-w-\[43px\]{min-width:43px}.min-w-\[120px\]{min-width:120px}.min-w-\[140px\]{min-width:140px}.flex-1{flex:1}.flex-none{flex:none}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.-translate-x-1\/2{--tw-translate-x:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-pointer{cursor:pointer}.flex-col{flex-direction:column}.flex-row-reverse{flex-direction:row-reverse}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-8{gap:calc(var(--spacing) * 8)}.gap-12{gap:calc(var(--spacing) * 12)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2.5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-px>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(1px * var(--tw-space-y-reverse));margin-block-end:calc(1px * calc(1 - var(--tw-space-y-reverse)))}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-x-6{column-gap:calc(var(--spacing) * 6)}:where(.space-x-2\.5>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 2.5) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 2.5) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-6>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 6) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-x-reverse)))}.gap-y-1{row-gap:var(--spacing)}.self-start{align-self:flex-start}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded-\[var\(--radius\)\]{border-radius:var(--radius)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-\[rgb\(var\(--color-gray-100\)\)\]{border-color:rgb(var(--color-gray-100))}.border-\[rgb\(var\(--color-gray-200\)\/0\.7\)\]{border-color:rgb(var(--color-gray-200)/.7)}.border-\[rgb\(var\(--color-gray-500\)\/0\.08\)\]{border-color:rgb(var(--color-gray-500)/.08)}.border-\[rgb\(var\(--color-stone-200\)\)\]{border-color:rgb(var(--color-stone-200))}.bg-\[rgb\(var\(--color-background-light\)\)\]{background-color:rgb(var(--color-background-light))}.bg-\[rgb\(var\(--color-code-block-light\)\)\]{background-color:rgb(var(--color-code-block-light))}.bg-\[rgb\(var\(--color-gray-100\)\/0\.5\)\]{background-color:rgb(var(--color-gray-100)/.5)}.bg-\[rgb\(var\(--color-primary\)\)\]{background-color:rgb(var(--color-primary))}.bg-\[rgb\(var\(--color-primary-dark\)\)\]{background-color:rgb(var(--color-primary-dark))}.bg-amber-100{background-color:var(--color-amber-100)}.bg-amber-100\/50{background-color:#fef3c680}@supports (color:color-mix(in lab, red, red)){.bg-amber-100\/50{background-color:color-mix(in oklab, var(--color-amber-100) 50%, transparent)}}.bg-blue-100{background-color:var(--color-blue-100)}.bg-blue-100\/50{background-color:#dbeafe80}@supports (color:color-mix(in lab, red, red)){.bg-blue-100\/50{background-color:color-mix(in oklab, var(--color-blue-100) 50%, transparent)}}.bg-blue-400\/20{background-color:#54a2ff33}@supports (color:color-mix(in lab, red, red)){.bg-blue-400\/20{background-color:color-mix(in oklab, var(--color-blue-400) 20%, transparent)}}.bg-blue-500{background-color:var(--color-blue-500)}.bg-gray-400\/20{background-color:color-mix(in srgb, 161 161 165 20%, transparent)}@supports (color:color-mix(in lab, red, red)){.bg-gray-400\/20{background-color:color-mix(in oklab, var(--color-gray-400) 20%, transparent)}}.bg-green-100{background-color:var(--color-green-100)}.bg-green-100\/50{background-color:#dcfce780}@supports (color:color-mix(in lab, red, red)){.bg-green-100\/50{background-color:color-mix(in oklab, var(--color-green-100) 50%, transparent)}}.bg-green-400\/20{background-color:#05df7233}@supports (color:color-mix(in lab, red, red)){.bg-green-400\/20{background-color:color-mix(in oklab, var(--color-green-400) 20%, transparent)}}.bg-green-500{background-color:var(--color-green-500)}.bg-orange-100{background-color:var(--color-orange-100)}.bg-purple-400\/20{background-color:#c07eff33}@supports (color:color-mix(in lab, red, red)){.bg-purple-400\/20{background-color:color-mix(in oklab, var(--color-purple-400) 20%, transparent)}}.bg-purple-500{background-color:var(--color-purple-500)}.bg-red-100{background-color:var(--color-red-100)}.bg-red-100\/50{background-color:#ffe2e280}@supports (color:color-mix(in lab, red, red)){.bg-red-100\/50{background-color:color-mix(in oklab, var(--color-red-100) 50%, transparent)}}.bg-gradient-to-b{--tw-gradient-position:to bottom in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-\[rgb\(var\(--color-background-light\)\)\]{--tw-gradient-from:rgb(var(--color-background-light));--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.object-contain{object-fit:contain}.p-2{padding:calc(var(--spacing) * 2)}.px-1{padding-inline:var(--spacing)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-12{padding-inline:calc(var(--spacing) * 12)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-3\.5{padding-block:calc(var(--spacing) * 3.5)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-8{padding-block:calc(var(--spacing) * 8)}.pt-5{padding-top:calc(var(--spacing) * 5)}.pt-6{padding-top:calc(var(--spacing) * 6)}.pt-10{padding-top:calc(var(--spacing) * 10)}.pt-\[8\.5rem\]{padding-top:8.5rem}.pr-3{padding-right:calc(var(--spacing) * 3)}.pr-8{padding-right:calc(var(--spacing) * 8)}.pb-2\.5{padding-bottom:calc(var(--spacing) * 2.5)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pb-10{padding-bottom:calc(var(--spacing) * 10)}.pl-3{padding-left:calc(var(--spacing) * 3)}.pl-3\.5{padding-left:calc(var(--spacing) * 3.5)}.pl-10{padding-left:calc(var(--spacing) * 10)}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[0\.55rem\]{font-size:.55rem}.leading-5{--tw-leading:calc(var(--spacing) * 5);line-height:calc(var(--spacing) * 5)}.leading-6{--tw-leading:calc(var(--spacing) * 6);line-height:calc(var(--spacing) * 6)}.leading-\[1\.35rem\]{--tw-leading:1.35rem;line-height:1.35rem}.leading-none{--tw-leading:1;line-height:1}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-\[0\.12em\]{--tw-tracking:.12em;letter-spacing:.12em}.tracking-\[0\.14em\]{--tw-tracking:.14em;letter-spacing:.14em}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.break-words{overflow-wrap:break-word}.\[word-break\:break-word\]{word-break:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.text-\[rgb\(var\(--color-gray-400\)\)\]{color:rgb(var(--color-gray-400))}.text-\[rgb\(var\(--color-gray-500\)\)\]{color:rgb(var(--color-gray-500))}.text-\[rgb\(var\(--color-gray-600\)\)\]{color:rgb(var(--color-gray-600))}.text-\[rgb\(var\(--color-gray-700\)\)\]{color:rgb(var(--color-gray-700))}.text-\[rgb\(var\(--color-gray-800\)\)\]{color:rgb(var(--color-gray-800))}.text-\[rgb\(var\(--color-gray-900\)\)\]{color:rgb(var(--color-gray-900))}.text-\[rgb\(var\(--color-primary\)\)\]{color:rgb(var(--color-primary))}.text-\[rgb\(var\(--color-primary-ink\)\)\]{color:rgb(var(--color-primary-ink))}.text-\[rgb\(var\(--color-stone-400\)\)\]{color:rgb(var(--color-stone-400))}.text-\[rgb\(var\(--color-stone-500\)\)\]{color:rgb(var(--color-stone-500))}.text-\[rgb\(var\(--color-stone-600\)\)\]{color:rgb(var(--color-stone-600))}.text-\[rgb\(var\(--color-stone-950\)\)\]{color:rgb(var(--color-stone-950))}.text-amber-600{color:var(--color-amber-600)}.text-amber-900{color:var(--color-amber-900)}.text-blue-600{color:var(--color-blue-600)}.text-blue-700{color:var(--color-blue-700)}.text-blue-800{color:var(--color-blue-800)}.text-gray-700{color:var(--color-gray-700)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-orange-900{color:var(--color-orange-900)}.text-purple-700{color:var(--color-purple-700)}.text-red-600{color:var(--color-red-600)}.text-red-800{color:var(--color-red-800)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.ordinal{--tw-ordinal:ordinal;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.no-underline{text-decoration-line:none}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-0{opacity:0}.opacity-90{opacity:.9}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-\[rgb\(var\(--color-gray-400\)\/0\.3\)\]{--tw-ring-color:rgb(var(--color-gray-400)/.3)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.outline-0{outline-style:var(--tw-outline-style);outline-width:0}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.prose-gray{--tw-prose-body:oklch(37.3% .034 259.733);--tw-prose-headings:oklch(21% .034 264.665);--tw-prose-lead:oklch(44.6% .03 256.802);--tw-prose-links:oklch(21% .034 264.665);--tw-prose-bold:oklch(21% .034 264.665);--tw-prose-counters:oklch(55.1% .027 264.364);--tw-prose-bullets:oklch(87.2% .01 258.338);--tw-prose-hr:oklch(92.8% .006 264.531);--tw-prose-quotes:oklch(21% .034 264.665);--tw-prose-quote-borders:oklch(92.8% .006 264.531);--tw-prose-captions:oklch(55.1% .027 264.364);--tw-prose-kbd:oklch(21% .034 264.665);--tw-prose-kbd-shadows:oklab(21% -.00316127 -.0338527/.1);--tw-prose-code:oklch(21% .034 264.665);--tw-prose-pre-code:oklch(92.8% .006 264.531);--tw-prose-pre-bg:oklch(27.8% .033 256.848);--tw-prose-th-borders:oklch(87.2% .01 258.338);--tw-prose-td-borders:oklch(92.8% .006 264.531);--tw-prose-invert-body:oklch(87.2% .01 258.338);--tw-prose-invert-headings:#fff;--tw-prose-invert-lead:oklch(70.7% .022 261.325);--tw-prose-invert-links:#fff;--tw-prose-invert-bold:#fff;--tw-prose-invert-counters:oklch(70.7% .022 261.325);--tw-prose-invert-bullets:oklch(44.6% .03 256.802);--tw-prose-invert-hr:oklch(37.3% .034 259.733);--tw-prose-invert-quotes:oklch(96.7% .003 264.542);--tw-prose-invert-quote-borders:oklch(37.3% .034 259.733);--tw-prose-invert-captions:oklch(70.7% .022 261.325);--tw-prose-invert-kbd:#fff;--tw-prose-invert-kbd-shadows:#ffffff1a;--tw-prose-invert-code:#fff;--tw-prose-invert-pre-code:oklch(87.2% .01 258.338);--tw-prose-invert-pre-bg:#00000080;--tw-prose-invert-th-borders:oklch(44.6% .03 256.802);--tw-prose-invert-td-borders:oklch(37.3% .034 259.733)}.select-none{-webkit-user-select:none;user-select:none}.\[text-shadow\:-0\.2px_0_0_currentColor\,0\.2px_0_0_currentColor\]{text-shadow:-.2px 0,.2px 0}@media (hover:hover){.group-hover\:bg-\[rgb\(var\(--color-gray-200\)\)\]:is(:where(.group):hover *){background-color:rgb(var(--color-gray-200))}.group-hover\:bg-\[rgb\(var\(--color-stone-200\)\/0\.5\)\]:is(:where(.group):hover *){background-color:rgb(var(--color-stone-200)/.5)}.group-hover\:text-\[rgb\(var\(--color-primary\)\)\]:is(:where(.group):hover *){color:rgb(var(--color-primary))}.group-hover\:text-\[rgb\(var\(--color-primary-ink\)\)\]:is(:where(.group):hover *){color:rgb(var(--color-primary-ink))}.group-hover\:opacity-90:is(:where(.group):hover *){opacity:.9}.group-hover\/copy\:text-\[rgb\(var\(--color-stone-500\)\)\]:is(:where(.group\/copy):hover *){color:rgb(var(--color-stone-500))}.peer-hover\:opacity-100:is(:where(.peer):hover~*){opacity:1}}.first\:mt-0:first-child{margin-top:0}@media (hover:hover){.hover\:border-\[rgb\(var\(--color-primary\)\/0\.4\)\]:hover{border-color:rgb(var(--color-primary)/.4)}.hover\:bg-\[rgb\(var\(--color-stone-100\)\)\]:hover{background-color:rgb(var(--color-stone-100))}.hover\:text-\[rgb\(var\(--color-gray-600\)\)\]:hover{color:rgb(var(--color-gray-600))}.hover\:text-\[rgb\(var\(--color-gray-700\)\)\]:hover{color:rgb(var(--color-gray-700))}.hover\:text-\[rgb\(var\(--color-gray-800\)\)\]:hover{color:rgb(var(--color-gray-800))}.hover\:text-\[rgb\(var\(--color-gray-900\)\)\]:hover{color:rgb(var(--color-gray-900))}.hover\:underline:hover{text-decoration-line:underline}.hover\:ring-\[rgb\(var\(--color-gray-600\)\/0\.3\)\]:hover{--tw-ring-color:rgb(var(--color-gray-600)/.3)}}@media (width>=40rem){.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}.sm\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}}@media (width>=64rem){.lg\:sticky{position:sticky}.lg\:mx-0{margin-inline:0}.lg\:mt-8{margin-top:calc(var(--spacing) * 8)}.lg\:-ml-12{margin-left:calc(var(--spacing) * -12)}.lg\:block{display:block}.lg\:flex{display:flex}.lg\:hidden{display:none}.lg\:flex-row{flex-direction:row}.lg\:px-8{padding-inline:calc(var(--spacing) * 8)}.lg\:px-12{padding-inline:calc(var(--spacing) * 12)}.lg\:pt-10{padding-top:calc(var(--spacing) * 10)}.lg\:pl-\[23\.7rem\]{padding-left:23.7rem}}@media (width>=80rem){.xl\:block{display:block}.xl\:flex{display:flex}.xl\:hidden{display:none}.xl\:w-\[calc\(100\%-28rem\)\]{width:calc(100% - 28rem)}.xl\:flex-col{flex-direction:column}.xl\:flex-row{flex-direction:row}}.dark\:block:where(.dark,.dark *){display:block}.dark\:hidden:where(.dark,.dark *){display:none}.dark\:border-\[rgb\(255_255_255\/0\.1\)\]:where(.dark,.dark *){border-color:#ffffff1a}.dark\:border-\[rgb\(var\(--color-border-dark-subtle\)\/0\.1\)\]:where(.dark,.dark *){border-color:rgb(var(--color-border-dark-subtle)/.1)}.dark\:border-\[rgb\(var\(--color-gray-300\)\/0\.06\)\]:where(.dark,.dark *){border-color:rgb(var(--color-gray-300)/.06)}.dark\:border-\[rgb\(var\(--color-gray-300\)\/0\.08\)\]:where(.dark,.dark *){border-color:rgb(var(--color-gray-300)/.08)}.dark\:border-\[rgb\(var\(--color-gray-800\)\)\]:where(.dark,.dark *){border-color:rgb(var(--color-gray-800))}.dark\:border-\[rgb\(var\(--color-gray-800\)\/0\.5\)\]:where(.dark,.dark *){border-color:rgb(var(--color-gray-800)/.5)}.dark\:bg-\[rgb\(var\(--color-background-dark\)\)\]:where(.dark,.dark *){background-color:rgb(var(--color-background-dark))}.dark\:bg-\[rgb\(var\(--color-code-block-dark\)\)\]:where(.dark,.dark *){background-color:rgb(var(--color-code-block-dark))}.dark\:bg-\[rgb\(var\(--color-primary-light\)\)\]:where(.dark,.dark *){background-color:rgb(var(--color-primary-light))}.dark\:bg-\[rgb\(var\(--color-stone-900\)\)\]:where(.dark,.dark *){background-color:rgb(var(--color-stone-900))}.dark\:bg-\[rgb\(var\(--color-surface-dark-tint\)\/0\.05\)\]:where(.dark,.dark *){background-color:rgb(var(--color-surface-dark-tint)/.05)}.dark\:bg-amber-400\/10:where(.dark,.dark *){background-color:#fcbb001a}@supports (color:color-mix(in lab, red, red)){.dark\:bg-amber-400\/10:where(.dark,.dark *){background-color:color-mix(in oklab, var(--color-amber-400) 10%, transparent)}}.dark\:bg-blue-400:where(.dark,.dark *){background-color:var(--color-blue-400)}.dark\:bg-blue-400\/10:where(.dark,.dark *){background-color:#54a2ff1a}@supports (color:color-mix(in lab, red, red)){.dark\:bg-blue-400\/10:where(.dark,.dark *){background-color:color-mix(in oklab, var(--color-blue-400) 10%, transparent)}}.dark\:bg-blue-400\/20:where(.dark,.dark *){background-color:#54a2ff33}@supports (color:color-mix(in lab, red, red)){.dark\:bg-blue-400\/20:where(.dark,.dark *){background-color:color-mix(in oklab, var(--color-blue-400) 20%, transparent)}}.dark\:bg-green-400:where(.dark,.dark *){background-color:var(--color-green-400)}.dark\:bg-green-400\/10:where(.dark,.dark *){background-color:#05df721a}@supports (color:color-mix(in lab, red, red)){.dark\:bg-green-400\/10:where(.dark,.dark *){background-color:color-mix(in oklab, var(--color-green-400) 10%, transparent)}}.dark\:bg-green-400\/20:where(.dark,.dark *){background-color:#05df7233}@supports (color:color-mix(in lab, red, red)){.dark\:bg-green-400\/20:where(.dark,.dark *){background-color:color-mix(in oklab, var(--color-green-400) 20%, transparent)}}.dark\:bg-orange-400\/20:where(.dark,.dark *){background-color:#ff8b1a33}@supports (color:color-mix(in lab, red, red)){.dark\:bg-orange-400\/20:where(.dark,.dark *){background-color:color-mix(in oklab, var(--color-orange-400) 20%, transparent)}}.dark\:bg-purple-400:where(.dark,.dark *){background-color:var(--color-purple-400)}.dark\:bg-purple-400\/20:where(.dark,.dark *){background-color:#c07eff33}@supports (color:color-mix(in lab, red, red)){.dark\:bg-purple-400\/20:where(.dark,.dark *){background-color:color-mix(in oklab, var(--color-purple-400) 20%, transparent)}}.dark\:bg-red-400\/10:where(.dark,.dark *){background-color:#ff65681a}@supports (color:color-mix(in lab, red, red)){.dark\:bg-red-400\/10:where(.dark,.dark *){background-color:color-mix(in oklab, var(--color-red-400) 10%, transparent)}}.dark\:bg-red-400\/20:where(.dark,.dark *){background-color:#ff656833}@supports (color:color-mix(in lab, red, red)){.dark\:bg-red-400\/20:where(.dark,.dark *){background-color:color-mix(in oklab, var(--color-red-400) 20%, transparent)}}.dark\:bg-yellow-400\/20:where(.dark,.dark *){background-color:#fac80033}@supports (color:color-mix(in lab, red, red)){.dark\:bg-yellow-400\/20:where(.dark,.dark *){background-color:color-mix(in oklab, var(--color-yellow-400) 20%, transparent)}}.dark\:from-\[rgb\(var\(--color-background-dark\)\)\]:where(.dark,.dark *){--tw-gradient-from:rgb(var(--color-background-dark));--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark\:text-\[rgb\(255_255_255\/0\.4\)\]:where(.dark,.dark *){color:#fff6}.dark\:text-\[rgb\(var\(--color-gray-50\)\)\]:where(.dark,.dark *){color:rgb(var(--color-gray-50))}.dark\:text-\[rgb\(var\(--color-gray-200\)\)\]:where(.dark,.dark *){color:rgb(var(--color-gray-200))}.dark\:text-\[rgb\(var\(--color-gray-300\)\)\]:where(.dark,.dark *){color:rgb(var(--color-gray-300))}.dark\:text-\[rgb\(var\(--color-gray-400\)\)\]:where(.dark,.dark *){color:rgb(var(--color-gray-400))}.dark\:text-\[rgb\(var\(--color-gray-500\)\)\]:where(.dark,.dark *){color:rgb(var(--color-gray-500))}.dark\:text-\[rgb\(var\(--color-primary-light\)\)\]:where(.dark,.dark *){color:rgb(var(--color-primary-light))}.dark\:text-\[rgb\(var\(--color-stone-50\)\)\]:where(.dark,.dark *){color:rgb(var(--color-stone-50))}.dark\:text-\[rgb\(var\(--color-stone-400\)\)\]:where(.dark,.dark *){color:rgb(var(--color-stone-400))}.dark\:text-amber-300:where(.dark,.dark *){color:var(--color-amber-300)}.dark\:text-blue-300:where(.dark,.dark *){color:var(--color-blue-300)}.dark\:text-blue-400:where(.dark,.dark *){color:var(--color-blue-400)}.dark\:text-gray-400:where(.dark,.dark *){color:var(--color-gray-400)}.dark\:text-green-300:where(.dark,.dark *){color:var(--color-green-300)}.dark\:text-green-400:where(.dark,.dark *){color:var(--color-green-400)}.dark\:text-orange-300:where(.dark,.dark *){color:var(--color-orange-300)}.dark\:text-purple-400:where(.dark,.dark *){color:var(--color-purple-400)}.dark\:text-red-300:where(.dark,.dark *){color:var(--color-red-300)}.dark\:text-yellow-300:where(.dark,.dark *){color:var(--color-yellow-300)}.dark\:ring-\[rgb\(var\(--color-gray-600\)\/0\.3\)\]:where(.dark,.dark *){--tw-ring-color:rgb(var(--color-gray-600)/.3)}.dark\:brightness-110:where(.dark,.dark *){--tw-brightness:brightness(110%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.dark\:prose-invert:where(.dark,.dark *){--tw-prose-body:var(--tw-prose-invert-body);--tw-prose-headings:var(--tw-prose-invert-headings);--tw-prose-lead:var(--tw-prose-invert-lead);--tw-prose-links:var(--tw-prose-invert-links);--tw-prose-bold:var(--tw-prose-invert-bold);--tw-prose-counters:var(--tw-prose-invert-counters);--tw-prose-bullets:var(--tw-prose-invert-bullets);--tw-prose-hr:var(--tw-prose-invert-hr);--tw-prose-quotes:var(--tw-prose-invert-quotes);--tw-prose-quote-borders:var(--tw-prose-invert-quote-borders);--tw-prose-captions:var(--tw-prose-invert-captions);--tw-prose-kbd:var(--tw-prose-invert-kbd);--tw-prose-kbd-shadows:var(--tw-prose-invert-kbd-shadows);--tw-prose-code:var(--tw-prose-invert-code);--tw-prose-pre-code:var(--tw-prose-invert-pre-code);--tw-prose-pre-bg:var(--tw-prose-invert-pre-bg);--tw-prose-th-borders:var(--tw-prose-invert-th-borders);--tw-prose-td-borders:var(--tw-prose-invert-td-borders)}@media (hover:hover){.dark\:group-hover\:bg-\[rgb\(var\(--color-gray-700\)\)\]:where(.dark,.dark *):is(:where(.group):hover *){background-color:rgb(var(--color-gray-700))}.dark\:group-hover\:bg-\[rgb\(var\(--color-stone-700\)\/0\.7\)\]:where(.dark,.dark *):is(:where(.group):hover *){background-color:rgb(var(--color-stone-700)/.7)}.dark\:group-hover\:text-\[rgb\(var\(--color-primary-light\)\)\]:where(.dark,.dark *):is(:where(.group):hover *){color:rgb(var(--color-primary-light))}.dark\:group-hover\/copy\:text-\[rgb\(255_255_255\/0\.6\)\]:where(.dark,.dark *):is(:where(.group\/copy):hover *){color:#fff9}.dark\:hover\:border-\[rgb\(var\(--color-primary-light\)\/0\.3\)\]:where(.dark,.dark *):hover{border-color:rgb(var(--color-primary-light)/.3)}.dark\:hover\:bg-\[rgb\(255_255_255\/0\.05\)\]:where(.dark,.dark *):hover{background-color:#ffffff0d}.dark\:hover\:text-\[rgb\(var\(--color-gray-200\)\)\]:where(.dark,.dark *):hover{color:rgb(var(--color-gray-200))}.dark\:hover\:text-\[rgb\(var\(--color-gray-300\)\)\]:where(.dark,.dark *):hover{color:rgb(var(--color-gray-300))}.dark\:hover\:ring-\[rgb\(var\(--color-gray-500\)\/0\.3\)\]:where(.dark,.dark *):hover{--tw-ring-color:rgb(var(--color-gray-500)/.3)}.dark\:hover\:brightness-125:where(.dark,.dark *):hover{--tw-brightness:brightness(125%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false} +/*$vite$:1*/ +/** + * Sourcey — Component styles. + * + * Layout and OpenAPI components use Tailwind classes in .tsx files. + * Markdown-generated components (code blocks, cards, steps, accordions, + * callouts, tabs) use semantic class names styled here — no Tailwind + * utilities in generated HTML. + */ + +/* ── Page Description (display font) ─────────────────────────────── */ + +#sourcey .page-description { + font-family: ui-serif, Georgia, Cambria, "Times New Roman", serif; + font-size: 1.125rem; + line-height: 1.35; + font-style: italic; + color: rgb(var(--color-gray-600)); +} +@media (min-width: 1024px) { + #sourcey .page-description { + font-size: 1.375rem; + line-height: 1.4; + } +} +.dark #sourcey .page-description { + color: rgb(var(--color-gray-400)); +} + +/* ── Page Top Gradient ────────────────────────────────────────────── */ + +#sourcey #page::before { + content: ""; + position: fixed; + top: 0; + left: 0; + right: 0; + height: 400px; + background: linear-gradient(to bottom, rgb(var(--color-primary) / 0.03), transparent); + pointer-events: none; + z-index: -1; +} +.dark #sourcey #page::before { + background: none; +} + +/* ── Scroll Offset (clears fixed navbar on anchor clicks) ──────────── */ + +[data-traverse-target], +h1[id], h2[id], h3[id], h4[id], h5[id], h6[id] { + scroll-margin-top: var(--header-height, 7rem); +} + +/* ── Focus ─────────────────────────────────────────────────────────── */ + +#sourcey button:focus-visible { + outline: 2px solid rgb(var(--color-primary)); + outline-offset: 2px; +} + +/* ── Shiki Dual-Theme (defaultColor: false) ───────────────────────── */ +/* Shiki outputs --shiki-light and --shiki-dark CSS vars on each span. */ +/* Light mode: use --shiki-light. Dark mode: use --shiki-dark. */ + +.shiki, +.shiki span { + color: var(--shiki-light); + background-color: transparent !important; +} + +.dark .shiki, +.dark .shiki span { + color: var(--shiki-dark); +} + +.shiki pre { + background-color: transparent !important; + margin: 0; +} + +.shiki code { + font-family: inherit; + background: transparent; + border: none; + padding: 0; +} + +/* ── Responsive Tables ────────────────────────────────────────────── */ + +#sourcey .table-wrap { + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} +#sourcey .table-wrap table { + margin: 0; +} +#sourcey .prose :where(thead th):not(:where([class~=not-prose],[class~=not-prose] *)) { + padding-top: .571429em; +} + +/* ── Prose Video (::video directive) ──────────────────────────────── */ + +#sourcey .prose-video { + position: relative; + width: 100%; + padding-bottom: 56.25%; /* 16:9 */ + margin: 1.5rem 0; + border-radius: var(--radius); + overflow: hidden; +} +#sourcey .prose-video iframe, +#sourcey .prose-video video { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +/* ── Prose Iframe (::iframe directive) ────────────────────────────── */ + +#sourcey .prose-iframe { + width: 100%; + margin: 1.5rem 0; + border-radius: var(--radius); + overflow: hidden; + border: 1px solid rgb(var(--color-stone-950) / 0.1); +} +.dark #sourcey .prose-iframe { + border-color: rgb(255 255 255 / 0.1); +} +#sourcey .prose-iframe iframe { + width: 100%; + height: 100%; + display: block; +} + +/* ── Inline Code (not-prose containers lose Tailwind prose styling) ── */ + +#sourcey .not-prose :not(pre) > code { + font-size: 0.875em; + font-weight: 600; + color: var(--tw-prose-code); +} +#sourcey .not-prose :not(pre) > code::before, +#sourcey .not-prose :not(pre) > code::after { + content: "`"; +} + +/* ── Prose Code Block (fenced code in markdown pages) ─────────────── */ + +#sourcey .prose-code-block { + position: relative; + border-radius: var(--radius); + border: 1px solid rgb(var(--color-stone-950) / 0.1); + margin: 1.25rem 0 2rem; + overflow: hidden; + color: rgb(var(--color-stone-950)); +} +.dark #sourcey .prose-code-block { + border-color: rgb(255 255 255 / 0.1); + color: rgb(var(--color-stone-50)); +} + +#sourcey .prose-code-copy { + position: absolute; + top: 0.75rem; + right: 1rem; + z-index: 10; +} + +#sourcey .prose-code-copy .copy-btn { + display: flex; + align-items: center; + justify-content: center; + width: 26px; + height: 26px; + border-radius: 0.375rem; + border: none; + background: transparent; + cursor: pointer; + color: rgb(var(--color-stone-400)); + transition: color 0.15s; +} +#sourcey .prose-code-copy .copy-btn:hover { + color: rgb(var(--color-stone-500)); +} +.dark #sourcey .prose-code-copy .copy-btn { + color: rgb(255 255 255 / 0.4); +} +.dark #sourcey .prose-code-copy .copy-btn:hover { + color: rgb(255 255 255 / 0.6); +} + +#sourcey .prose-code-copy .copy-btn svg { + width: 1rem; + height: 1rem; +} + +#sourcey .prose-code-content { + padding: 0.875rem 1rem; + border-radius: var(--radius); + background: rgb(var(--color-code-block-light)); + overflow-x: auto; + font-variant-ligatures: none; +} +.dark #sourcey .prose-code-content { + background: rgb(var(--color-code-block-dark)); +} + +#sourcey .prose-code-content pre { + margin: 0; + font-family: var(--font-mono); + font-size: 0.875rem; + line-height: 1.5rem; + white-space: pre; +} + +/* ── Nav Links ────────────────────────────────────────────────────── */ + +#sourcey .nav-group-label { + padding: 0 0.75rem 0.25rem 1rem; + margin-bottom: 0.625rem; + font-size: inherit; + font-weight: 600; + color: rgb(var(--color-gray-900)); +} +.dark #sourcey .nav-group-label { + color: rgb(var(--color-gray-200)); +} + +#sourcey .nav-tab-label { + display: block; + padding: 0.5rem 0.75rem 0.25rem 1rem; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: rgb(var(--color-gray-400)); +} +.dark #sourcey .nav-tab-label { + color: rgb(var(--color-gray-500)); +} + +#sourcey .nav-link { + display: flex; + align-items: flex-start; + padding: 0.375rem 0.75rem 0.375rem 1rem; + gap: 0.75rem; + cursor: pointer; + text-align: left; + overflow-wrap: break-word; + hyphens: auto; + border-radius: 0.75rem; + width: 100%; + color: rgb(var(--color-gray-700)); + transition: color 0.15s, background-color 0.15s; +} +.dark #sourcey .nav-link { + color: rgb(var(--color-gray-400)); +} +#sourcey .nav-link:hover { + color: rgb(var(--color-gray-900)); + background: rgb(var(--color-gray-100) / 0.6); +} +.dark #sourcey .nav-link:hover { + color: rgb(var(--color-gray-300)); + background: rgb(var(--color-gray-800) / 0.4); +} +#sourcey .nav-link.active { + color: rgb(var(--color-primary-ink)); + background: rgb(var(--color-primary) / 0.08); + text-shadow: -0.2px 0 0 currentColor, 0.2px 0 0 currentColor; +} +.dark #sourcey .nav-link.active { + color: rgb(var(--color-primary-light)); + background: rgb(var(--color-primary-light) / 0.08); +} + +/* ── TOC Active State ─────────────────────────────────────────────── */ + +#sourcey #toc .toc-item { + border-left: 2px solid rgb(var(--color-gray-200)); + padding-left: 0.75rem; + transition: color 0.15s, border-color 0.15s; +} +#sourcey #toc ul ul .toc-item { + margin-left: 0.75rem; + border-left: none; +} +.dark #sourcey #toc .toc-item { + border-left-color: rgb(var(--color-gray-700)); +} +#sourcey #toc .toc-item.active { + color: rgb(var(--color-primary-ink)); + border-left-color: rgb(var(--color-primary-ink)); + font-weight: 500; +} +.dark #sourcey #toc .toc-item.active { + color: rgb(var(--color-primary-light)); + border-left-color: rgb(var(--color-primary-light)); +} + +/* ── Code Block Panels (language dropdown + response tabs) ─────────── */ + +#sourcey .code-lang-panel { display: none; } +#sourcey .code-lang-panel.active { display: block; } + +#sourcey .lang-icon { + width: 0.875rem; + height: 0.875rem; + flex-shrink: 0; + vertical-align: -0.125rem; +} + +#sourcey .lang-icon[data-lang] { + display: inline-block; + background: currentColor; + -webkit-mask-size: contain; + mask-size: contain; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-position: center; + mask-position: center; +} + +#sourcey .response-panel { display: none; } +#sourcey .response-panel.active { display: block; } + +/* ── Response Tabs (status code tabs on code block) ───────────────── */ + +#sourcey .response-tab { + color: rgb(var(--color-gray-500)); + transition: color 0.15s; +} +.dark #sourcey .response-tab { + color: rgb(var(--color-gray-400)); +} + +#sourcey .response-tab.active { + color: rgb(var(--color-primary-ink)); +} +.dark #sourcey .response-tab.active { + color: rgb(var(--color-primary-light)); +} + +/* Active response tab underline */ +#sourcey .response-tab.active::after { + content: ''; + position: absolute; + right: 0; + bottom: -0.375rem; + left: 0; + height: 2px; + border-radius: 9999px; + background: rgb(var(--color-primary)); +} +.dark #sourcey .response-tab.active::after { + background: rgb(var(--color-primary-light)); +} + +/* ── Copy Button Feedback ─────────────────────────────────────────── */ + +#sourcey .copy-btn.copied { + color: rgb(var(--color-success)); +} + +/* ── Schema Utilities (used by SchemaView.tsx) ─────────────────────── */ + +/* ── Collapsible child attributes ── */ + +#sourcey .schema-expandable { + margin-top: 1rem; + border: 1px solid rgb(var(--color-gray-200) / 0.7); + border-radius: 0.75rem; +} +.dark #sourcey .schema-expandable { + border-color: rgb(255 255 255 / 0.1); +} + +#sourcey .schema-expandable-toggle { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem 1rem; + font-size: 0.875rem; + font-weight: 400; + color: rgb(var(--color-gray-600)); + cursor: pointer; + list-style: none; + user-select: none; +} +#sourcey .schema-expandable-toggle::-webkit-details-marker { + display: none; +} +.dark #sourcey .schema-expandable-toggle { + color: rgb(var(--color-gray-300)); +} +#sourcey .schema-expandable-toggle:hover { + background: rgb(var(--color-gray-100) / 0.5); + color: rgb(var(--color-gray-900)); + border-radius: 0.75rem; +} +.dark #sourcey .schema-expandable-toggle:hover { + background: rgb(255 255 255 / 0.05); + color: rgb(var(--color-gray-200)); +} + +#sourcey .schema-expandable-icon { + width: 0.625rem; + height: 0.625rem; + flex-shrink: 0; + transition: transform 75ms; + color: rgb(var(--color-gray-400)); +} +#sourcey details[open] > .schema-expandable-toggle > .schema-expandable-icon { + transform: rotate(90deg); +} + +#sourcey .schema-expandable-content { + padding: 0 1rem 0.5rem; + border-top: 1px solid rgb(var(--color-gray-100)); +} +.dark #sourcey .schema-expandable-content { + border-top-color: rgb(255 255 255 / 0.1); +} + +/* Legacy schema-nested — kept for variant nesting */ +#sourcey .schema-nested { + padding-left: 1rem; + border-left: 2px solid rgb(var(--color-gray-200)); + margin-top: 0.25rem; + margin-bottom: 0.25rem; +} +.dark #sourcey .schema-nested { + border-left-color: rgb(var(--color-gray-800)); +} + +#sourcey .schema-variant-option { + padding-left: 1rem; + border-left: 2px solid rgb(var(--color-gray-200)); + margin-bottom: 0.5rem; +} +.dark #sourcey .schema-variant-option { + border-left-color: rgb(var(--color-gray-800)); +} + +#sourcey .schema-variant-label { + font-size: 0.75rem; + font-weight: 600; + letter-spacing: 0.05em; + color: rgb(var(--color-gray-400)); + margin-bottom: 0.25rem; +} + +/* ── Type Display (used by SchemaDatatype.tsx) ─────────────────────── */ + +#sourcey .json-property-type { + display: inline-flex; + align-items: center; + font-style: normal; + font-weight: 500; + font-size: 0.75rem; + line-height: 1; + white-space: nowrap; + color: rgb(var(--color-gray-600)); + background: rgb(var(--color-gray-100) / 0.5); + padding: 0.25rem 0.5rem; + border-radius: 0.375rem; +} +.dark #sourcey .json-property-type { + background: rgb(var(--color-surface-dark-tint) / 0.05); + color: rgb(var(--color-gray-200)); +} + +#sourcey .json-property-format { + font-size: 0.75rem; + color: rgb(var(--color-gray-400)); +} + +#sourcey .json-property-enum { + display: inline-flex; + align-items: baseline; + gap: 0.25rem; + flex-wrap: wrap; +} + +#sourcey .json-property-enum-item, +#sourcey .json-property-default-value, +#sourcey .json-property-range { + display: inline-flex; + align-items: center; + font-size: 0.6875rem; + font-family: var(--font-mono); + line-height: 1; + white-space: nowrap; + color: rgb(var(--color-gray-500)); + background: rgb(var(--color-gray-100) / 0.5); + padding: 0.125rem 0.375rem; + border-radius: 9999px; +} +.dark #sourcey .json-property-enum-item, +.dark #sourcey .json-property-default-value, +.dark #sourcey .json-property-range { + background: rgb(var(--color-surface-dark-tint) / 0.05); + color: rgb(var(--color-gray-400)); +} + +#sourcey .json-property-enum-item { color: rgb(var(--color-gray-700)); } +.dark #sourcey .json-property-enum-item { color: rgb(var(--color-gray-300)); } + +#sourcey .json-property-default-value::before { + content: '= '; + color: rgb(var(--color-gray-400)); +} + +/* ── Parameter/Schema List (used by SchemaView.tsx, Parameters.tsx) ── */ + +#sourcey .param-item { + padding: 1rem 0; + border-bottom: 1px solid rgb(var(--color-gray-100)); +} +.dark #sourcey .param-item { + border-bottom-color: rgb(var(--color-gray-800)); +} + +#sourcey .param-item:last-child { + border-bottom: none; +} + +#sourcey .param-header { + display: flex; + align-items: baseline; + gap: 0.5rem; + flex-wrap: wrap; +} + +#sourcey .param-name { + font-family: var(--font-mono); + font-size: 0.875rem; + font-weight: 600; + color: rgb(var(--color-primary-ink)); + background: transparent; + border: none; + padding: 0; +} +.dark #sourcey .param-name { + color: rgb(var(--color-primary-light)); +} + +#sourcey .param-type { + display: inline-flex; + align-items: baseline; + gap: 0.375rem; +} + +#sourcey .param-in { + display: inline-flex; + align-items: center; + padding: 0.125rem 0.5rem; + font-size: 0.75rem; + font-weight: 500; + border-radius: 0.375rem; + background: rgb(var(--color-gray-100) / 0.5); + color: rgb(var(--color-gray-600)); + margin-left: auto; +} +.dark #sourcey .param-in { + background: rgb(var(--color-surface-dark-tint) / 0.05); + color: rgb(var(--color-gray-200)); +} + +#sourcey .param-description { + padding-top: 0.5rem; + font-size: 0.875rem; + color: rgb(var(--color-gray-700)); +} +.dark #sourcey .param-description { + color: rgb(var(--color-gray-400)); +} + + +/* ── Steps (numbered step list with vertical connector) ────────────── */ + +#sourcey .steps { + margin-left: 0.875rem; + margin-top: 1rem; + margin-bottom: 1.5rem; +} + +#sourcey .steps .step-item { + position: relative; + display: flex; + align-items: flex-start; + padding-bottom: 1.25rem; +} + +#sourcey .steps .step-item::before { + content: ''; + position: absolute; + left: 13px; + top: 2.75rem; + bottom: 0; + width: 1px; + background: rgb(var(--color-gray-200) / 0.7); +} +.dark #sourcey .steps .step-item::before { + background: rgb(255 255 255 / 0.1); +} + +#sourcey .steps .step-item:last-child::before { + background: linear-gradient(to bottom, rgb(var(--color-gray-200) / 0.7), transparent); +} +.dark #sourcey .steps .step-item:last-child::before { + background: linear-gradient(to bottom, rgb(255 255 255 / 0.1), transparent); +} + +#sourcey .steps .step-number { + position: relative; + display: flex; + align-items: center; + justify-content: center; + width: 1.75rem; + height: 1.75rem; + flex-shrink: 0; + border-radius: 9999px; + background: rgb(var(--color-gray-50)); + font-size: 0.75rem; + font-weight: 600; + color: rgb(var(--color-gray-900)); + margin-top: 0.5rem; +} +.dark #sourcey .steps .step-number { + background: rgb(255 255 255 / 0.1); + color: rgb(var(--color-gray-50)); +} + +#sourcey .steps .step-body { + padding-left: 1rem; + flex: 1; + overflow: hidden; +} + +#sourcey .steps .step-title { + margin-top: 0.5rem; + font-weight: 600; + color: rgb(var(--color-gray-900)); +} +.dark #sourcey .steps .step-title { + color: rgb(var(--color-gray-200)); +} + +#sourcey .steps .step-content { + margin-top: 0.25rem; +} + +#sourcey .steps .step-content p { + margin: 0.25rem 0; +} + +/* ── Card Group (icon cards in a grid) ─────────────────────────────── */ + +#sourcey .card-group { + display: grid; + gap: 1rem; + margin-top: 1.25rem; + margin-bottom: 2rem; +} +@media (min-width: 640px) { + #sourcey .card-group[data-cols="2"] { grid-template-columns: repeat(2, 1fr); } + #sourcey .card-group[data-cols="3"] { grid-template-columns: repeat(3, 1fr); } + #sourcey .card-group[data-cols="4"] { grid-template-columns: repeat(4, 1fr); } +} + +#sourcey .card-item { + display: block; + position: relative; + border: 1px solid rgb(var(--color-gray-950) / 0.1); + border-radius: var(--radius); + padding: 1.25rem 1.5rem; + background: rgb(var(--color-background-light)); + text-decoration: none; + color: inherit; + cursor: pointer; + overflow: hidden; + transition: border-color 0.15s; +} +.dark #sourcey .card-item { + border-color: rgb(255 255 255 / 0.1); + background: rgb(var(--color-background-dark)); +} + +#sourcey .card-item:hover { + border-color: rgb(var(--color-primary)); +} +.dark #sourcey .card-item:hover { + border-color: rgb(var(--color-primary-light)); +} + +#sourcey .card-icon { + width: 1.5rem; + height: 1.5rem; + flex-shrink: 0; + color: rgb(var(--color-primary)); + margin-bottom: 0.75rem; +} +.dark #sourcey .card-icon { + color: rgb(var(--color-primary-light)); +} + +#sourcey .card-item-title { + font-size: 1rem; + font-weight: 600; + color: rgb(var(--color-gray-800)); + margin: 0 0 0.25rem; +} +.dark #sourcey .card-item-title { + color: #fff; +} + +#sourcey .card-item-content { + margin-top: 0.25rem; + font-size: 1rem; + line-height: 1.5; + color: rgb(var(--color-gray-600)); +} +.dark #sourcey .card-item-content { + color: rgb(var(--color-gray-400)); +} + +#sourcey .card-item-content p { + margin: 0; +} + +/* ── Accordion (expandable details) ────────────────────────────────── */ + +#sourcey .accordion-group { + margin-top: 0; + margin-bottom: 0.75rem; + border-radius: var(--radius); + border: 1px solid rgb(var(--color-gray-200) / 0.7); + overflow: hidden; +} +.dark #sourcey .accordion-group { + border-color: rgb(var(--color-gray-800) / 0.5); +} + +#sourcey .accordion-item { + border-bottom: 1px solid rgb(var(--color-gray-200) / 0.7); + background: rgb(var(--color-background-light)); +} +.dark #sourcey .accordion-item { + border-bottom-color: rgb(var(--color-gray-800) / 0.5); + background: rgb(var(--color-code-block-dark)); +} + +#sourcey .accordion-item:last-child { + border-bottom: none; +} + +#sourcey .accordion-trigger { + display: flex; + align-items: center; + gap: 0.5rem; + width: 100%; + padding: 1rem 1.25rem; + cursor: pointer; + font-weight: 500; + color: rgb(var(--color-gray-900)); + list-style: none; +} +#sourcey .accordion-trigger::-webkit-details-marker { display: none; } +.dark #sourcey .accordion-trigger { + color: rgb(var(--color-gray-200)); +} + +#sourcey .accordion-trigger:hover { + background: rgb(var(--color-gray-100)); +} +.dark #sourcey .accordion-trigger:hover { + background: rgb(var(--color-gray-800)); +} + +#sourcey .accordion-trigger::before { + content: ''; + display: inline-block; + width: 0.75rem; + height: 0.75rem; + flex-shrink: 0; + background: rgb(var(--color-gray-700)); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 512'%3E%3Cpath d='M246.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-128-128c-9.2-9.2-22.9-11.9-34.9-6.9s-19.8 16.6-19.8 29.6l0 256c0 12.9 7.8 24.6 19.8 29.6s25.7 2.2 34.9-6.9l128-128z'/%3E%3C/svg%3E"); + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + transition: transform 0.15s; +} +.dark #sourcey .accordion-trigger::before { + background: rgb(var(--color-gray-400)); +} + +#sourcey .accordion-item[open] > .accordion-trigger::before { + transform: rotate(90deg); +} + +#sourcey .accordion-content { + padding: 0 1.25rem 1rem 2.5rem; + color: rgb(var(--color-gray-700)); +} +.dark #sourcey .accordion-content { + color: rgb(var(--color-gray-400)); +} + +#sourcey .accordion-content p { + margin: 0.25rem 0; +} + +/* ── Callouts (:::note, :::warning, :::tip, :::info) ───────────────── */ + +#sourcey .callout { + margin: 1.25rem 0; + border: none; + border-left: 2px solid; + border-radius: 0; + padding: 0.875rem 1rem; + font-size: 0.875rem; + line-height: 1.6; + color: rgb(var(--color-gray-700)); + border-left-color: rgb(var(--color-gray-300)); +} +.dark #sourcey .callout { + border-left-color: rgb(var(--color-gray-700)); + color: rgb(var(--color-gray-400)); +} + +#sourcey .callout-title { + font-weight: 600; + font-size: 0.8125rem; + text-transform: uppercase; + letter-spacing: 0.04em; +} + +/* Remove bottom margin when no content follows */ +#sourcey .callout-title:last-child { + margin-bottom: 0; +} + +#sourcey .callout-content { + margin-top: 0.375rem; +} + +#sourcey .callout-content p { + margin: 0.25rem 0; +} + +#sourcey .callout-content p:last-child { + margin-bottom: 0; +} + +/* Note — blue */ +#sourcey .callout-note { border-left-color: #3b82f6; } +.dark #sourcey .callout-note { border-left-color: #60a5fa; } +#sourcey .callout-note .callout-title { color: #3b82f6; } +.dark #sourcey .callout-note .callout-title { color: #60a5fa; } + +/* Warning — amber */ +#sourcey .callout-warning { border-left-color: #f59e0b; } +.dark #sourcey .callout-warning { border-left-color: #fbbf24; } +#sourcey .callout-warning .callout-title { color: #f59e0b; } +.dark #sourcey .callout-warning .callout-title { color: #fbbf24; } + +/* ── Changelog ───────────────────────────────────────────────────── */ + +#sourcey .sourcey-changelog-page { + width: 100%; +} + +#sourcey .sourcey-changelog-header { + margin-bottom: 2rem; +} + +#sourcey .sourcey-changelog-description { + margin-top: 0.75rem; + font-size: 1.05rem; + line-height: 1.7; + color: rgb(var(--color-gray-600)); + max-width: 65ch; + text-wrap: pretty; +} + +.dark #sourcey .sourcey-changelog-description { + color: rgb(var(--color-gray-400)); +} + +#sourcey .sourcey-changelog-description a { + color: rgb(var(--color-primary-ink)); + text-decoration: none; + border-bottom: 1px solid rgb(var(--color-primary-ink) / 0.3); +} + +#sourcey .sourcey-changelog-description a:hover { + border-bottom-color: rgb(var(--color-primary-ink)); +} + +.dark #sourcey .sourcey-changelog-description a { + color: rgb(var(--color-primary-light)); + border-bottom-color: rgb(var(--color-primary-light) / 0.3); +} + +.dark #sourcey .sourcey-changelog-description a:hover { + border-bottom-color: rgb(var(--color-primary-light)); +} + +#sourcey .sourcey-changelog-description code { + font-size: 0.92em; + padding: 0.05rem 0.3rem; + border-radius: 0.25rem; + background: rgb(var(--color-gray-100)); + color: rgb(var(--color-gray-800)); +} + +.dark #sourcey .sourcey-changelog-description code { + background: rgb(255 255 255 / 0.08); + color: rgb(var(--color-gray-200)); +} + +#sourcey .sourcey-changelog-list { + display: flex; + flex-direction: column; + gap: 1rem; +} + +#sourcey .sourcey-changelog-version { + border: 1px solid rgb(var(--color-gray-200) / 0.8); + border-radius: 1rem; + padding: 1.25rem 1.25rem 1rem; + background: linear-gradient(180deg, rgb(var(--color-gray-50) / 0.75), rgb(255 255 255)); +} + +.dark #sourcey .sourcey-changelog-version { + border-color: rgb(var(--color-gray-800) / 0.8); + background: linear-gradient(180deg, rgb(255 255 255 / 0.03), rgb(255 255 255 / 0.015)); +} + +#sourcey .sourcey-changelog-version-unreleased { + border-color: rgb(var(--color-primary) / 0.18); + border-left: 3px solid rgb(var(--color-primary)); + padding-left: calc(1.25rem - 2px); + background: linear-gradient(180deg, rgb(var(--color-primary) / 0.04), rgb(var(--color-primary) / 0.015)); +} + +.dark #sourcey .sourcey-changelog-version-unreleased { + border-color: rgb(var(--color-primary-light) / 0.22); + border-left-color: rgb(var(--color-primary-light)); + background: linear-gradient(180deg, rgb(var(--color-primary-light) / 0.06), rgb(var(--color-primary-light) / 0.02)); +} + +#sourcey .sourcey-changelog-version-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 1rem; + margin-bottom: 1rem; +} + +@media (max-width: 768px) { + #sourcey .sourcey-changelog-version-header { + flex-direction: column; + } +} + +#sourcey .sourcey-changelog-version-heading { + display: flex; + align-items: center; + gap: 0.5rem; + flex-wrap: wrap; +} + +#sourcey .sourcey-changelog-version-link { + font-size: 1.25rem; + font-weight: 700; + color: rgb(var(--color-gray-900)); + text-decoration: none; +} + +.dark #sourcey .sourcey-changelog-version-link { + color: rgb(var(--color-gray-200)); +} + +#sourcey .sourcey-changelog-version-link:hover { + color: rgb(var(--color-primary-ink)); +} + +.dark #sourcey .sourcey-changelog-version-link:hover { + color: rgb(var(--color-primary-light)); +} + +#sourcey .sourcey-changelog-version-meta { + display: flex; + align-items: center; + gap: 0.75rem; + font-size: 0.925rem; + color: rgb(var(--color-gray-500)); +} + +.dark #sourcey .sourcey-changelog-version-meta { + color: rgb(var(--color-gray-400)); +} + +#sourcey .sourcey-changelog-compare-link { + color: rgb(var(--color-primary-ink)); + text-decoration: none; +} + +.dark #sourcey .sourcey-changelog-compare-link { + color: rgb(var(--color-primary-light)); +} + +#sourcey .sourcey-changelog-pill { + display: inline-flex; + align-items: center; + border-radius: 999px; + padding: 0.15rem 0.5rem; + font-size: 0.72rem; + font-weight: 700; + letter-spacing: 0.03em; +} + +#sourcey .sourcey-changelog-pill-yanked { + background: rgb(239 68 68 / 0.12); + color: rgb(185 28 28); +} + +.dark #sourcey .sourcey-changelog-pill-yanked { + background: rgb(248 113 113 / 0.15); + color: rgb(252 165 165); +} + +#sourcey .sourcey-changelog-pill-pre { + background: rgb(var(--color-gray-100)); + color: rgb(var(--color-gray-600)); +} + +.dark #sourcey .sourcey-changelog-pill-pre { + background: rgb(255 255 255 / 0.08); + color: rgb(var(--color-gray-300)); +} + +#sourcey .sourcey-changelog-pill-next { + background: rgb(var(--color-primary) / 0.1); + color: rgb(var(--color-primary-ink)); + text-transform: uppercase; + letter-spacing: 0.06em; + font-size: 0.68rem; +} + +.dark #sourcey .sourcey-changelog-pill-next { + background: rgb(var(--color-primary-light) / 0.14); + color: rgb(var(--color-primary-light)); +} + +#sourcey .sourcey-changelog-summary { + margin-bottom: 1rem; +} + +#sourcey .sourcey-changelog-summary p:first-child { + margin-top: 0; +} + +#sourcey .sourcey-changelog-sections { + display: flex; + flex-direction: column; + gap: 0.875rem; +} + +#sourcey .sourcey-changelog-section { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +#sourcey .sourcey-changelog-section-header { + display: flex; + align-items: center; + gap: 0.5rem; +} + +#sourcey .sourcey-changelog-entry-list { + margin: 0; + padding-left: 1.25rem; + color: rgb(var(--color-gray-700)); +} + +.dark #sourcey .sourcey-changelog-entry-list { + color: rgb(var(--color-gray-300)); +} + +#sourcey .sourcey-changelog-entry-list li { + margin: 0.35rem 0; +} + +#sourcey .sourcey-changelog-entry-list li > p { + margin: 0; +} + +#sourcey .sourcey-changelog-badge { + text-transform: none; +} + +#sourcey .sourcey-changelog-badge-added { + background: rgb(var(--color-success) / 0.12); + color: rgb(21 128 61); +} + +.dark #sourcey .sourcey-changelog-badge-added { + background: rgb(var(--color-success) / 0.16); + color: rgb(134 239 172); +} + +#sourcey .sourcey-changelog-badge-changed { + background: rgb(var(--color-primary) / 0.12); + color: rgb(var(--color-primary-ink)); +} + +.dark #sourcey .sourcey-changelog-badge-changed { + background: rgb(var(--color-primary-light) / 0.12); + color: rgb(var(--color-primary-light)); +} + +#sourcey .sourcey-changelog-badge-fixed { + background: rgb(245 158 11 / 0.14); + color: rgb(180 83 9); +} + +.dark #sourcey .sourcey-changelog-badge-fixed { + background: rgb(251 191 36 / 0.16); + color: rgb(253 224 71); +} + +#sourcey .sourcey-changelog-badge-removed, +#sourcey .sourcey-changelog-badge-security { + background: rgb(239 68 68 / 0.12); + color: rgb(185 28 28); +} + +.dark #sourcey .sourcey-changelog-badge-removed, +.dark #sourcey .sourcey-changelog-badge-security { + background: rgb(248 113 113 / 0.16); + color: rgb(252 165 165); +} + +#sourcey .sourcey-changelog-badge-deprecated, +#sourcey .sourcey-changelog-badge-other { + background: rgb(var(--color-gray-100)); + color: rgb(var(--color-gray-600)); +} + +.dark #sourcey .sourcey-changelog-badge-deprecated, +.dark #sourcey .sourcey-changelog-badge-other { + background: rgb(255 255 255 / 0.08); + color: rgb(var(--color-gray-300)); +} + +/* Tip — green */ +#sourcey .callout-tip { border-left-color: #22c55e; } +.dark #sourcey .callout-tip { border-left-color: #4ade80; } +#sourcey .callout-tip .callout-title { color: #22c55e; } +.dark #sourcey .callout-tip .callout-title { color: #4ade80; } + +/* Info — violet */ +#sourcey .callout-info { border-left-color: #8b5cf6; } +.dark #sourcey .callout-info { border-left-color: #a78bfa; } +#sourcey .callout-info .callout-title { color: #8b5cf6; } +.dark #sourcey .callout-info .callout-title { color: #a78bfa; } + +/* ── Directive Tabs (:::tabs, :::code-group) ───────────────────────── */ + +#sourcey .directive-tabs { + margin: 1.25rem 0; + border: 1px solid rgb(var(--color-stone-950) / 0.1); + border-radius: var(--radius); + overflow: hidden; +} +.dark #sourcey .directive-tabs { + border-color: rgb(255 255 255 / 0.1); +} + +#sourcey .directive-tab-bar { + display: flex; + gap: 0; + border-bottom: 1px solid rgb(var(--color-stone-200)); + background: rgb(var(--color-stone-50)); + padding: 0 0.25rem; +} +.dark #sourcey .directive-tab-bar { + border-bottom-color: rgb(255 255 255 / 0.06); + background: rgb(var(--color-code-block-dark)); +} + +#sourcey .directive-tab { + position: relative; + padding: 0.625rem 1rem; + font-size: 0.8125rem; + font-weight: 500; + color: rgb(var(--color-stone-500)); + background: transparent; + border: none; + cursor: pointer; + transition: color 0.15s; +} +#sourcey .directive-tab:hover { + color: rgb(var(--color-gray-900)); +} +.dark #sourcey .directive-tab:hover { + color: rgb(var(--color-gray-300)); +} + +#sourcey .directive-tab.active { + color: rgb(var(--color-primary-ink)); +} +.dark #sourcey .directive-tab.active { + color: rgb(var(--color-primary-light)); +} + +#sourcey .directive-tab.active::after { + content: ''; + position: absolute; + right: 0.5rem; + bottom: -1px; + left: 0.5rem; + height: 2px; + border-radius: 9999px; + background: rgb(var(--color-primary)); +} +.dark #sourcey .directive-tab.active::after { + background: rgb(var(--color-primary-light)); +} + +#sourcey .directive-tab-panel { + display: none; + padding: 1rem 1.25rem; +} + +#sourcey .directive-tab-panel.active { + display: block; +} + +/* Code group: tighter padding, code blocks fill the panel */ +#sourcey .directive-code-group .directive-tab-panel { + padding: 0; +} + +#sourcey .directive-code-group .directive-tab-panel pre { + margin: 0; + border-radius: 0; +} + +/* Strip nested code block chrome inside tab panels */ +#sourcey .directive-tabs .prose-code-block { + border: none; + border-radius: 0; + margin: 0; +} + +#sourcey .directive-tabs .prose-code-block .prose-code-copy { + display: none; +} + +/* ── Mobile navigation drawer (dialog) ────────────────────────────── */ + +.mobile-nav-dialog { + padding: 0; + border: none; + max-width: 20rem; + width: 80%; + height: 100%; + max-height: 100%; + margin: 0; + overflow-y: auto; + font-family: var(--font-sans); + font-size: 0.875rem; + line-height: 1.5rem; + background: rgb(var(--color-background-light)); + color: rgb(var(--color-gray-500)); +} +.mobile-nav-dialog[open] { + display: flex; + flex-direction: column; +} +.dark .mobile-nav-dialog { + background: rgb(var(--color-background-dark)); + color: rgb(var(--color-gray-400)); +} +.mobile-nav-dialog::backdrop { + background: rgb(0 0 0 / 0.25); + backdrop-filter: blur(2px); +} +.mobile-nav-dialog[open] { + animation: drawer-slide-in 0.2s ease-out; +} +.mobile-nav-dialog[open]::backdrop { + animation: drawer-fade-in 0.2s ease-out; +} + +/* ── Drawer group dropdown ─────────────────────────────────────────── */ + +.drawer-dropdown { + position: relative; +} + +.drawer-dropdown-trigger { + position: relative; + z-index: 11; + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.5rem; + width: 100%; + padding: 0.625rem 1.125rem; + border-radius: var(--radius, 0.5rem); + font-size: 1rem; + font-weight: 600; + font-family: var(--font-sans); + color: rgb(var(--color-gray-900)); + background: rgb(var(--color-background-light)); + border: none; + box-shadow: inset 0 0 0 1px rgb(var(--color-gray-200)); + cursor: pointer; + text-align: left; +} +.dark .drawer-dropdown-trigger { + color: rgb(var(--color-gray-200)); + background: rgb(var(--color-background-dark)); + box-shadow: inset 0 0 0 1px rgb(var(--color-gray-600)); +} +.drawer-dropdown-trigger[aria-expanded="true"] { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + box-shadow: inset 1px 0 0 rgb(var(--color-gray-200)), inset -1px 0 0 rgb(var(--color-gray-200)), inset 0 1px 0 rgb(var(--color-gray-200)); +} +.dark .drawer-dropdown-trigger[aria-expanded="true"] { + box-shadow: inset 1px 0 0 rgb(var(--color-gray-600)), inset -1px 0 0 rgb(var(--color-gray-600)), inset 0 1px 0 rgb(var(--color-gray-600)); +} + +.drawer-dropdown-chevron { + color: rgb(var(--color-gray-400)); + transition: transform 0.15s ease; + flex-shrink: 0; +} +.drawer-dropdown-trigger[aria-expanded="true"] .drawer-dropdown-chevron { + transform: rotate(180deg); +} + +.drawer-dropdown-list { + position: absolute; + top: 100%; + left: 0; + right: 0; + z-index: 10; + list-style: none; + margin: 0; + padding: 0.375rem 0; + border-radius: 0 0 var(--radius, 0.5rem) var(--radius, 0.5rem); + background: rgb(var(--color-background-light)); + border: none; + box-shadow: inset 1px 0 0 rgb(var(--color-gray-200)), inset -1px 0 0 rgb(var(--color-gray-200)), inset 0 -1px 0 rgb(var(--color-gray-200)), 0 6px 16px rgb(0 0 0 / 0.08); +} +.dark .drawer-dropdown-list { + background: rgb(var(--color-background-dark)); + box-shadow: inset 1px 0 0 rgb(var(--color-gray-600)), inset -1px 0 0 rgb(var(--color-gray-600)), inset 0 -1px 0 rgb(var(--color-gray-600)), 0 6px 16px rgb(0 0 0 / 0.3); +} + +.drawer-dropdown-item { + display: block; + width: 100%; + padding: 0.5rem 1.125rem; + font-size: 1rem; + font-family: var(--font-sans); + text-align: left; + color: rgb(var(--color-gray-600)); + background: none; + border: none; + cursor: pointer; +} +.drawer-dropdown-item:hover { + background: rgb(var(--color-gray-50)); +} +.dark .drawer-dropdown-item { + color: rgb(var(--color-gray-400)); +} +.dark .drawer-dropdown-item:hover { + background: rgb(var(--color-gray-800)); +} +.drawer-dropdown-item.active { + color: rgb(var(--color-primary-ink)); + font-weight: 500; +} +.dark .drawer-dropdown-item.active { + color: rgb(var(--color-primary-light)); +} + +/* Drawer nav items — larger touch targets */ +#sourcey .mobile-nav-dialog .nav-link, +#sourcey .mobile-nav-dialog .nav-link:hover, +#sourcey .mobile-nav-dialog .nav-link.active { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} +.mobile-nav-dialog ul { + display: flex; + flex-direction: column; + gap: 0.125rem; +} + +@keyframes drawer-slide-in { + from { transform: translateX(-100%); } + to { transform: translateX(0); } +} +@keyframes drawer-fade-in { + from { opacity: 0; } + to { opacity: 1; } +} + +/* ── Code group cards (API right-hand panels) ─────────────────────── */ + +#sourcey .code-group { + position: relative; + display: flex; + flex-direction: column; + /* Visible so an open header dropdown menu is not clipped by the card; the + code body's rounded corners are clipped by .code-card-body instead. */ + overflow: visible; + border-radius: var(--radius); + border: 1px solid rgb(var(--color-stone-950) / 0.1); +} +.dark #sourcey .code-group { + border-color: rgb(255 255 255 / 0.1); +} + +#sourcey .code-group > div:first-child { + background: rgb(var(--color-stone-50)); + border-top-left-radius: var(--radius); + border-top-right-radius: var(--radius); +} +.dark #sourcey .code-group > div:first-child { + background: rgb(255 255 255 / 0.03); +} + +/* Clips the code panels' rounded bottom corners now that the card itself does + not clip. Horizontal code scrolling stays on the inner body element. */ +#sourcey .code-card-body { + overflow: hidden; + border-bottom-left-radius: var(--radius); + border-bottom-right-radius: var(--radius); +} + +/* ── Selection ─────────────────────────────────────────────────────── */ + +#sourcey ::selection { background: rgb(var(--color-primary) / 0.12); } + +/* ── Search Dialog ────────────────────────────────────────────────── */ + +#sourcey #search-dialog { + display: none; + position: fixed; + inset: 0; + z-index: 500; + background: transparent; +} + +#sourcey #search-dialog.open { + display: block; +} + +#sourcey .search-dialog-inner { + position: absolute; + background: rgb(var(--color-background-light)); + border-radius: var(--radius); + border: 1px solid rgb(var(--color-gray-200) / 0.7); + box-shadow: 0 8px 32px rgb(var(--color-overlay) / 0.12); + overflow: hidden; +} +.dark #sourcey .search-dialog-inner { + background: rgb(var(--color-gray-900)); + border-color: rgb(var(--color-gray-700) / 0.5); + box-shadow: 0 8px 32px rgb(var(--color-overlay) / 0.4); +} + +#sourcey .search-input-row { + display: flex; + align-items: center; + gap: 0.625rem; + padding: 0.75rem 1rem; + border-bottom: 1px solid rgb(var(--color-gray-200)); +} +.dark #sourcey .search-input-row { + border-bottom-color: rgb(var(--color-gray-800)); +} + +#sourcey .search-input-icon { + width: 1.125rem; + height: 1.125rem; + flex-shrink: 0; + color: rgb(var(--color-gray-400)); +} + +#sourcey #search-input { + width: 100%; + border: none; + font-family: var(--font-sans); + font-size: 0.9375rem; + color: rgb(var(--color-gray-900)); + background: transparent; + outline: none; +} +.dark #sourcey #search-input { + color: rgb(var(--color-gray-100)); +} + +#sourcey #search-input::placeholder { color: rgb(var(--color-gray-400)); } + +#sourcey #search-results { + max-height: 50vh; + overflow-y: auto; + padding: 0.25rem; +} + +#sourcey .search-category { + padding: 0.5rem 0.75rem 0.25rem; + font-size: 0.6875rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: rgb(var(--color-gray-400)); +} + +#sourcey .search-footer { + display: flex; + align-items: center; + gap: 1rem; + padding: 0.5rem 0.75rem; + border-top: 1px solid rgb(var(--color-gray-200)); + font-size: 0.6875rem; + color: rgb(var(--color-gray-400)); +} +.dark #sourcey .search-footer { + border-top-color: rgb(var(--color-gray-800)); +} + +#sourcey .search-footer-hint { + display: flex; + align-items: center; + gap: 0.25rem; +} + +#sourcey .search-footer kbd, +#sourcey #search-open kbd { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 1.25rem; + height: 1.125rem; + padding: 0 0.25rem; + font-family: var(--font-sans); + font-size: 0.625rem; + font-weight: 500; + border-radius: 0.25rem; + border: 1px solid rgb(var(--color-gray-200)); + background: rgb(var(--color-gray-50)); + color: rgb(var(--color-gray-500)); +} +.dark #sourcey .search-footer kbd, +.dark #sourcey #search-open kbd { + border-color: rgb(var(--color-gray-700)); + background: rgb(var(--color-gray-800)); + color: rgb(var(--color-gray-400)); +} + +#sourcey .search-result { + display: flex; + align-items: baseline; + gap: 0.5rem; + padding: 0.5rem 0.75rem; + text-decoration: none; + color: rgb(var(--color-gray-700)); + border-radius: 0.375rem; + cursor: pointer; + transition: background 0.1s; +} +.dark #sourcey .search-result { + color: rgb(var(--color-gray-400)); +} + +#sourcey .search-result:hover, +#sourcey .search-result.active { + background: rgb(var(--color-primary) / 0.06); + color: rgb(var(--color-gray-900)); +} +.dark #sourcey .search-result:hover, +.dark #sourcey .search-result.active { + background: rgb(var(--color-primary-light) / 0.08); + color: rgb(var(--color-gray-200)); +} + +#sourcey .search-result-main { + display: flex; + align-items: baseline; + gap: 0.375rem; + flex: 1; + min-width: 0; +} + +#sourcey .search-result-method { + display: inline-flex; + align-items: center; + padding: 0.125rem 0.375rem; + font-size: 0.625rem; + font-weight: 700; + line-height: 1; + letter-spacing: 0.04em; + text-transform: uppercase; + white-space: nowrap; + border: 1px solid transparent; + border-radius: 3px; + color: rgb(var(--color-background-light)); + flex-shrink: 0; +} + +#sourcey .search-result-method.method-get { background: var(--method-get); } +#sourcey .search-result-method.method-post { background: var(--method-post); } +#sourcey .search-result-method.method-put { background: var(--method-put); } +#sourcey .search-result-method.method-delete { background: var(--method-delete); } +#sourcey .search-result-method.method-patch { background: var(--method-patch); } +#sourcey .search-result-method.method-tool { background: var(--method-tool); } +#sourcey .search-result-method.method-resource { background: var(--method-resource); } +#sourcey .search-result-method.method-prompt { background: var(--method-prompt); } + +#sourcey .search-result-path { + font-family: var(--font-mono); + font-size: 0.8125rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +#sourcey .search-result-summary { + font-size: 0.8125rem; + color: rgb(var(--color-gray-500)); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +#sourcey .search-result-tag { + font-size: 0.6875rem; + color: rgb(var(--color-gray-400)); + white-space: nowrap; + flex-shrink: 0; +} + +#sourcey .search-loading { + padding: 1rem; + color: rgb(var(--color-gray-500)); + font-size: 0.8125rem; +} + +/* ── Godoc rendering ─────────────────────────────────────────────── */ + +#sourcey .godoc-import, +#sourcey .godoc-import-path { + font-size: 0.8125rem; + color: rgb(var(--color-gray-500)); + margin: 0.25rem 0 1.5rem; +} + +#sourcey .godoc-import code, +#sourcey .godoc-import-path code { + font-family: var(--font-mono); + background: rgba(var(--color-gray-100), 0.6); + padding: 0.125rem 0.375rem; + border-radius: 3px; +} + +#sourcey .godoc-source { + margin: -0.25rem 0 0.625rem; + font-size: 0.75rem; + color: rgb(var(--color-gray-500)); +} + +#sourcey .godoc-source a { + color: inherit; + text-decoration: none; +} + +#sourcey .godoc-source a:hover { + color: rgb(var(--color-primary-ink)); +} + +#sourcey .godoc-toc { + margin: 1.5rem 0 2.5rem; + padding: 0.875rem 1rem; + border: 1px solid rgb(var(--color-gray-200)); + border-radius: 6px; + background: rgba(var(--color-gray-50), 0.5); +} + +#sourcey .godoc-toc h4 { + margin: 0 0 0.5rem; + font-size: 0.6875rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.04em; + color: rgb(var(--color-gray-500)); +} + +#sourcey .godoc-toc ul { + list-style: none; + margin: 0; + padding: 0; + font-size: 0.8125rem; +} + +#sourcey .godoc-toc li { + margin: 0.125rem 0; +} + +#sourcey .godoc-toc li.godoc-toc-sub { + padding-left: 1rem; + font-family: var(--font-mono); + font-size: 0.75rem; + color: rgb(var(--color-gray-600)); +} + +#sourcey .godoc-toc a { + color: inherit; + text-decoration: none; + border-bottom: 1px dotted transparent; +} + +#sourcey .godoc-toc a:hover { + color: rgb(var(--color-primary)); + border-bottom-color: currentColor; +} + +#sourcey .godoc-symbol { + font-family: var(--font-mono); + font-size: 0.95rem; + font-weight: 600; + margin: 1.75rem 0 0.5rem; + color: rgb(var(--color-gray-900)); + scroll-margin-top: 5rem; +} + +#sourcey .godoc-doc { + margin: 0.5rem 0 0.75rem; + color: rgb(var(--color-gray-700)); + font-size: 0.9375rem; + line-height: 1.65; +} + +#sourcey .godoc-doc p:first-child { + margin-top: 0; +} + +#sourcey .godoc-doc p:last-child { + margin-bottom: 0; +} + +#sourcey .godoc-value, +#sourcey .godoc-func, +#sourcey .godoc-type { + margin: 1rem 0 1.5rem; + scroll-margin-top: 5rem; +} + +#sourcey .godoc-type { + border-top: 1px solid rgb(var(--color-gray-200)); + padding-top: 1.25rem; + margin-top: 2rem; +} + +#sourcey .godoc-anchor { + display: block; + height: 0; + scroll-margin-top: 5rem; +} + +#sourcey .godoc-fields { + margin: 0.75rem 0 1rem; + padding: 0; + border: 1px solid rgb(var(--color-gray-200)); + border-radius: 6px; + background: rgba(var(--color-gray-50), 0.4); +} + +#sourcey .godoc-fields > summary { + cursor: pointer; + padding: 0.5rem 0.875rem; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: rgb(var(--color-gray-600)); + list-style: none; +} + +#sourcey .godoc-fields > summary::-webkit-details-marker { + display: none; +} + +#sourcey .godoc-fields > ul { + list-style: none; + margin: 0; + padding: 0; + border-top: 1px solid rgb(var(--color-gray-200)); +} + +#sourcey .godoc-field { + padding: 0.625rem 0.875rem; + border-bottom: 1px solid rgb(var(--color-gray-200)); + font-size: 0.875rem; +} + +#sourcey .godoc-field:last-child { + border-bottom: none; +} + +#sourcey .godoc-field-sig { + font-family: var(--font-mono); + font-size: 0.8125rem; + font-weight: 600; + color: rgb(var(--color-gray-900)); + background: transparent; + padding: 0; +} + +#sourcey .godoc-field-type { + font-weight: 400; + color: rgb(var(--color-gray-700)); +} + +#sourcey .godoc-tag { + font-family: var(--font-mono); + font-size: 0.75rem; + color: rgb(var(--color-gray-500)); + background: transparent; + padding: 0; + margin-left: 0.5rem; +} + +#sourcey .godoc-field-doc { + margin-top: 0.25rem; + font-size: 0.8125rem; + color: rgb(var(--color-gray-600)); + line-height: 1.55; +} + +#sourcey .godoc-example { + margin: 0.75rem 0 1.25rem; + padding: 0; + border: 1px solid rgb(var(--color-gray-200)); + border-radius: 6px; + background: rgba(var(--color-gray-50), 0.4); +} + +#sourcey .godoc-example > summary { + cursor: pointer; + padding: 0.5rem 0.875rem; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: rgb(var(--color-gray-600)); + list-style: none; +} + +#sourcey .godoc-example > summary::-webkit-details-marker { + display: none; +} + +#sourcey .godoc-example > summary::before { + content: "▸ "; + color: rgb(var(--color-gray-400)); + margin-right: 0.25rem; +} + +#sourcey .godoc-example[open] > summary::before { + content: "▾ "; +} + +#sourcey .godoc-example > pre, +#sourcey .godoc-example > .godoc-doc, +#sourcey .godoc-example > p { + margin: 0.5rem 0.875rem; +} + +#sourcey .godoc-example-output-label { + margin: 0.5rem 0.875rem 0; + font-size: 0.6875rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.04em; + color: rgb(var(--color-gray-500)); +} + +#sourcey .godoc-func .godoc-symbol, +#sourcey .godoc-type .godoc-symbol { + word-break: break-word; +} + +/* ── Rust API rendering (rustdoc adapter) ────────────────────────── */ +/* Shared API primitives (api-*) introduced for rustdoc and reusable + by godoc/doxygen in a follow-on retrofit. Class names mirror + rustdoc's DOM so deep-links from a rustdoc URL line up with + sourcey output. */ + +#sourcey .rust-item { + border-top: 1px solid rgb(var(--color-gray-200)); + padding-top: 1rem; + margin-top: 1.25rem; +} +.dark #sourcey .rust-item { + border-top-color: rgb(var(--color-gray-800)); +} + +#sourcey .code-header, +#sourcey .rust-signature { + font-family: ui-monospace, "SF Mono", "Cascadia Code", Consolas, "Liberation Mono", Menlo, monospace; + font-size: 0.9375rem; + color: rgb(var(--color-gray-900)); + white-space: pre-wrap; + word-break: break-word; +} +.dark #sourcey .code-header, +.dark #sourcey .rust-signature { + color: rgb(var(--color-gray-100)); +} + +#sourcey .rust-signature .kw { + color: rgb(var(--color-rose-600)); + font-weight: 600; +} +.dark #sourcey .rust-signature .kw { + color: rgb(var(--color-rose-300)); +} + +#sourcey .rust-signature .ident { + color: rgb(var(--color-indigo-600)); +} +.dark #sourcey .rust-signature .ident { + color: rgb(var(--color-indigo-300)); +} + +#sourcey .rust-signature .lifetime { + color: rgb(var(--color-amber-700)); + font-style: italic; +} +.dark #sourcey .rust-signature .lifetime { + color: rgb(var(--color-amber-300)); +} + +#sourcey .api-rightside, +#sourcey .rightside { + float: right; + font-size: 0.8125rem; + color: rgb(var(--color-gray-500)); +} + +#sourcey .api-since, +#sourcey .since { + font-feature-settings: "tnum"; +} + +#sourcey .api-srclink, +#sourcey .srclink { + color: rgb(var(--color-indigo-600)); + text-decoration: none; +} +#sourcey .api-srclink:hover, +#sourcey .srclink:hover { + text-decoration: underline; +} +.dark #sourcey .api-srclink, +.dark #sourcey .srclink { + color: rgb(var(--color-indigo-300)); +} + +#sourcey .docblock, +#sourcey .rust-doc { + color: rgb(var(--color-gray-700)); + margin-top: 0.5rem; +} +.dark #sourcey .docblock, +.dark #sourcey .rust-doc { + color: rgb(var(--color-gray-300)); +} + +#sourcey .stab, +#sourcey .api-stab { + display: block; + margin: 0.5rem 0; + padding: 0.5rem 0.75rem; + border-radius: 0.375rem; + font-size: 0.875rem; + border: 1px solid transparent; +} +#sourcey .stab.unstable, +#sourcey .api-stab-unstable { + background-color: rgb(var(--color-amber-50)); + border-color: rgb(var(--color-amber-200)); + color: rgb(var(--color-amber-900)); +} +#sourcey .stab.deprecated, +#sourcey .api-stab-deprecated { + background-color: rgb(var(--color-rose-50)); + border-color: rgb(var(--color-rose-200)); + color: rgb(var(--color-rose-900)); +} +#sourcey .stab.portability, +#sourcey .api-stab-portability { + background-color: rgb(var(--color-emerald-50)); + border-color: rgb(var(--color-emerald-200)); + color: rgb(var(--color-emerald-900)); +} +.dark #sourcey .stab.unstable, +.dark #sourcey .api-stab-unstable { + background-color: rgba(251, 191, 36, 0.08); + border-color: rgba(251, 191, 36, 0.25); + color: rgb(var(--color-amber-200)); +} +.dark #sourcey .stab.deprecated, +.dark #sourcey .api-stab-deprecated { + background-color: rgba(244, 63, 94, 0.08); + border-color: rgba(244, 63, 94, 0.25); + color: rgb(var(--color-rose-200)); +} +.dark #sourcey .stab.portability, +.dark #sourcey .api-stab-portability { + background-color: rgba(16, 185, 129, 0.08); + border-color: rgba(16, 185, 129, 0.25); + color: rgb(var(--color-emerald-200)); +} + +/* Collapsible trait-impl block. Chevron pattern mirrors .godoc-example so the + disclosure reads the same across adapters. */ +#sourcey .api-toggle { + margin: 0.75rem 0; + border: 1px solid rgb(var(--color-gray-200)); + border-radius: 6px; + background: rgba(var(--color-gray-50), 0.4); +} +.dark #sourcey .api-toggle { + border-color: rgb(var(--color-gray-800)); +} +#sourcey .api-toggle > summary { + cursor: pointer; + padding: 0.5rem 0.875rem; + list-style: none; + font-family: var(--font-mono); + font-size: 0.8125rem; + color: rgb(var(--color-gray-700)); +} +.dark #sourcey .api-toggle > summary { + color: rgb(var(--color-gray-300)); +} +#sourcey .api-toggle > summary::-webkit-details-marker { + display: none; +} +#sourcey .api-toggle > summary::before { + content: "▸ "; + color: rgb(var(--color-gray-400)); + margin-right: 0.25rem; +} +#sourcey .api-toggle[open] > summary::before { + content: "▾ "; +} +/* The impl header lives inside the summary; keep it inline with the chevron + and strip heading margins. */ +#sourcey .api-toggle > summary .rust-impl-header { + display: inline; + margin: 0; + font-size: inherit; +} +#sourcey .api-toggle-body { + padding: 0.25rem 0.875rem 0.5rem; + border-top: 1px solid rgb(var(--color-gray-200)); +} +.dark #sourcey .api-toggle-body { + border-top-color: rgb(var(--color-gray-800)); +} +/* Marker / auto / blanket trait impls: a quiet list, not expandable boxes. */ +#sourcey .rust-impl-rows { + margin: 0.5rem 0 0.75rem; +} +#sourcey .rust-impl-row { + padding: 0.2rem 0; +} +#sourcey .rust-impl-row .rust-impl-header { + display: inline; + margin: 0; + font-size: 0.8125rem; + font-weight: 400; + color: rgb(var(--color-gray-600)); +} +.dark #sourcey .rust-impl-row .rust-impl-header { + color: rgb(var(--color-gray-400)); +} + +#sourcey .anchor, +#sourcey .api-anchor { + margin-left: 0.4rem; + color: rgb(var(--color-gray-400)); + text-decoration: none; + opacity: 0; + transition: opacity 0.1s ease; +} +#sourcey .section-header:hover .anchor, +#sourcey .section-header:hover .api-anchor, +#sourcey .code-header:hover .anchor, +#sourcey .code-header:hover .api-anchor { + opacity: 1; +} + +/* Inherent-impl methods and trait members render inline as their own sections. + Code (signatures, doctests) flows through the shared Shiki code block, so no + bespoke code styling lives here. */ +#sourcey .rust-member { + margin: 0.75rem 0; +} +#sourcey .rust-method-header { + margin: 0.5rem 0 0.25rem; +} +#sourcey .rust-doctest { + margin: 0.75rem 0; +} + +#sourcey .rust-doctest-badges { + display: flex; + flex-wrap: wrap; + gap: 0.25rem; + margin-bottom: 0.25rem; +} +#sourcey .rust-doctest-badge { + display: inline-block; + padding: 0.05rem 0.4rem; + font-size: 0.6875rem; + font-weight: 600; + border-radius: 0.25rem; + background-color: rgb(var(--color-gray-100)); + color: rgb(var(--color-gray-700)); + text-transform: lowercase; + letter-spacing: 0.02em; +} +.dark #sourcey .rust-doctest-badge { + background-color: rgb(var(--color-gray-800)); + color: rgb(var(--color-gray-300)); +} +#sourcey .rust-doctest-badge-should_panic, +#sourcey .rust-doctest-badge-compile_fail { + background-color: rgb(var(--color-rose-100)); + color: rgb(var(--color-rose-800)); +} +#sourcey .rust-doctest-badge-no_run, +#sourcey .rust-doctest-badge-ignore { + background-color: rgb(var(--color-amber-100)); + color: rgb(var(--color-amber-800)); +} + +#sourcey .rust-doctest-controls { + display: flex; + gap: 0.5rem; + margin-top: 0.5rem; + font-size: 0.8125rem; +} +#sourcey .rust-doctest-run, +#sourcey .rust-doctest-toggle-hidden { + cursor: pointer; + padding: 0.2rem 0.6rem; + border-radius: 0.375rem; + border: 1px solid rgb(var(--color-gray-300)); + background-color: rgb(var(--color-white)); + color: rgb(var(--color-gray-800)); + text-decoration: none; + line-height: 1; +} +#sourcey .rust-doctest-run:hover, +#sourcey .rust-doctest-toggle-hidden:hover { + background-color: rgb(var(--color-gray-50)); +} +.dark #sourcey .rust-doctest-run, +.dark #sourcey .rust-doctest-toggle-hidden { + background-color: rgb(var(--color-gray-900)); + border-color: rgb(var(--color-gray-700)); + color: rgb(var(--color-gray-200)); +} + +/* On the aggregated doctests page the code block already renders as a card, so + the section itself stays borderless. Just space the entries and accent the + heading so they don't read as cards within cards. */ +#sourcey .rust-doctests-index .rust-doctest { + margin: 1.5rem 0; +} +#sourcey .rust-doctests-index .rust-doctest > h3 { + padding-left: 0.625rem; + border-left: 3px solid rgb(var(--color-emerald-500)); +} +.dark #sourcey .rust-doctests-index .rust-doctest > h3 { + border-left-color: rgb(var(--color-emerald-400)); +} diff --git a/docs/sourcey/sourcey.js b/docs/sourcey/sourcey.js new file mode 100644 index 00000000000..53a955649a1 --- /dev/null +++ b/docs/sourcey/sourcey.js @@ -0,0 +1 @@ +(function(){(function(){function e(e){try{return window.sessionStorage.getItem(e)}catch{return null}}function t(e,t){try{window.sessionStorage.setItem(e,t)}catch{}}function n(e){try{window.sessionStorage.removeItem(e)}catch{}}function r(e){var t=(e||`/`).replace(/\/+$/,``)||`/`;return t=t.replace(/\/index(?:\.html)?$/,``)||`/`,t=t.replace(/\.html$/,``),t||`/`}function i(e){if(!e.length)return null;for(var t=e.map(function(e){return r(e).split(`/`).filter(Boolean)}),n=[],i=0;i=document.body.scrollHeight-10){var l=c[c.length-1];i=n.length?l.getAttribute(`data-traverse-target`):l.id}i&&d(i)}}var _=!1;window.addEventListener(`scroll`,function(){_||(_=!0,requestAnimationFrame(function(){g(),_=!1}))},{passive:!0});var v=window.location.hash.slice(1)||new URLSearchParams(window.location.search).get(`target`);v&&document.getElementById(v)?(d(v),requestAnimationFrame(function(){f(v,`instant`)})):g(),window.addEventListener(`hashchange`,function(){var e=window.location.hash.slice(1);e&&(d(e,!0),f(e,`smooth`))})}window.location.hash.slice(1)||new URLSearchParams(window.location.search).get(`target`)?requestAnimationFrame(e):`requestIdleCallback`in window?window.requestIdleCallback(e,{timeout:250}):window.addEventListener(`load`,e,{once:!0})})(),(function(){function e(){var e=null;document.addEventListener(`click`,function(e){var t=e.target.closest(`.code-lang-trigger`);if(document.querySelectorAll(`.code-lang-menu`).forEach(function(e){if(!t||!e.parentElement.contains(t)){e.classList.add(`hidden`);var n=e.parentElement.querySelector(`.code-lang-trigger`);n&&n.setAttribute(`aria-expanded`,`false`)}}),t){e.stopPropagation();var n=t.nextElementSibling;if(n){var r=!n.classList.contains(`hidden`);n.classList.toggle(`hidden`,r),t.setAttribute(`aria-expanded`,r?`false`:`true`)}}}),document.addEventListener(`click`,function(t){var r=t.target.closest(`.code-lang-option`);if(r){var i=r.closest(`.code-lang-dropdown`),a=!!(i&&i.hasAttribute(`data-lang-sync`)),o=r.textContent.trim(),s=r.closest(`.code-lang-menu`);if(s){s.classList.add(`hidden`);var c=s.parentElement.querySelector(`.code-lang-trigger`);c&&c.setAttribute(`aria-expanded`,`false`)}var l=window.scrollY;if(n(r),a&&o!==e){e=o;var u=r.closest(`.code-group`);document.querySelectorAll(`.code-group`).forEach(function(e){e===u||!e.querySelector(`.code-lang-dropdown[data-lang-sync]`)||e.querySelectorAll(`.code-lang-option`).forEach(function(e){e.textContent.trim()===o&&n(e)})})}window.scrollTo(0,l)}});function t(e){var t=e.closest(`[data-response-dropdown]`);if(t){var n=t.getAttribute(`data-response-dropdown`),r=t.closest(`.code-group`),i=r&&r.querySelector(`.response-panel[data-response-panel="`+n+`"]`);if(i)return i}return e.closest(`.code-lang-scope`)||e.closest(`.code-group`)}function n(e){var n=e.closest(`.code-lang-dropdown`),r=t(e),i=e.getAttribute(`data-lang-index`);if(n){var a=n.querySelector(`.code-lang-label`);n.querySelectorAll(`.code-lang-option`).forEach(function(e){var t=e.getAttribute(`data-lang-index`)===i;if(e.setAttribute(`aria-selected`,t?`true`:`false`),e.className=e.className.replace(/(dark:)?text-\[rgb\([^\]]+\)\]/g,``).trim(),t){e.classList.add(`text-[rgb(var(--color-primary))]`,`dark:text-[rgb(var(--color-primary-light))]`),a&&(a.textContent=e.textContent.trim());var r=n.querySelector(`.code-lang-trigger .code-lang-icon`),o=e.querySelector(`.lang-icon`);r&&o&&(r.innerHTML=o.outerHTML)}else e.classList.add(`text-[rgb(var(--color-stone-600))]`,`dark:text-[rgb(var(--color-stone-400))]`)})}r&&r.querySelectorAll(`.code-lang-panel`).forEach(function(e){e.classList.toggle(`active`,e.getAttribute(`data-lang-panel`)===i)})}document.addEventListener(`click`,function(e){var t=e.target.closest(`.response-tab`);if(t){var n=t.closest(`.response-tabs`),r=t.getAttribute(`data-response-index`),i=window.scrollY;n.querySelectorAll(`.response-tab`).forEach(function(e){var t=e.getAttribute(`data-response-index`)===r;e.classList.toggle(`active`,t),e.setAttribute(`aria-selected`,t?`true`:`false`)}),n.querySelectorAll(`.response-panel`).forEach(function(e){e.classList.toggle(`active`,e.getAttribute(`data-response-panel`)===r)}),n.querySelectorAll(`[data-response-dropdown]`).forEach(function(e){e.classList.toggle(`hidden`,e.getAttribute(`data-response-dropdown`)!==r)}),window.scrollTo(0,i)}}),document.addEventListener(`click`,function(e){var t=e.target.closest(`.copy-btn`);if(t){var n=t.closest(`.code-group`)||t.closest(`.prose-code-block`);if(n){var r=n.querySelectorAll(`.code-lang-panel.active, .response-panel.active`),i=r.length?r[r.length-1]:null,a=i?i.querySelector(`code, .code-block, .font-mono`):n.querySelector(`code, .code-block, .font-mono`);if(a){var o=a.textContent||``;navigator.clipboard.writeText(o).then(function(){t.classList.add(`copied`);var e=t.nextElementSibling;e&&e.classList.contains(`copy-tooltip`)&&(e.textContent=`Copied!`),setTimeout(function(){t.classList.remove(`copied`),e&&e.classList.contains(`copy-tooltip`)&&(e.textContent=`Copy`)},2e3)})}}}}),document.addEventListener(`click`,function(e){var t=e.target.closest(`.rust-doctest-toggle-hidden`);if(t){var n=t.getAttribute(`data-target`),r=t.getAttribute(`data-display`),i=n?document.getElementById(n.replace(/^#/,``)):null,a=r?document.getElementById(r.replace(/^#/,``)):null;!i||!a||(i.hasAttribute(`hidden`)?(i.removeAttribute(`hidden`),a.setAttribute(`hidden`,``),t.textContent=`Hide hidden lines`):(i.setAttribute(`hidden`,``),a.removeAttribute(`hidden`),t.textContent=`Show hidden lines`))}}),document.addEventListener(`keydown`,function(e){e.key===`Escape`&&document.querySelectorAll(`.code-lang-menu`).forEach(function(e){e.classList.add(`hidden`);var t=e.parentElement.querySelector(`.code-lang-trigger`);t&&t.setAttribute(`aria-expanded`,`false`)})}),document.addEventListener(`click`,function(e){var t=e.target.closest(`.directive-tab`);if(t){var n=t.getAttribute(`data-tab-group`),r=t.getAttribute(`data-tab-index`),i=window.scrollY;document.querySelectorAll(`.directive-tab[data-tab-group="`+n+`"]`).forEach(function(e){e.classList.toggle(`active`,e.getAttribute(`data-tab-index`)===r)}),document.querySelectorAll(`.directive-tab-panel[data-tab-group="`+n+`"]`).forEach(function(e){e.classList.toggle(`active`,e.getAttribute(`data-tab-index`)===r)}),window.scrollTo(0,i)}})}`requestIdleCallback`in window?window.requestIdleCallback(e,{timeout:250}):window.addEventListener(`load`,e,{once:!0})})(),(function(){var e=`sourcey-theme`,t=document.getElementById(`theme-toggle`),n=document.documentElement;function r(){var t=localStorage.getItem(e);return t===`dark`||t===`light`?t:`light`}function i(e){e===`dark`?n.classList.add(`dark`):n.classList.remove(`dark`),n.style.colorScheme=e,t&&(t.setAttribute(`aria-label`,e===`dark`?`Switch to light mode`:`Switch to dark mode`),t.setAttribute(`title`,e===`dark`?`Light mode`:`Dark mode`))}i(r()),t&&t.addEventListener(`click`,function(){var t=n.classList.contains(`dark`)?`light`:`dark`;localStorage.setItem(e,t),i(t)})})(),(function(){function e(){var e=document.getElementById(`search-dialog`),t=document.getElementById(`search-input`),n=document.getElementById(`search-results`),r=document.getElementById(`search-open`);if(!e||!t||!n)return;var i=[],a=-1,o=[],s=!1,c=document.querySelector(`meta[name="sourcey-search"]`);function l(e){if(s){e();return}if(!c){s=!0,e();return}var t=c.getAttribute(`content`);fetch(t).then(function(e){return e.json()}).then(function(t){i=t.map(function(e){var t=e.title||``,n=e.qualifiedName||``,r=e.owner||``,i=e.tab||``;return{url:e.url,method:e.method||``,path:e.path||``,title:t,summary:n||t,tag:r||i,content:e.content||``,category:e.category||``,featured:!!e.featured,symbolKind:e.symbolKind||``,owner:r,ownerKind:e.ownerKind||``,namespace:e.namespace||``,qualifiedName:n,titleLower:t.toLowerCase(),pathLower:(e.path||``).toLowerCase(),qualifiedLower:n.toLowerCase(),ownerLower:r.toLowerCase(),searchText:[e.method||``,e.path||``,t,n,r,e.ownerKind||``,e.namespace||``,e.symbolKind||``,e.content||``,i].join(` `).toLowerCase()}}),s=!0,e()}).catch(function(){s=!0,e()})}var u=e.querySelector(`.search-dialog-inner`);function d(){if(!(!r||!u)){var e=r.getBoundingClientRect();u.style.position=`absolute`,u.style.top=e.top-4+`px`;var t=Math.min(e.width*.5,200);u.style.left=e.left-t/2+`px`,u.style.width=e.width+t+`px`,u.style.maxWidth=`none`,u.style.transform=`none`,u.style.margin=`0`}}function f(){d(),e.classList.add(`open`),t.value=``,t.focus(),s?m(``):(n.innerHTML=`
Loading…
`,l(function(){m(``)})),document.addEventListener(`keydown`,b)}function p(){e.classList.remove(`open`),document.removeEventListener(`keydown`,b)}function m(e){var t=e.toLowerCase().trim();if(t){var n=t.split(/\s+/);o=i.map(function(e){return{entry:e,score:h(e,t,n)}}).filter(function(e){return e.score>0});var r={Pages:0,Endpoints:1,Models:2,Functions:3,Types:4,Enums:5,"Enum Values":6,Variables:7,Friends:8,Properties:9,Members:10,Sections:20};o.sort(function(e,t){return t.score===e.score?(r[e.entry.category]||19)-(r[t.entry.category]||19):t.score-e.score}),o=o.map(function(e){return e.entry}).slice(0,50)}else{var s=i.filter(function(e){return e.featured}),c=i.filter(function(e){return!e.featured&&e.category!==`Sections`});o=s.concat(c).slice(0,30)}a=o.length?0:-1,g()}function h(e,t,n){if(!n.every(function(t){return e.searchText.indexOf(t)!==-1}))return 0;var r=t.replace(/\s+/g,``),i=e.qualifiedLower||``,a=e.titleLower||``,o=e.pathLower||``,s=e.ownerLower||``,c=1;return i&&i===r&&(c+=1200),a&&a===t&&(c+=900),o&&o===t&&(c+=850),i&&i.endsWith(`::`+r)&&(c+=780),s&&r===s+`::`+a&&(c+=760),i&&r.indexOf(`::`)!==-1&&i.indexOf(r)!==-1&&(c+=700),a&&a.indexOf(t)===0&&(c+=500),i&&i.indexOf(r)!==-1&&(c+=420),s&&s.indexOf(r)!==-1&&(c+=240),o&&o.indexOf(t)!==-1&&(c+=180),e.content.toLowerCase().indexOf(t)!==-1&&(c+=80),e.category===`Sections`&&(c-=70),e.symbolKind&&(c+=35),e.featured&&(c+=20),c}function g(){for(var e=``,t=``,r=0;r`+_(s)+``,t=s);var c=`search-result`+(r===a?` active`:``),l=i.method?``+i.method+` `+_(i.path)+``:``+_(i.summary)+``,u=i.tag?``+_(i.tag)+``:``,d=i.method&&i.summary?``+_(i.summary)+``:``;e+=`
`+l+d+`
`+u+`
`}n.innerHTML=e;var f=n.querySelector(`.search-result.active`);f&&f.scrollIntoView({block:`nearest`})}function _(e){var t=document.createElement(`span`);return t.textContent=e,t.innerHTML}function v(e){return _(e).replace(/"/g,`"`).replace(/'/g,`'`)}function y(e){if(!(e<0||e>=o.length)){var t=o[e];p(),window.location.href=t.url}}function b(e){if(e.key===`Escape`){p(),e.preventDefault();return}if(e.key===`ArrowDown`){e.preventDefault(),a=Math.min(a+1,o.length-1),g();return}if(e.key===`ArrowUp`){e.preventDefault(),a=Math.max(a-1,0),g();return}if(e.key===`Enter`){e.preventDefault(),a>=0&&y(a);return}}t.addEventListener(`input`,function(){m(t.value)}),n.addEventListener(`click`,function(e){var t=e.target.closest(`.search-result`);t&&(e.preventDefault(),y(parseInt(t.getAttribute(`data-index`),10)))}),r&&r.addEventListener(`click`,f);var x=document.getElementById(`search-open-mobile`);x&&x.addEventListener(`click`,f),document.addEventListener(`keydown`,function(e){(e.ctrlKey||e.metaKey)&&e.key===`k`&&(e.preventDefault(),f()),e.key===`/`&&!S(e.target)&&(e.preventDefault(),f())}),e.addEventListener(`click`,function(t){t.target===e&&p()});function S(e){var t=e.tagName;return t===`INPUT`||t===`TEXTAREA`||t===`SELECT`||e.isContentEditable}}`requestIdleCallback`in window?window.requestIdleCallback(e,{timeout:250}):window.addEventListener(`load`,e,{once:!0})})()})(); \ No newline at end of file diff --git a/docs/sourcey/sponsor_logos/backbase.png b/docs/sourcey/sponsor_logos/backbase.png new file mode 100644 index 00000000000..22acf3aac8e Binary files /dev/null and b/docs/sourcey/sponsor_logos/backbase.png differ diff --git a/docs/sourcey/sponsor_logos/cirrus_labs.jpg b/docs/sourcey/sponsor_logos/cirrus_labs.jpg new file mode 100644 index 00000000000..3bec84f0a22 Binary files /dev/null and b/docs/sourcey/sponsor_logos/cirrus_labs.jpg differ diff --git a/docs/sourcey/sponsor_logos/elastic.png b/docs/sourcey/sponsor_logos/elastic.png new file mode 100644 index 00000000000..393e6711c45 Binary files /dev/null and b/docs/sourcey/sponsor_logos/elastic.png differ diff --git a/docs/sourcey/sponsor_logos/jooq.jpg b/docs/sourcey/sponsor_logos/jooq.jpg new file mode 100644 index 00000000000..7a64f773d61 Binary files /dev/null and b/docs/sourcey/sponsor_logos/jooq.jpg differ diff --git a/docs/sourcey/sponsor_logos/red_hat.png b/docs/sourcey/sponsor_logos/red_hat.png new file mode 100644 index 00000000000..b8c28b5d7b0 Binary files /dev/null and b/docs/sourcey/sponsor_logos/red_hat.png differ diff --git a/docs/sourcey/sponsor_logos/spotify.png b/docs/sourcey/sponsor_logos/spotify.png new file mode 100644 index 00000000000..41f149f713b Binary files /dev/null and b/docs/sourcey/sponsor_logos/spotify.png differ diff --git a/docs/sourcey/sponsor_logos/vivy.png b/docs/sourcey/sponsor_logos/vivy.png new file mode 100644 index 00000000000..2faa4742dd1 Binary files /dev/null and b/docs/sourcey/sponsor_logos/vivy.png differ diff --git a/docs/sourcey/test-framework-integration/junit-5.html b/docs/sourcey/test-framework-integration/junit-5.html new file mode 100644 index 00000000000..2e43b2cadf6 --- /dev/null +++ b/docs/sourcey/test-framework-integration/junit-5.html @@ -0,0 +1,85 @@ + +Jupiter / JUnit 5 - Testcontainers for Java Sourcey Reference
Integration

Jupiter / JUnit 5

This module provides an API that is based on the JUnit Jupiter extension model.

This module provides an API that is based on the JUnit Jupiter extension model.

+

The extension supports two modes:

+
    +
  • containers that are restarted for every test method
  • +
  • containers that are shared between all methods of a test class
  • +
+

Note that Jupiter/JUnit 5 integration is packaged as a separate library JAR; see below for details.

+

Extension

+

Jupiter integration is provided by means of the @Testcontainers annotation.

+

The extension finds all fields that are annotated with @Container and calls their container lifecycle +methods (methods on the Startable interface). Containers declared as static fields will be shared between test +methods. They will be started only once before any test method is executed and stopped after the last test method has +executed. Containers declared as instance fields will be started and stopped for every test method.

+

Note: This extension has only been tested with sequential test execution. Using it with parallel test execution is +unsupported and may have unintended side effects.

+

Example:

+ +

Mixed Lifecycle inside_block:testClass

+ + +

Examples

+

To use the Testcontainers extension annotate your test class with @Testcontainers.

+

Restarted containers

+

To define a restarted container, define an instance field inside your test class and annotate it with +the @Container annotation.

+ +

Restarted Containers inside_block:testClass

+ + + +

Shared containers

+

Shared containers are defined as static fields in a top level test class and have to be annotated with @Container. +Note that shared containers can't be declared inside nested test classes. +This is because nested test classes have to be defined non-static and can't therefore have static fields.

+ +

Shared Container lines:18-23,32-33,35-36

+ + +

Singleton containers

+

Note that the singleton container pattern is also an option when +using JUnit 5.

+

Limitations

+

This extension has only been tested with sequential test execution. Using it with parallel test execution is unsupported and may have unintended side effects.

+

Adding Testcontainers JUnit 5 support to your project dependencies

+

Add the following dependency to your pom.xml/build.gradle file:

+

Gradle

+
+
+ +
+
testImplementation "org.testcontainers:testcontainers-junit-jupiter:{{latest_version}}"
+

Maven

+
+
+ +
+
&lt;dependency&gt;
+    &lt;groupId&gt;org.testcontainers</groupId>
+    &lt;artifactId&gt;testcontainers-junit-jupiter</artifactId>
+    &lt;version&gt;{{latest_version}}</version>
+    &lt;scope&gt;test</scope>
+</dependency>
+
\ No newline at end of file diff --git a/docs/sourcey/testcontainers-logo.svg b/docs/sourcey/testcontainers-logo.svg new file mode 100644 index 00000000000..4b099f34a8e --- /dev/null +++ b/docs/sourcey/testcontainers-logo.svg @@ -0,0 +1,22 @@ + + + Testcontainers + + + + + + + + + + + + + + + + + + + \ No newline at end of file